ソースを参照

Merge branch 'develop' of https://github.com/Dolibarr/dolibarr into NEW---Do-not-select-a-default-recipient

Anthony Berton 2 年 前
コミット
06b37482b1
100 ファイル変更8382 行追加2028 行削除
  1. 1 1
      .github/workflows/code_quality.yml
  2. 1 0
      .gitignore
  3. 2 2
      .idea/php.xml
  4. 6 0
      .mailmap
  5. 10 10
      .travis.yml
  6. 147 147
      .tx/config
  7. 7 7
      COPYRIGHT
  8. 441 152
      ChangeLog
  9. 1 1
      README.md
  10. 3 3
      SECURITY.md
  11. 1 1
      build/debian/compat
  12. 5 1
      build/debian/rules
  13. 3 1
      build/debian/source/options
  14. 2 2
      build/doxygen/dolibarr-doxygen.doxyfile
  15. 1 1
      build/exe/doliwamp/Languages/MyCatalan.isl
  16. 22 22
      build/exe/doliwamp/Languages/MyEnglish.isl
  17. 1 1
      build/exe/doliwamp/Languages/MyFrench.isl
  18. 1 1
      build/exe/doliwamp/Languages/MyGerman.isl
  19. 1 1
      build/exe/doliwamp/Languages/MySpanish.isl
  20. 5 1
      build/generate_filelist_xml.php
  21. 9 8
      build/makepack-dolibarr.pl
  22. 3 3
      build/makepack-howto.txt
  23. 7 5
      build/tgz/tar_exclude.txt
  24. 5 0
      dev/dolibarr_changes.txt
  25. 0 28
      dev/initdemo/README
  26. 32 0
      dev/initdemo/README.md
  27. 2 2
      dev/initdemo/mysqldump_dolibarr_17.0.0.sql
  28. 14 0
      dev/initdemo/savedemo.sh
  29. 2 3
      dev/resources/iso-normes/qr-bar-codes/QR code for invoices.txt
  30. 3676 0
      dev/resources/iso-normes/qr-bar-codes/ig-qr-bill-v2.2-fr.pdf
  31. 33 22
      dev/setup/apache/virtualhost
  32. 2 1
      dev/setup/fail2ban/jail.local
  33. 1 1
      dev/translation/txpull.sh
  34. 7 7
      htdocs/accountancy/admin/account.php
  35. 2 2
      htdocs/accountancy/admin/accountmodel.php
  36. 4 4
      htdocs/accountancy/admin/card.php
  37. 3 3
      htdocs/accountancy/admin/categories_list.php
  38. 5 1
      htdocs/accountancy/admin/defaultaccounts.php
  39. 2 2
      htdocs/accountancy/admin/export.php
  40. 9 4
      htdocs/accountancy/admin/fiscalyear.php
  41. 44 69
      htdocs/accountancy/admin/index.php
  42. 3 3
      htdocs/accountancy/admin/journals_list.php
  43. 4 4
      htdocs/accountancy/admin/productaccount.php
  44. 9 9
      htdocs/accountancy/admin/subaccount.php
  45. 3 3
      htdocs/accountancy/bookkeeping/balance.php
  46. 417 360
      htdocs/accountancy/bookkeeping/card.php
  47. 1404 0
      htdocs/accountancy/bookkeeping/export.php
  48. 19 278
      htdocs/accountancy/bookkeeping/list.php
  49. 16 7
      htdocs/accountancy/bookkeeping/listbyaccount.php
  50. 13 4
      htdocs/accountancy/class/accountancycategory.class.php
  51. 452 310
      htdocs/accountancy/class/accountancyexport.class.php
  52. 5 0
      htdocs/accountancy/class/accountancysystem.class.php
  53. 9 9
      htdocs/accountancy/class/accountingaccount.class.php
  54. 4 21
      htdocs/accountancy/class/accountingjournal.class.php
  55. 276 0
      htdocs/accountancy/class/api_accountancy.class.php
  56. 4 4
      htdocs/accountancy/class/bookkeeping.class.php
  57. 6 3
      htdocs/accountancy/class/lettering.class.php
  58. 67 35
      htdocs/accountancy/customer/index.php
  59. 40 7
      htdocs/accountancy/customer/lines.php
  60. 35 6
      htdocs/accountancy/customer/list.php
  61. 3 3
      htdocs/accountancy/expensereport/index.php
  62. 10 2
      htdocs/accountancy/expensereport/lines.php
  63. 13 7
      htdocs/accountancy/expensereport/list.php
  64. 2 6
      htdocs/accountancy/index.php
  65. 40 23
      htdocs/accountancy/journal/bankjournal.php
  66. 11 0
      htdocs/accountancy/journal/expensereportsjournal.php
  67. 156 9
      htdocs/accountancy/journal/purchasesjournal.php
  68. 51 20
      htdocs/accountancy/journal/sellsjournal.php
  69. 14 3
      htdocs/accountancy/journal/variousjournal.php
  70. 29 29
      htdocs/accountancy/supplier/index.php
  71. 10 2
      htdocs/accountancy/supplier/lines.php
  72. 13 7
      htdocs/accountancy/supplier/list.php
  73. 3 2
      htdocs/accountancy/tpl/export_journal.tpl.php
  74. 1 1
      htdocs/adherents/admin/member.php
  75. 15 15
      htdocs/adherents/admin/member_emails.php
  76. 1 1
      htdocs/adherents/admin/member_extrafields.php
  77. 26 6
      htdocs/adherents/admin/website.php
  78. 6 0
      htdocs/adherents/agenda.php
  79. 6 11
      htdocs/adherents/canvas/actions_adherentcard_common.class.php
  80. 0 1
      htdocs/adherents/canvas/default/actions_adherentcard_default.class.php
  81. 6 15
      htdocs/adherents/card.php
  82. 113 39
      htdocs/adherents/class/adherent.class.php
  83. 64 15
      htdocs/adherents/class/adherent_type.class.php
  84. 4 5
      htdocs/adherents/class/api_members.class.php
  85. 3 4
      htdocs/adherents/class/api_memberstypes.class.php
  86. 2 3
      htdocs/adherents/class/api_subscriptions.class.php
  87. 15 9
      htdocs/adherents/class/subscription.class.php
  88. 1 1
      htdocs/adherents/index.php
  89. 1 1
      htdocs/adherents/ldap.php
  90. 37 27
      htdocs/adherents/list.php
  91. 1 1
      htdocs/adherents/stats/geo.php
  92. 1 1
      htdocs/adherents/stats/index.php
  93. 14 6
      htdocs/adherents/subscription.php
  94. 5 5
      htdocs/adherents/subscription/card.php
  95. 227 95
      htdocs/adherents/subscription/list.php
  96. 159 65
      htdocs/adherents/type.php
  97. 1 1
      htdocs/adherents/type_translation.php
  98. 1 0
      htdocs/admin/accounting.php
  99. 7 3
      htdocs/admin/agenda.php
  100. 1 1
      htdocs/admin/agenda_extrafields.php

+ 1 - 1
.github/workflows/code_quality.yml

@@ -18,7 +18,7 @@ jobs:
           fetch-depth: 1
           #php-version: '7.1'
       - name: 'Qodana Scan'
-        uses: JetBrains/qodana-action@v2022.3.0
+        uses: JetBrains/qodana-action@v2023.1.0
         #with:
         #  php-version: '7.1'
         env:

+ 1 - 0
.gitignore

@@ -57,3 +57,4 @@ yarn.lock
 package-lock.json
 
 doc/install.lock
+/.asciidoctorconfig.adoc

+ 2 - 2
.idea/php.xml

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<project version="7.0">
-    <component name="PhpProjectSharedConfiguration" php_language_level="7.0" />
+<project version="7.1">
+    <component name="PhpProjectSharedConfiguration" php_language_level="7.1" />
 </project>
 

+ 6 - 0
.mailmap

@@ -89,3 +89,9 @@ Baffir Abbes <bafbes@users.noreply.github.com> bafbes <bafbes@users.noreply.gith
 Kevin Guerrier <guerrier.k@gmail.com> GUERRIER Kevin <guerrier.k@gmail.com>
 Remy Younes <ryounes@gmail.com> remy <ryounes@gmail.com>
 Estephe Loridan <github@estephe.me> Estephe L. <github@estephe.me>
+Anthony Berton <anthony.berton@bb2a.fr> Anthony Berton <anthony.berton@bb2a.fr>
+Anthony Berton <anthony.berton@bb2a.fr> Berton Anthony <anthony.berton@bb2a.fr>
+Anthony Berton <anthony.berton@bb2a.fr> BB2A-Anthony <anthony.berton@bb2a.fr>
+Anthony Berton <anthony.berton@bb2a.fr> Anthony Berton <bertonanthony@gmail.com>
+Anthony Berton <anthony.berton@bb2a.fr> Berton Anthony <bertonanthony@gmail.com>
+Anthony Berton <anthony.berton@bb2a.fr> BB2A-Anthony <bertonanthony@gmail.com>

+ 10 - 10
.travis.yml

@@ -53,6 +53,10 @@ addons:
     - php8.1-mysqli
     - php8.1-xml
     - php8.1-intl
+    - php8.2-pgsql
+    - php8.2-mysqli
+    - php8.2-xml
+    - php8.2-intl
     
 env:
   global:
@@ -73,12 +77,8 @@ jobs:
       php: '8.1'
       env: DB=mysql
     - stage: PHP Dev
-      if: type = push AND branch = develop
-      php: nightly
-      env: DB=mysql
-    - stage: PHP Dev
-      if: type = push AND branch = 17.0
-      php: nightly
+      if: type = push AND branch = developdisabled
+      php: '8.2'
       env: DB=mysql
 
 notifications:
@@ -124,7 +124,7 @@ install:
                         squizlabs/php_codesniffer ^3
   fi
   # phpunit 9 is required for php 8
-  if [ "$TRAVIS_PHP_VERSION" = '8.0' ] || [ "$TRAVIS_PHP_VERSION" = '8.1' ] || [ "$TRAVIS_PHP_VERSION" = 'nightly' ]; then
+  if [ "$TRAVIS_PHP_VERSION" = '8.0' ] || [ "$TRAVIS_PHP_VERSION" = '8.1' ] || [ "$TRAVIS_PHP_VERSION" = '8.2' ] || [ "$TRAVIS_PHP_VERSION" = 'nightly' ]; then
       composer self-update 2.4.4
       composer -n require --ignore-platform-reqs phpunit/phpunit ^8 \
                                                  php-parallel-lint/php-parallel-lint ^1.2 \
@@ -264,7 +264,7 @@ before_script:
   # enable php-fpm
   - sudo cp ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf.default ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf
   - |
-    if [ "$TRAVIS_PHP_VERSION" = '7.0' ] || [ "$TRAVIS_PHP_VERSION" = '7.1' ] || [ "$TRAVIS_PHP_VERSION" = '7.2' ] || [ "$TRAVIS_PHP_VERSION" = '7.3' ] || [ "$TRAVIS_PHP_VERSION" = '7.4' ] || [ "$TRAVIS_PHP_VERSION" = '8.0' ] || [ "$TRAVIS_PHP_VERSION" = '8.1' ] || [ "$TRAVIS_PHP_VERSION" = 'nightly' ]; then
+    if [ "$TRAVIS_PHP_VERSION" = '7.0' ] || [ "$TRAVIS_PHP_VERSION" = '7.1' ] || [ "$TRAVIS_PHP_VERSION" = '7.2' ] || [ "$TRAVIS_PHP_VERSION" = '7.3' ] || [ "$TRAVIS_PHP_VERSION" = '7.4' ] || [ "$TRAVIS_PHP_VERSION" = '8.0' ] || [ "$TRAVIS_PHP_VERSION" = '8.1' ] || [ "$TRAVIS_PHP_VERSION" = '8.2' ] || [ "$TRAVIS_PHP_VERSION" = 'nightly' ]; then
       # Copy the included pool
       sudo cp ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.d/www.conf.default ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.d/www.conf
     fi
@@ -477,7 +477,7 @@ script:
 
 - |
   echo "Unit testing"
-  # Ensure we catch errors. Set this to +e if you want to go to the end to see dolibarr.log file.
+  # Ensure we catch errors. Set this to +e instead of -e if you want to go to the end to see dolibarr.log file.
   set -e
   phpunit -d memory_limit=-1 -c test/phpunit/phpunittest.xml test/phpunit/AllTests.php
   phpunitresult=$?
@@ -505,7 +505,7 @@ after_failure:
   # Show upgrade log files
   for ficlog in `ls $TRAVIS_BUILD_DIR/*.log`
   do
-    echo "Debugging informations for file $ficlog"
+    #echo "Debugging informations for file $ficlog"
     #cat $ficlog
   done
   # Show Apache log file

+ 147 - 147
.tx/config

@@ -1,442 +1,442 @@
 [main]
-host = https://www.transifex.com
+host     = https://www.transifex.com
 lang_map = uz: uz_UZ, sw: sw_SW, sr@latin: sr_RS
 
-[dolibarr.accountancy]
+[o:dolibarr-association:p:dolibarr:r:accountancy]
 file_filter = htdocs/langs/<lang>/accountancy.lang
 source_file = htdocs/langs/en_US/accountancy.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.admin]
+[o:dolibarr-association:p:dolibarr:r:admin]
 file_filter = htdocs/langs/<lang>/admin.lang
 source_file = htdocs/langs/en_US/admin.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.agenda]
+[o:dolibarr-association:p:dolibarr:r:agenda]
 file_filter = htdocs/langs/<lang>/agenda.lang
 source_file = htdocs/langs/en_US/agenda.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.assets]
+[o:dolibarr-association:p:dolibarr:r:assets]
 file_filter = htdocs/langs/<lang>/assets.lang
 source_file = htdocs/langs/en_US/assets.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.banks]
+[o:dolibarr-association:p:dolibarr:r:banks]
 file_filter = htdocs/langs/<lang>/banks.lang
 source_file = htdocs/langs/en_US/banks.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.bills]
+[o:dolibarr-association:p:dolibarr:r:bills]
 file_filter = htdocs/langs/<lang>/bills.lang
 source_file = htdocs/langs/en_US/bills.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.blockedlog]
+[o:dolibarr-association:p:dolibarr:r:blockedlog]
 file_filter = htdocs/langs/<lang>/blockedlog.lang
 source_file = htdocs/langs/en_US/blockedlog.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.bookmarks]
+[o:dolibarr-association:p:dolibarr:r:bookmarks]
 file_filter = htdocs/langs/<lang>/bookmarks.lang
 source_file = htdocs/langs/en_US/bookmarks.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.boxes]
+[o:dolibarr-association:p:dolibarr:r:boxes]
 file_filter = htdocs/langs/<lang>/boxes.lang
 source_file = htdocs/langs/en_US/boxes.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.cashdesk]
+[o:dolibarr-association:p:dolibarr:r:cashdesk]
 file_filter = htdocs/langs/<lang>/cashdesk.lang
 source_file = htdocs/langs/en_US/cashdesk.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.categories]
+[o:dolibarr-association:p:dolibarr:r:categories]
 file_filter = htdocs/langs/<lang>/categories.lang
 source_file = htdocs/langs/en_US/categories.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.commercial]
+[o:dolibarr-association:p:dolibarr:r:commercial]
 file_filter = htdocs/langs/<lang>/commercial.lang
 source_file = htdocs/langs/en_US/commercial.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.companies]
+[o:dolibarr-association:p:dolibarr:r:companies]
 file_filter = htdocs/langs/<lang>/companies.lang
 source_file = htdocs/langs/en_US/companies.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.compta]
+[o:dolibarr-association:p:dolibarr:r:compta]
 file_filter = htdocs/langs/<lang>/compta.lang
 source_file = htdocs/langs/en_US/compta.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.contracts]
+[o:dolibarr-association:p:dolibarr:r:contracts]
 file_filter = htdocs/langs/<lang>/contracts.lang
 source_file = htdocs/langs/en_US/contracts.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.cron]
+[o:dolibarr-association:p:dolibarr:r:cron]
 file_filter = htdocs/langs/<lang>/cron.lang
 source_file = htdocs/langs/en_US/cron.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.datapolicy]
+[o:dolibarr-association:p:dolibarr:r:datapolicy]
 file_filter = htdocs/langs/<lang>/datapolicy.lang
 source_file = htdocs/langs/en_US/datapolicy.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.deliveries]
+[o:dolibarr-association:p:dolibarr:r:deliveries]
 file_filter = htdocs/langs/<lang>/deliveries.lang
 source_file = htdocs/langs/en_US/deliveries.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.dict]
+[o:dolibarr-association:p:dolibarr:r:dict]
 file_filter = htdocs/langs/<lang>/dict.lang
 source_file = htdocs/langs/en_US/dict.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.donations]
+[o:dolibarr-association:p:dolibarr:r:donations]
 file_filter = htdocs/langs/<lang>/donations.lang
 source_file = htdocs/langs/en_US/donations.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.ecm]
+[o:dolibarr-association:p:dolibarr:r:ecm]
 file_filter = htdocs/langs/<lang>/ecm.lang
 source_file = htdocs/langs/en_US/ecm.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.errors]
+[o:dolibarr-association:p:dolibarr:r:errors]
 file_filter = htdocs/langs/<lang>/errors.lang
 source_file = htdocs/langs/en_US/errors.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.eventorganization]
+[o:dolibarr-association:p:dolibarr:r:eventorganization]
 file_filter = htdocs/langs/<lang>/eventorganization.lang
 source_file = htdocs/langs/en_US/eventorganization.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.exports]
+[o:dolibarr-association:p:dolibarr:r:exports]
 file_filter = htdocs/langs/<lang>/exports.lang
 source_file = htdocs/langs/en_US/exports.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.help]
+[o:dolibarr-association:p:dolibarr:r:help]
 file_filter = htdocs/langs/<lang>/help.lang
 source_file = htdocs/langs/en_US/help.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.holiday]
+[o:dolibarr-association:p:dolibarr:r:holiday]
 file_filter = htdocs/langs/<lang>/holiday.lang
 source_file = htdocs/langs/en_US/holiday.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.hrm]
+[o:dolibarr-association:p:dolibarr:r:hrm]
 file_filter = htdocs/langs/<lang>/hrm.lang
 source_file = htdocs/langs/en_US/hrm.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.install]
+[o:dolibarr-association:p:dolibarr:r:install]
 file_filter = htdocs/langs/<lang>/install.lang
 source_file = htdocs/langs/en_US/install.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.interventions]
+[o:dolibarr-association:p:dolibarr:r:interventions]
 file_filter = htdocs/langs/<lang>/interventions.lang
 source_file = htdocs/langs/en_US/interventions.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.intracommreport]
+[o:dolibarr-association:p:dolibarr:r:intracommreport]
 file_filter = htdocs/langs/<lang>/intracommreport.lang
 source_file = htdocs/langs/en_US/intracommreport.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.knowledgemanagement]
+[o:dolibarr-association:p:dolibarr:r:knowledgemanagement]
 file_filter = htdocs/langs/<lang>/knowledgemanagement.lang
 source_file = htdocs/langs/en_US/knowledgemanagement.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.languages-not-res]
+[o:dolibarr-association:p:dolibarr:r:languages-not-res]
 file_filter = htdocs/langs/<lang>/languages.lang
 source_file = htdocs/langs/en_US/languages.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.ldap]
+[o:dolibarr-association:p:dolibarr:r:ldap]
 file_filter = htdocs/langs/<lang>/ldap.lang
 source_file = htdocs/langs/en_US/ldap.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.link]
+[o:dolibarr-association:p:dolibarr:r:link]
 file_filter = htdocs/langs/<lang>/link.lang
 source_file = htdocs/langs/en_US/link.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.loan]
+[o:dolibarr-association:p:dolibarr:r:loan]
 file_filter = htdocs/langs/<lang>/loan.lang
 source_file = htdocs/langs/en_US/loan.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.mailmanspip]
+[o:dolibarr-association:p:dolibarr:r:mailmanspip]
 file_filter = htdocs/langs/<lang>/mailmanspip.lang
 source_file = htdocs/langs/en_US/mailmanspip.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.mails]
+[o:dolibarr-association:p:dolibarr:r:mails]
 file_filter = htdocs/langs/<lang>/mails.lang
 source_file = htdocs/langs/en_US/mails.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.main]
+[o:dolibarr-association:p:dolibarr:r:main]
 file_filter = htdocs/langs/<lang>/main.lang
 source_file = htdocs/langs/en_US/main.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.margins]
+[o:dolibarr-association:p:dolibarr:r:margins]
 file_filter = htdocs/langs/<lang>/margins.lang
 source_file = htdocs/langs/en_US/margins.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.members]
+[o:dolibarr-association:p:dolibarr:r:members]
 file_filter = htdocs/langs/<lang>/members.lang
 source_file = htdocs/langs/en_US/members.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.modulebuilder]
+[o:dolibarr-association:p:dolibarr:r:modulebuilder]
 file_filter = htdocs/langs/<lang>/modulebuilder.lang
 source_file = htdocs/langs/en_US/modulebuilder.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.mrp]
+[o:dolibarr-association:p:dolibarr:r:mrp]
 file_filter = htdocs/langs/<lang>/mrp.lang
 source_file = htdocs/langs/en_US/mrp.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.multicurrency]
+[o:dolibarr-association:p:dolibarr:r:multicurrency]
 file_filter = htdocs/langs/<lang>/multicurrency.lang
 source_file = htdocs/langs/en_US/multicurrency.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.oauth]
+[o:dolibarr-association:p:dolibarr:r:oauth]
 file_filter = htdocs/langs/<lang>/oauth.lang
 source_file = htdocs/langs/en_US/oauth.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.opensurvey]
+[o:dolibarr-association:p:dolibarr:r:opensurvey]
 file_filter = htdocs/langs/<lang>/opensurvey.lang
 source_file = htdocs/langs/en_US/opensurvey.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.orders]
+[o:dolibarr-association:p:dolibarr:r:orders]
 file_filter = htdocs/langs/<lang>/orders.lang
 source_file = htdocs/langs/en_US/orders.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.other]
+[o:dolibarr-association:p:dolibarr:r:other]
 file_filter = htdocs/langs/<lang>/other.lang
 source_file = htdocs/langs/en_US/other.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.partnership]
+[o:dolibarr-association:p:dolibarr:r:partnership]
 file_filter = htdocs/langs/<lang>/partnership.lang
 source_file = htdocs/langs/en_US/partnership.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.paybox]
+[o:dolibarr-association:p:dolibarr:r:paybox]
 file_filter = htdocs/langs/<lang>/paybox.lang
 source_file = htdocs/langs/en_US/paybox.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.paypal]
+[o:dolibarr-association:p:dolibarr:r:paypal]
 file_filter = htdocs/langs/<lang>/paypal.lang
 source_file = htdocs/langs/en_US/paypal.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.printing]
+[o:dolibarr-association:p:dolibarr:r:printing]
 file_filter = htdocs/langs/<lang>/printing.lang
 source_file = htdocs/langs/en_US/printing.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.productbatch]
+[o:dolibarr-association:p:dolibarr:r:productbatch]
 file_filter = htdocs/langs/<lang>/productbatch.lang
 source_file = htdocs/langs/en_US/productbatch.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.products]
+[o:dolibarr-association:p:dolibarr:r:products]
 file_filter = htdocs/langs/<lang>/products.lang
 source_file = htdocs/langs/en_US/products.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.projects]
+[o:dolibarr-association:p:dolibarr:r:projects]
 file_filter = htdocs/langs/<lang>/projects.lang
 source_file = htdocs/langs/en_US/projects.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.propal]
+[o:dolibarr-association:p:dolibarr:r:propal]
 file_filter = htdocs/langs/<lang>/propal.lang
 source_file = htdocs/langs/en_US/propal.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.receiptprinter]
+[o:dolibarr-association:p:dolibarr:r:receiptprinter]
 file_filter = htdocs/langs/<lang>/receiptprinter.lang
 source_file = htdocs/langs/en_US/receiptprinter.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.receptions]
+[o:dolibarr-association:p:dolibarr:r:receptions]
 file_filter = htdocs/langs/<lang>/receptions.lang
 source_file = htdocs/langs/en_US/receptions.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.recruitment]
+[o:dolibarr-association:p:dolibarr:r:recruitment]
 file_filter = htdocs/langs/<lang>/recruitment.lang
 source_file = htdocs/langs/en_US/recruitment.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.resource]
+[o:dolibarr-association:p:dolibarr:r:resource]
 file_filter = htdocs/langs/<lang>/resource.lang
 source_file = htdocs/langs/en_US/resource.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.salaries]
+[o:dolibarr-association:p:dolibarr:r:salaries]
 file_filter = htdocs/langs/<lang>/salaries.lang
 source_file = htdocs/langs/en_US/salaries.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.sendings]
+[o:dolibarr-association:p:dolibarr:r:sendings]
 file_filter = htdocs/langs/<lang>/sendings.lang
 source_file = htdocs/langs/en_US/sendings.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.sms]
+[o:dolibarr-association:p:dolibarr:r:sms]
 file_filter = htdocs/langs/<lang>/sms.lang
 source_file = htdocs/langs/en_US/sms.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.stocks]
+[o:dolibarr-association:p:dolibarr:r:stocks]
 file_filter = htdocs/langs/<lang>/stocks.lang
 source_file = htdocs/langs/en_US/stocks.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.stripe]
+[o:dolibarr-association:p:dolibarr:r:stripe]
 file_filter = htdocs/langs/<lang>/stripe.lang
 source_file = htdocs/langs/en_US/stripe.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.supplier_proposal]
+[o:dolibarr-association:p:dolibarr:r:supplier_proposal]
 file_filter = htdocs/langs/<lang>/supplier_proposal.lang
 source_file = htdocs/langs/en_US/supplier_proposal.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.suppliers]
+[o:dolibarr-association:p:dolibarr:r:suppliers]
 file_filter = htdocs/langs/<lang>/suppliers.lang
 source_file = htdocs/langs/en_US/suppliers.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.ticket]
+[o:dolibarr-association:p:dolibarr:r:ticket]
 file_filter = htdocs/langs/<lang>/ticket.lang
 source_file = htdocs/langs/en_US/ticket.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.trips]
+[o:dolibarr-association:p:dolibarr:r:trips]
 file_filter = htdocs/langs/<lang>/trips.lang
 source_file = htdocs/langs/en_US/trips.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.users]
+[o:dolibarr-association:p:dolibarr:r:users]
 file_filter = htdocs/langs/<lang>/users.lang
 source_file = htdocs/langs/en_US/users.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.website]
+[o:dolibarr-association:p:dolibarr:r:website]
 file_filter = htdocs/langs/<lang>/website.lang
 source_file = htdocs/langs/en_US/website.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.withdrawals]
+[o:dolibarr-association:p:dolibarr:r:withdrawals]
 file_filter = htdocs/langs/<lang>/withdrawals.lang
 source_file = htdocs/langs/en_US/withdrawals.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.workflow]
+[o:dolibarr-association:p:dolibarr:r:workflow]
 file_filter = htdocs/langs/<lang>/workflow.lang
 source_file = htdocs/langs/en_US/workflow.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 
-[dolibarr.zapier]
+[o:dolibarr-association:p:dolibarr:r:zapier]
 file_filter = htdocs/langs/<lang>/zapier.lang
 source_file = htdocs/langs/en_US/zapier.lang
 source_lang = en_US
-type = MOZILLAPROPERTIES
+type        = MOZILLAPROPERTIES
 

+ 7 - 7
COPYRIGHT

@@ -24,16 +24,16 @@ Component              Version       License                     GPL Compatible
 -------------------------------------------------------------------------------------
 PHP libraries:
 EvalMath               1.0           BSD                         Yes             Safe math expressions evaluation
-Escpos-php             2.2           MIT License                 Yes             Thermal receipt printer library, for use with ESC/POS compatible printers
+Escpos-php             3.0           MIT License                 Yes             Thermal receipt printer library, for use with ESC/POS compatible printers
 GeoIP2                 0.2.0         Apache License 2.0          Yes             Lib to make geoip convert
-Mobiledetect           2.8.39        MIT License                 Yes             Detect mobile devices browsers
+Mobiledetect           2.8.41        MIT License                 Yes             Detect mobile devices browsers
 NuSoap                 0.9.5         LGPL 2.1+                   Yes             Library to develop SOAP Web services (not into rpm and deb package)
 PEAR Mail_MIME         1.8.9         BSD                         Yes             NuSoap dependency
-ParseDown              1.6           MIT License                 Yes             Markdown parser
+ParseDown              1.7.4         MIT License                 Yes             Markdown parser
 PCLZip                 2.8.4         LGPL-3+                     Yes             Library to zip/unzip files
-PHPDebugBar            1.15.1        MIT License                 Yes             Used only by the module "debugbar" for developers
+PHPDebugBar            1.18.2        MIT License                 Yes             Used only by the module "debugbar" for developers
 PHP-Imap               2.7.2         MIT License                 Yes             Library to use IMAP with OAuth
-PHPSpreadSheet         1.8.2         LGPL-2.1+                   Yes             Read/Write XLS files, read ODS files
+PHPSpreadSheet         1.12.0        LGPL-2.1+                   Yes             Read/Write XLS files, read ODS files
 php-iban               4.1.1         LGPL-3+                     Yes             Parse and validate IBAN (and IIBAN) bank account information in PHP
 PHPoAuthLib            0.8.2         MIT License                 Yes             Library to provide oauth1 and oauth2 to different service
 PHPPrintIPP            1.3           GPL-2+                      Yes             Library to send print IPP requests
@@ -43,7 +43,7 @@ Restler                3.1.1         LGPL-3+                     Yes
 Sabre                  3.2.2         BSD                         Yes             DAV support
 Swift Mailer           5.4.2-DEV     MIT License                 Yes             Comprehensive mailing tools for PHP
 Symfony/var-dumper     ??? 			 MIT License                 Yes             Library to make var dump (used by DebugBar)
-Stripe                 7.67.0        MIT Licence                 Yes             Library for Stripe module
+Stripe                 10.7.0        MIT Licence                 Yes             Library for Stripe module
 TCPDF                  6.3.2         LGPL-3+                     Yes             PDF generation
 TCPDI                  1.0.0         LGPL-3+ / Apache 2.0        Yes             FPDI replacement
 
@@ -51,7 +51,7 @@ JS libraries:
 Ace                    1.4.14        BSD                         Yes             JS library to get code syntaxique coloration in a textarea.
 ChartJS                3.7.1         MIT License                 Yes             JS library for graph
 CKEditor               4.18          LGPL-2.1+                   Yes             Editor WYSIWYG
-jQuery                 3.6.0         MIT License                 Yes             JS library
+jQuery                 3.6.4         MIT License                 Yes             JS library
 jQuery UI              1.13.2        GPL and MIT License         Yes             JS library plugin UI
 jQuery select2         4.0.13        GPL and Apache License      Yes             JS library plugin for sexier multiselect. Warning: 4.0.6+ create troubles without patching css
 jQuery blockUI         2.70.0        GPL and MIT License         Yes             JS library plugin blockUI (to use ajax popups)

+ 441 - 152
ChangeLog

@@ -5,16 +5,253 @@ English Dolibarr ChangeLog
 
 ***** ChangeLog for 18.0.0 compared to 17.0.0 *****
 
-NEW: PHP 8.2 compatibility:
+For uses:
+---------
+
+NEW: PHP 8.2 compatibility (test not yet completed).
+NEW: #22740 Add OpenID Connect impl
+NEW: #23436 Group social networks fields
+NEW: Accountancy - Add specific page to export accounting data rather than the journals page
+NEW: Accountancy - Add sub-account balance FPC22
+NEW: Accountancy - Manage customer retained warranty FPC21+
+NEW: Accountancy - Manage intra-community VAT on supplier invoices - FPC22
+NEW: adapt category and product pictures sizes on takepos
+NEW: Add ability of ODT support to supplier invoices
+NEW: Add a public virtual card page for each user
+NEW: Add a status on supplier price ref (WIP to close a supplier ref)
+NEW: Add a trigger when create a shipping line batch and fix propagate missing errors
+NEW: add bookmarks in selectable landing pages for users
+NEW: Add column ext_payment_site for societe_rib
+NEW: add convertion of images to webp for a single image
+NEW: Add CRC for currency symbol before amount
+NEW: add css editor to admin ihm
+NEW: add customer code to invoices listing
+NEW: Add Customer Order delivered (ORDER_NEW) in module Notification
+NEW: add customize CSS in admin/ihm settings
+NEW: Add date due and labels into customer comm card
+NEW: Add dropdown button actions (example on Create button on project)
+NEW: added an option to display the progress of tickets on the public interface
+NEW: Add field reply-to in email collector as possible filter
+NEW: Add filter on nb of generation done in list of recurring invoices
+NEW: Add filters and sort on product unit column
+NEW: adding button Send Email on the salary file
+NEW: Add link to edit VAT list from error message of missing VAT
+NEW: add link to thirdparty tickets history
+NEW: add margin in paiement/card.php
+NEW: Add mass action delete on VAT
+NEW: Add more zip, town, country for owner of a bank account
+NEW: add numbering modules for members
+NEW: add option keepspace into dol_string_nospecialchar()
+NEW: Add origin info when create a product batch when created from a movement stock
+NEW: Add possibility to choose format #21426
+NEW: Add SQL contraint on product_stock table to allow only exsting product and warehouse#23543
+NEW: Add statistics by amount on statistics of products.
+NEW: Add STRIPE_DEBUG, a way to log Stripe IPN
+NEW: Add sub total in order list det
+NEW: Add warehouse create and modify triggers.
+NEW: Add widget box_members_by_tags.php
+NEW: An external module can modify the quick search fields
+NEW: Auto activate some modules on install (export/import/wysiwyg editor)
+NEW: Autofill email form with the email template with status "Default" on
+NEW: Bank name no more mandatory on creation. Can be generated if empty.
+NEW: batch referential objets
+NEW: Better responsive for mass actions
+NEW: Can add any contact on events if global MAIN_ACTIONCOM_CAN_ADD_ANY_CONTACT is set at 1
+NEW: Can add the add now link on date into addfieldvalue()
+NEW: Can bin accounting line for a given month
+NEW: Can edit account on miscellaneous payment (if not transfered)
+NEW: Can edit inline the vat number from supplier tab
+NEW: Can fill date of salary payment with date of start of salary
+NEW: Can filter on a custom group of accounts. Perf or ledger list.
+NEW: Can go back to draft on shipment when stock change not on validate
+NEW: Can modify bank account of sepa payment (if file not sent yet)
+NEW: Can modify margin rates in offers like VAT rates.
+NEW: Can modify the date of payment of a salary (if not reconciled)
+NEW: Can now edit service name for oauth token
+NEW: Can receive more than qty ordered on reception
+NEW: Can select several warehouses into the view stock at date in past
+NEW: Can select the export format during export of journals
+NEW: Can set a checkbox in formconfirm by clicking on the label
+NEW: Can set background style with MAIN_LOGIN_BACKGROUND_STYLE
+NEW: Can set flag default value on email templates
+NEW: Can set the page "List of opportunities" as landing page
+NEW: Can show the sql request used on emailing selection
+NEW: can stay on edit field when errors occurs
+NEW: Can test a geoip conversion from the geoip setup page
+NEW: Captcha for public member's subscription form
+NEW: category of operation for crabe PDF model
+NEW: color for start date and owner
+NEW: comment in api_mymodule for seperate methods
+NEW: constant PROPALE_ADDON_NOTE_PUBLIC_DEFAULT
+NEW: create email substitution variable for intervention signature URL
+NEW: Date for salary payment includes the hour/min
+NEW: Debug the custom CSS feature to avoid a directory search/scan at
+NEW: dev name
+NEW: Disable bad reputation product price
+NEW: dolExplodeIntoArray can accept regex
+NEW: dol_sort_array can sort on alphabetical order even if val is num
+NEW: don't have closed contact proposed as receiver for the mails
+NEW: Dynamic choice of warehouse and batch in MO production.
+NEW: element time integration code + sql
+NEW: events list with color
+NEW: expend/collapse list of social networks
+NEW: filter for Signed+Billed in proposals
+NEW: Filter on amount and qty on list of service's contracts
+NEW: filter on entity in import
+NEW: formconfirm can support field with format datetime
+NEW: getCommonSubstitutionArray to have more substitute keys
+NEW: helper functions for dates + small demo case
+NEW: hook printFieldListFrom in contact list
+NEW: inc.php: handle parameters from argv
+NEW: Increment website counter on each page access in website module
+NEW: Invalidate all sessions of a user when password is modified.
+NEW: Invoice - Show Category of operations
+NEW: iSuiteExpert export model
+NEW: Keep a link between user created from recruitment and application
+NEW: limit load products in takepos
+NEW: List product in orders
+NEW: Make it possible to select hours and minutes in form_confirm
+NEW: map table to element for get entity in import
+NEW: migration script + delete old table + rename fields and indexes
+NEW: Multicurrency REST API to create, update, delete, update rate...
+NEW: Multiselect for filter on prospection status
+NEW: Name and date to print on PDF Sign
+NEW: [Bulk delete Project tasks]
+NEW: No overwrite of optionals during put() contact
+NEW: Notification for Sign or Refused Propal from Online Page
+NEW: notify also the contributor affected to a ticket if a new message public is post (add global TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_ALSO_CONTRIBUTOR)
+NEW: Now we can edit amount on vat and salaries clone action
+NEW: only get openned contact from liste_contact function, to not have acces to closed contact as mail receiver
+NEW: operation type in email collector to load or create contact
+NEW: option filter for NoSalesRepresentativeAffected in proposals list
+NEW: Option to manage deposit slips for more payment modes (not only
+NEW: Option to show column for field and line selection on the left
+NEW: Order export : allow to export field 'shipment method'.
+NEW: payment default values when supplier order created from reception
+NEW: Payment : manage contracts
+NEW: Possibility to link to German pages in help
+NEW: presend mass action in contact list
+NEW: product images on popup are cached
+NEW: Provide the oldcopy value when calling setValueFrom() function with a trigger key
+NEW: Quadratus export with attachments in accountancy export
+NEW: referential objects of batch
+NEW: remove default percentage for event creation url
+NEW: remove keys whose table element is the same as element in map list
+NEW: repair script skip views
+NEW: Save date to invalidate other session into user table
+NEW: search on time spent duration range
+NEW: Send an email when ticket assigned
+NEW: Send a notification email when ticket assigned
+NEW: sepaStripe now creates the payment mode with type pm_ using new API
+NEW: set payment default values when supplier order created from reception
+NEW: set ticket status to answered if the client has answered from the public interface
+NEW: set today start time at begining
+NEW: Setup emailcollector easier. Can also use ! for negative search.
+NEW: Show counter of access of website in website list
+NEW: Show main currency in company info user tooltip
+NEW: Show module into list of emails templates
+NEW: Show picto into the combobox of widgets
+NEW: Show supplier invoice ref of direct debit transfer tab invoices
+NEW: show supplier name in getNomUrl of supplier order
+NEW: sort of column of custom group of account
+NEW: Stock limit for alert and desired optimal stock by product and warehouse import
+NEW: substitute date now in email collector
+NEW: Support different bank account for several direct debit payments
+NEW: Support multiselect in the warehouse selection combo box
+NEW: Support option MAIN_SECURITY_MAXFILESIZE_DOWNLOADED #yogosha10660
+NEW: table llx_element_time to store time spent on several elements (mo, ticket...)
+NEW: The batch for remind on due date can be setup for using validation date
+NEW: The refresh link for imap collector is always visible
+NEW: The upgrade process can be done by creating a file upgrade.unlock
+NEW: Tooltip to explain how to add a photo on a product
+NEW: Use a cache file for external RSS in calendar
+NEW: Use by default the domain $dolibarr_main_url_root for SMTP HELO
+NEW: use more recent model by default
+NEW: VAT can be modified during add of line
+NEW: write all fields and their properties in asciidoc format
+NEW: Can add an array of several links in date selector
+
+For developers or integrators:
+------------------------------
+
+NEW: triggers on mailing
+NEW: add function for listiong objects from directory
+NEW: Add helplist property to describe fields of obects
+NEW: Add hook in loadLotStock() in html.formproduct.class.php file, add hook 'llxFooter', Add hook online sign
+NEW: Update lib parsedownto 1.7.4, phpspreadsheet lib to v1.12, ESCPOS v3.0
+NEW: Support contact in post() document API
+NEW: updating in modulbuilder on tab Menu when adding object
+NEW: Add/Edit permissions in ModuleBuilder
+NEW: Better generated documentation for modulebuilder
+NEW: Add sent info in the parameters provided to the hook sendMailAfter
+NEW: add setAsSelectUser into factory for generic setup page
 
 
 WARNING:
 
 Following changes may create regressions for some external modules, but were necessary to make Dolibarr better:
+* Minimal PHP version is now PHP 7.1 instead of PHP 7.0
 * The deprecated method "escapeunderscore()" of database handlers has been removed. You must use "escapeforlike()" instead.
 * The method "nb_expedition()" has been renamed into "countNbOfShipments()" 
 * Revert default type of hooks. Default is now 'addreplace' hooks (and exception become 'output' hooks, that become deprecated).
 * Deprecated property libelle removed from entrepot class.
+* The type 'text' in ->fields property dos not accept html content anymore. Use the type 'html' for that.
+* The module for WebService SOAP API have been deprecated. Use instead the Webservice REST API module.
+* The method htmlPrintOnlinePaymentFooter() used for public footer pages has been renamed into htmlPrintOnlineFooter() and moved into company.lib.php
+
+
+
+***** ChangeLog for 17.0.1 compared to 17.0.0 *****
+
+FIX: 17.0 PHP Warning invalid argument supplied for foreach
+FIX: #[23799] - External users are not able to create events - correction
+FIX: #23966 Error "Param dbt_keyfield is required but not defined
+FIX: #24138 Fix box_birthdays SQL for postgres
+FIX: #24201 Upload of external module fails to copy from incorrectly generated temp source dir
+FIX: #24240 Dolibarr V17.0.0 PHP8 fatal error
+FIX: accountancy lettering: better error management
+FIX: accountancy lettering: correctly calculated number of lettering operations done
+FIX: accountancy lettering: error management and prevention
+FIX: accountancy lettering: prevent null results when fetching link with payments
+FIX: action delete card fac rec
+FIX: Add bookmark with search fields that are arrays (backport 4157263cb898f1847cfcfc22dee6007c01b13a4d)
+FIX: Add missing hook on LibStatut
+FIX: Add more context for selectForFormsListWhere Hook
+FIX: Autofill / clear qty in inventory page
+FIX: avoid php8 warnings
+FIX: avoid phpunit error
+FIX: can not show all csv fields (a reason for that ?)
+FIX: change date on select date input when prefix is used
+FIX: dol_textishtml() function
+FIX: expense report accountancy: sql syntax error when performing automatic linking
+FIX: Extrafields in Notes to unify with orders or invoices.
+FIX: fatal error when margin enable (missing check on element), fix User::hasRight() when checking a margin right
+FIX: feedbacks
+FIX: FILTER_VALIDATE_EMAIL param is not a string
+FIX: #24298 No error or 0.00 instead of NULL in database anymore when emptying an extrafield of type price on a propal card
+FIX: full group by handle
+FIX: holiday counter massaction: ErrorBadValueForParamNotAString and PHP 8 warning when no approval user right
+FIX: installation superadmin creation: PHP 8 warning
+FIX: invoices order on sells journal
+FIX: it was not possible to update extrafields of expedition lines with batch without editing batch value
+FIX: limit after order in get objects in category
+FIX: method dolGetGlobalString not defined with saphir
+FIX: missing column default workstation
+FIX: missing drop foreign key before modify field
+FIX: missing "multidir_output" for project sharing (Multicompany)
+FIX: missing protection on ajax public ticket page for valid email
+FIX: ODT management inverted between purchase invoice and order
+FIX: PDF Espadon => display extrafields
+FIX: PDF Espadon Expedition : notes and tracking number
+FIX: Phpunit Rename WebsiteTest.class.php to WebsiteTest.php
+FIX: project referent elements list: conf to hide tasks was flipped
+FIX: Protection on agenda view for a thirdparty id that does not exist
+FIX: search_project_user
+FIX: societe list: regression to redirection to customer card when single result of search filters
+FIX: SQL error "unknown column p.fk_soc" because ANSI-92 joins take precedence over ANSI-89 joins
+FIX: task have the same entity of project
+FIX: token error when closing ticket from public interface
+FIX: Warning on purchase order + Property fk_commande not defined
 
 
 
@@ -24,10 +261,9 @@ For users:
 ---------------
 
 NEW: Minimal PHP version is now PHP 7.0 instead of PHP 5.6
-NEW: #19680 Add option PRODUCT_ALLOW_EXTERNAL_DOWNLOAD to automatically have uploaded files shared publicly by a link
+NEW: #21780 Add pid field to Cronjob class and store PID on job execution
 NEW: #20650 can move the checkbox column on left (experimental option MAIN_CHECKBOX_LEFT_COLUMN)
 NEW: #21000 Added columns 'alias_name' on project, supplier invoice, supplier order, supplier proposals and task list
-NEW: #21780 Add pid field to Cronjob class and store PID on job execution
 NEW: #21395 Added option for dark theme mode in display - color and theme
 NEW: #21397 added option to auto define barcode numbers for third-parties in barcode module setup
 NEW: #21399
@@ -43,233 +279,286 @@ NEW: #22546 can now set user supervisors using mass action in htdocs/user
 NEW: #22594 can chose if VAT ID is unique or not for third parties
 NEW: #22622 all partnerships displayed on tab partnership of a thirdparty and member
 NEW: #22676 massaction for updating product prices
-NEW: #22735 Massaction to affect users on projects
-NEW: #25594 can chose if VAT ID is unique or not for third parties
+NEW: #22735 Massaction to assign users on projects
 NEW: #4482 adding js to hide/show advanced option on the export data page
-
+NEW: Accountancy - Add a graphic option to enable lettering function - FPC21
+NEW: Accountancy - Add a way to clean some words when you generate thirdparty accounting account
+NEW: Accountancy - Added an option during export to export or not the lettering FPC21
+NEW: Accountancy - Manage supplier deposit with specific account
+NEW: Accountancy - Model Digitaria - Add a way to clean some words when you generate thirdparty accounting account FPC22
+NEW: Add a button "Test collect" in email collector
 NEW: Add a constant to disallow modification of the product reference.
+NEW: Add a method doAutoRenewContracts that can be used as a cron task.
+NEW: Add " as enclosure by default for CSV export. Keep removing CR/LF.
 NEW: add attached file in presend email form of thirdparty card
 NEW: Add a way to enter LICENSE file content in property of website
+NEW: Add badge in admin extrafields setup
+NEW: add constant PROPAL_BYPASS_VALIDATED_STATUS
+NEW: Add date event (!= date project) and location on event organization
 NEW: Add employment anniversary in birthday box
+NEW: Add extrafield type "IP" to store IP addresses
+NEW: Add fail2ban rules examples to limit access to /public pages
+NEW: Add filter "Product subject to lot/Serial" in stock per lot/serial
 NEW: Add hidden option MAIN_EMAIL_SUPPORT_ACK to restore Email ack checkbox (feature abandonned by mailers)
+NEW: Add IMAP port setting on email collector module
+NEW: Adding JAPAN Chart-of-Account and regions/departments
+NEW: Adding NIF verification for Algeria
 NEW: Add link to create an element from the category page
+NEW: add margin infos to takepos invoice lines
 NEW: Add max size send for "backup and link to mail" option
 NEW: Add method httponly_accessforbidden()
 NEW: Add more advices into the Setup security page
-NEW: Add new global variable for keeping the previous signature information on proposale (case of reopen a proposale)
-NEW: Add objectLink on expedition
-NEW: Add oldcopy to Ticket so triggers intercepting TICKET_MODIFY have access to old values of the updated properties
+NEW: Add new global variable for keeping the previous signature information on proposal (case of reopening a proposal)
+NEW: Add objectLink on shipment
 NEW: Add option --force on CLI cron_run_jobs.php
 NEW: Add option "Show price on the generated documents for receptions"
+NEW: Add performance index (name for company and contact) and llx_bank_url(url_id)
+NEW: Add picto property on sub-module for password generation
+NEW: add redirect on action confirm addconsumedline and addproduceline
+NEW: Add a new advanced permission "read price"
+NEW: Add substitution key __SENDEREMAIL_SIGNATURE__
+NEW: Add the referrer-policy to "same-origin" by default on all public pages.
+NEW: Add the SMTP header References on ticket email created by email
+NEW: Add the thirdparty column to the time list (projet/tasks/time.php)
+NEW: Add trigger to record the event of sending an email from a project #20912
+NEW: Allow download link option in module configuration (propal,invoice,supplier proposal, order)
+NEW: Bulk action to remove a category in list/search website pages
+NEW: Can copy/paste images into emails sent.
+NEW: Can edit label of an emailing even once sent
+NEW: Can edit property css, cssview, csslist on extrafields
+NEW: Can enter the unit price including the vat when adding new product lines on invoices, orders, proposals, ...
+NEW: Can invoice task time per different services
+NEW: Can join several files by default on email form
+NEW: Can send an email on scheduled job error
+NEW: Can set a commercial discount by entering amount including VAT
+NEW: Can set a monthly frequency (or multiple) in cron tasks.
+NEW: Can set start and end dates and comment on button "Activate all services"
+NEW: Can sort on preselected best supplier price
+NEW: Can use products categories to make inventory
+NEW: Change filter type on tickets list into a multiselect combo
+NEW: conf TIMESPENT_ALWAYS_UPDATE_THM, when it's on we always check current thm of user to update it in task time line
+NEW: constant PROPAL_NEW_AS_SIGNED
+NEW: show date delivery planned on orders linked to company and product
+NEW: Default doc template of contracts is not mandatory
+NEW: Default values in extrafields are no more limited to 255 char.
+NEW: display currency in takepos menu
+NEW: Enable online signature for interventions
+NEW: extrafield price with currency
+NEW: filter on reception dates (from / to) in cheque paiement card
+NEW: Members: default_lang for members
+NEW: Members: Table of membership types
+NEW: Members: add free membership amounts at the membership type level
+NEW: TakePOS: Header Scroll in TakePOS
+NEW: TakePOS: add price to product box in TakePOS
+NEW: TakePOS: add setup parameters, can setup terminal name
+NEW: TakePOS: support of Stripe Terminal with TakePOS
+NEW: TakePOS: Receipt preview in TakePOS setup
+NEW: TakePOS: different product list on smartphone
+NEW: Website: can delete a whole website if disabled
+NEW: Website: can remove a website template
+NEW: Website: can set header "Strict-Transport-Security" in web sites.
+NEW: Website: can switch status of website and page from the website toolbar
+NEW: Website: Templates of websites are now directories and not zip into core repo
+NEW: Website: add 4 other templates in website module
+NEW: Website: Add counters for public access of pages on a website
+NEW: If we select another view list mode, we keep it
+NEW: Init module bookcal
+NEW: Encrypt all sensitive constants in llx_const using dolEncrypt/dolDecrypt
+NEW: Invoice - Add french mention on pdf when vat debit option is on
 NEW: invoice export : add accounting affectation
 NEW: label on products categories filter
+NEW: The link "add to bookmark" is always on top in the bookmark popup
+NEW: MAIN_SEARCH_CATEGORY_PRODUCT_ON_LISTS const to show category customer filter
+NEW: Make module WebservicesClient deprecated. Use module WebHook instead.
 NEW: manage no email with thirdparties (better for GDPR)
+NEW: Manage Position (Rank) on Contract Lines
 NEW: Manage VAT on all lines on purchases cycle
+NEW: Page to show virtual stock at a future date
 NEW: On a bank reconciled line, we can modify the bank receipt
+NEW: On a form to send an email, we show all emails of all contacts of object
+NEW: Option PRODUCTBATCH_SHOW_WAREHOUSE_ON_SHIPMENT showing wh on PDF
+NEW: Option PRODUIT_DESC_IN_FORM accept (desktop only or +smartphone)
+NEW: Page for mass stock transfer can be used with no source stock
 NEW: parent company column and filter in invoice and order list
+NEW: Add "Show Sales rep" option for PDF
+NEW: Picto for shared link is clickable
 NEW: possibility to select scopes with checkbox for Oauth tokens
 NEW: private and public note on user, thirdparty and contact list
-NEW: Public counters feature
+NEW: product categories filter on inventory list
+NEW: Product supplier price: autofill default supplier VAT
+NEW: Project - author field become an available column on lists
+NEW: Reception - Add a from/to on search on date field
+NEW: Start a simple support of recurrent events on agenda
+NEW: Resize parent company column in order list
 NEW: Saved token of OAUTH module are now encrypted into llx_oauth_token
 NEW: Save one click to select on delivery ack, on emails.
 NEW: scheduled job to send unpaid invoice reminder can now use the cc and bcc from email template
+NEW: set thirdparty type with company modify trigger
+NEW: Show also scheduled task never finished in scheduled task widget
+NEW: show badge with number of extrafields in setup
+NEW: show category tree in sellist and chkbxlst for common object
+NEW: Show picto and color into combo for selection of tags
+NEW: show product label on inventory
+NEW: show sell-by and eat-by dates only if not empty
+NEW: show SellBy/EatBy dates for each batch product in shipment card
+NEW: Can skip accept/refuse steps for proposals (option PROPAL_SKIP_ACCEPT_REFUSE)
 NEW: experimental SMTP using PhpImap allowing OAuth2 authentication (need to add option MAIN_IMAP_USE_PHPIMAP)
 NEW: can substitue project title in mail template
+NEW: Supplier order list - Add column private and public note
 NEW: The purge of files can purge only if older than a number of seconds
 NEW: Update ActionComm type_code on email message ticket
-NEW: Finance - VAT - Admin - Add information on deadline day for submission of VAT declaration
+NEW: VAT - Admin - Add information on deadline day for submission of VAT declaration
+NEW: expand/collapse permissions on user permission page
+NEW: Show delivery mode on PDF for proposals
 NEW: Add the target to select attendees of event for emailings
-NEW: add redirect on action confirm addconsumedline and addproduceline
-NEW: Add the referrer-policy to "same-origin" by default on all public pages.
-NEW: Add trigger to record the event of sending an email from a project #20912
-NEW: Allow download link option in module configuration (propal, order, invoice, supplier proposal)
-NEW: Can enter the unit price including the VAT
-NEW: Can invoice task time per different services
-NEW: Can set a commercial discount by entering amount including VAT
-NEW: Can set start and end dates and comment on button "Activate all services"
-NEW: can sort and preselected best supplier price
-NEW: show date delivery planned on orders linked to company and product
-NEW: filter on reception dates (from / to) in cheque paiement card
-
-NEW: Accountancy - Add a graphic option to enable lettering function - FPC21
-NEW: Accountancy - Add a way to clean some words when you generate thirdparty accounting account
-NEW: Accountancy - Added an option during export to export or not the lettering FPC21
-NEW: Accountancy - Manage supplier deposit with specific account
-NEW: Accountancy - Model Digitaria - Add a way to clean some words when you generate thirdparty accounting account FPC22
-NEW: Agenda - start a simple support of recurrent events on agenda
-NEW: Bank - add salaries & VAT in tab planned entries
-NEW: Contracts - add a method doAutoRenewContracts that can be used as a cron task
-NEW: Contracts - default template of contract is not mandatory
-NEW: Contracts - Manage Position (Rank) on Contract Lines
-NEW: EMail - can copy/paste images into emails sent
-NEW: EMail - can edit label of an emailing even once sent
-NEW: EMail - can join several files by default on email form
-NEW: EMail - can send an email on scheduled job error
-NEW: EMail - on a form to send an email, we show all emails of all contacts of object
-NEW: EMail - add the SMTP header References on ticket email created by email
-NEW: EMail - add substitution key __SENDEREMAIL_SIGNATURE__
-NEW: EMail-Collector - add IMAP port setting 
-NEW: EMail-Collector - add a button "Test collect"
-NEW: Export - Add " as enclosure by default for CSV export. Keep removing CR/LF.
-NEW: Event-Organization - add date event (!= date project) and location on event organization
-NEW: Extrafields - add badge in admin extrafields setup
-NEW: Extrafields - can edit property css, cssview, csslist on extrafields
-NEW: Extrafields - default values in extrafields are not more limited to 255 char.
-NEW: Extrafields - field price with currency
-NEW: Extrafields - support IP type to store IP addresses
-NEW: Interventions - enable online signature for interventions
-NEW: Invoice - Add french mention on pdf when vat debit option is on
-NEW: Members - default_lang for members
-NEW: Members - Table of membership types
-NEW: Members - add free membership amounts at the membership type level
-NEW: Orders - resize parent company column in order list
-NEW: Products supplier price - autofill default supplier VAT
-NEW: Projects - add author on list
-NEW: Projects - add thirdparty column to the time list (projet/tasks/time.php)
-NEW: Proposals - show delivery mode on PDF for proposals
-NEW: Proposals - skip accept/refuse process for proposals (option PROPAL_SKIP_ACCEPT_REFUSE)
-NEW: Reception - add a from/to on search on date field
-NEW: Stock - page for mass stock transfer can be used with no source stock
-NEW: Stock - product categories filter on inventory list
-NEW: Stock - show product label on inventory
-NEW: Stock - manage virtual stock at a future date
-NEW: Stock Inventory - add filter "Product subject to lot/Serial" in stock per lot/serial
-NEW: Stock Inventory - can use products categories to make inventory
-NEW: Supplier Order List - add column private and public note
-NEW: TakePOS - add margin infos to TakePOS invoice lines
-NEW: TakePOS - add price to product box in TakePOS
-NEW: TakePOS - add setup parameters, can setup terminal name
-NEW: TakePOS - different product list on smartphone
-NEW: TakePOS - display currency in TakePOS menu
-NEW: TakePOS - Header Scroll in TakePOS
-NEW: TakePOS - Receipt preview in TakePOS setup
-NEW: TakePOS - support of Stripe Terminal with TakePOS
-NEW: Thirdparty - set thirdparty type with company modify trigger
-NEW: Tickets - change filter type on tickets list into a multiselect combo
-NEW: Website - can delete a whole website if disabled
-NEW: Website - can remove a website template
-NEW: Website - can set header "Strict-Transport-Security" in web sites.
-NEW: Website - can switch status of website and page from the website toolbar
-NEW: Website - Templates of websites are now directories and not zip into core repo
-NEW: Website - add 4 other templates in website module
-
-General:
-NEW: Actions:  Bulk action to remove a category in list/search website pages
-NEW: Cronjobs: can set a monthly frequency (or multiple) in cron tasks
-NEW: Database: Encrypt all sensitive constants in llx_const
-NEW: Database: Add performance index (name for company and contact) and llx_bank_url(url_id)
-NEW: Database: Introduce dolEncrypt and dolDecrypt to be able to encrypt data in db
-NEW: GUI: If we select another view list mode, we keep it
-NEW: GUI: the link "add to bookmark" is always on top in the bookmark popup
-NEW: GUI: Picto for shared link is clickable
-NEW: GUI: add picto property on sub-module for password generation
-NEW: GUI: show also scheduled task never finished in scheduled task widget
-NEW: GUI: show badge with number of extrafields in setup
-NEW: GUI: show category tree in sellist and chkbxlst for common object
-NEW: GUI: show picto and color into combo for selection of tags
-NEW: GUI: show sell-by and eat-by dates only if not empty
-NEW: GUI: show SellBy/EatBy dates for each batch product in shipment card
-NEW: GUI/Permissions: expand/collapse permissions on user permission page
-NEW: Permissions: add a new advanced permission "read price"
-NEW: Print: add show "Sales rep" option for PDF
-NEW: Security: add fail2ban rules examples to limit access to /public pages
-
- Option / Const for System: 
-NEW: FICHINTER_ALLOW_EXTERNAL_DOWNLOAD
-NEW: MAIN_SEARCH_CATEGORY_PRODUCT_ON_LISTS  -  const to show category customer filter
-NEW: PRODUCTBATCH_SHOW_WAREHOUSE_ON_SHIPMENT  -  showing warehouse on PDF
-NEW: PRODUIT_DESC_IN_FORM accept  -  desktop only or +smartphone
-NEW: PROPAL_BYPASS_VALIDATED_STATUS
-NEW: PROPAL_NEW_AS_SIGNED
-NEW: TIMESPENT_ALWAYS_UPDATE_THM  -  when it's on we always check current thm of user to update it in task time line
-
- Localisation:
-NEW: adding JAPAN Chart-of-Account and regions/departments
-NEW: adding NIF verification for Algeria
+NEW: Can set background style with MAIN_LOGIN_BACKGROUND_STYLE
 
  Modules
 NEW: Experimental module Asset
-NEW: Init module bookcal
-NEW: Make module WebservicesClient deprecated. Use module WebHook instead.
-
 
 For developers or integrators:
 ------------------------------
 
-NEW: ModuleBuilder can generate code of class from an existing SQL table
+NEW: ModuleBuilder can generate code for a class from an existing SQL table
+NEW: #22370 Modulebuilder supports 'alwayseditable' (like extrafields)
 NEW: #20912 Add trigger to record the event of sending an email from a project
 NEW: #21750 Added "Get lines and Post lines from BOM" at the REST Service
-NEW: #22370 Modulebuilder supports 'alwayseditable' (like extrafields)
+NEW: Removed completely the need for the library adodbtime
+NEW: hook on agenda pages
+NEW: hook to complete payment in TakePOS
+NEW: hook "changeHelpURL" to modify target of the help button
+NEW: hook formConfirm on action comm card
+NEW: hook to modify supplier product html select
+NEW: Add new hook for show virtual stock details on product stock card
+NEW: Add new hooks for actioncomm
 NEW: conf->global->SYSLOG_FILE_ONEPERSESSION accept a string
-NEW: All ajax pages have now a top_httphead()
-
- API:
+NEW: translation for contact type API, setup/ticket API, shipping method API
+NEW: All ajax pages have now a header build with top_httphead()
+NEW: support multilang in Civilities API
 NEW: Add API for the partnership module
 NEW: Add "Get lines and Post lines from BOM" in the API
-NEW: translate for contact type API, setup/ticket API, shipping method API
-NEW: support multilang in Civilities API
-
- Hooks:
-NEW: Actioncomm - add new hooks for actioncomm
-NEW: Actioncomm - hook formConfirm on actioncomm card
-NEW: Agenda - hook on agenda pages
-NEW: Help - hook "changeHelpURL" to modify target of the help button
-NEW: Product - add hook to show virtual stock details on product stock card
-NEW: Product - add hook to modify supplier product html select
-NEW: TakePOS - add hook to complete payment in TakePOS
-
-
-NEW: Removed completely the need for the library adodbtime
-NEW: Replace fk_categories_product with categories_product in inventory
-NEW: Rewrite of SQL request. Removed the join on category (for filter on categ), replaced with a EXISTS/NOT
+NEW: Replace property fk_categories_product with categories_product in inventory class
+NEW: Rewrite of SQL request. Removed the join on category table (for filter on category), replaced with a EXISTS/NOT
+NEW: Add oldcopy to Ticket so triggers intercepting TICKET_MODIFY have access to old values of the updated properties
+NEW: #19680 Add option PRODUCT_ALLOW_EXTERNAL_DOWNLOAD to automatically have uploaded files shared publicly by a link
+NEW: Add option FICHINTER_ALLOW_EXTERNAL_DOWNLOAD
 
 
 WARNING:
 
 Following changes may create regressions for some external modules, but were necessary to make Dolibarr better:
 * Minimal PHP version is now PHP 7.0 instead of PHP 5.6
-* The signature of method getNomUrl() of class ProductFournisseur has been modified to match the signature of method Product
+* Core has introduced a Universal Filter Syntax for seach criteria. Example: ((((field1:=:value1) OR (field2:in:1,2,3)) AND ...). In rare case, some filters
+  could be provided by URL parameters. For such cases (societe/ajax/company.php), use of Universal Filter Syntax become mandatory. 
+* The signature of method getNomUrl() of class ProductFournisseur has been modified to match the signature of method Product->getNomUrl()
 * Trigger ORDER_SUPPLIER_DISPATCH is removed, use ORDER_SUPPLIER_RECEIVE and/or LINEORDER_SUPPLIER_DISPATCH instead.
 * All functions fetch_all() have been set to deprecated for naming consitency, use fetchAll() instead.
 * Code standardization: '$user->rights->propale' is now '$user->rights->propal' everywhere.
 * Deprecated method set_billed() on shipment and reception class has been removed. Use setBilled() instead.
 * Tables llx_prelevement_facture and llx_prelevement_facture_demande have been renamed into llx_prelevement and llx_prelevement_demande.
-* Rename MAIN_LIST_ALLOW_NOTES into MAIN_LIST_HIDE_NOTES and rename MAIN_LIST_ALLOW_PRIVATE_NOTES into MAIN_LIST_HIDE_PRIVATE_NOTES
-* Rename the substitution for project label instead of project title in substitution variables
-
-
-***** ChangeLog for 16.0.4 compared to 16.0.2 *****
+* Renamed MAIN_LIST_ALLOW_NOTES into MAIN_LIST_HIDE_NOTES and renamed MAIN_LIST_ALLOW_PRIVATE_NOTES into MAIN_LIST_HIDE_PRIVATE_NOTES
+* Renamed the substitution for "project label" instead of "project title" in substitution variables
+* You must use "$objectoffield" to manipulate the current object inside the form of computed custom extrafields instead of $obj/$object.
+* Making a global search is sending the parameter using always the name search_all (instead of sometimes sall and search_all)
+* The property $url_last_version must be public if defined into module descriptor files;
+
+
+***** ChangeLog for 16.0.5 compared to 16.0.4 *****
+
+FIX: 16.0 propalestats Unknown column 'p.fk_soc' in 'on clause'
+FIX: #23804
+FIX: #23860
+FIX: #23966 Error "Param dbt_keyfield is required but not defined"
+FIX: accountancy lettering: better error management
+FIX: accountancy lettering: correctly calculated number of lettering operations done
+FIX: accountancy lettering: error management and prevention
+FIX: accountancy lettering: prevent null results when fetching link with payments
+FIX: Add missing hook on LibStatut
+FIX: Add more context for selectForFormsListWhere Hook
+FIX: attach file and send by mail in ticket
+FIX: bad check on if in get_all_ways
+FIX: Cannot import find type_fees with cgenericdic.class because it has id and not rowid
+FIX: clicktodial backtopage
+FIX: discount wasn't taken into account when adding a line in BOM
+FIX: expense reports: error when selecting mileage fees expense type if MAIN_USE_EXPENSE_IK disabled
+FIX: expense reports: JS error when selecting mileage fees expense type if MAIN_USE_EXPENSE_IK disabled
+FIX: Extrafields in Notes to unify with orders or invoices.
+FIX: fatal error on clicktodial backtopage
+FIX: filter sql accounting account
+FIX: Get data back on product update
+FIX: Get data back when error on command create
+FIX: label dictionary is used by barcode and member module
+FIX: mandatory date for service didnt work for invoice
+FIX: missing "authorid" for getNomUrl link right access
+FIX: missing getEntity filter
+FIX: vulnerability: missing protection on ajax public ticket page for valid email.
+FIX: Missing right to edit service note when module product is disabled
+FIX: multicompany compatibility
+FIX: object $user is not defined
+FIX: Object of class LDAP\Connection could not be converted to string
+FIX: parse error and NAN
+FIX: product ref fourn same size in supplier order/invoice as in product price fourn
+FIX: Profit calculation on project preview tab.
+FIX: Remove orphelan $this->db->rollback() in the function insertExtrafields()
+FIX: request new password with "mc" and "twofactor" authentication
+FIX: Resolve error message due to missing arguments
+FIX: select for task in event card
+FIX: several email sent to the same recipient when adding message from ticket
+FIX: shipping list for external user
+FIX: SQL error "unknown column p.fk_soc" because ANSI-92 joins take precedence over ANSI-89 joins
+FIX: strato pdf
+FIX: typos in getAttchments() $arrayobject
+FIX: whitespaces
+FIX: wrong url param name action
+
+
+***** ChangeLog for 16.0.4 compared to 16.0.3 *****
 
 FIX: Amount of localtax1 and 2 not correctly save on purchase order (the rate was saved instead)
 FIX: #20415
 FIX: #21280
-FIX: #23008
 FIX: #22271
+FIX: #22524
 FIX: #22837
+FIX: #22964
+FIX: #23008
+FIX: #23012
 FIX: #23019 Impossible to add task times to an existing draft invoice
 FIX: #23072
+FIX: #23075
 FIX: #23087
 FIX: #23115 
 FIX: #23116
+FIX: #23117
 FIX: #23281
+FIX: #23420 : wrong check on $search_categ value causing FATAL ERROR
+FIX: Accountancy - Quadra export
+FIX: add border left on image product when conf activated
+FIX: Add missing token when deleting template inn order_supplier admin menu
+FIX: API access for deactivated users
 FIX: bad selection of barcode numbering module
 FIX: Can't see all time spent by all user
 FIX: CI
 FIX: CommonObject - showOptionals - Display blank td when MAIN_VIEW_LINE_NUMBER is enabled and action is confirm_valid
 FIX: Documents API inconsistency
-FIX: #23075
-FIX: #23117
+FIX: Empty FormSetup emailTemplate type IF empty fieldvalue
+FIX: Errors Handling for CreateFrom Hooks
+FIX: error with dol_banner_tab, ref is needed
+FIX: ExpenseReport card was not reloaded after addline
 FIX: get multicurrency infos of propal when create order from propal with "WORKFLOW_PROPAL_AUTOCREATE_ORDER" conf
 FIX: Give predictable order to inventory lines
 FIX: include class multicurrency
 FIX: methods declaration (backport fix 67b9a7dc07d708231d12b5e58800334d4a01ef98)
 FIX: multicurrency_tx and not currency_tx
-FIX: PGSQL Integer type does not have a free length
+FIX: on public ticket list, only the page 1 was accessible. Other pages were 404 error.
+FIX: PGSQL Integer type does not have a free lenght
 FIX: Product list in setup.php in new Module
 FIX: propal and order stats broken on Tag+User(retricted customer list)
 FIX: saving of numbering module for jobs
 FIX: Stickler
 FIX: travis
+FIX: wrong check on $search_categ value causing fatal error
+FIX: wrong stock list with multicompany and without stock sharing
 
 ***** ChangeLog for 16.0.3 compared to 16.0.2 *****
 

+ 1 - 1
README.md

@@ -2,7 +2,7 @@
 
 ![Downloads per day](https://img.shields.io/sourceforge/dw/dolibarr.svg)
 ![Build status](https://img.shields.io/travis/Dolibarr/dolibarr/develop.svg)
-[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%205.6-8892BF.svg?style=flat-square)](https://php.net/)
+[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.0-8892BF.svg?style=flat-square)](https://php.net/)
 [![GitHub release](https://img.shields.io/github/v/release/Dolibarr/dolibarr)](https://github.com/Dolibarr/dolibarr)
 [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5521/badge)](https://bestpractices.coreinfrastructure.org/projects/5521)
 

+ 3 - 3
SECURITY.md

@@ -6,8 +6,8 @@ This file contains some policies about the security reports on Dolibarr ERP CRM
 
 | Version    | Supported              |
 | ---------- | ---------------------- |
-| <= 16.0.2  | :x:                    |
-| >= 16.0.3  | :white_check_mark:     |
+| <= 17.0.0  | :x:                    |
+| >= 17.0.1  | :white_check_mark:     |
 | >= develop | :white_check_mark:     |
 
 ## Reporting a Vulnerability
@@ -67,7 +67,7 @@ Scope is the web application (back office) and the APIs.
 
 * Remote code execution (RCE)
 * Local files access and manipulation (LFI, RFI, XXE, SSRF, XSPA)
-* Code injections (HTML, JS, SQL, PHP, ...)
+* Code injections (JS, SQL, PHP). HTML are covered only for fields that are not description, notes or comments fields (where rich content is allowed on purpose).
 * Cross-Site Scripting (XSS), except from setup page of module "External web site" (allowing any content here, editable by admin user only, is accepted on purpose) and except into module "Web site" when permission to edit website content is allowed (injecting any data in this case is allowed too).
 * Cross-Site Requests Forgery (CSRF) with real security impact (when using GET URLs, CSRF are qualified only for creating, updating or deleting data from pages restricted to admin users)
 * Open redirect

+ 1 - 1
build/debian/compat

@@ -1 +1 @@
-7
+10

+ 5 - 1
build/debian/rules

@@ -19,6 +19,10 @@ override_dh_auto_clean:
 override_dh_auto_build:
 # Do nothing. Added to disable launchpad to use bugged dh_auto_build search for ant
 
+# Force the compression format for control files
+override_dh_builddeb:
+	dh_builddeb -- -Zxz
+
 #override_dh_compress:
 #	dh_compress --no-act -X.png
 
@@ -124,4 +128,4 @@ override_dh_fixperms:
 	# Give rights to the webserver on the upload directory
 	chown www-data:www-data debian/dolibarr/var/lib/dolibarr/documents
 	chmod 2775 debian/dolibarr/var/lib/dolibarr/documents
-	
+

+ 3 - 1
build/debian/source/options

@@ -1,3 +1,5 @@
-# Force use of gzip compression by dpkg-buildpackage
+# Force use of gzip compression by dpkg-buildpackage for the tarball *.debian.tar.gz
+# See also option --compression from command line of dpkg-buildpackage
+# Format for the control files are defined into the rules file in override_dh_builddeb section
 compression = "gzip"
 #compression-level = 9

+ 2 - 2
build/doxygen/dolibarr-doxygen.doxyfile

@@ -220,7 +220,7 @@ OPTIMIZE_OUTPUT_VHDL = NO
 # (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
 # you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
 
-EXTENSION_MAPPING = 
+EXTENSION_MAPPING = example=PHP
 
 # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
 # to include (a tag file for) the STL sources as input, then you should
@@ -602,7 +602,7 @@ INPUT_ENCODING = UTF-8
 # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
 # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
 
-FILE_PATTERNS = *.php *.pl
+FILE_PATTERNS = *.php *.pl *.sql *.example
 
 # The RECURSIVE tag can be used to turn specify whether or not subdirectories
 # should be searched for input files as well. Possible values are YES and NO.

+ 1 - 1
build/exe/doliwamp/Languages/MyCatalan.isl

@@ -43,5 +43,5 @@ DoliWampWillStartApacheMysql=L'instal·lador DoliWamp intentarà iniciar o reini
 OldVersionFoundAndMoveInNew=S'ha trobat una versió antiga de base de dades i ha estat moguda per a ser utilitzada per la nova versió de Dolibarr
 OldVersionFoundButFailedToMoveInNew=S'ha trobat una versió antiga de base de dades, però no es pot moure per a ser utilitzada per la nova versió de Dolibarr
 
-DLLMissing=La teva instal·lació windows no té el component "Microsoft Visual C++ Redistributable for Visual Studio 2012". Instal·la primer la versió de 32-bit (vcredist_x86.exe) (pots trobar-la a https://www.microsoft.com/en-us/download/) i reiniciar després la instal·lació/actualització de DoliWamp.
+DLLMissing=La teva instal·lació windows no té el component "Microsoft Visual C++ Redistributable for Visual Studio 2015". Instal·la primer la versió de 32-bit (vcredist_x86.exe) (pots trobar-la a https://www.microsoft.com/en-us/download/) i reiniciar després la instal·lació/actualització de DoliWamp.
 ContinueAnyway=Continua igualment (el procés d'instal·lació podria fallar sense aquest prerequisit)

+ 22 - 22
build/exe/doliwamp/Languages/MyEnglish.isl

@@ -11,38 +11,38 @@ LaunchProgram=Launch %1
 AssocFileExtension=&Associate %1 with the %2 file extension
 AssocingFileExtension=Associating %1 with the %2 file extension...
 
-YouWillInstallDoliWamp=You will install DoliWamp (so Dolibarr + all required third party software like Apache, Mysql and PHP) on your computer.
-ThisAssistantInstallOrUpgrade=WARNING: Using an ERP CRM installed on a local computer can be dangerous: if your computer break down, you can lose all your data. Do this if you are ready to manage backup yourself seriously. If not, use an installation in Saas instead (see https://saas.dolibarr.org).
-IfYouHaveTechnicalKnowledge=Moreover, if you have technical knowledges and want to manage your Apache, Mysql and PHP yourself, you should not use this assistant and make a manual installation of Dolibarr on your existing server with Apache, Mysql and PHP.
-ButIfYouLook=But if you look for an automatic setup on your local computer, you''re on the good way...
-DoYouWantToStart=Do you want to start installation process ?
+YouWillInstallDoliWamp=You will install DoliWamp (so Dolibarr plus all required third-party software like Apache, MySQL and PHP) on your computer.
+ThisAssistantInstallOrUpgrade=WARNING: Using an ERP CRM installed on a local computer can be dangerous: if your computer breaks down, you can lose all your data. Do this if you are ready to manage backups yourself seriously. If not, use an installation in SaaS instead (see https://saas.dolibarr.org).
+IfYouHaveTechnicalKnowledge=Moreover, if you have technical knowledge and want to manage Apache, MySQL and PHP yourself, you should not use this assistant and instead make a manual installation of Dolibarr on your existing server with Apache, MySQL and PHP.
+ButIfYouLook=But if you are looking for an automatic setup on your local computer, you're on the right path...
+DoYouWantToStart=Do you want to start the installation process?
 
 TechnicalParameters=Technical parameters
-IfFirstInstall=If first install, please specify some technical parameters. If you don't understand, are not sure, or are doing an upgrade, just leave the default values.
+IfFirstInstall=If this is the first install, please specify some technical parameters. If you don't understand, are not sure, or are doing an upgrade, just keep the default values.
 
 ; WARNING !!! STRINGS HERE MUST BE LOWER THAN 60 CHARACTERS
-SMTPServer=SMTP server (your own or ISP SMTP server, first install only) :
-ApachePort=Apache port (first install only, common choice is 80) :
-MySqlPort=MySql port (first install only, common choice is 3306) :
-MySqlPassword=MySql server and database password you want for root (first install only):
+SMTPServer=SMTP server (your own or ISP SMTP server, first install only):
+ApachePort=Apache port (first install only, common choice is 80):
+MySqlPort=MySQL port (first install only, common choice is 3306):
+MySqlPassword=MySQL server and database password you want for root (first install only):
 
-FailedToDeleteLock=Failed to delete the file %1/www/dolibarr/install.lock. You can ignore warning but you may have to remove it manually later when asked. Click OK to continue...
+FailedToDeleteLock=Failed to delete the file %1/www/dolibarr/install.lock. You can ignore this warning but you may have to remove the file manually later when asked. Click OK to continue...
 
-PortAlreadyInUse=Port %1 seems to be already in use. You should cancel to go back and choose another value for %2 port. Cancel choice and choose another value ?
+PortAlreadyInUse=Port %1 seems to already be in use. You should cancel to go back and choose another value for %2 port. Cancel choice and choose another value?
 
-FirefoxDetected=Firefox has been detected on your computer. Would you like to use it as the default browser for Dolibarr ?
-ChromeDetected=Chrome has been detected on your computer. Would you like to use it as the default browser for Dolibarr ?
-MicrosoftEdgeDetected=Microsoft Edge has been detected on your computer. Would you like to use it as the default browser for Dolibarr ?
-ChooseDefaultBrowser=Please choose your default browser (iexplore.exe, firefox.exe, chrome.exe, MicrosoftEdge.exe...). If you are not sure, just click Open :
+FirefoxDetected=Firefox has been detected on your computer. Would you like to use it as the default browser for Dolibarr?
+ChromeDetected=Chrome has been detected on your computer. Would you like to use it as the default browser for Dolibarr?
+MicrosoftEdgeDetected=Microsoft Edge has been detected on your computer. Would you like to use it as the default browser for Dolibarr?
+ChooseDefaultBrowser=Please choose your default browser (iexplore.exe, firefox.exe, chrome.exe, MicrosoftEdge.exe...). If you are not sure, just click Open:
 
 LaunchNow=Launch Dolibarr now
 
-ProgramHasBeenRemoved=Dolibarr program files have been removed. However, all your data files are still in directory %1. You must remove this directory manually for a complete uninstall.
+ProgramHasBeenRemoved=Dolibarr's program files have been removed. However, all your data files are still in directory %1. You must remove this directory manually for a complete uninstall.
 
-DoliWampWillStartApacheMysql=DoliWamp installer will now start or restart Apache and Mysql, this may last from several seconds to one minute after this confirmation. Start to install or upgrade the web and database server required by Dolibarr ?
+DoliWampWillStartApacheMysql=DoliWamp installer will now start or restart Apache and MySQL. This may take from several seconds to one minute. Start to install or upgrade the web and database server required by Dolibarr?
 
-OldVersionFoundAndMoveInNew=An old database version has been found and moved to be used by new Dolibarr version
-OldVersionFoundButFailedToMoveInNew=An old database version has been found but could not be moved to be used with new Dolibarr version
+OldVersionFoundAndMoveInNew=An old database version has been found and moved to be used by the new Dolibarr version
+OldVersionFoundButFailedToMoveInNew=An old database version has been found but could not be moved to be used with the new Dolibarr version
 
-DLLMissing=Your Windows installation is missing The "Micrsoft Visual C++ Redistributable for Visual Studio 2012" component. Please install the 32-bit version (vcredist_x86.exe) first (you can find it at https://www.microsoft.com/en-us/download/) and restart DoliWamp installation/upgrade after.
-ContinueAnyway=Continue anyway (install process may fails without this prerequisite)
+DLLMissing=Your Windows installation is missing The "Microsoft Visual C++ Redistributable for Visual Studio 2015" component. Please install the 32-bit version (vcredist_x86.exe) first (you can find it at https://www.microsoft.com/en-us/download/) and restart DoliWamp installation/upgrade after.
+ContinueAnyway=Continue anyway (install process may fail without this prerequisite)

+ 1 - 1
build/exe/doliwamp/Languages/MyFrench.isl

@@ -44,5 +44,5 @@ DoliWampWillStartApacheMysql=L'installeur DoliWamp va maintenant d
 OldVersionFoundAndMoveInNew=Une ancienne version de base a été trouvée et déplacée pour fonctionner avec la nouvelle version de Dolibarr.
 OldVersionFoundButFailedToMoveInNew=Une ancienne version de base a été trouvée mais ne peut être déplacée pour être utilisée avec la nouvelle version de Dolibarr.
 
-DLLMissing=L'installation de votre Windows est incomplète. Il manque le composant "Micrsoft Visual C++ Redistributable for Visual Studio 2012". Installer la version 32-bit (vcredist_x86.exe) d'abord (vous pourrez le trouver à https://www.microsoft.com/fr-fr/download/) puis relancer l'installation de DoliWamp après.
+DLLMissing=L'installation de votre Windows est incomplète. Il manque le composant "Micrsoft Visual C++ Redistributable for Visual Studio 2015". Installer la version 32-bit (vcredist_x86.exe) d'abord (vous pourrez le trouver à https://www.microsoft.com/fr-fr/download/) puis relancer l'installation de DoliWamp après.
 ContinueAnyway=Continuer malgré tout (le process d'installaton échouera)

+ 1 - 1
build/exe/doliwamp/Languages/MyGerman.isl

@@ -43,5 +43,5 @@ DoliWampWillStartApacheMysql=Die DoliWamp-Installation wird nun starten oder Apa
 OldVersionFoundAndMoveInNew=Eine alte Datenbankversion wurde gefunden und verschoben, um von der neuen Dolibarr-Version verwendet zu werden.
 OldVersionFoundButFailedToMoveInNew=Eine alte Datenbankversion wurde gefunden, konnte jedoch nicht verschoben werden, um mit der neuen Dolibarr-Version verwendet zu werden.
 
-DLLMissing=Your Windows installation is missing The "Micrsoft Visual C++ Redistributable for Visual Studio 2012" component. Please install the 32-bit version (vcredist_x86.exe) first (you can find it at https://www.microsoft.com/en-us/download/) and restart DoliWamp installation/upgrade after.
+DLLMissing=Your Windows installation is missing The "Micrsoft Visual C++ Redistributable for Visual Studio 2015" component. Please install the 32-bit version (vcredist_x86.exe) first (you can find it at https://www.microsoft.com/en-us/download/) and restart DoliWamp installation/upgrade after.
 ContinueAnyway=Fahren Sie trotzdem fort (der Installationsvorgang kann ohne diese Voraussetzung fehlschlagen).

+ 1 - 1
build/exe/doliwamp/Languages/MySpanish.isl

@@ -43,5 +43,5 @@ DoliWampWillStartApacheMysql=El instalador DoliWamp intentará iniciar o reinici
 OldVersionFoundAndMoveInNew=Se ha encontrado una versión antigua de base de datos y ha sido movida para ser utilizada por la nueva versión de Dolibarr
 OldVersionFoundButFailedToMoveInNew=Se ha encontrado una versión antigua de base de datos, pero no se pudo mover para ser utilizada por la nueva versión de Dolibarr
  	  	 
-DLLMissing=Su instalación Windows no tiene el componente "Microsoft Visual C++ Redistributable for Visual Studio 2012". Instale primero la versión de 32-bit (vcredist_x86.exe) (puedes encontrarlo en https://www.microsoft.com/en-us/download/) y reiniciar después la instalación/actualización de DoliWamp.
+DLLMissing=Su instalación Windows no tiene el componente "Microsoft Visual C++ Redistributable for Visual Studio 2015". Instale primero la versión de 32-bit (vcredist_x86.exe) (puedes encontrarlo en https://www.microsoft.com/en-us/download/) y reiniciar después la instalación/actualización de DoliWamp.
 ContinueAnyway=Continua igualmente (el proceso de instalación podría fallar sin este prerequisito)

+ 5 - 1
build/generate_filelist_xml.php

@@ -46,6 +46,7 @@ require_once DOL_DOCUMENT_ROOT."/core/lib/files.lib.php";
 
 $includecustom=0;
 $includeconstants=array();
+$buildzip=0;
 
 if (empty($argv[1])) {
 	print "Usage:   ".$script_file." release=autostable|auto[-mybuild]|x.y.z[-mybuild] [includecustom=1] [includeconstant=CC:MY_CONF_NAME:value] [buildzip=1]\n";
@@ -55,7 +56,7 @@ if (empty($argv[1])) {
 
 
 $i=0;
-$result=array();
+$result = array();
 while ($i < $argc) {
 	if (!empty($argv[$i])) {
 		parse_str($argv[$i], $result);	// set all params $release, $includecustom, $includeconstant, $buildzip ...
@@ -69,6 +70,9 @@ while ($i < $argc) {
 	if (!empty($result["includeconstant"])) {
 		$includeconstants[$i] = $result["includeconstant"];
 	}
+	if (!empty($result["buildzip"])) {
+		$buildzip=1;
+	}
 	if (preg_match('/includeconstant=/', strval($argv[$i]))) {
 		$tmp=explode(':', $result['includeconstant'], 3);			// $includeconstant has been set with previous parse_str()
 		if (count($tmp) != 3) {

+ 9 - 8
build/makepack-dolibarr.pl

@@ -524,12 +524,13 @@ if ($nboftargetok) {
 		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/ansible`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/codesniffer`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/codetemplates`;
-		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/dbmodel`;
+		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/examples/ldap`;
+		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/examples/zapier`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/initdata`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/initdemo`;
-		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/iso-normes`;
-		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/ldap`;
-		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/licence`;
+		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/resources/dbmodel`;
+		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/resources/iso-normes`;
+		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/resources/licence`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/mail`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/multitail`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/dev/phpcheckstyle`;
@@ -894,7 +895,7 @@ if ($nboftargetok) {
 
 			$ret=`rm -fr $BUILDROOT/$PROJECT.tmp`;
 			$ret=`rm -fr $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build`;
-			
+
 			print "Copy $BUILDROOT/$PROJECT to $BUILDROOT/$PROJECT.tmp\n";
 			$cmd="cp -pr \"$BUILDROOT/$PROJECT\" \"$BUILDROOT/$PROJECT.tmp\"";
 			$ret=`$cmd`;
@@ -1049,16 +1050,16 @@ if ($nboftargetok) {
 			print "Go into directory $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build\n";
 			chdir("$BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build");
 			#$cmd="dpkg-source -b $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build";
-			$cmd="dpkg-buildpackage -us -uc";
+			$cmd="dpkg-buildpackage -us -uc --compression=gzip";
 			print "Launch DEB build ($cmd)\n";
 			$ret=`$cmd 2>&1 3>&1`;
 			print $ret."\n";
 
 			chdir("$olddir");
-			
+
 			print "You can check bin package with lintian --pedantic -E -I \"$NEWDESTI/${FILENAMEDEB}_all.deb\"\n";
 			print "You can check src package with lintian --pedantic -E -I \"$NEWDESTI/${FILENAMEDEB}.dsc\"\n";
-			
+
 			# Move to final dir
 			print "Move *_all.deb *.dsc *.orig.tar.gz *.changes to $NEWDESTI\n";
 			$ret=`mv $BUILDROOT/*_all.deb "$NEWDESTI/"`;

+ 3 - 3
build/makepack-howto.txt

@@ -27,12 +27,12 @@ Prerequisites to build autoexe DoliWamp package from Linux (solution seems broke
 
 Prerequisites to build autoexe DoliWamp package from Windows:
 
-> Install Perl for Windwos (https://strawberryperl.com/)
+> Install Perl for Windows (https://strawberryperl.com/)
 > Install isetup-5.5.8.exe (https://www.jrsoftware.org)
 > Install WampServer-3.2.*-64.exe (Apache 2.4.51, PHP 7.3.33, MariaDB 10.6.5 for example. Version must match the values found into doliwamp.iss)
 > Install GIT for Windows (https://git-scm.com/ => You must choose option "Add Git bash profile", "Git commit as-is")
-> Install Dolibarr verions:   
-  git clone https://github.com/dolibarr/dolibarr
+> Install Dolibarr current version:   
+  git clone https://github.com/dolibarr/dolibarr  or  git clone --branch X.Y https://github.com/dolibarr/dolibarr  
 
 > Add the path of PHP (C:\wamp64\bin\php\php7.3.33) and InnoSetup (C:\Program Files (x86)\Inno Setup 5) into the %PATH% of Windows.
 

+ 7 - 5
build/tgz/tar_exclude.txt

@@ -1,15 +1,17 @@
 *~
 .#*
+.git
+.gitignore
+.scrutinizer.yml
 Thumbs.db
 build/exe
 build/html
-dev/dbmodel
-dev/fpdf
+dev/examples/zapier
 dev/initdemo
 dev/initdata
-dev/iso-normes
-dev/licence
-dev/load
+dev/resources/dbmodel
+dev/resources/iso-normes
+dev/resources/licence
 htdocs/conf/conf.php
 htdocs/conf/conf.php.mysql
 htdocs/conf/conf.php.old

+ 5 - 0
dev/dolibarr_changes.txt

@@ -209,6 +209,11 @@ with
 with
 	foreach ($value[1] as $k => $v) {
 
+* Fix by replacing 
+	if ($res[0] == PDF_TYPE_OBJECT)
+with
+	if (isset($res[0]) && $res[0] == PDF_TYPE_OBJECT)
+
 
 
 JSGANTT:

+ 0 - 28
dev/initdemo/README

@@ -1,28 +0,0 @@
-README
-------
-
-Scripts in this directory can be used to reload or save a demo database.
-Install of package "dialog" is required.
-
-
-*** Init demo
-
-The script initdemo.sh will erase current database with data into mysqldump_dolibarr_x.y.z.sql and copy files into documents_demo into officiel document directory.
-
-Do a chmod 700 initdemo.sh
-then run ./initdemo.sh to launch Graphic User Interface.
-
-After loading the demo files, admin login may be:
-- admin / admin
-or
-- admin / adminadmin
-
-
-*** Save demo
-
-The script savedemo.sh will save current database into a database dump file.
-
-
-*** Update demo
-
-The goal of script updatedemo.php is to update dates into the demo data so samples are up to date.

+ 32 - 0
dev/initdemo/README.md

@@ -0,0 +1,32 @@
+README
+======
+
+Scripts in this directory can be used to reload or save a demo database.
+Install of package "dialog" is required.
+
+
+Init demo
+-------------
+
+The script initdemo.sh will erase current database with data intodev/initdemo/mysqldump_dolibarr_x.y.z.sql and copy files into documents_demo into officiel document directory.
+
+Do a chmod 700 initdemo.sh
+then run ./initdemo.sh to launch Graphic User Interface.
+
+After loading the demo files, admin login may be:
+- admin / admin
+or
+- admin / adminadmin
+
+
+Update demo
+-------------
+
+The goal of script dev/initdemo/updatedemo.php is to update dates into the demo data so samples are up to date.
+
+
+Save demo
+-------------
+
+The script dev/initdemo.savedemo.sh will save current database into a database dump file.
+

ファイルの差分が大きいため隠しています
+ 2 - 2
dev/initdemo/mysqldump_dolibarr_17.0.0.sql


+ 14 - 0
dev/initdemo/savedemo.sh

@@ -290,6 +290,7 @@ export list="
 	--ignore-table=$base.llx_monmodule_abcdef
 	--ignore-table=$base.llx_notes
 	--ignore-table=$base.llx_packages
+	--ignore-table=$base.llx_packages_extrafields
 	--ignore-table=$base.llx_pos_cash
 	--ignore-table=$base.llx_pos_control_cash
 	--ignore-table=$base.llx_pos_facture
@@ -305,6 +306,19 @@ export list="
 	--ignore-table=$base.llx_residence
 	--ignore-table=$base.llx_residence_building
 	--ignore-table=$base.llx_residence_building_links
+	--ignore-table=$base.llx_scaninvoices_filestoimport
+	--ignore-table=$base.llx_scaninvoices_filestoimport_extrafields
+	--ignore-table=$base.llx_scaninvoices_settings
+	--ignore-table=$base.llx_scaninvoices_settings_extrafields
+	--ignore-table=$base.llx_sellyoursaas_blacklistcontent
+	--ignore-table=$base.llx_sellyoursaas_blacklistdir
+	--ignore-table=$base.llx_sellyoursaas_blacklistfrom
+	--ignore-table=$base.llx_sellyoursaas_blacklistip
+	--ignore-table=$base.llx_sellyoursaas_blacklistmail
+	--ignore-table=$base.llx_sellyoursaas_blacklistto
+	--ignore-table=$base.llx_sellyoursaas_deploymentserver
+	--ignore-table=$base.llx_sellyoursaas_stats
+	--ignore-table=$base.llx_sellyoursaas_whitelistip
 	--ignore-table=$base.llx_societe_rib2
 	--ignore-table=$base.llx_sellyoursaas_cancellation
 	--ignore-table=$base.llx_ticketsup

+ 2 - 3
dev/resources/iso-normes/qr-bar-codes/QR code for invoices.txt

@@ -22,8 +22,7 @@ https://www.tecklenborgh.com/post/ksa-zatca-publishes-guide-on-how-to-develop-a-
 Method to encode/decode ZATCA string is available in test/phpunit/BarcodeTest.php 
 
 
-* FOR QR-Bill in switzerland
-----------------------------
-Syntax of QR Code https://www.swiss-qr-invoice.org/fr/
+* FOR QR-Bill in switzerland - Facture-QR
+Syntax of QR Code - See file ig-qr-bill-v2.2-fr.pdf  (more doc on https://www.swiss-qr-invoice.org/downloads/)
 Syntax of complentary field named "structured information of invoice S1": https://www.swiss-qr-invoice.org/downloads/qr-bill-s1-syntax-fr.pdf
 To test/validate: https://www.swiss-qr-invoice.org/validator/

ファイルの差分が大きいため隠しています
+ 3676 - 0
dev/resources/iso-normes/qr-bar-codes/ig-qr-bill-v2.2-fr.pdf


+ 33 - 22
dev/setup/apache/virtualhost

@@ -12,6 +12,7 @@
 	</IfModule>
 
     
+    # The URLs of the web site
 	ServerName myvirtualalias
 	ServerAlias myvirtualalias
 	
@@ -23,8 +24,13 @@
 	
 	AddDefaultCharset UTF-8
 	
-	DocumentRoot "/home/.../htdocs"
 	
+	# Detect if we are using DoliDroid
+	#SetEnvIf User-Agent DoliDroid dolidroid
+	
+	
+	# The directory and permissions for the web site
+	DocumentRoot "/home/.../htdocs"
 	<Directory /home/.../htdocs/>
 	   	AllowOverride None
 	   	Options       -Indexes -MultiViews +FollowSymLinks -ExecCGI
@@ -40,39 +46,43 @@
 	# Leaving /public and /api, /dav, .well_known but also wrappers for document, viewimage and public json/img accessible to everyone
     <Directory /home/admin/wwwroot/dolibarr/htdocs/public/>
         AuthType None
-        Require all granted
         Satisfy any
+        Require all granted
     </Directory>
     <Directory /home/admin/wwwroot/dolibarr/htdocs/api/>
         AuthType None
-        Require all granted
         Satisfy any
+        Require all granted
     </Directory>
     <Directory /home/admin/wwwroot/dolibarr/htdocs/dav/>
         AuthType None
-        Require all granted
         Satisfy any
+        Require all granted
     </Directory>
     <Directory /home/admin/wwwroot/dolibarr/htdocs/.well-known/>
         AuthType None
-        Require all granted
         Satisfy any
+        Require all granted
     </Directory>
     <Files ~ "(document\.php|viewimage\.php|\.js\.php|\.json\.php|\.js|\.css\.php|\.css|\.gif|\.png|\.svg|\.woff2|favicon\.ico)$">
         AuthType None
-        Require all granted
         Satisfy any
+        Require all granted
     </Files>
 
-        
+
+	# Log directoves        
 	ErrorLog /var/log/apache2/myvirtualalias_error_log
 	TransferLog /var/log/apache2/myvirtualalias_access_log
 	
-	# Compress returned resources of type php pages, text file export, css and javascript
+		
+	# Compress is done on resources of type php pages, text file export, css and javascript
 	AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/x-javascript
-	
 	AddType text/javascript .jgz
 	AddEncoding gzip .jgz
+
+
+	# Add cach performance directives
 	ExpiresActive On
 	ExpiresByType image/x-icon A2592000
 	ExpiresByType image/gif A2592000
@@ -83,19 +93,20 @@
 	ExpiresByType application/x-javascript A2592000
 	ExpiresByType application/javascript A2592000
 	
-	SSLEngine On
 	
-	#   A self-signed (snakeoil) certificate can be created by installing
-	#   the ssl-cert package. See
-	#   /usr/share/doc/apache2.2-common/README.Debian.gz for more info.
-	#   If both key and certificate are stored in the same file, only the
-	#   SSLCertificateFile directive is needed.
-	#SSLCertificateFile    /etc/letsencrypt/live/www.mydomain.com/cert.pem
-	#SSLCertificateKeyFile /etc/letsencrypt/live/www.mydomain.com/privkey.pem
-	#SSLCertificateChainFile /etc/letsencrypt/live/www.mydomain.com/chain.pem
+	# To enable the SSL if the certificate file exists
+	<IfFile "/etc/letsencrypt/live/www.mydomain.com/cert.pem">
+		SSLEngine On
+		
+		# If both key and certificate are stored in the same file, only the
+		# SSLCertificateFile directive is needed.
+		SSLCertificateFile    /etc/letsencrypt/live/www.mydomain.com/cert.pem
+		SSLCertificateKeyFile /etc/letsencrypt/live/www.mydomain.com/privkey.pem
+		SSLCertificateChainFile /etc/letsencrypt/live/www.mydomain.com/chain.pem
+		
+		#RewriteEngine   on
+		#RewriteCond     %{SERVER_PORT} ^80$
+		#RewriteRule     ^(.*)$ https://%{SERVER_NAME}$1 [L,R]
+	</IfFile>
 	
-	#RewriteEngine   on
-	#RewriteCond     %{SERVER_PORT} ^80$
-	#RewriteRule     ^(.*)$ https://%{SERVER_NAME}$1 [L,R]
-
 </VirtualHost>

+ 2 - 1
dev/setup/fail2ban/jail.local

@@ -31,6 +31,7 @@ maxretry = 10
 [web-dol-limitpublic]
 
 ; rule to add rate limit on some public pages
+; note you must keep enough for public access like agenda export, emailing trackers, stripe ipn access, ...
 enabled = true
 port    = http,https
 filter  = web-dolibarr-limitpublic
@@ -38,5 +39,5 @@ logpath = /mypath/documents/documents/dolibarr.log
 action  = %(action_mw)s
 bantime  = 86400     ; 1 day
 findtime = 86400     ; 1 day
-maxretry = 500
+maxretry = 1000
 

+ 1 - 1
dev/translation/txpull.sh

@@ -36,7 +36,7 @@ then
 	    echo "tx pull -a"
 	    tx pull -a
 	    
-	    echo "Remove some language directories (not enough translated)"
+	    echo "Remove some language directories (not enough translated) like ach, br_FR, en, frp, fy_NL, ..."
 	    rm -fr htdocs/langs/ach
 	    rm -fr htdocs/langs/br_FR
 	    rm -fr htdocs/langs/en

+ 7 - 7
htdocs/accountancy/admin/account.php

@@ -95,7 +95,7 @@ $arrayfields = array(
 	'aa.import_key'=>array('label'=>"ImportId", 'checked'=>-1)
 );
 
-if ($conf->global->MAIN_FEATURES_LEVEL < 2) {
+if (getDolGlobalInt('MAIN_FEATURES_LEVEL') < 2) {
 	unset($arrayfields['categories']);
 	unset($arrayfields['aa.reconcilable']);
 }
@@ -146,7 +146,7 @@ if (empty($reshook)) {
 		$search_array_options = array();
 	}
 	if ((GETPOST('valid_change_chart', 'alpha') && GETPOST('chartofaccounts', 'int') > 0)	// explicit click on button 'Change and load' with js on
-		|| (GETPOST('chartofaccounts', 'int') > 0 && GETPOST('chartofaccounts', 'int') != $conf->global->CHARTOFACCOUNTS)) {	// a submit of form is done and chartofaccounts combo has been modified
+		|| (GETPOST('chartofaccounts', 'int') > 0 && GETPOST('chartofaccounts', 'int') != getDolGlobalInt('CHARTOFACCOUNTS'))) {	// a submit of form is done and chartofaccounts combo has been modified
 		if ($chartofaccounts > 0 && $permissiontoadd) {
 			// Get language code for this $chartofaccounts
 			$sql = 'SELECT code FROM '.MAIN_DB_PREFIX.'c_country as c, '.MAIN_DB_PREFIX.'accounting_system as a';
@@ -228,7 +228,7 @@ if ($action == 'delete') {
 	print $formconfirm;
 }
 
-$pcgver = $conf->global->CHARTOFACCOUNTS;
+$pcgver = getDolGlobalInt('CHARTOFACCOUNTS');
 
 $sql = "SELECT aa.rowid, aa.fk_pcg_version, aa.pcg_type, aa.account_number, aa.account_parent, aa.label, aa.labelshort, aa.fk_accounting_category,";
 $sql .= " aa.reconcilable, aa.active, aa.import_key,";
@@ -240,8 +240,8 @@ $sql .= " WHERE asy.rowid = ".((int) $pcgver);
 //print $sql;
 if (strlen(trim($search_account))) {
 	$lengthpaddingaccount = 0;
-	if ($conf->global->ACCOUNTING_LENGTH_GACCOUNT || $conf->global->ACCOUNTING_LENGTH_AACCOUNT) {
-		$lengthpaddingaccount = max($conf->global->ACCOUNTING_LENGTH_GACCOUNT, $conf->global->ACCOUNTING_LENGTH_AACCOUNT);
+	if (getDolGlobalInt('ACCOUNTING_LENGTH_GACCOUNT') || getDolGlobalInt('ACCOUNTING_LENGTH_AACCOUNT')) {
+		$lengthpaddingaccount = max(getDolGlobalInt('ACCOUNTING_LENGTH_GACCOUNT'), getDolGlobalInt('ACCOUNTING_LENGTH_AACCOUNT'));
 	}
 	$search_account_tmp = $search_account;
 	$weremovedsomezero = 0;
@@ -289,7 +289,7 @@ $sql .= $db->order($sortfield, $sortorder);
 
 // Count total nb of records
 $nbtotalofrecords = '';
-if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
+if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
 	$resql = $db->query($sql);
 	$nbtotalofrecords = $db->num_rows($resql);
 	if (($page * $limit) > $nbtotalofrecords) {	// if total resultset is smaller then paging size (filtering), goto and load page 0
@@ -321,7 +321,7 @@ if ($resql) {
 		$param .= '&contextpage='.urlencode($contextpage);
 	}
 	if ($limit > 0 && $limit != $conf->liste_limit) {
-		$param .= '&limit='.urlencode($limit);
+		$param .= '&limit='.((int) $limit);
 	}
 	if ($search_account) {
 		$param .= '&search_account='.urlencode($search_account);

+ 2 - 2
htdocs/accountancy/admin/accountmodel.php

@@ -504,8 +504,8 @@ if ($id) {
 		print '<td>';
 		print '<input type="hidden" name="id" value="'.$id.'">';
 		print '</td>';
-		print '<td style="min-width: 26px;"></td>';
-		print '<td style="min-width: 26px;"></td>';
+		print '<td></td>';
+		print '<td></td>';
 		print '</tr>';
 
 		// Line to enter new values

+ 4 - 4
htdocs/accountancy/admin/card.php

@@ -42,7 +42,7 @@ $ref = GETPOST('ref', 'alpha');
 $rowid = GETPOST('rowid', 'int');
 $cancel = GETPOST('cancel', 'alpha');
 
-$account_number = GETPOST('account_number', 'string');
+$account_number = GETPOST('account_number', 'alphanohtml');
 $label = GETPOST('label', 'alpha');
 
 // Security check
@@ -76,7 +76,7 @@ if ($action == 'add' && $user->hasRight('accounting', 'chartofaccount')) {
 			setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentities("Label")), null, 'errors');
 			$action = 'create';
 		} else {
-			$sql = "SELECT pcg_version FROM " . MAIN_DB_PREFIX . "accounting_system WHERE rowid = ".((int) $conf->global->CHARTOFACCOUNTS);
+			$sql = "SELECT pcg_version FROM " . MAIN_DB_PREFIX . "accounting_system WHERE rowid = ".((int) getDolGlobalInt('CHARTOFACCOUNTS'));
 
 			dol_syslog('accountancy/admin/card.php:: $sql=' . $sql);
 			$result = $db->query($sql);
@@ -139,7 +139,7 @@ if ($action == 'add' && $user->hasRight('accounting', 'chartofaccount')) {
 		} else {
 			$result = $object->fetch($id);
 
-			$sql = "SELECT pcg_version FROM ".MAIN_DB_PREFIX."accounting_system WHERE rowid=".((int) $conf->global->CHARTOFACCOUNTS);
+			$sql = "SELECT pcg_version FROM ".MAIN_DB_PREFIX."accounting_system WHERE rowid=".((int) getDolGlobalInt('CHARTOFACCOUNTS'));
 
 			dol_syslog('accountancy/admin/card.php:: $sql=' . $sql);
 			$result2 = $db->query($sql);
@@ -211,7 +211,7 @@ $form = new Form($db);
 $formaccounting = new FormAccounting($db);
 
 $accountsystem = new AccountancySystem($db);
-$accountsystem->fetch($conf->global->CHARTOFACCOUNTS);
+$accountsystem->fetch(getDolGlobalInt('CHARTOFACCOUNTS'));
 
 $title = $langs->trans('AccountAccounting')." - ".$langs->trans('Card');
 

+ 3 - 3
htdocs/accountancy/admin/categories_list.php

@@ -545,9 +545,9 @@ if ($tabname[$id]) {
 	print '<td>';
 	print '<input type="hidden" name="id" value="'.$id.'">';
 	print '</td>';
-	print '<td style="min-width: 26px;"></td>';
-	print '<td style="min-width: 26px;"></td>';
-	print '<td style="min-width: 26px;"></td>';
+	print '<td></td>';
+	print '<td></td>';
+	print '<td></td>';
 	print '</tr>';
 
 	// Line to enter new values

+ 5 - 1
htdocs/accountancy/admin/defaultaccounts.php

@@ -1,7 +1,7 @@
 <?php
 /* Copyright (C) 2013-2014  Olivier Geffroy         <jeff@jeffinfo.com>
  * Copyright (C) 2013-2014  Florian Henry           <florian.henry@open-concept.pro>
- * Copyright (C) 2013-2020  Alexandre Spangaro      <aspangaro@open-dsi.fr>
+ * Copyright (C) 2013-2023  Alexandre Spangaro      <aspangaro@open-dsi.fr>
  * Copyright (C) 2014-2015  Ari Elbaz (elarifr)     <github@accedinfo.com>
  * Copyright (C) 2014       Marcos García           <marcosgdf@gmail.com>
  * Copyright (C) 2014       Juanjo Menent           <jmenent@2byte.es>
@@ -81,6 +81,10 @@ $list_account[] = '---Others---';
 $list_account[] = 'ACCOUNTING_VAT_BUY_ACCOUNT';
 $list_account[] = 'ACCOUNTING_VAT_SOLD_ACCOUNT';
 $list_account[] = 'ACCOUNTING_VAT_PAY_ACCOUNT';
+if (!empty($conf->global->ACCOUNTING_FORCE_ENABLE_VAT_REVERSE_CHARGE)) {
+	$list_account[] = 'ACCOUNTING_VAT_BUY_REVERSE_CHARGES_CREDIT';
+	$list_account[] = 'ACCOUNTING_VAT_BUY_REVERSE_CHARGES_DEBIT';
+}
 if (isModEnabled('banque')) {
 	$list_account[] = 'ACCOUNTING_ACCOUNT_TRANSFER_CASH';
 }

+ 2 - 2
htdocs/accountancy/admin/export.php

@@ -269,9 +269,9 @@ if ($num2) {
 		// Value
 		print '<td>';
 		if (is_array($key['param'])) {
-			print $form->selectarray($label, $key['param'], $conf->global->$label, 0);
+			print $form->selectarray($label, $key['param'], getDolGlobalString($label), 0);
 		} else {
-			print '<input type="text" size="20" id="'.$label.'" name="'.$key['label'].'" value="'.$conf->global->$label.'">';
+			print '<input type="text" size="20" id="'.$label.'" name="'.$key['label'].'" value="'.getDolGlobalString($label).'">';
 		}
 
 		print '</td></tr>';

+ 9 - 4
htdocs/accountancy/admin/fiscalyear.php

@@ -97,14 +97,14 @@ $help_url = "EN:Module_Double_Entry_Accounting";
 
 llxHeader('', $title, $help_url);
 
-$sql = "SELECT f.rowid, f.label, f.date_start, f.date_end, f.statut, f.entity";
+$sql = "SELECT f.rowid, f.label, f.date_start, f.date_end, f.statut as status, f.entity";
 $sql .= " FROM ".MAIN_DB_PREFIX."accounting_fiscalyear as f";
 $sql .= " WHERE f.entity = ".$conf->entity;
 $sql .= $db->order($sortfield, $sortorder);
 
 // Count total nb of records
 $nbtotalofrecords = '';
-if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
+if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
 	$result = $db->query($sql);
 	$nbtotalofrecords = $db->num_rows($result);
 	if (($page * $limit) > $nbtotalofrecords) {	// if total resultset is smaller then paging size (filtering), goto and load page 0
@@ -137,14 +137,19 @@ if ($result) {
 	print '<td>'.$langs->trans("DateEnd").'</td>';
 	print '<td class="center">'.$langs->trans("NumberOfAccountancyEntries").'</td>';
 	print '<td class="center">'.$langs->trans("NumberOfAccountancyMovements").'</td>';
-	print '<td class="right">'.$langs->trans("Statut").'</td>';
+	print '<td class="right">'.$langs->trans("Status").'</td>';
 	print '</tr>';
 
 	if ($num) {
 		while ($i < $num && $i < $max) {
 			$obj = $db->fetch_object($result);
 
+			$fiscalyearstatic->ref = $obj->rowid;
 			$fiscalyearstatic->id = $obj->rowid;
+			$fiscalyearstatic->date_start = $obj->date_start;
+			$fiscalyearstatic->date_end = $obj->date_end;
+			$fiscalyearstatic->statut = $obj->status;
+			$fiscalyearstatic->status = $obj->status;
 
 			print '<tr class="oddeven">';
 			print '<td>';
@@ -155,7 +160,7 @@ if ($result) {
 			print '<td class="left">'.dol_print_date($db->jdate($obj->date_end), 'day').'</td>';
 			print '<td class="center">'.$object->getAccountancyEntriesByFiscalYear($obj->date_start, $obj->date_end).'</td>';
 			print '<td class="center">'.$object->getAccountancyMovementsByFiscalYear($obj->date_start, $obj->date_end).'</td>';
-			print '<td class="right">'.$fiscalyearstatic->LibStatut($obj->statut, 5).'</td>';
+			print '<td class="right">'.$fiscalyearstatic->LibStatut($obj->status, 5).'</td>';
 			print '</tr>';
 			$i++;
 		}

+ 44 - 69
htdocs/accountancy/admin/index.php

@@ -1,7 +1,7 @@
 <?php
 /* Copyright (C) 2013-2014 Olivier Geffroy      <jeff@jeffinfo.com>
  * Copyright (C) 2013-2014 Florian Henry        <florian.henry@open-concept.pro>
- * Copyright (C) 2013-2021 Alexandre Spangaro   <aspangaro@open-dsi.fr>
+ * Copyright (C) 2013-2023 Alexandre Spangaro   <aspangaro@open-dsi.fr>
  * Copyright (C) 2014-2015 Ari Elbaz (elarifr)  <github@accedinfo.com>
  * Copyright (C) 2014      Marcos García        <marcosgdf@gmail.com>
  * Copyright (C) 2014      Juanjo Menent        <jmenent@2byte.es>
@@ -66,12 +66,7 @@ $error = 0;
  * Actions
  */
 
-if (in_array($action, array(
-	'setBANK_DISABLE_DIRECT_INPUT',
-	'setACCOUNTANCY_COMBO_FOR_AUX',
-	'setACCOUNTING_MANAGE_ZERO',
-	'setACCOUNTING_LIST_SORT_VENTILATION_TODO',
-	'setACCOUNTING_LIST_SORT_VENTILATION_DONE'))) {
+if (in_array($action, array('setBANK_DISABLE_DIRECT_INPUT', 'setACCOUNTANCY_COMBO_FOR_AUX', 'setACCOUNTING_MANAGE_ZERO'))) {
 	$constname = preg_replace('/^set/', '', $action);
 	$constvalue = GETPOST('value', 'int');
 	$res = dolibarr_set_const($db, $constname, $constvalue, 'yesno', 0, '', $conf->entity);
@@ -121,34 +116,6 @@ if ($action == 'update') {
 	}
 }
 
-if ($action == 'setlistsorttodo') {
-	$setlistsorttodo = GETPOST('value', 'int');
-	$res = dolibarr_set_const($db, "ACCOUNTING_LIST_SORT_VENTILATION_TODO", $setlistsorttodo, 'yesno', 0, '', $conf->entity);
-	if (!($res > 0)) {
-		$error++;
-	}
-
-	if (!$error) {
-		setEventMessages($langs->trans("SetupSaved"), null, 'mesgs');
-	} else {
-		setEventMessages($langs->trans("Error"), null, 'mesgs');
-	}
-}
-
-if ($action == 'setlistsortdone') {
-	$setlistsortdone = GETPOST('value', 'int');
-	$res = dolibarr_set_const($db, "ACCOUNTING_LIST_SORT_VENTILATION_DONE", $setlistsortdone, 'yesno', 0, '', $conf->entity);
-	if (!($res > 0)) {
-		$error++;
-	}
-
-	if (!$error) {
-		setEventMessages($langs->trans("SetupSaved"), null, 'mesgs');
-	} else {
-		setEventMessages($langs->trans("Error"), null, 'mesgs');
-	}
-}
-
 if ($action == 'setmanagezero') {
 	$setmanagezero = GETPOST('value', 'int');
 	$res = dolibarr_set_const($db, "ACCOUNTING_MANAGE_ZERO", $setmanagezero, 'yesno', 0, '', $conf->entity);
@@ -275,6 +242,19 @@ if ($action == 'setenableautolettering') {
 	}
 }
 
+if ($action == 'setenablevatreversecharge') {
+	$setenablevatreversecharge = GETPOST('value', 'int');
+	$res = dolibarr_set_const($db, "ACCOUNTING_FORCE_ENABLE_VAT_REVERSE_CHARGE", $setenablevatreversecharge, 'yesno', 0, '', $conf->entity);
+	if (!($res > 0)) {
+		$error++;
+	}
+
+	if (!$error) {
+		setEventMessages($langs->trans("SetupSaved"), null, 'mesgs');
+	} else {
+		setEventMessages($langs->trans("Error"), null, 'mesgs');
+	}
+}
 
 /*
  * View
@@ -381,7 +361,7 @@ print '</tr>';
 foreach ($list as $key) {
 	print '<tr class="oddeven value">';
 
-	if (!empty($conf->global->ACCOUNTING_MANAGE_ZERO) && ($key == 'ACCOUNTING_LENGTH_GACCOUNT' || $key == 'ACCOUNTING_LENGTH_AACCOUNT')) {
+	if (getDolGlobalInt('ACCOUNTING_MANAGE_ZERO') && ($key == 'ACCOUNTING_LENGTH_GACCOUNT' || $key == 'ACCOUNTING_LENGTH_AACCOUNT')) {
 		continue;
 	}
 
@@ -404,33 +384,6 @@ print '<tr class="liste_titre">';
 print '<td colspan="2">'.$langs->trans('BindingOptions').'</td>';
 print "</tr>\n";
 
-// TO DO Mutualize code for yes/no constants
-print '<tr class="oddeven">';
-print '<td>'.$langs->trans("ACCOUNTING_LIST_SORT_VENTILATION_TODO").'</td>';
-if (!empty($conf->global->ACCOUNTING_LIST_SORT_VENTILATION_TODO)) {
-	print '<td class="right"><a class="reposition" href="'.$_SERVER['PHP_SELF'].'?token='.newToken().'&action=setACCOUNTING_LIST_SORT_VENTILATION_TODO&value=0">';
-	print img_picto($langs->trans("Activated"), 'switch_on');
-	print '</a></td>';
-} else {
-	print '<td class="right"><a class="reposition" href="'.$_SERVER['PHP_SELF'].'?token='.newToken().'&action=setACCOUNTING_LIST_SORT_VENTILATION_TODO&value=1">';
-	print img_picto($langs->trans("Disabled"), 'switch_off');
-	print '</a></td>';
-}
-print '</tr>';
-
-print '<tr class="oddeven">';
-print '<td>'.$langs->trans("ACCOUNTING_LIST_SORT_VENTILATION_DONE").'</td>';
-if (!empty($conf->global->ACCOUNTING_LIST_SORT_VENTILATION_DONE)) {
-	print '<td class="right"><a class="reposition" href="'.$_SERVER['PHP_SELF'].'?token='.newToken().'&action=setACCOUNTING_LIST_SORT_VENTILATION_DONE&value=0">';
-	print img_picto($langs->trans("Activated"), 'switch_on');
-	print '</a></td>';
-} else {
-	print '<td class="right"><a class="reposition" href="'.$_SERVER['PHP_SELF'].'?token='.newToken().'&action=setACCOUNTING_LIST_SORT_VENTILATION_DONE&value=1">';
-	print img_picto($langs->trans("Disabled"), 'switch_off');
-	print '</a></td>';
-}
-print '</tr>';
-
 // Param a user $user->rights->accounting->chartofaccount can access
 foreach ($list_binding as $key) {
 	print '<tr class="oddeven value">';
@@ -441,10 +394,10 @@ foreach ($list_binding as $key) {
 	// Value
 	print '<td class="right">';
 	if ($key == 'ACCOUNTING_DATE_START_BINDING') {
-		print $form->selectDate((!empty($conf->global->$key) ? $db->idate($conf->global->$key) : -1), $key, 0, 0, 1);
+		print $form->selectDate((getDolGlobalInt($key) ? (int) getDolGlobalInt($key) : -1), $key, 0, 0, 1);
 	} elseif ($key == 'ACCOUNTING_DEFAULT_PERIOD_ON_TRANSFER') {
 		$array = array(0=>$langs->trans("PreviousMonth"), 1=>$langs->trans("CurrentMonth"), 2=>$langs->trans("Fiscalyear"));
-		print $form->selectarray($key, $array, (isset($conf->global->ACCOUNTING_DEFAULT_PERIOD_ON_TRANSFER) ? $conf->global->ACCOUNTING_DEFAULT_PERIOD_ON_TRANSFER : 0), 0, 0, 0, '', 0, 0, 0, '', 'onrightofpage');
+		print $form->selectarray($key, $array, getDolGlobalInt('ACCOUNTING_DEFAULT_PERIOD_ON_TRANSFER', 0), 0, 0, 0, '', 0, 0, 0, '', 'onrightofpage');
 	} else {
 		print '<input type="text" class="maxwidth100" id="'.$key.'" name="'.$key.'" value="'.getDolGlobalString($key).'">';
 	}
@@ -495,14 +448,20 @@ print '</tr>';
 print '</table>';
 print '<br>';
 
-// Lettering params
+
+// Show advanced options
+print '<br>';
+
+
+// Advanced params
 print '<table class="noborder centpercent">';
 print '<tr class="liste_titre">';
-print '<td colspan="2">'.$langs->trans('Options').' '.$langs->trans('Lettering').'</td>';
+print '<td colspan="2">' . $langs->trans('OptionsAdvanced') . '</td>';
 print "</tr>\n";
 
 print '<tr class="oddeven">';
-print '<td>'.$langs->trans("ACCOUNTING_ENABLE_LETTERING").'</td>';
+print '<td>';
+print $form->textwithpicto($langs->trans("ACCOUNTING_ENABLE_LETTERING"), $langs->trans("ACCOUNTING_ENABLE_LETTERING_DESC", $langs->transnoentitiesnoconv("NumMvts")).'<br>'.$langs->trans("EnablingThisFeatureIsNotNecessary")).'</td>';
 if (!empty($conf->global->ACCOUNTING_ENABLE_LETTERING)) {
 	print '<td class="right"><a class="reposition" href="'.$_SERVER['PHP_SELF'].'?token='.newToken().'&action=setenablelettering&value=0">';
 	print img_picto($langs->trans("Activated"), 'switch_on');
@@ -516,7 +475,8 @@ print '</tr>';
 
 if (!empty($conf->global->ACCOUNTING_ENABLE_LETTERING)) {
 	print '<tr class="oddeven">';
-	print '<td>' . $langs->trans("ACCOUNTING_ENABLE_AUTOLETTERING") . '</td>';
+	print '<td>';
+	print $form->textwithpicto($langs->trans("ACCOUNTING_ENABLE_AUTOLETTERING"), $langs->trans("ACCOUNTING_ENABLE_AUTOLETTERING_DESC")) . '</td>';
 	if (!empty($conf->global->ACCOUNTING_ENABLE_AUTOLETTERING)) {
 		print '<td class="right"><a class="reposition" href="' . $_SERVER['PHP_SELF'] . '?token=' . newToken() . '&action=setenableautolettering&value=0">';
 		print img_picto($langs->trans("Activated"), 'switch_on');
@@ -529,8 +489,23 @@ if (!empty($conf->global->ACCOUNTING_ENABLE_LETTERING)) {
 	print '</tr>';
 }
 
+print '<tr class="oddeven">';
+print '<td>';
+print $form->textwithpicto($langs->trans("ACCOUNTING_FORCE_ENABLE_VAT_REVERSE_CHARGE"), $langs->trans("ACCOUNTING_FORCE_ENABLE_VAT_REVERSE_CHARGE_DESC", $langs->transnoentities("MenuDefaultAccounts"))).'</td>';
+if (!empty($conf->global->ACCOUNTING_FORCE_ENABLE_VAT_REVERSE_CHARGE)) {
+	print '<td class="right"><a class="reposition" href="' . $_SERVER['PHP_SELF'] . '?token=' . newToken() . '&action=setenablevatreversecharge&value=0">';
+	print img_picto($langs->trans("Activated"), 'switch_on');
+	print '</a></td>';
+} else {
+	print '<td class="right"><a class="reposition" href="' . $_SERVER['PHP_SELF'] . '?token=' . newToken() . '&action=setenablevatreversecharge&value=1">';
+	print img_picto($langs->trans("Disabled"), 'switch_off');
+	print '</a></td>';
+}
+print '</tr>';
+
 print '</table>';
 
+
 print '<div class="center"><input type="submit" class="button button-edit" name="button" value="'.$langs->trans('Modify').'"></div>';
 
 print '</form>';

+ 3 - 3
htdocs/accountancy/admin/journals_list.php

@@ -431,9 +431,9 @@ if ($id) {
 		print '<td>';
 		print '<input type="hidden" name="id" value="'.$id.'">';
 		print '</td>';
-		print '<td style="min-width: 26px;"></td>';
-		print '<td style="min-width: 26px;"></td>';
-		print '<td style="min-width: 26px;"></td>';
+		print '<td></td>';
+		print '<td></td>';
+		print '<td></td>';
 		print '</tr>';
 
 		// Line to enter new values

+ 4 - 4
htdocs/accountancy/admin/productaccount.php

@@ -194,7 +194,7 @@ if ($action == 'update') {
 				}
 				if ($result <= 0) {
 					// setEventMessages(null, $accounting->errors, 'errors');
-					$msg .= '<div><span style="color:red">'.$langs->trans("ErrorDB").' : '.$langs->trans("Product").' '.$productid.' '.$langs->trans("NotVentilatedinAccount").' : id='.$accounting_account_id.'<br> <pre>'.$sql.'</pre></span></div>';
+					$msg .= '<div><span class="error">'.$langs->trans("ErrorDB").' : '.$langs->trans("Product").' '.$productid.' '.$langs->trans("NotVentilatedinAccount").' : id='.$accounting_account_id.'<br> <pre>'.$sql.'</pre></span></div>';
 					$ko++;
 				} else {
 					$sql = '';
@@ -203,7 +203,7 @@ if ($action == 'update') {
 						$sql_exists .= " WHERE fk_product = " . ((int) $productid) . " AND entity = " . ((int) $conf->entity);
 						$resql_exists = $db->query($sql_exists);
 						if (!$resql_exists) {
-							$msg .= '<div><span style="color:red">'.$langs->trans("ErrorDB").' : '.$langs->trans("Product").' '.$productid.' '.$langs->trans("NotVentilatedinAccount").' : id='.$accounting_account_id.'<br> <pre>'.$resql_exists.'</pre></span></div>';
+							$msg .= '<div><span class="error">'.$langs->trans("ErrorDB").' : '.$langs->trans("Product").' '.$productid.' '.$langs->trans("NotVentilatedinAccount").' : id='.$accounting_account_id.'<br> <pre>'.$resql_exists.'</pre></span></div>';
 							$ko++;
 						} else {
 							$nb_exists = $db->num_rows($resql_exists);
@@ -394,7 +394,7 @@ if (empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
 $sql .= $db->order($sortfield, $sortorder);
 
 $nbtotalofrecords = '';
-if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
+if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
 	$resql = $db->query($sql);
 	$nbtotalofrecords = $db->num_rows($resql);
 	if (($page * $limit) > $nbtotalofrecords) {	// if total resultset is smaller then paging size (filtering), goto and load page 0
@@ -416,7 +416,7 @@ if ($resql) {
 		$param .= '&contextpage='.urlencode($contextpage);
 	}
 	if ($limit > 0 && $limit != $conf->liste_limit) {
-		$param .= '&limit='.urlencode($limit);
+		$param .= '&limit='.((int) $limit);
 	}
 	if ($searchCategoryProductOperator == 1) {
 		$param .= "&search_category_product_operator=".urlencode($searchCategoryProductOperator);

+ 9 - 9
htdocs/accountancy/admin/subaccount.php

@@ -78,7 +78,7 @@ $arrayfields = array(
 	'reconcilable'=>array('label'=>$langs->trans("Reconcilable"), 'checked'=>1)
 );
 
-if ($conf->global->MAIN_FEATURES_LEVEL < 2) {
+if (getDolGlobalInt('MAIN_FEATURES_LEVEL') < 2) {
 	unset($arrayfields['reconcilable']);
 }
 
@@ -137,8 +137,8 @@ $sql .= " AND sa.code_compta <> ''";
 //print $sql;
 if (strlen(trim($search_subaccount))) {
 	$lengthpaddingaccount = 0;
-	if ($conf->global->ACCOUNTING_LENGTH_AACCOUNT) {
-		$lengthpaddingaccount = max($conf->global->ACCOUNTING_LENGTH_AACCOUNT);
+	if (getDolGlobalInt('ACCOUNTING_LENGTH_AACCOUNT')) {
+		$lengthpaddingaccount = getDolGlobalInt('ACCOUNTING_LENGTH_AACCOUNT');
 	}
 	$search_subaccount_tmp = $search_subaccount;
 	$weremovedsomezero = 0;
@@ -184,8 +184,8 @@ $sql .= " AND sa.code_compta_fournisseur <> ''";
 //print $sql;
 if (strlen(trim($search_subaccount))) {
 	$lengthpaddingaccount = 0;
-	if ($conf->global->ACCOUNTING_LENGTH_AACCOUNT) {
-		$lengthpaddingaccount = max($conf->global->ACCOUNTING_LENGTH_AACCOUNT);
+	if (getDolGlobalInt('ACCOUNTING_LENGTH_AACCOUNT')) {
+		$lengthpaddingaccount = getDolGlobalInt('ACCOUNTING_LENGTH_AACCOUNT');
 	}
 	$search_subaccount_tmp = $search_subaccount;
 	$weremovedsomezero = 0;
@@ -231,8 +231,8 @@ $sql .= " AND u.accountancy_code <> ''";
 //print $sql;
 if (strlen(trim($search_subaccount))) {
 	$lengthpaddingaccount = 0;
-	if ($conf->global->ACCOUNTING_LENGTH_AACCOUNT) {
-		$lengthpaddingaccount = max($conf->global->ACCOUNTING_LENGTH_AACCOUNT);
+	if (getDolGlobalInt('ACCOUNTING_LENGTH_AACCOUNT')) {
+		$lengthpaddingaccount = getDolGlobalInt('ACCOUNTING_LENGTH_AACCOUNT');
 	}
 	$search_subaccount_tmp = $search_subaccount;
 	$weremovedsomezero = 0;
@@ -274,7 +274,7 @@ $sql .= $db->order($sortfield, $sortorder);
 
 // Count total nb of records
 $nbtotalofrecords = '';
-if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
+if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
 	$resql = $db->query($sql);
 	$nbtotalofrecords = $db->num_rows($resql);
 	if (($page * $limit) > $nbtotalofrecords) {	// if total resultset is smaller then paging size (filtering), goto and load page 0
@@ -296,7 +296,7 @@ if ($resql) {
 		$param .= '&contextpage='.urlencode($contextpage);
 	}
 	if ($limit > 0 && $limit != $conf->liste_limit) {
-		$param .= '&limit='.urlencode($limit);
+		$param .= '&limit='.((int) $limit);
 	}
 	if ($search_subaccount) {
 		$param .= '&search_subaccount='.urlencode($search_subaccount);

+ 3 - 3
htdocs/accountancy/bookkeeping/balance.php

@@ -254,7 +254,7 @@ llxHeader('', $title_page);
 if ($action != 'export_csv') {
 	// List
 	$nbtotalofrecords = '';
-	if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
+	if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
 		if ($type == 'sub') {
 			$nbtotalofrecords = $object->fetchAllBalance($sortorder, $sortfield, 0, 0, $filter, 'AND', 1);
 		} else {
@@ -304,7 +304,7 @@ if ($action != 'export_csv') {
 
 		print '<script type="text/javascript">
 		jQuery(document).ready(function() {
-			jQuery("#exportcsvbutton").click(function() {
+			jQuery("#exportcsvbutton").click(function(event) {
 				event.preventDefault();
 				console.log("Set action to export_csv");
 				jQuery("#action").val("export_csv");
@@ -327,7 +327,7 @@ if ($action != 'export_csv') {
 		$param .= '&contextpage='.urlencode($contextpage);
 	}
 	if ($limit > 0 && $limit != $conf->liste_limit) {
-		$param .= '&limit='.urlencode($limit);
+		$param .= '&limit='.((int) $limit);
 	}
 
 	print_barre_liste($title_page, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $result, $nbtotalofrecords, 'title_accountancy', 0, $newcardbutton, '', $limit, 0, 0, 1);

+ 417 - 360
htdocs/accountancy/bookkeeping/card.php

@@ -4,7 +4,6 @@
  * Copyright (C) 2013-2022  Alexandre Spangaro      <aspangaro@open-dsi.fr>
  * Copyright (C) 2017       Laurent Destailleur     <eldy@users.sourceforge.net>
  * Copyright (C) 2018-2020  Frédéric France         <frederic.france@netlogic.fr>
- * Copyright (C) 2022       Waël Almoman            <info@almoman.com>
  *
  * 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
@@ -45,7 +44,7 @@ $cancel = GETPOST('cancel', 'aZ09');
 $optioncss = GETPOST('optioncss', 'aZ'); // Option for the css output (always '' except when 'print')
 
 $id = GETPOST('id', 'int'); // id of record
-$mode = $mode = $action == 'create' ? "_tmp" : GETPOST('mode', 'aZ09'); // '' or '_tmp'
+$mode = GETPOST('mode', 'aZ09'); // '' or '_tmp'
 $piece_num = GETPOST("piece_num", 'int'); // id of transaction (several lines share the same transaction id)
 
 $accountingaccount = new AccountingAccount($db);
@@ -55,15 +54,10 @@ $accountingaccount_number = GETPOST('accountingaccount_number', 'alphanohtml');
 $accountingaccount->fetch(null, $accountingaccount_number, true);
 $accountingaccount_label = $accountingaccount->label;
 
-$journal_code = GETPOST('code_journal', 'alpha') ? GETPOST('code_journal', 'alpha') : "NULL";
+$journal_code = GETPOST('code_journal', 'alpha');
 $accountingjournal->fetch(null, $journal_code);
 $journal_label = $accountingjournal->label;
 
-$next_num_mvt = (int) GETPOST('next_num_mvt', 'alpha');
-$doc_ref = (string) GETPOST('doc_ref', 'alpha');
-$doc_date = (string) GETPOST('doc_date', 'alpha');
-$doc_date = $doc_date = dol_mktime(0, 0, 0, GETPOST('doc_datemonth', 'int'), GETPOST('doc_dateday', 'int'), GETPOST('doc_dateyear', 'int'));
-
 $subledger_account = GETPOST('subledger_account', 'alphanohtml');
 if ($subledger_account == -1) {
 	$subledger_account = null;
@@ -78,10 +72,6 @@ $save = GETPOST('save', 'alpha');
 if (!empty($save)) {
 	$action = 'add';
 }
-$valid = GETPOST('validate', 'alpha');
-if (!empty($valid)) {
-	$action = 'valid';
-}
 $update = GETPOST('update', 'alpha');
 if (!empty($update)) {
 	$action = 'confirm_update';
@@ -166,79 +156,64 @@ if ($action == "confirm_update") {
 			}
 		}
 	}
-} elseif ($action == 'add' || $action == 'valid') {
+} elseif ($action == "add") {
 	$error = 0;
 
-	if (array_sum($debit) != array_sum($credit)) {
-		$action = 'add';
+	if ((floatval($debit) != 0.0) && (floatval($credit) != 0.0)) {
+		$error++;
+		setEventMessages($langs->trans('ErrorDebitCredit'), null, 'errors');
+		$action = '';
+	}
+	if (empty($accountingaccount_number) || $accountingaccount_number == '-1') {
+		$error++;
+		setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv("AccountAccountingShort")), null, 'errors');
+		$action = '';
 	}
 
-	foreach ($accountingaccount_number as $key => $value) {
-		$accountingaccount->fetch(null, $accountingaccount_number[$key], true);
-		$accountingaccount_label[$key] = $accountingaccount->label[$key];
+	if (!$error) {
+		$object = new BookKeeping($db);
 
-		// if one added row is empty remove it before continue
-		if ($key < 1 && (empty($accountingaccount_number[$key]) || $accountingaccount_number[$key] == '-1') || (floatval($debit[$key]) == 0.0) && (floatval($credit[$key]) == 0.0)) {
-			continue;
-		}
+		$object->numero_compte = $accountingaccount_number;
+		$object->subledger_account = $subledger_account;
+		$object->subledger_label = $subledger_label;
+		$object->label_compte = $accountingaccount_label;
+		$object->label_operation = $label_operation;
+		$object->debit = $debit;
+		$object->credit = $credit;
+		$object->doc_date = (string) GETPOST('doc_date', 'alpha');
+		$object->doc_type = (string) GETPOST('doc_type', 'alpha');
+		$object->piece_num = $piece_num;
+		$object->doc_ref = (string) GETPOST('doc_ref', 'alpha');
+		$object->code_journal = $journal_code;
+		$object->journal_label = $journal_label;
+		$object->fk_doc = GETPOSTINT('fk_doc');
+		$object->fk_docdet = GETPOSTINT('fk_docdet');
 
-		if ((floatval($debit[$key]) != 0.0) && (floatval($credit[$key]) != 0.0)) {
-			$error++;
-			setEventMessages($langs->trans('ErrorDebitCredit'), null, 'errors');
-			$action = '';
+		if (floatval($debit) != 0.0) {
+			$object->montant = $debit; // deprecated
+			$object->amount = $debit;
+			$object->sens = 'D';
 		}
 
-		if (empty($accountingaccount_number[$key]) || $accountingaccount_number[$key] == '-1') {
-			$error++;
-			setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv("AccountAccountingShort")), null, 'errors');
-			$action = '';
+		if (floatval($credit) != 0.0) {
+			$object->montant = $credit; // deprecated
+			$object->amount = $credit;
+			$object->sens = 'C';
 		}
 
-		if (!$error) {
-			$object = new BookKeeping($db);
-			$object->numero_compte = $accountingaccount_number[$key];
-			$object->subledger_account = $subledger_account[$key];
-			$object->subledger_label = $subledger_label[$key];
-			$object->label_compte = $accountingaccount_label[$key];
-			$object->label_operation = $label_operation[$key];
-			$object->debit = price2num($debit[$key]);
-			$object->credit = price2num($credit[$key]);
-			$object->doc_date = $doc_date;
-			$object->doc_type = (string) GETPOST('doc_type', 'alpha');
-			$object->piece_num = $piece_num;
-			$object->doc_ref = $doc_ref;
-			$object->code_journal = $journal_code;
-			$object->journal_label = $journal_label;
-			$object->fk_doc = GETPOSTINT('fk_doc');
-			$object->fk_docdet = GETPOSTINT('fk_docdet');
-
-			if (floatval($debit[$key]) != 0.0) {
-				$object->montant = $object->debit; // deprecated
-				$object->amount = $object->debit;
-				$object->sens = 'D';
+		$result = $object->createStd($user, false, $mode);
+		if ($result < 0) {
+			setEventMessages($object->error, $object->errors, 'errors');
+		} else {
+			if ($mode != '_tmp') {
+				setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
 			}
 
-			if (floatval($credit[$key]) != 0.0) {
-				$object->montant = $object->credit; // deprecated
-				$object->amount = $object->credit;
-				$object->sens = 'C';
-			}
+			$debit = 0;
+			$credit = 0;
 
-			$result = $object->createStd($user, false, $mode);
-			if ($result < 0) {
-				$error++;
-				setEventMessages($object->error, $object->errors, 'errors');
-			}
-		}
-	}
-	if (empty($error)) {
-		if ($mode != '_tmp') {
-			setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
+			$action = '';
 		}
-		$debit = 0;
-		$credit = 0;
-
-		$action = $action == 'add' ? '' : $action ; // stay in valid mode when not adding line
 	}
 } elseif ($action == "confirm_delete") {
 	$object = new BookKeeping($db);
@@ -255,28 +230,17 @@ if ($action == "confirm_update") {
 		}
 	}
 	$action = '';
-} elseif ($action == 'create') {
+} elseif ($action == "confirm_create") {
 	$error = 0;
 
 	$object = new BookKeeping($db);
 
-	$next_num_mvt =  !empty($next_num_mvt) ? $next_num_mvt : $object->getNextNumMvt('_tmp');
-	$doc_ref = !empty($doc_ref) ? $doc_ref : $next_num_mvt;
-
-	if (empty($doc_date)) {
-		$tmp_date = dol_getdate(dol_now());
-		$_POST['doc_dateday'] =  $tmp_date['mday'];
-		$_POST['doc_datemonth'] = $tmp_date['mon'];
-		$_POST['doc_dateyear'] = $tmp_date['year'];
-		unset($tmp_date);
-	}
-
 	if (!$journal_code || $journal_code == '-1') {
 		setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv("Journal")), null, 'errors');
 		$action = 'create';
 		$error++;
 	}
-	if (empty($doc_ref)) {
+	if (!GETPOST('doc_ref', 'alpha')) {
 		setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Piece")), null, 'errors');
 		$action = 'create';
 		$error++;
@@ -288,8 +252,8 @@ if ($action == "confirm_update") {
 		$object->credit = 0;
 		$object->doc_date = $date_start = dol_mktime(0, 0, 0, GETPOST('doc_datemonth', 'int'), GETPOST('doc_dateday', 'int'), GETPOST('doc_dateyear', 'int'));
 		$object->doc_type = GETPOST('doc_type', 'alpha');
-		$object->piece_num = $next_num_mvt;
-		$object->doc_ref = $doc_ref;
+		$object->piece_num = GETPOST('next_num_mvt', 'alpha');
+		$object->doc_ref = GETPOST('doc_ref', 'alpha');
 		$object->code_journal = $journal_code;
 		$object->journal_label = $journal_label;
 		$object->fk_doc = 0;
@@ -338,7 +302,7 @@ if ($action == 'setjournal') {
 }
 
 if ($action == 'setdocref') {
-	$refdoc = $doc_ref;
+	$refdoc = GETPOST('doc_ref', 'alpha');
 	$result = $object->updateByMvt($piece_num, 'doc_ref', $refdoc, $mode);
 	if ($result < 0) {
 		setEventMessages($object->error, $object->errors, 'errors');
@@ -369,7 +333,7 @@ if ($action == 'valid') {
 $html = new Form($db);
 $formaccounting = new FormAccounting($db);
 
-$title = $langs->trans($mode =="_tmp" ? "CreateMvts": "UpdateMvts");
+$title = $langs->trans("CreateMvts");
 
 llxHeader('', $title);
 
@@ -379,37 +343,28 @@ if ($action == 'delete') {
 	print $formconfirm;
 }
 
+if ($action == 'create') {
+	print load_fiche_titre($title);
 
-$object = new BookKeeping($db);
-$result = $object->fetchPerMvt($piece_num, $mode);
-if ($result < 0) {
-	setEventMessages($object->error, $object->errors, 'errors');
-}
-
-if (!empty($object->piece_num)) {
-	$backlink = '<a href="'.DOL_URL_ROOT.'/accountancy/bookkeeping/list.php?restore_lastsearch_values=1">'.$langs->trans('BackToList').'</a>';
-
-	print load_fiche_titre($langs->trans($mode =="_tmp" ? "CreateMvts": "UpdateMvts"), $backlink);
+	$object = new BookKeeping($db);
+	$next_num_mvt = $object->getNextNumMvt('_tmp');
 
-	print '<form action="'.$_SERVER["PHP_SELF"].'?piece_num='.$object->piece_num.'" method="post">';	if ($optioncss != '') {
-		print '<input type="hidden" name="optioncss" value="'.$optioncss.'" />';
+	if (empty($next_num_mvt)) {
+		dol_print_error('', 'Failed to get next piece number');
 	}
-	$head = array();
-	$h = 0;
-	$head[$h][0] = $_SERVER['PHP_SELF'].'?piece_num='.$object->piece_num.($mode ? '&mode='.$mode : '');
-	$head[$h][1] = $langs->trans("Transaction");
-	$head[$h][2] = 'transaction';
-	$h++;
-
-	print dol_get_fiche_head($head, 'transaction', '', -1);
 
-	//dol_banner_tab($object, '', $backlink);
+	print '<form action="'.$_SERVER["PHP_SELF"].'" name="create_mvt" method="POST">';
+	if ($optioncss != '') {
+		print '<input type="hidden" name="optioncss" value="'.$optioncss.'">';
+	}
+	print '<input type="hidden" name="token" value="'.newToken().'">';
+	print '<input type="hidden" name="action" value="confirm_create">'."\n";
+	print '<input type="hidden" name="next_num_mvt" value="'.$next_num_mvt.'">'."\n";
+	print '<input type="hidden" name="mode" value="_tmp">'."\n";
 
-	print '<div class="fichecenter">';
-	print '<div class="fichehalfleft">';
+	print dol_get_fiche_head();
 
-	print '<div class="underbanner clearboth"></div>';
-	print '<table class="border tableforfield" width="100%">';
+	print '<table class="border centpercent">';
 
 	/*print '<tr>';
 	print '<td class="titlefieldcreate fieldrequired">' . $langs->trans("NumPiece") . '</td>';
@@ -419,7 +374,7 @@ if (!empty($object->piece_num)) {
 	print '<tr>';
 	print '<td class="titlefieldcreate fieldrequired">'.$langs->trans("Docdate").'</td>';
 	print '<td>';
-	print $html->selectDate($doc_date, 'doc_date', '', '', '', "create_mvt", 1, 1);
+	print $html->selectDate('', 'doc_date', '', '', '', "create_mvt", 1, 1);
 	print '</td>';
 	print '</tr>';
 
@@ -430,218 +385,298 @@ if (!empty($object->piece_num)) {
 
 	print '<tr>';
 	print '<td class="fieldrequired">'.$langs->trans("Piece").'</td>';
-	print '<td><input type="text" class="minwidth200" name="doc_ref" value="'.$doc_ref.'" /></td>';
+	print '<td><input type="text" class="minwidth200" name="doc_ref" value="'.GETPOST('doc_ref', 'alpha').'"></td>';
 	print '</tr>';
 
 	/*
 	print '<tr>';
 	print '<td>' . $langs->trans("Doctype") . '</td>';
-	print '<td><input type="text" class="minwidth200 name="doc_type" value="" /></td>';
+	print '<td><input type="text" class="minwidth200 name="doc_type" value=""/></td>';
 	print '</tr>';
 	*/
 
 	print '</table>';
 
-	print '</div>';
-
-	print '<div class="fichehalfright">';
+	print dol_get_fiche_end();
 
-	print '<div class="underbanner clearboth"></div>';
-	print '<table class="border tableforfield centpercent">';
+	print $form->buttonsSaveCancel("Create");
 
-	// Doc type
-	if (!empty($object->doc_type)) {
-		print '<tr>';
-		print '<td class="titlefield">'.$langs->trans("Doctype").'</td>';
-		print '<td>'.$object->doc_type.'</td>';
-		print '</tr>';
+	print '</form>';
+} else {
+	$object = new BookKeeping($db);
+	$result = $object->fetchPerMvt($piece_num, $mode);
+	if ($result < 0) {
+		setEventMessages($object->error, $object->errors, 'errors');
 	}
 
-	// Date document creation
-	print '<tr>';
-	print '<td class="titlefield">'.$langs->trans("DateCreation").'</td>';
-	print '<td>';
-	print $object->date_creation ? dol_print_date($object->date_creation, 'day') : '&nbsp;';
-	print '</td>';
-	print '</tr>';
+	if (!empty($object->piece_num)) {
+		$backlink = '<a href="'.DOL_URL_ROOT.'/accountancy/bookkeeping/list.php?restore_lastsearch_values=1">'.$langs->trans('BackToList').'</a>';
+
+		if ($mode == '_tmp') {
+			print load_fiche_titre($langs->trans("CreateMvts"), $backlink);
+		} else {
+			print load_fiche_titre($langs->trans("UpdateMvts"), $backlink);
+		}
+
+		$head = array();
+		$h = 0;
+		$head[$h][0] = $_SERVER['PHP_SELF'].'?piece_num='.$object->piece_num.($mode ? '&mode='.$mode : '');
+		$head[$h][1] = $langs->trans("Transaction");
+		$head[$h][2] = 'transaction';
+		$h++;
+
+		print dol_get_fiche_head($head, 'transaction', '', -1);
+
+		//dol_banner_tab($object, '', $backlink);
 
-	// Don't show in tmp mode, inevitably empty
-	if ($mode != "_tmp") {
-		// Date document export
+		print '<div class="fichecenter">';
+		print '<div class="fichehalfleft">';
+
+		print '<div class="underbanner clearboth"></div>';
+		print '<table class="border tableforfield" width="100%">';
+
+		// Account movement
 		print '<tr>';
-		print '<td class="titlefield">'.$langs->trans("DateExport").'</td>';
-		print '<td>';
-		print $object->date_export ? dol_print_date($object->date_export, 'dayhour') : '&nbsp;';
+		print '<td class="titlefield">'.$langs->trans("NumMvts").'</td>';
+		print '<td>'.($mode == '_tmp' ? '<span class="opacitymedium" title="Id tmp '.$object->piece_num.'">'.$langs->trans("Draft").'</span>' : $object->piece_num).'</td>';
+		print '</tr>';
+
+		// Date
+		print '<tr><td>';
+		print '<table class="nobordernopadding centpercent"><tr><td>';
+		print $langs->trans('Docdate');
+		print '</td>';
+		if ($action != 'editdate') {
+			print '<td class="right"><a class="editfielda reposition" href="'.$_SERVER["PHP_SELF"].'?action=editdate&token='.newToken().'&piece_num='.urlencode($object->piece_num).'&mode='.urlencode($mode).'">'.img_edit($langs->transnoentitiesnoconv('SetDate'), 1).'</a></td>';
+		}
+		print '</tr></table>';
+		print '</td><td colspan="3">';
+		if ($action == 'editdate') {
+			print '<form name="setdate" action="'.$_SERVER["PHP_SELF"].'?piece_num='.$object->piece_num.'" method="post">';
+			if ($optioncss != '') {
+				print '<input type="hidden" name="optioncss" value="'.$optioncss.'">';
+			}
+			print '<input type="hidden" name="token" value="'.newToken().'">';
+			print '<input type="hidden" name="action" value="setdate">';
+			print '<input type="hidden" name="mode" value="'.$mode.'">';
+			print $form->selectDate($object->doc_date ? $object->doc_date : - 1, 'doc_date', '', '', '', "setdate");
+			print '<input type="submit" class="button button-edit" value="'.$langs->trans('Modify').'">';
+			print '</form>';
+		} else {
+			print $object->doc_date ? dol_print_date($object->doc_date, 'day') : '&nbsp;';
+		}
 		print '</td>';
 		print '</tr>';
 
-		// Date document validation
-		print '<tr>';
-		print '<td class="titlefield">'.$langs->trans("DateValidation").'</td>';
-		print '<td>';
-		print $object->date_validation ? dol_print_date($object->date_validation, 'dayhour') : '&nbsp;';
+		// Journal
+		print '<tr><td>';
+		print '<table class="nobordernopadding" width="100%"><tr><td>';
+		print $langs->trans('Codejournal');
+		print '</td>';
+		if ($action != 'editjournal') {
+			print '<td class="right"><a class="editfielda reposition" href="'.$_SERVER["PHP_SELF"].'?action=editjournal&token='.newToken().'&piece_num='.urlencode($object->piece_num).'&mode='.urlencode($mode).'">'.img_edit($langs->transnoentitiesnoconv('Edit'), 1).'</a></td>';
+		}
+		print '</tr></table>';
+		print '</td><td>';
+		if ($action == 'editjournal') {
+			print '<form name="setjournal" action="'.$_SERVER["PHP_SELF"].'?piece_num='.$object->piece_num.'" method="post">';
+			if ($optioncss != '') {
+				print '<input type="hidden" name="optioncss" value="'.$optioncss.'">';
+			}
+			print '<input type="hidden" name="token" value="'.newToken().'">';
+			print '<input type="hidden" name="action" value="setjournal">';
+			print '<input type="hidden" name="mode" value="'.$mode.'">';
+			print $formaccounting->select_journal($object->code_journal, 'code_journal', 0, 0, array(), 1, 1);
+			print '<input type="submit" class="button button-edit" value="'.$langs->trans('Modify').'">';
+			print '</form>';
+		} else {
+			print $object->code_journal;
+		}
 		print '</td>';
 		print '</tr>';
-	}
-	// Validate
-	/*
-	print '<tr>';
-	print '<td class="titlefield">' . $langs->trans("Status") . '</td>';
-	print '<td>';
-	if (empty($object->validated)) {
-		print '<a class="reposition" href="' . $_SERVER["PHP_SELF"] . '?piece_num=' . $line->id . '&action=enable&token='.newToken().'">';
-		print img_picto($langs->trans("Disabled"), 'switch_off');
-		print '</a>';
-	} else {
-		print '<a class="reposition" href="' . $_SERVER["PHP_SELF"] . '?piece_num=' . $line->id . '&action=disable&token='.newToken().'">';
-		print img_picto($langs->trans("Activated"), 'switch_on');
-		print '</a>';
-	}
-	print '</td>';
-	print '</tr>';
-	*/
 
-		// check data
-	/*
-	print '<tr>';
-	print '<td class="titlefield">' . $langs->trans("Control") . '</td>';
-	if ($object->doc_type == 'customer_invoice') {
-		$sqlmid = 'SELECT rowid as ref';
-		$sqlmid .= " FROM ".MAIN_DB_PREFIX."facture as fac";
-		$sqlmid .= " WHERE fac.rowid=" . ((int) $object->fk_doc);
-		dol_syslog("accountancy/bookkeeping/card.php::sqlmid=" . $sqlmid, LOG_DEBUG);
-		$resultmid = $db->query($sqlmid);
-		if ($resultmid) {
-			$objmid = $db->fetch_object($resultmid);
-			$invoicestatic = new Facture($db);
-			$invoicestatic->fetch($objmid->ref);
-			$ref=$langs->trans("Invoice").' '.$invoicestatic->getNomUrl(1);
+		// Ref document
+		print '<tr><td>';
+		print '<table class="nobordernopadding" width="100%"><tr><td>';
+		print $langs->trans('Piece');
+		print '</td>';
+		if ($action != 'editdocref') {
+			print '<td class="right"><a class="editfielda reposition" href="'.$_SERVER["PHP_SELF"].'?action=editdocref&token='.newToken().'&piece_num='.urlencode($object->piece_num).'&mode='.urlencode($mode).'">'.img_edit($langs->transnoentitiesnoconv('Edit'), 1).'</a></td>';
+		}
+		print '</tr></table>';
+		print '</td><td>';
+		if ($action == 'editdocref') {
+			print '<form name="setdocref" action="'.$_SERVER["PHP_SELF"].'?piece_num='.$object->piece_num.'" method="post">';
+			if ($optioncss != '') {
+				print '<input type="hidden" name="optioncss" value="'.$optioncss.'">';
+			}
+			print '<input type="hidden" name="token" value="'.newToken().'">';
+			print '<input type="hidden" name="action" value="setdocref">';
+			print '<input type="hidden" name="mode" value="'.$mode.'">';
+			print '<input type="text" size="20" name="doc_ref" value="'.dol_escape_htmltag($object->doc_ref).'">';
+			print '<input type="submit" class="button button-edit" value="'.$langs->trans('Modify').'">';
+			print '</form>';
 		} else {
-			dol_print_error($db);
+			print $object->doc_ref;
 		}
-	}
-	print '<td>' . $ref .'</td>';
-	print '</tr>';
-	*/
-	print "</table>\n";
+		print '</td>';
+		print '</tr>';
 
-	print dol_get_fiche_end();
+		print '</table>';
 
-	print '<div style="clear:both"></div>';
+		print '</div>';
 
-	print '<br>';
+		print '<div class="fichehalfright">';
 
-	$result = $object->fetchAllPerMvt($piece_num, $mode);	// This load $object->linesmvt
-	if ($result < 0) {
-		setEventMessages($object->error, $object->errors, 'errors');
-	} else {
-		// List of movements
-		print load_fiche_titre($langs->trans("ListeMvts"), '', '');
+		print '<div class="underbanner clearboth"></div>';
+		print '<table class="border tableforfield centpercent">';
 
-		if ($optioncss != '') {
-			print '<input type="hidden" name="optioncss" value="'.$optioncss.'" />';
+		// Doc type
+		if (!empty($object->doc_type)) {
+			print '<tr>';
+			print '<td class="titlefield">'.$langs->trans("Doctype").'</td>';
+			print '<td>'.$object->doc_type.'</td>';
+			print '</tr>';
 		}
 
-		print '<input type="hidden" name="token" value="'.newToken().'" />';
-		print '<input type="hidden" name="doc_type" value="'.$object->doc_type.'" />'."\n";
-		print '<input type="hidden" name="fk_doc" value="'.$object->fk_doc.'" />'."\n";
-		print '<input type="hidden" name="fk_docdet" value="'.$object->fk_docdet.'" />'."\n";
-		print '<input type="hidden" name="mode" value="'.$mode.'" />'."\n";
-
-		if (count($object->linesmvt) > 0) {
-			print '<div class="div-table-responsive-no-min">';
-			print '<table class="noborder centpercent">';
-
-			$total_debit = 0;
-			$total_credit = 0;
-
-			// Don't show in tmp mode, inevitably empty
-			if ($mode != "_tmp") {
-				// Date document export
-				print '<tr>';
-				print '<td class="titlefield">' . $langs->trans("DateExport") . '</td>';
-				print '<td>';
-				print $object->date_export ? dol_print_date($object->date_export, 'dayhour') : '&nbsp;';
-				print '</td>';
-				print '</tr>';
-
-				// Date document validation
-				print '<tr>';
-				print '<td class="titlefield">' . $langs->trans("DateValidation") . '</td>';
-				print '<td>';
-				print $object->date_validation ? dol_print_date($object->date_validation, 'dayhour') : '&nbsp;';
-				print '</td>';
-				print '</tr>';
-			}
+		// Date document creation
+		print '<tr>';
+		print '<td class="titlefield">'.$langs->trans("DateCreation").'</td>';
+		print '<td>';
+		print $object->date_creation ? dol_print_date($object->date_creation, 'day') : '&nbsp;';
+		print '</td>';
+		print '</tr>';
 
-			print '<tr class="liste_titre">';
+		// Don't show in tmp mode, inevitably empty
+		if ($mode != "_tmp") {
+			// Date document export
+			print '<tr>';
+			print '<td class="titlefield">' . $langs->trans("DateExport") . '</td>';
+			print '<td>';
+			print $object->date_export ? dol_print_date($object->date_export, 'dayhour') : '&nbsp;';
+			print '</td>';
+			print '</tr>';
+
+			// Date document validation
+			print '<tr>';
+			print '<td class="titlefield">' . $langs->trans("DateValidation") . '</td>';
+			print '<td>';
+			print $object->date_validation ? dol_print_date($object->date_validation, 'dayhour') : '&nbsp;';
+			print '</td>';
+			print '</tr>';
+		}
 
-			print_liste_field_titre("AccountAccountingShort");
-			print_liste_field_titre("SubledgerAccount");
-			print_liste_field_titre("LabelOperation");
-			print_liste_field_titre("AccountingDebit", "", "", "", "", 'class="right"');
-			print_liste_field_titre("AccountingCredit", "", "", "", "", 'class="right"');
-			if (empty($object->date_validation)) {
-				print_liste_field_titre("Action", "", "", "", "", 'width="60"', "", "", 'center ');
+		// Validate
+		/*
+		print '<tr>';
+		print '<td class="titlefield">' . $langs->trans("Status") . '</td>';
+		print '<td>';
+			if (empty($object->validated)) {
+				print '<a class="reposition" href="' . $_SERVER["PHP_SELF"] . '?piece_num=' . $line->id . '&action=enable&token='.newToken().'">';
+				print img_picto($langs->trans("Disabled"), 'switch_off');
+				print '</a>';
 			} else {
-				print_liste_field_titre("");
+				print '<a class="reposition" href="' . $_SERVER["PHP_SELF"] . '?piece_num=' . $line->id . '&action=disable&token='.newToken().'">';
+				print img_picto($langs->trans("Activated"), 'switch_on');
+				print '</a>';
 			}
+			print '</td>';
+		print '</tr>';
+		*/
 
-			print "</tr>\n";
-
-			// In _tmp mode the first line is empty so we remove it
-			if ($mode == "_tmp") {
-				array_shift($object->linesmvt);
+		// check data
+		/*
+		print '<tr>';
+		print '<td class="titlefield">' . $langs->trans("Control") . '</td>';
+		if ($object->doc_type == 'customer_invoice')
+		{
+		 $sqlmid = 'SELECT rowid as ref';
+			$sqlmid .= " FROM ".MAIN_DB_PREFIX."facture as fac";
+			$sqlmid .= " WHERE fac.rowid=" . ((int) $object->fk_doc);
+			dol_syslog("accountancy/bookkeeping/card.php::sqlmid=" . $sqlmid, LOG_DEBUG);
+			$resultmid = $db->query($sqlmid);
+			if ($resultmid) {
+				$objmid = $db->fetch_object($resultmid);
+				$invoicestatic = new Facture($db);
+				$invoicestatic->fetch($objmid->ref);
+				$ref=$langs->trans("Invoice").' '.$invoicestatic->getNomUrl(1);
 			}
+			else dol_print_error($db);
+		}
+		print '<td>' . $ref .'</td>';
+		print '</tr>';
+		*/
+		print "</table>\n";
+
+		print '</div>';
+
+		print dol_get_fiche_end();
+
+		print '<div class="clearboth"></div>';
+
+		print '<br>';
 
-			// Add an empty line at the end to be able to add transaction
-			$line = new BookKeepingLine();
-			$object->linesmvt[] = $line;
+		$result = $object->fetchAllPerMvt($piece_num, $mode);	// This load $object->linesmvt
 
-			// Add a second line empty line if there is not yet
-			if (empty($object->linesmvt[1])) {
-				$line = new BookKeepingLine();
-				$object->linesmvt[] = $line;
+		if ($result < 0) {
+			setEventMessages($object->error, $object->errors, 'errors');
+		} else {
+			// List of movements
+			print load_fiche_titre($langs->trans("ListeMvts"), '', '');
+
+			print '<form action="'.$_SERVER["PHP_SELF"].'?piece_num='.$object->piece_num.'" method="post">';
+			if ($optioncss != '') {
+				print '<input type="hidden" name="optioncss" value="'.$optioncss.'">';
 			}
+			print '<input type="hidden" name="token" value="'.newToken().'">';
+			print '<input type="hidden" name="doc_date" value="'.$object->doc_date.'">'."\n";
+			print '<input type="hidden" name="doc_type" value="'.$object->doc_type.'">'."\n";
+			print '<input type="hidden" name="doc_ref" value="'.$object->doc_ref.'">'."\n";
+			print '<input type="hidden" name="code_journal" value="'.$object->code_journal.'">'."\n";
+			print '<input type="hidden" name="fk_doc" value="'.$object->fk_doc.'">'."\n";
+			print '<input type="hidden" name="fk_docdet" value="'.$object->fk_docdet.'">'."\n";
+			print '<input type="hidden" name="mode" value="'.$mode.'">'."\n";
+
+			if (count($object->linesmvt) > 0) {
+				print '<div class="div-table-responsive-no-min">';
+				print '<table class="noborder centpercent">';
+
+				$total_debit = 0;
+				$total_credit = 0;
+
+				print '<tr class="liste_titre">';
+
+				print_liste_field_titre("AccountAccountingShort");
+				print_liste_field_titre("SubledgerAccount");
+				print_liste_field_titre("LabelOperation");
+				print_liste_field_titre("AccountingDebit", "", "", "", "", 'class="right"');
+				print_liste_field_titre("AccountingCredit", "", "", "", "", 'class="right"');
+				if (empty($object->date_validation)) {
+					print_liste_field_titre("Action", "", "", "", "", 'width="60"', "", "", 'center ');
+				} else {
+					print_liste_field_titre("");
+				}
 
-			$count_line = count($object->linesmvt);
-			$num_line = 0;
-			foreach ($object->linesmvt as $key => $line) {
-				$num_line++;
-				print '<tr class="oddeven" data-lineid="'.((int) $line->id).'">';
-				$total_debit += $line->debit;
-				$total_credit += $line->credit;
-
-				if ($action == 'update' && $line->id == $id) {
-					print '<!-- td columns in edit mode -->';
-					print '<td>';
-					print $formaccounting->select_account((GETPOSTISSET("accountingaccount_number") ? GETPOST("accountingaccount_number", "alpha") : $line->numero_compte), '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((GETPOSTISSET("subledger_account") ? GETPOST("subledger_account", "alpha") : $line->subledger_account), 'subledger_account', 1, 'maxwidth250', '', 'subledger_label');
-					} else {
-						print '<input type="text" class="maxwidth150" name="subledger_account" value="'.(GETPOSTISSET("subledger_account") ? GETPOST("subledger_account", "alpha") : $line->subledger_account).'" placeholder="'.dol_escape_htmltag($langs->trans("SubledgerAccount")).'" />';
+				print "</tr>\n";
+
+				// Add an empty line if there is not yet
+				if (!empty($object->linesmvt[0])) {
+					$tmpline = $object->linesmvt[0];
+					if (!empty($tmpline->numero_compte)) {
+						$line = new BookKeepingLine();
+						$object->linesmvt[] = $line;
 					}
-					// Add also input for subledger label
-					print '<br><input type="text" class="maxwidth150" name="subledger_label" value="'.(GETPOSTISSET("subledger_label") ? GETPOST("subledger_label", "alpha") : $line->subledger_label).'" placeholder="'.dol_escape_htmltag($langs->trans("SubledgerAccountLabel")).'" />';
-					print '</td>';
-					print '<td><input type="text" class="minwidth200" name="label_operation" value="'.(GETPOSTISSET("label_operation") ? GETPOST("label_operation", "alpha") : $line->label_operation).'" /></td>';
-					print '<td class="right"><input type="text" size="6" class="right" name="debit" value="'.(GETPOSTISSET("debit") ? GETPOST("debit", "alpha") : price($line->debit)).'" /></td>';
-					print '<td class="right"><input type="text" size="6" class="right" name="credit" value="'.(GETPOSTISSET("credit") ? GETPOST("credit", "alpha") : price($line->credit)).'" /></td>';
-					print '<td>';
-					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 -->';
+				}
+
+				foreach ($object->linesmvt as $line) {
+					print '<tr class="oddeven" data-lineid="'.((int) $line->id).'">';
+					$total_debit += $line->debit;
+					$total_credit += $line->credit;
+
+					if ($action == 'update' && $line->id == $id) {
+						print '<!-- td columns in edit mode -->';
 						print '<td>';
-						print $formaccounting->select_account((is_array($accountingaccount_number) ? $accountingaccount_number[$key] : $accountingaccount_number ), 'accountingaccount_number['.$key.']', 1, array(), 1, 1, '');
+						print $formaccounting->select_account((GETPOSTISSET("accountingaccount_number") ? GETPOST("accountingaccount_number", "alpha") : $line->numero_compte), 'accountingaccount_number', 1, array(), 1, 1, 'minwidth200 maxwidth500');
 						print '</td>';
 						print '<td>';
 						// TODO For the moment we keep a free input text instead of a combo. The select_auxaccount has problem because:
@@ -649,102 +684,124 @@ if (!empty($object->piece_num)) {
 						// 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((is_array($subledger_account) ? $subledger_account[$key] : $subledger_account ), 'subledger_account['.$key.']', 1, 'maxwidth250', '', 'subledger_label');
+							print $formaccounting->select_auxaccount((GETPOSTISSET("subledger_account") ? GETPOST("subledger_account", "alpha") : $line->subledger_account), 'subledger_account', 1, 'maxwidth250', '', 'subledger_label');
 						} else {
-							print '<input type="text" class="maxwidth150" name="subledger_account['.$key.']" value="' . (is_array($subledger_account) ? $subledger_account[$key] : $subledger_account ) . '" placeholder="' . dol_escape_htmltag($langs->trans("SubledgerAccount")) . '" />';
+							print '<input type="text" class="maxwidth150" name="subledger_account" value="'.(GETPOSTISSET("subledger_account") ? GETPOST("subledger_account", "alpha") : $line->subledger_account).'" placeholder="'.dol_escape_htmltag($langs->trans("SubledgerAccount")).'">';
 						}
-						print '<br><input type="text" class="maxwidth150" name="subledger_label['.$key.']" value="' . (is_array($subledger_label) ? $subledger_label[$key] : $subledger_label ) . '" placeholder="' . dol_escape_htmltag($langs->trans("SubledgerAccountLabel")) . '" />';
+						// Add also input for subledger label
+						print '<br><input type="text" class="maxwidth150" name="subledger_label" value="'.(GETPOSTISSET("subledger_label") ? GETPOST("subledger_label", "alpha") : $line->subledger_label).'" placeholder="'.dol_escape_htmltag($langs->trans("SubledgerAccountLabel")).'">';
 						print '</td>';
-						print '<td><input type="text" class="minwidth200" name="label_operation['.$key.']" value="' . (is_array($label_operation) ? $label_operation[$key] : $label_operation ) . '"/></td>';
-						print '<td class="right"><input type="text" size="6" class="right" name="debit['.$key.']" value="' . (is_array($debit) ? $debit[$key] : $debit ) . '" /></td>';
-						print '<td class="right"><input type="text" size="6" class="right" name="credit['.$key.']" value="' . (is_array($credit) ? $credit[$key] : $credit ) . '" /></td>';
-						// Add button should not appear twice
-						if ($num_line === $count_line) {
-							print '<td><input type="submit" class="button small" name="save" value="' . $langs->trans("Add") . '" /></td>';
-						} else {
-							print '<td class="right"></td>';
+						print '<td><input type="text" class="minwidth200" name="label_operation" value="'.(GETPOSTISSET("label_operation") ? GETPOST("label_operation", "alpha") : $line->label_operation).'"></td>';
+						print '<td class="right"><input type="text" size="6" class="right" name="debit" value="'.(GETPOSTISSET("debit") ? GETPOST("debit", "alpha") : price($line->debit)).'"></td>';
+						print '<td class="right"><input type="text" size="6" class="right" name="credit" value="'.(GETPOSTISSET("credit") ? GETPOST("credit", "alpha") : price($line->credit)).'"></td>';
+						print '<td>';
+						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, 'minwidth200 maxwidth500');
+							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 class="center"><input type="submit" class="button small" name="save" value="' . $langs->trans("Add") . '"></td>';
 						}
-					}
-				} else {
-					print '<!-- td columns in display mode -->';
-					$resultfetch = $accountingaccount->fetch(null, $line->numero_compte, true);
-					print '<td>';
-					if ($resultfetch > 0) {
-						print $accountingaccount->getNomUrl(0, 1, 1, '', 0);
 					} else {
-						print $line->numero_compte.' <span class="warning">('.$langs->trans("AccountRemovedFromCurrentChartOfAccount").')</span>';
-					}
-					print '</td>';
-					print '<td>'.length_accounta($line->subledger_account);
-					if ($line->subledger_label) {
-						print ' - <span class="opacitymedium">'.$line->subledger_label.'</span>';
-					}
-					print '</td>';
-					print '<td>'.$line->label_operation.'</td>';
-					print '<td class="right nowraponall amount">'.($line->debit != 0 ? price($line->debit) : '').'</td>';
-					print '<td class="right nowraponall amount">'.($line->credit != 0 ? price($line->credit) : '').'</td>';
-
-					print '<td class="center nowraponall">';
-					if (empty($line->date_export) && empty($line->date_validation)) {
-						print '<a class="editfielda reposition" href="' . $_SERVER["PHP_SELF"] . '?action=update&id=' . $line->id . '&piece_num=' . urlencode($line->piece_num) . '&mode=' . urlencode($mode) . '&token=' . urlencode(newToken()) . '">';
-						print img_edit('', 0, 'class="marginrightonly"');
-						print '</a> &nbsp;';
-					} else {
-						print '<a class="editfielda nohover cursornotallowed reposition disabled" href="#" title="'.dol_escape_htmltag($langs->trans("ForbiddenTransactionAlreadyExported")).'">';
-						print img_edit($langs->trans("ForbiddenTransactionAlreadyExported"), 0, 'class="marginrightonly"');
-						print '</a> &nbsp;';
-					}
+						print '<!-- td columns in display mode -->';
+						$resultfetch = $accountingaccount->fetch(null, $line->numero_compte, true);
+						print '<td>';
+						if ($resultfetch > 0) {
+							print $accountingaccount->getNomUrl(0, 1, 1, '', 0);
+						} else {
+							print $line->numero_compte.' <span class="warning">('.$langs->trans("AccountRemovedFromCurrentChartOfAccount").')</span>';
+						}
+						print '</td>';
+						print '<td>'.length_accounta($line->subledger_account);
+						if ($line->subledger_label) {
+							print ' - <span class="opacitymedium">'.$line->subledger_label.'</span>';
+						}
+						print '</td>';
+						print '<td>'.$line->label_operation.'</td>';
+						print '<td class="right nowraponall amount">'.($line->debit != 0 ? price($line->debit) : '').'</td>';
+						print '<td class="right nowraponall amount">'.($line->credit != 0 ? price($line->credit) : '').'</td>';
+
+						print '<td class="center nowraponall">';
+						if (empty($line->date_export) && empty($line->date_validation)) {
+							print '<a class="editfielda reposition" href="' . $_SERVER["PHP_SELF"] . '?action=update&id=' . $line->id . '&piece_num=' . urlencode($line->piece_num) . '&mode=' . urlencode($mode) . '&token=' . urlencode(newToken()) . '">';
+							print img_edit('', 0, 'class="marginrightonly"');
+							print '</a> &nbsp;';
+						} else {
+							print '<a class="editfielda nohover cursornotallowed reposition disabled" href="#" title="'.dol_escape_htmltag($langs->trans("ForbiddenTransactionAlreadyExported")).'">';
+							print img_edit($langs->trans("ForbiddenTransactionAlreadyExported"), 0, 'class="marginrightonly"');
+							print '</a> &nbsp;';
+						}
 
-					if (empty($line->date_validation)) {
-						$actiontodelete = 'delete';
-						if ($mode == '_tmp' || $action != 'delmouv') {
-							$actiontodelete = 'confirm_delete';
+						if (empty($line->date_validation)) {
+							$actiontodelete = 'delete';
+							if ($mode == '_tmp' || $action != 'delmouv') {
+								$actiontodelete = 'confirm_delete';
+							}
+
+							print '<a href="' . $_SERVER["PHP_SELF"] . '?action=' . $actiontodelete . '&id=' . $line->id . '&piece_num=' . urlencode($line->piece_num) . '&mode=' . urlencode($mode) . '&token=' . urlencode(newToken()) . '">';
+							print img_delete();
+							print '</a>';
+						} else {
+							print '<a class="editfielda nohover cursornotallowed disabled" href="#" title="'.dol_escape_htmltag($langs->trans("ForbiddenTransactionAlreadyExported")).'">';
+							print img_delete($langs->trans("ForbiddenTransactionAlreadyValidated"));
+							print '</a>';
 						}
 
-						print '<a href="' . $_SERVER["PHP_SELF"] . '?action=' . $actiontodelete . '&id=' . $line->id . '&piece_num=' . urlencode($line->piece_num) . '&mode=' . urlencode($mode) . '&token=' . urlencode(newToken()) . '">';
-						print img_delete();
-						print '</a>';
-					} else {
-						print '<a class="editfielda nohover cursornotallowed disabled" href="#" title="'.dol_escape_htmltag($langs->trans("ForbiddenTransactionAlreadyExported")).'">';
-						print img_delete($langs->trans("ForbiddenTransactionAlreadyValidated"));
-						print '</a>';
+						print '</td>';
 					}
-
-					print '</td>';
+					print "</tr>\n";
 				}
-				print "</tr>\n";
-			}
 
-			$total_debit = price2num($total_debit, 'MT');
-			$total_credit = price2num($total_credit, 'MT');
+				$total_debit = price2num($total_debit, 'MT');
+				$total_credit = price2num($total_credit, 'MT');
 
-			if ($total_debit != $total_credit) {
-				setEventMessages(null, array($langs->trans('MvtNotCorrectlyBalanced', $total_debit, $total_credit)), 'warnings');
-			}
+				if ($total_debit != $total_credit) {
+					setEventMessages(null, array($langs->trans('MvtNotCorrectlyBalanced', $total_debit, $total_credit)), 'warnings');
+				}
 
-			print '</table>';
-			print '</div>';
+				print '</table>';
+				print '</div>';
 
-			if ($mode == '_tmp' && $action == '') {
-				print '<br>';
-				print '<div class="center">';
-				if ($total_debit == $total_credit) {
-					print '<input type="submit" class="button" name="validate" value="' . $langs->trans("ValidTransaction") . '" />';
-				} else {
-					print '<input type="submit" class="button" disabled="disabled" href="#" title="'.dol_escape_htmltag($langs->trans("MvtNotCorrectlyBalanced", $debit, $credit)).'" value="'.dol_escape_htmltag($langs->trans("ValidTransaction")).'" />';
-				}
+				if ($mode == '_tmp' && $action == '') {
+					print '<br>';
+					print '<div class="center">';
+					if ($total_debit == $total_credit) {
+						print '<a class="button" href="'.$_SERVER["PHP_SELF"].'?piece_num='.$object->piece_num.'&action=valid">'.$langs->trans("ValidTransaction").'</a>';
+					} else {
+						print '<input type="submit" class="button" disabled="disabled" href="#" title="'.dol_escape_htmltag($langs->trans("MvtNotCorrectlyBalanced", $debit, $credit)).'" value="'.dol_escape_htmltag($langs->trans("ValidTransaction")).'">';
+					}
 
-				print ' &nbsp; ';
-				print '<a class="button button-cancel" href="'.DOL_URL_ROOT.'/accountancy/bookkeeping/list.php">'.$langs->trans("Cancel").'</a>';
+					print ' &nbsp; ';
+					print '<a class="button button-cancel" href="'.DOL_URL_ROOT.'/accountancy/bookkeeping/list.php">'.$langs->trans("Cancel").'</a>';
 
-				print "</div>";
+					print "</div>";
+				}
 			}
-		}
 
-		print '</form>';
+			print '</form>';
+		}
+	} else {
+		print load_fiche_titre($langs->trans("NoRecords"));
 	}
-} else {
-	print load_fiche_titre($langs->trans("NoRecords"));
 }
 
 print dol_get_fiche_end();

+ 1404 - 0
htdocs/accountancy/bookkeeping/export.php

@@ -0,0 +1,1404 @@
+<?php
+/* Copyright (C) 2013-2016  Olivier Geffroy         <jeff@jeffinfo.com>
+ * Copyright (C) 2013-2016  Florian Henry           <florian.henry@open-concept.pro>
+ * Copyright (C) 2013-2023  Alexandre Spangaro      <aspangaro@open-dsi.fr>
+ * Copyright (C) 2022       Lionel Vessiller        <lvessiller@open-dsi.fr>
+ * Copyright (C) 2016-2017  Laurent Destailleur     <eldy@users.sourceforge.net>
+ * Copyright (C) 2018-2021  Frédéric France         <frederic.france@netlogic.fr>
+ * Copyright (C) 2022       Progiseize              <a.bisotti@progiseize.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/export.php
+ * \ingroup		Accountancy (Double entries)
+ * \brief 		Export operation of book keeping
+ */
+
+// Load Dolibarr environment
+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';
+require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
+
+// Load translation files required by the page
+$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');
+$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_creation_startyear =  GETPOST('search_date_creation_startyear', 'int');
+$search_date_creation_startmonth =  GETPOST('search_date_creation_startmonth', 'int');
+$search_date_creation_startday =  GETPOST('search_date_creation_startday', 'int');
+$search_date_creation_endyear =  GETPOST('search_date_creation_endyear', 'int');
+$search_date_creation_endmonth =  GETPOST('search_date_creation_endmonth', 'int');
+$search_date_creation_endday =  GETPOST('search_date_creation_endday', 'int');
+$search_date_creation_start = dol_mktime(0, 0, 0, $search_date_creation_startmonth, $search_date_creation_startday, $search_date_creation_startyear);
+$search_date_creation_end = dol_mktime(23, 59, 59, $search_date_creation_endmonth, $search_date_creation_endday, $search_date_creation_endyear);
+$search_date_modification_startyear =  GETPOST('search_date_modification_startyear', 'int');
+$search_date_modification_startmonth =  GETPOST('search_date_modification_startmonth', 'int');
+$search_date_modification_startday =  GETPOST('search_date_modification_startday', 'int');
+$search_date_modification_endyear =  GETPOST('search_date_modification_endyear', 'int');
+$search_date_modification_endmonth =  GETPOST('search_date_modification_endmonth', 'int');
+$search_date_modification_endday =  GETPOST('search_date_modification_endday', 'int');
+$search_date_modification_start = dol_mktime(0, 0, 0, $search_date_modification_startmonth, $search_date_modification_startday, $search_date_modification_startyear);
+$search_date_modification_end = dol_mktime(23, 59, 59, $search_date_modification_endmonth, $search_date_modification_endday, $search_date_modification_endyear);
+$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_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")) {
+	$action = 'delbookkeepingyear';
+}
+if (GETPOST("button_export_file_x") || GETPOST("button_export_file.x") || GETPOST("button_export_file")) {
+	$action = 'export_file';
+}
+
+$search_account_category = GETPOST('search_account_category', 'int');
+
+$search_accountancy_code = GETPOST("search_accountancy_code", 'alpha');
+$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_accountancy_aux_code = GETPOST("search_accountancy_aux_code", 'alpha');
+$search_accountancy_aux_code_start = GETPOST('search_accountancy_aux_code_start', 'alpha');
+if ($search_accountancy_aux_code_start == - 1) {
+	$search_accountancy_aux_code_start = '';
+}
+$search_accountancy_aux_code_end = GETPOST('search_accountancy_aux_code_end', 'alpha');
+if ($search_accountancy_aux_code_end == - 1) {
+	$search_accountancy_aux_code_end = '';
+}
+$search_mvt_label = GETPOST('search_mvt_label', 'alpha');
+$search_direction = GETPOST('search_direction', 'alpha');
+$search_debit = GETPOST('search_debit', 'alpha');
+$search_credit = GETPOST('search_credit', 'alpha');
+$search_ledger_code = GETPOST('search_ledger_code', 'array');
+$search_lettering_code = GETPOST('search_lettering_code', 'alpha');
+$search_not_reconciled = GETPOST('search_not_reconciled', 'alpha');
+
+// 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');
+$optioncss = GETPOST('optioncss', 'alpha');
+$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.piece_num,t.rowid";
+}
+
+// Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context
+$object = new BookKeeping($db);
+$hookmanager->initHooks(array('bookkeepingexport'));
+
+$formaccounting = new FormAccounting($db);
+$form = new Form($db);
+
+if (!in_array($action, array('export_file', 'delmouv', 'delmouvconfirm')) && !GETPOSTISSET('begin') && !GETPOSTISSET('formfilteraction') && GETPOST('page', 'int') == '' && !GETPOST('noreset', 'int') && $user->hasRight('accounting', 'mouvements', 'export')) {
+	if (empty($search_date_start) && empty($search_date_end) && !GETPOSTISSET('restore_lastsearch_values') && !GETPOST('search_accountancy_code_start')) {
+		$query = "SELECT date_start, date_end from ".MAIN_DB_PREFIX."accounting_fiscalyear ";
+		$query .= " where date_start < '".$db->idate(dol_now())."' and date_end > '".$db->idate(dol_now())."' limit 1";
+		$res = $db->query($query);
+
+		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.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.numero_compte'=>array('label'=>$langs->trans("AccountAccountingShort"), 'checked'=>1),
+	't.subledger_account'=>array('label'=>$langs->trans("SubledgerAccount"), 'checked'=>1),
+	't.label_operation'=>array('label'=>$langs->trans("Label"), 'checked'=>1),
+	't.debit'=>array('label'=>$langs->trans("AccountingDebit"), 'checked'=>1),
+	't.credit'=>array('label'=>$langs->trans("AccountingCredit"), 'checked'=>1),
+	't.lettering_code'=>array('label'=>$langs->trans("LetteringCode"), 'checked'=>1),
+	't.date_creation'=>array('label'=>$langs->trans("DateCreation"), 'checked'=>0),
+	'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, 'enabled'=>!getDolGlobalString("ACCOUNTANCY_DISABLE_CLOSURE_LINE_BY_LINE")),
+	't.import_key'=>array('label'=>$langs->trans("ImportId"), 'checked'=>0, 'position'=>1100),
+);
+
+if (empty($conf->global->ACCOUNTING_ENABLE_LETTERING)) {
+	unset($arrayfields['t.lettering_code']);
+}
+
+$accountancyexport = new AccountancyExport($db);
+$listofformat = $accountancyexport->getType();
+$formatexportset = getDolGlobalString('ACCOUNTING_EXPORT_MODELCSV');
+if (empty($listofformat[$formatexportset])) {
+	$formatexportset = 1;
+}
+
+$error = 0;
+
+if (!isModEnabled('accounting')) {
+	accessforbidden();
+}
+if ($user->socid > 0) {
+	accessforbidden();
+}
+if (!$user->hasRight('accounting', 'mouvements', 'lire')) {
+	accessforbidden();
+}
+
+
+/*
+ * Actions
+ */
+
+$param = '';
+
+if (GETPOST('cancel', 'alpha')) {
+	$action = 'list'; $massaction = '';
+}
+if (!GETPOST('confirmmassaction', 'alpha')) {
+	$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_mvt_num = '';
+		$search_doc_type = '';
+		$search_doc_ref = '';
+		$search_doc_date = '';
+		$search_account_category = '';
+		$search_accountancy_code = '';
+		$search_accountancy_code_start = '';
+		$search_accountancy_code_end = '';
+		$search_accountancy_aux_code = '';
+		$search_accountancy_aux_code_start = '';
+		$search_accountancy_aux_code_end = '';
+		$search_mvt_label = '';
+		$search_direction = '';
+		$search_ledger_code = array();
+		$search_date_startyear = '';
+		$search_date_startmonth = '';
+		$search_date_startday = '';
+		$search_date_endyear = '';
+		$search_date_endmonth = '';
+		$search_date_endday = '';
+		$search_date_start = '';
+		$search_date_end = '';
+		$search_date_creation_startyear = '';
+		$search_date_creation_startmonth = '';
+		$search_date_creation_startday = '';
+		$search_date_creation_endyear = '';
+		$search_date_creation_endmonth = '';
+		$search_date_creation_endday = '';
+		$search_date_creation_start = '';
+		$search_date_creation_end = '';
+		$search_date_modification_startyear = '';
+		$search_date_modification_startmonth = '';
+		$search_date_modification_startday = '';
+		$search_date_modification_endyear = '';
+		$search_date_modification_endmonth = '';
+		$search_date_modification_endday = '';
+		$search_date_modification_start = '';
+		$search_date_modification_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_export_start = '';
+		$search_date_export_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_date_validation_start = '';
+		$search_date_validation_end = '';
+		$search_debit = '';
+		$search_credit = '';
+		$search_lettering_code = '';
+		$search_not_reconciled = '';
+		$search_import_key = '';
+		$toselect = array();
+	}
+
+	// Must be after the remove filter action, before the export.
+	$filter = array();
+	if (!empty($search_date_start)) {
+		$filter['t.doc_date>='] = $search_date_start;
+		$tmp = dol_getdate($search_date_start);
+		$param .= '&search_date_startmonth='.urlencode($tmp['mon']).'&search_date_startday='.urlencode($tmp['mday']).'&search_date_startyear='.urlencode($tmp['year']);
+	}
+	if (!empty($search_date_end)) {
+		$filter['t.doc_date<='] = $search_date_end;
+		$tmp = dol_getdate($search_date_end);
+		$param .= '&search_date_endmonth='.urlencode($tmp['mon']).'&search_date_endday='.urlencode($tmp['mday']).'&search_date_endyear='.urlencode($tmp['year']);
+	}
+	if (!empty($search_doc_date)) {
+		$filter['t.doc_date'] = $search_doc_date;
+		$tmp = dol_getdate($search_doc_date);
+		$param .= '&doc_datemonth='.urlencode($tmp['mon']).'&doc_dateday='.urlencode($tmp['mday']).'&doc_dateyear='.urlencode($tmp['year']);
+	}
+	if (!empty($search_doc_type)) {
+		$filter['t.doc_type'] = $search_doc_type;
+		$param .= '&search_doc_type='.urlencode($search_doc_type);
+	}
+	if (!empty($search_doc_ref)) {
+		$filter['t.doc_ref'] = $search_doc_ref;
+		$param .= '&search_doc_ref='.urlencode($search_doc_ref);
+	}
+	if ($search_account_category != '-1' && !empty($search_account_category)) {
+		require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountancycategory.class.php';
+		$accountingcategory = new AccountancyCategory($db);
+
+		$listofaccountsforgroup = $accountingcategory->getCptsCat(0, 'fk_accounting_category = '.((int) $search_account_category));
+		$listofaccountsforgroup2 = array();
+		if (is_array($listofaccountsforgroup)) {
+			foreach ($listofaccountsforgroup as $tmpval) {
+				$listofaccountsforgroup2[] = "'".$db->escape($tmpval['id'])."'";
+			}
+		}
+		$filter['t.search_accounting_code_in'] = join(',', $listofaccountsforgroup2);
+		$param .= '&search_account_category='.urlencode($search_account_category);
+	}
+	if (!empty($search_accountancy_code)) {
+		$filter['t.numero_compte'] = $search_accountancy_code;
+		$param .= '&search_accountancy_code='.urlencode($search_accountancy_code);
+	}
+	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 (!empty($search_accountancy_code_end)) {
+		$filter['t.numero_compte<='] = $search_accountancy_code_end;
+		$param .= '&search_accountancy_code_end='.urlencode($search_accountancy_code_end);
+	}
+	if (!empty($search_accountancy_aux_code)) {
+		$filter['t.subledger_account'] = $search_accountancy_aux_code;
+		$param .= '&search_accountancy_aux_code='.urlencode($search_accountancy_aux_code);
+	}
+	if (!empty($search_accountancy_aux_code_start)) {
+		$filter['t.subledger_account>='] = $search_accountancy_aux_code_start;
+		$param .= '&search_accountancy_aux_code_start='.urlencode($search_accountancy_aux_code_start);
+	}
+	if (!empty($search_accountancy_aux_code_end)) {
+		$filter['t.subledger_account<='] = $search_accountancy_aux_code_end;
+		$param .= '&search_accountancy_aux_code_end='.urlencode($search_accountancy_aux_code_end);
+	}
+	if (!empty($search_mvt_label)) {
+		$filter['t.label_operation'] = $search_mvt_label;
+		$param .= '&search_mvt_label='.urlencode($search_mvt_label);
+	}
+	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_mvt_num)) {
+		$filter['t.piece_num'] = $search_mvt_num;
+		$param .= '&search_mvt_num='.urlencode($search_mvt_num);
+	}
+	if (!empty($search_date_creation_start)) {
+		$filter['t.date_creation>='] = $search_date_creation_start;
+		$tmp = dol_getdate($search_date_creation_start);
+		$param .= '&search_date_creation_startmonth='.urlencode($tmp['mon']).'&search_date_creation_startday='.urlencode($tmp['mday']).'&search_date_creation_startyear='.urlencode($tmp['year']);
+	}
+	if (!empty($search_date_creation_end)) {
+		$filter['t.date_creation<='] = $search_date_creation_end;
+		$tmp = dol_getdate($search_date_creation_end);
+		$param .= '&search_date_creation_endmonth='.urlencode($tmp['mon']).'&search_date_creation_endday='.urlencode($tmp['mday']).'&search_date_creation_endyear='.urlencode($tmp['year']);
+	}
+	if (!empty($search_date_modification_start)) {
+		$filter['t.tms>='] = $search_date_modification_start;
+		$tmp = dol_getdate($search_date_modification_start);
+		$param .= '&search_date_modification_startmonth='.urlencode($tmp['mon']).'&search_date_modification_startday='.urlencode($tmp['mday']).'&search_date_modification_startyear='.urlencode($tmp['year']);
+	}
+	if (!empty($search_date_modification_end)) {
+		$filter['t.tms<='] = $search_date_modification_end;
+		$tmp = dol_getdate($search_date_modification_end);
+		$param .= '&search_date_modification_endmonth='.urlencode($tmp['mon']).'&search_date_modification_endday='.urlencode($tmp['mday']).'&search_date_modification_endyear='.urlencode($tmp['year']);
+	}
+	if (!empty($search_date_export_start)) {
+		$filter['t.date_export>='] = $search_date_export_start;
+		$tmp = dol_getdate($search_date_export_start);
+		$param .= '&search_date_export_startmonth='.urlencode($tmp['mon']).'&search_date_export_startday='.urlencode($tmp['mday']).'&search_date_export_startyear='.urlencode($tmp['year']);
+	}
+	if (!empty($search_date_export_end)) {
+		$filter['t.date_export<='] = $search_date_export_end;
+		$tmp = dol_getdate($search_date_export_end);
+		$param .= '&search_date_export_endmonth='.urlencode($tmp['mon']).'&search_date_export_endday='.urlencode($tmp['mday']).'&search_date_export_endyear='.urlencode($tmp['year']);
+	}
+	if (!empty($search_date_validation_start)) {
+		$filter['t.date_validated>='] = $search_date_validation_start;
+		$tmp = dol_getdate($search_date_validation_start);
+		$param .= '&search_date_validation_startmonth='.urlencode($tmp['mon']).'&search_date_validation_startday='.urlencode($tmp['mday']).'&search_date_validation_startyear='.urlencode($tmp['year']);
+	}
+	if (!empty($search_date_validation_end)) {
+		$filter['t.date_validated<='] = $search_date_validation_end;
+		$tmp = dol_getdate($search_date_validation_end);
+		$param .= '&search_date_validation_endmonth='.urlencode($tmp['mon']).'&search_date_validation_endday='.urlencode($tmp['mday']).'&search_date_validation_endyear='.urlencode($tmp['year']);
+	}
+	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_import_key)) {
+		$filter['t.import_key'] = $search_import_key;
+		$param .= '&search_import_key='.urlencode($search_import_key);
+	}
+
+	if ($action == 'setreexport') {
+		$setreexport = GETPOST('value', 'int');
+		if (!dolibarr_set_const($db, "ACCOUNTING_REEXPORT", $setreexport, 'yesno', 0, '', $conf->entity)) {
+			$error++;
+		}
+
+		if (!$error) {
+			if (empty($conf->global->ACCOUNTING_REEXPORT)) {
+				setEventMessages($langs->trans("ExportOfPiecesAlreadyExportedIsDisable"), null, 'mesgs');
+			} else {
+				setEventMessages($langs->trans("ExportOfPiecesAlreadyExportedIsEnable"), null, 'warnings');
+			}
+		} else {
+			setEventMessages($langs->trans("Error"), null, 'errors');
+		}
+	}
+
+	// Mass actions
+	$objectclass = 'Bookkeeping';
+	$objectlabel = 'Bookkeeping';
+	$permissiontoread = $user->hasRight('societe', 'lire');
+	$permissiontodelete = $user->hasRight('societe', 'supprimer');
+	$permissiontoadd = $user->hasRight('societe', 'creer');
+	$uploaddir = $conf->societe->dir_output;
+	include DOL_DOCUMENT_ROOT.'/core/actions_massactions.inc.php';
+}
+
+// Build and execute select (used by page and export action)
+// must de set after the action that set $filter
+// --------------------------------------------------------------------
+
+$sql = 'SELECT';
+$sql .= ' t.rowid,';
+$sql .= " t.doc_date,";
+$sql .= " t.doc_type,";
+$sql .= " t.doc_ref,";
+$sql .= " t.fk_doc,";
+$sql .= " t.fk_docdet,";
+$sql .= " t.thirdparty_code,";
+$sql .= " t.subledger_account,";
+$sql .= " t.subledger_label,";
+$sql .= " t.numero_compte,";
+$sql .= " t.label_compte,";
+$sql .= " t.label_operation,";
+$sql .= " t.debit,";
+$sql .= " t.credit,";
+$sql .= " t.lettering_code,";
+$sql .= " t.montant as amount,";
+$sql .= " t.sens,";
+$sql .= " t.fk_user_author,";
+$sql .= " t.import_key,";
+$sql .= " t.code_journal,";
+$sql .= " t.journal_label,";
+$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.import_key";
+
+$sqlfields = $sql; // $sql fields to remove for count total
+
+$sql .= ' FROM '.MAIN_DB_PREFIX.$object->table_element.' as t';
+// Manage filter
+$sqlwhere = array();
+if (count($filter) > 0) {
+	foreach ($filter as $key => $value) {
+		if ($key == 't.doc_date') {
+			$sqlwhere[] = $key."='".$db->idate($value)."'";
+		} elseif ($key == 't.doc_date>=' || $key == 't.doc_date<=') {
+			$sqlwhere[] = $key."'".$db->idate($value)."'";
+		} elseif ($key == 't.numero_compte>=' || $key == 't.numero_compte<=' || $key == 't.subledger_account>=' || $key == 't.subledger_account<=') {
+			$sqlwhere[] = $key."'".$db->escape($value)."'";
+		} elseif ($key == 't.fk_doc' || $key == 't.fk_docdet' || $key == 't.piece_num') {
+			$sqlwhere[] = $key.'='.((int) $value);
+		} elseif ($key == 't.subledger_account' || $key == 't.numero_compte') {
+			$sqlwhere[] = $key." LIKE '".$db->escape($value)."%'";
+		} elseif ($key == 't.subledger_account') {
+			$sqlwhere[] = natural_search($key, $value, 0, 1);
+		} elseif ($key == 't.date_creation>=' || $key == 't.date_creation<=') {
+			$sqlwhere[] = $key."'".$db->idate($value)."'";
+		} elseif ($key == 't.tms>=' || $key == 't.tms<=') {
+			$sqlwhere[] = $key."'".$db->idate($value)."'";
+		} elseif ($key == 't.date_export>=' || $key == 't.date_export<=') {
+			$sqlwhere[] = $key."'".$db->idate($value)."'";
+		} elseif ($key == 't.date_validated>=' || $key == 't.date_validated<=') {
+			$sqlwhere[] = $key."'".$db->idate($value)."'";
+		} elseif ($key == 't.credit' || $key == 't.debit') {
+			$sqlwhere[] = natural_search($key, $value, 1, 1);
+		} elseif ($key == 't.reconciled_option') {
+			$sqlwhere[] = 't.lettering_code IS NULL';
+		} elseif ($key == 't.code_journal' && !empty($value)) {
+			if (is_array($value)) {
+				$sqlwhere[] = natural_search("t.code_journal", join(',', $value), 3, 1);
+			} else {
+				$sqlwhere[] = natural_search("t.code_journal", $value, 3, 1);
+			}
+		} elseif ($key == 't.search_accounting_code_in' && !empty($value)) {
+			$sqlwhere[] = 't.numero_compte IN ('.$db->sanitize($value, 1).')';
+		} else {
+			$sqlwhere[] = natural_search($key, $value, 0, 1);
+		}
+	}
+}
+$sql .= ' WHERE t.entity IN ('.getEntity('accountancy').')';
+if (empty($conf->global->ACCOUNTING_REEXPORT)) {	// Reexport not enabled (default mode)
+	$sql .= " AND t.date_export IS NULL";
+}
+if (count($sqlwhere) > 0) {
+	$sql .= ' AND '.implode(' AND ', $sqlwhere);
+}
+//print $sql;
+
+
+// Export into a file with format defined into setup (FEC, CSV, ...)
+// Must be after definition of $sql
+if ($action == 'export_fileconfirm' && $user->hasRight('accounting', 'mouvements', 'export')) {
+	// TODO Replace the fetchAll to get all ->line followed by call to ->export(). fetchAll() currently consumes too much memory on large export.
+	// Replace this with the query($sql) and loop on each line to export them.
+	$result = $object->fetchAll($sortorder, $sortfield, 0, 0, $filter, 'AND', (empty($conf->global->ACCOUNTING_REEXPORT) ? 0 : 1));
+
+	if ($result < 0) {
+		setEventMessages($object->error, $object->errors, 'errors');
+	} else {
+		// Export files then exit
+		$accountancyexport = new AccountancyExport($db);
+
+		$formatexport = GETPOST('formatexport', 'int');
+		$notexportlettering = GETPOST('notexportlettering', 'alpha');
+
+		if (!empty($notexportlettering)) {
+			if (is_array($object->lines)) {
+				foreach ($object->lines as $k => $movement) {
+					unset($object->lines[$k]->lettering_code);
+					unset($object->lines[$k]->date_lettering);
+				}
+			}
+		}
+
+		$notifiedexportdate = GETPOST('notifiedexportdate', 'alpha');
+		$notifiedvalidationdate = GETPOST('notifiedvalidationdate', 'alpha');
+		$withAttachment = !empty(trim(GETPOST('notifiedexportfull', 'alphanohtml'))) ? 1 : 0;
+
+		// Output data on screen or download
+		$result = $accountancyexport->export($object->lines, $formatexport, $withAttachment);
+
+		$error = 0;
+		if ($result < 0) {
+			$error++;
+		} else {
+			if (!empty($notifiedexportdate) || !empty($notifiedvalidationdate)) {
+				if (is_array($object->lines)) {
+					dol_syslog("/accountancy/bookkeeping/list.php Function export_file Specify movements as exported", LOG_DEBUG);
+
+					// Specify as export : update field date_export or date_validated
+					$db->begin();
+
+					// TODO Merge update for each line into one global using rowid IN (list of movement ids)
+					foreach ($object->lines as $movement) {
+						$now = dol_now();
+
+						$setfields = '';
+						if (!empty($notifiedexportdate) && empty($movement->date_export)) {
+							$setfields .= ($setfields ? "," : "")." date_export = '".$db->idate($now)."'";
+						}
+						if (!empty($notifiedvalidationdate) && empty($movement->date_validation)) {
+							$setfields .= ($setfields ? "," : "")." date_validated = '".$db->idate($now)."'";
+						}
+
+						if ($setfields) {
+							$sql = " UPDATE ".MAIN_DB_PREFIX."accounting_bookkeeping";
+							$sql .= " SET ".$setfields;
+							$sql .= " WHERE rowid = ".((int) $movement->id);
+
+							$result = $db->query($sql);
+							if (!$result) {
+								$error++;
+								break;
+							}
+						}
+					}
+
+					if (!$error) {
+						$db->commit();
+					} else {
+						$error++;
+						$accountancyexport->errors[] = $langs->trans('NotAllExportedMovementsCouldBeRecordedAsExportedOrValidated');
+						$db->rollback();
+					}
+				}
+			}
+		}
+
+		if ($error) {
+			setEventMessages('', $accountancyexport->errors, 'errors');
+			header('Location: '.$_SERVER['PHP_SELF']);
+		}
+		exit(); // download or show errors
+	}
+}
+
+
+/*
+ * View
+ */
+
+$formother = new FormOther($db);
+$formfile = new FormFile($db);
+
+$title_page = $langs->trans("Operations").' - '.$langs->trans("ExportAccountancy");
+
+// Count total nb of records
+$nbtotalofrecords = '';
+if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
+	/* The fast and low memory method to get and count full list converts the sql into a sql count */
+	$sqlforcount = preg_replace('/^'.preg_quote($sqlfields, '/').'/', 'SELECT COUNT(*) as nbtotalofrecords', $sql);
+	$sqlforcount = preg_replace('/GROUP BY .*$/', '', $sqlforcount);
+	$resql = $db->query($sqlforcount);
+	if ($resql) {
+		$objforcount = $db->fetch_object($resql);
+		$nbtotalofrecords = $objforcount->nbtotalofrecords;
+	} else {
+		dol_print_error($db);
+	}
+
+	if (($page * $limit) > $nbtotalofrecords) {	// if total resultset is smaller then paging size (filtering), goto and load page 0
+		$page = 0;
+		$offset = 0;
+	}
+	$db->free($resql);
+}
+
+// Complete request and execute it with limit
+$sql .= $db->order($sortfield, $sortorder);
+if ($limit) {
+	$sql .= $db->plimit($limit + 1, $offset);
+}
+
+$resql = $db->query($sql);
+if (!$resql) {
+	dol_print_error($db);
+	exit;
+}
+
+$num = $db->num_rows($resql);
+
+$arrayofselected = is_array($toselect) ? $toselect : array();
+
+// Output page
+// --------------------------------------------------------------------
+
+llxHeader('', $title_page);
+
+$formconfirm = '';
+
+if ($action == 'export_file') {
+	$form_question = array();
+
+	$form_question['formatexport'] = array(
+		'name' => 'formatexport',
+		'type' => 'select',
+		'label' => $langs->trans('Modelcsv'),		// TODO  Use Selectmodelcsv and show a select combo
+		'values' => $listofformat,
+		'default' => $formatexportset,
+		'morecss' => 'minwidth200 maxwidth200'
+	);
+
+	$form_question['separator0'] = array('name'=>'separator0', 'type'=>'separator');
+
+	if (getDolGlobalInt("ACCOUNTING_ENABLE_LETTERING")) {
+		// If 1, we check by default.
+		$checked = !empty($conf->global->ACCOUNTING_DEFAULT_NOT_EXPORT_LETTERING) ? 'true' : 'false';
+		$form_question['notexportlettering'] = array(
+			'name' => 'notexportlettering',
+			'type' => 'checkbox',
+			'label' => $langs->trans('NotExportLettering'),
+			'value' => $checked,
+		);
+
+		$form_question['separator1'] = array('name'=>'separator1', 'type'=>'separator');
+	}
+
+	// If 1 or not set, we check by default.
+	$checked = (!isset($conf->global->ACCOUNTING_DEFAULT_NOT_NOTIFIED_EXPORT_DATE) || !empty($conf->global->ACCOUNTING_DEFAULT_NOT_NOTIFIED_EXPORT_DATE));
+	$form_question['notifiedexportdate'] = array(
+		'name' => 'notifiedexportdate',
+		'type' => 'checkbox',
+		'label' => $langs->trans('NotifiedExportDate'),
+		'value' => (!empty($conf->global->ACCOUNTING_DEFAULT_NOT_NOTIFIED_EXPORT_DATE) ? 'false' : 'true'),
+	);
+
+	$form_question['separator2'] = array('name'=>'separator2', 'type'=>'separator');
+
+	if (!getDolGlobalString("ACCOUNTANCY_DISABLE_CLOSURE_LINE_BY_LINE")) {
+		// If 0 or not set, we NOT check by default.
+		$checked = (isset($conf->global->ACCOUNTING_DEFAULT_NOT_NOTIFIED_VALIDATION_DATE) || !empty($conf->global->ACCOUNTING_DEFAULT_NOT_NOTIFIED_VALIDATION_DATE));
+		$form_question['notifiedvalidationdate'] = array(
+			'name' => 'notifiedvalidationdate',
+			'type' => 'checkbox',
+			'label' => $langs->trans('NotifiedValidationDate', $langs->transnoentitiesnoconv("MenuAccountancyClosure")),
+			'value' => $checked,
+		);
+
+		$form_question['separator3'] = array('name'=>'separator3', 'type'=>'separator');
+	}
+
+	// add documents in an archive for accountancy export (Quadratus)
+	if (getDolGlobalString('ACCOUNTING_EXPORT_MODELCSV') == AccountancyExport::$EXPORT_TYPE_QUADRATUS) {
+		$form_question['notifiedexportfull'] = array(
+			'name' => 'notifiedexportfull',
+			'type' => 'checkbox',
+			'label' => $langs->trans('NotifiedExportFull'),
+			'value' => 'false',
+		);
+	}
+
+	$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?'.$param, $langs->trans("ExportFilteredList").'...', $langs->trans('ConfirmExportFile'), 'export_fileconfirm', $form_question, '', 1, 420, 600);
+}
+
+// Print form confirm
+print $formconfirm;
+
+//$param='';	param started before
+if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) {
+	$param .= '&contextpage='.urlencode($contextpage);
+}
+if ($limit > 0 && $limit != $conf->liste_limit) {
+	$param .= '&limit='.urlencode($limit);
+}
+
+// List of mass actions available
+$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">';
+if ($optioncss != '') {
+	print '<input type="hidden" name="optioncss" value="'.urlencode($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.'">';
+print '<input type="hidden" name="contextpage" value="'.$contextpage.'">';
+
+if (count($filter)) {
+	$buttonLabel = $langs->trans("ExportFilteredList");
+} else {
+	$buttonLabel = $langs->trans("ExportList");
+}
+
+$parameters = array('param' => $param);
+$reshook = $hookmanager->executeHooks('addMoreActionsButtonsList', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+if ($reshook < 0) {
+	setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
+}
+
+$newcardbutton = empty($hookmanager->resPrint) ? '' : $hookmanager->resPrint;
+if (empty($reshook)) {
+	// Button re-export
+	if (empty($conf->global->ACCOUNTING_REEXPORT)) {
+		$newcardbutton .= '<a class="valignmiddle" href="'.$_SERVER['PHP_SELF'].'?action=setreexport&token='.newToken().'&value=1'.($param ? '&'.$param : '').'&sortfield='.urlencode($sortfield).'&sortorder='.urlencode($sortorder).'">'.img_picto($langs->trans("ClickToShowAlreadyExportedLines"), 'switch_off', 'class="small size15x valignmiddle"');
+		$newcardbutton .= '<span class="valignmiddle marginrightonly paddingleft">'.$langs->trans("ClickToShowAlreadyExportedLines").'</span>';
+		$newcardbutton .= '</a>';
+	} else {
+		$newcardbutton .= '<a class="valignmiddle" href="'.$_SERVER['PHP_SELF'].'?action=setreexport&token='.newToken().'&value=0'.($param ? '&'.$param : '').'&sortfield='.urlencode($sortfield).'&sortorder='.urlencode($sortorder).'">'.img_picto($langs->trans("DocsAlreadyExportedAreIncluded"), 'switch_on', 'class="warning size15x valignmiddle"');
+		$newcardbutton .= '<span class="valignmiddle marginrightonly paddingleft">'.$langs->trans("DocsAlreadyExportedAreIncluded").'</span>';
+		$newcardbutton .= '</a>';
+	}
+
+	if ($user->hasRight('accounting', 'mouvements', 'export')) {
+		$newcardbutton .= dolGetButtonTitle($buttonLabel, $langs->trans("ExportFilteredList"), 'fa fa-file-export paddingleft', $_SERVER["PHP_SELF"].'?action=export_file&token='.newToken().($param ? '&'.$param : '').'&sortfield='.urlencode($sortfield).'&sortorder='.urlencode($sortorder), $user->hasRight('accounting', 'mouvements', 'export'));
+	}
+}
+
+print_barre_liste($title_page, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, 'title_accountancy', 0, $newcardbutton, '', $limit, 0, 0, 1);
+
+// Not display message when all the list of docs are included
+if (empty($conf->global->ACCOUNTING_REEXPORT)) {
+	print info_admin($langs->trans("WarningDataDisappearsWhenDataIsExported"), 0, 0, 0, 'hideonsmartphone info');
+}
+
+//$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, getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN', '')); // This also change content of $arrayfields
+if ($massactionbutton && $contextpage != 'poslist') {
+	$selectedfields .= $form->showCheckAddButtons('checkforselect', 1);
+}
+
+$moreforfilter = '';
+$moreforfilter .= '<div class="divsearchfield">';
+$moreforfilter .= $langs->trans('AccountingCategory').': ';
+$moreforfilter .= '<div class="nowrap inline-block">';
+$moreforfilter .= $formaccounting->select_accounting_category($search_account_category, 'search_account_category', 1, 0, 0, 0);
+$moreforfilter .= '</div>';
+$moreforfilter .= '</div>';
+
+$parameters = array();
+$reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters, $object, $action); // 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'.($moreforfilter ? " listwithfilterbefore" : "").'">';
+
+// Filters lines
+print '<tr class="liste_titre_filter">';
+// Action column
+if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+	print '<td class="liste_titre center">';
+	$searchpicto = $form->showFilterButtons('left');
+	print $searchpicto;
+	print '</td>';
+}
+// 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, 'small maxwidth75');
+	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 : -1, '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 : -1, '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" name="search_doc_ref" size="8" value="'.dol_escape_htmltag($search_doc_ref).'"></td>';
+}
+// Accountancy account
+if (!empty($arrayfields['t.numero_compte']['checked'])) {
+	print '<td class="liste_titre">';
+	print '<div class="nowrap">';
+	print $formaccounting->select_account($search_accountancy_code_start, 'search_accountancy_code_start', $langs->trans('From'), array(), 1, 1, 'maxwidth150', 'account');
+	print '</div>';
+	print '<div class="nowrap">';
+	print $formaccounting->select_account($search_accountancy_code_end, 'search_accountancy_code_end', $langs->trans('to'), array(), 1, 1, 'maxwidth150', 'account');
+	print '</div>';
+	print '</td>';
+}
+// Subledger account
+if (!empty($arrayfields['t.subledger_account']['checked'])) {
+	print '<td class="liste_titre">';
+	// TODO For the moment we keep a free input text instead of a combo. The select_auxaccount has problem because it does not
+	// use setup of keypress to select thirdparty and this hang browser on large database.
+	if (!empty($conf->global->ACCOUNTANCY_COMBO_FOR_AUX)) {
+		print '<div class="nowrap">';
+		//print $langs->trans('From').' ';
+		print $formaccounting->select_auxaccount($search_accountancy_aux_code_start, 'search_accountancy_aux_code_start', $langs->trans('From'), 'maxwidth250', 'subledgeraccount');
+		print '</div>';
+		print '<div class="nowrap">';
+		print $formaccounting->select_auxaccount($search_accountancy_aux_code_end, 'search_accountancy_aux_code_end', $langs->trans('to'), 'maxwidth250', 'subledgeraccount');
+		print '</div>';
+	} else {
+		print '<input type="text" class="maxwidth75" name="search_accountancy_aux_code" value="'.dol_escape_htmltag($search_accountancy_aux_code).'">';
+	}
+	print '</td>';
+}
+// Label operation
+if (!empty($arrayfields['t.label_operation']['checked'])) {
+	print '<td class="liste_titre">';
+	print '<input type="text" size="7" class="flat" name="search_mvt_label" value="'.dol_escape_htmltag($search_mvt_label).'"/>';
+	print '</td>';
+}
+// Debit
+if (!empty($arrayfields['t.debit']['checked'])) {
+	print '<td class="liste_titre right">';
+	print '<input type="text" class="flat" name="search_debit" size="4" value="'.dol_escape_htmltag($search_debit).'">';
+	print '</td>';
+}
+// Credit
+if (!empty($arrayfields['t.credit']['checked'])) {
+	print '<td class="liste_titre right">';
+	print '<input type="text" class="flat" name="search_credit" size="4" value="'.dol_escape_htmltag($search_credit).'">';
+	print '</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="'.dol_escape_htmltag($search_lettering_code).'"/>';
+	print '<br><span class="nowrap"><input type="checkbox" id="search_not_reconciled" name="search_not_reconciled" value="notreconciled"'.($search_not_reconciled == 'notreconciled' ? ' checked' : '').'><label for="search_not_reconciled">'.$langs->trans("NotReconciled").'</label></span>';
+	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;
+
+// Date creation
+if (!empty($arrayfields['t.date_creation']['checked'])) {
+	print '<td class="liste_titre center">';
+	print '<div class="nowrap">';
+	print $form->selectDate($search_date_creation_start, 'search_date_creation_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("From"));
+	print '</div>';
+	print '<div class="nowrap">';
+	print $form->selectDate($search_date_creation_end, 'search_date_creation_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("to"));
+	print '</div>';
+	print '</td>';
+}
+// Date modification
+if (!empty($arrayfields['t.tms']['checked'])) {
+	print '<td class="liste_titre center">';
+	print '<div class="nowrap">';
+	print $form->selectDate($search_date_modification_start, 'search_date_modification_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("From"));
+	print '</div>';
+	print '<div class="nowrap">';
+	print $form->selectDate($search_date_modification_end, 'search_date_modification_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("From"));
+	print '</div>';
+	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>';
+}
+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
+if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+	print '<td class="liste_titre center">';
+	$searchpicto = $form->showFilterButtons();
+	print $searchpicto;
+	print '</td>';
+}
+print "</tr>\n";
+
+print '<tr class="liste_titre">';
+if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+	print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', '', $sortfield, $sortorder, 'center maxwidthsearch actioncolumn ');}
+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.numero_compte']['checked'])) {
+	print_liste_field_titre($arrayfields['t.numero_compte']['label'], $_SERVER['PHP_SELF'], "t.numero_compte", "", $param, "", $sortfield, $sortorder);
+}
+if (!empty($arrayfields['t.subledger_account']['checked'])) {
+	print_liste_field_titre($arrayfields['t.subledger_account']['label'], $_SERVER['PHP_SELF'], "t.subledger_account", "", $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 ');
+}
+// 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;
+if (!empty($arrayfields['t.date_creation']['checked'])) {
+	print_liste_field_titre($arrayfields['t.date_creation']['label'], $_SERVER['PHP_SELF'], "t.date_creation", "", $param, '', $sortfield, $sortorder, 'center ');
+}
+if (!empty($arrayfields['t.tms']['checked'])) {
+	print_liste_field_titre($arrayfields['t.tms']['label'], $_SERVER['PHP_SELF'], "t.tms", "", $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,t.doc_date", "", $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,t.doc_date", "", $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 ');
+}
+if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+	print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', '', $sortfield, $sortorder, 'center maxwidthsearch ');
+}
+print "</tr>\n";
+
+
+$line = new BookKeepingLine();
+
+// Loop on record
+// --------------------------------------------------------------------
+$i = 0;
+$totalarray = array();
+$totalarray['nbfield'] = 0;
+$total_debit = 0;
+$total_credit = 0;
+$totalarray['val'] = array ();
+$totalarray['val']['totaldebit'] = 0;
+$totalarray['val']['totalcredit'] = 0;
+
+while ($i < min($num, $limit)) {
+	$obj = $db->fetch_object($resql);
+	if (empty($obj)) {
+		break; // Should not happen
+	}
+
+	$line->id = $obj->rowid;
+	$line->doc_date = $db->jdate($obj->doc_date);
+	$line->doc_type = $obj->doc_type;
+	$line->doc_ref = $obj->doc_ref;
+	$line->fk_doc = $obj->fk_doc;
+	$line->fk_docdet = $obj->fk_docdet;
+	$line->thirdparty_code = $obj->thirdparty_code;
+	$line->subledger_account = $obj->subledger_account;
+	$line->subledger_label = $obj->subledger_label;
+	$line->numero_compte = $obj->numero_compte;
+	$line->label_compte = $obj->label_compte;
+	$line->label_operation = $obj->label_operation;
+	$line->debit = $obj->debit;
+	$line->credit = $obj->credit;
+	$line->montant = $obj->amount; // deprecated
+	$line->amount = $obj->amount;
+	$line->sens = $obj->sens;
+	$line->lettering_code = $obj->lettering_code;
+	$line->fk_user_author = $obj->fk_user_author;
+	$line->import_key = $obj->import_key;
+	$line->code_journal = $obj->code_journal;
+	$line->journal_label = $obj->journal_label;
+	$line->piece_num = $obj->piece_num;
+	$line->date_creation = $db->jdate($obj->date_creation);
+	$line->date_modification = $db->jdate($obj->date_modification);
+	$line->date_export = $db->jdate($obj->date_export);
+	$line->date_validation = $db->jdate($obj->date_validation);
+
+	$total_debit += $line->debit;
+	$total_credit += $line->credit;
+
+	print '<tr class="oddeven">';
+	// Action column
+	if (getDolGlobalString('MAIN_CHECKBOX_LEFT_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>';
+		if (!$i) {
+			$totalarray['nbfield']++;
+		}
+	}
+
+	// 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 tdoverflowmax150">'.$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
+		}
+
+		$labeltoshow = '';
+		$labeltoshowalt = '';
+		if ($line->doc_type == 'customer_invoice' || $line->doc_type == 'supplier_invoice' || $line->doc_type == 'expense_report') {
+			$labeltoshow .= $objectstatic->getNomUrl(1, '', 0, 0, '', 0, -1, 1);
+			$labeltoshow .= $documentlink;
+			$labeltoshowalt .= $objectstatic->ref;
+		} elseif ($line->doc_type == 'bank') {
+			$labeltoshow .= $objectstatic->getNomUrl(1);
+			$labeltoshowalt .= $objectstatic->ref;
+			$bank_ref = strstr($line->doc_ref, '-');
+			$labeltoshow .= " " . $bank_ref;
+			$labeltoshowalt .= " " . $bank_ref;
+		} else {
+			$labeltoshow .= $line->doc_ref;
+			$labeltoshowalt .= $line->doc_ref;
+		}
+
+		print '<td class="nowraponall tdoverflowmax200" title="'.dol_escape_htmltag($labeltoshowalt).'">';
+		print $labeltoshow;
+		print "</td>\n";
+		if (!$i) {
+			$totalarray['nbfield']++;
+		}
+	}
+
+	// Account number
+	if (!empty($arrayfields['t.numero_compte']['checked'])) {
+		print '<td>'.length_accountg($line->numero_compte).'</td>';
+		if (!$i) {
+			$totalarray['nbfield']++;
+		}
+	}
+
+	// Subledger account
+	if (!empty($arrayfields['t.subledger_account']['checked'])) {
+		print '<td>'.length_accounta($line->subledger_account).'</td>';
+		if (!$i) {
+			$totalarray['nbfield']++;
+		}
+	}
+
+	// Label operation
+	if (!empty($arrayfields['t.label_operation']['checked'])) {
+		print '<td class="small tdoverflowmax200" title="'.dol_escape_htmltag($line->label_operation).'">'.dol_escape_htmltag($line->label_operation).'</td>';
+		if (!$i) {
+			$totalarray['nbfield']++;
+		}
+	}
+
+	// Amount debit
+	if (!empty($arrayfields['t.debit']['checked'])) {
+		print '<td class="right nowraponall amount">'.($line->debit != 0 ? 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 != 0 ? 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']++;
+		}
+	}
+
+	// Fields from hook
+	$parameters = array('arrayfields'=>$arrayfields, 'obj'=>$obj, 'i'=>$i, 'totalarray'=>&$totalarray);
+	$reshook = $hookmanager->executeHooks('printFieldListValue', $parameters); // Note that $action and $object may have been modified by hook
+	print $hookmanager->resPrint;
+
+	// Creation operation date
+	if (!empty($arrayfields['t.date_creation']['checked'])) {
+		print '<td class="center">'.dol_print_date($line->date_creation, 'dayhour', 'tzuserrel').'</td>';
+		if (!$i) {
+			$totalarray['nbfield']++;
+		}
+	}
+
+	// Modification operation date
+	if (!empty($arrayfields['t.tms']['checked'])) {
+		print '<td class="center">'.dol_print_date($line->date_modification, 'dayhour', 'tzuserrel').'</td>';
+		if (!$i) {
+			$totalarray['nbfield']++;
+		}
+	}
+
+	// Exported operation date
+	if (!empty($arrayfields['t.date_export']['checked'])) {
+		print '<td class="center nowraponall">'.dol_print_date($line->date_export, 'dayhour', 'tzuserrel').'</td>';
+		if (!$i) {
+			$totalarray['nbfield']++;
+		}
+	}
+
+	// Validated operation date
+	if (!empty($arrayfields['t.date_validated']['checked'])) {
+		print '<td class="center nowraponall">'.dol_print_date($line->date_validation, 'dayhour', 'tzuserrel').'</td>';
+		if (!$i) {
+			$totalarray['nbfield']++;
+		}
+	}
+
+	if (!empty($arrayfields['t.import_key']['checked'])) {
+		print '<td class="tdoverflowmax100">'.$obj->import_key."</td>\n";
+		if (!$i) {
+			$totalarray['nbfield']++;
+		}
+	}
+
+	// Action column
+	if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_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>';
+		if (!$i) {
+			$totalarray['nbfield']++;
+		}
+	}
+
+
+	print "</tr>\n";
+
+	$i++;
+}
+
+// Show total line
+include DOL_DOCUMENT_ROOT.'/core/tpl/list_print_total.tpl.php';
+
+// If no record found
+if ($num == 0) {
+	$colspan = 1;
+	foreach ($arrayfields as $key => $val) {
+		if (!empty($val['checked'])) {
+			$colspan++;
+		}
+	}
+	print '<tr><td colspan="'.$colspan.'"><span class="opacitymedium">'.$langs->trans("NoRecordFound").'</span></td></tr>';
+}
+
+$parameters = array('arrayfields'=>$arrayfields, 'sql'=>$sql);
+$reshook = $hookmanager->executeHooks('printFieldListFooter', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+print $hookmanager->resPrint;
+
+print "</table>";
+print '</div>';
+
+print '</form>';
+
+// End of page
+llxFooter();
+
+$db->close();

+ 19 - 278
htdocs/accountancy/bookkeeping/list.php

@@ -1,7 +1,7 @@
 <?php
 /* Copyright (C) 2013-2016  Olivier Geffroy         <jeff@jeffinfo.com>
  * Copyright (C) 2013-2016  Florian Henry           <florian.henry@open-concept.pro>
- * Copyright (C) 2013-2022  Alexandre Spangaro      <aspangaro@open-dsi.fr>
+ * Copyright (C) 2013-2023  Alexandre Spangaro      <aspangaro@open-dsi.fr>
  * Copyright (C) 2022  		Lionel Vessiller        <lvessiller@open-dsi.fr>
  * Copyright (C) 2016-2017  Laurent Destailleur     <eldy@users.sourceforge.net>
  * Copyright (C) 2018-2021  Frédéric France         <frederic.france@netlogic.fr>
@@ -29,7 +29,6 @@
 
 // Load Dolibarr environment
 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';
@@ -96,14 +95,6 @@ $search_date_validation_start = dol_mktime(0, 0, 0, $search_date_validation_star
 $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")) {
-	$action = 'delbookkeepingyear';
-}
-if (GETPOST("button_export_file_x") || GETPOST("button_export_file.x") || GETPOST("button_export_file")) {
-	$action = 'export_file';
-}
-
 $search_account_category = GETPOST('search_account_category', 'int');
 
 $search_accountancy_code = GETPOST("search_accountancy_code", 'alpha');
@@ -159,7 +150,7 @@ $hookmanager->initHooks(array('bookkeepinglist'));
 $formaccounting = new FormAccounting($db);
 $form = new Form($db);
 
-if (!in_array($action, array('export_file', 'delmouv', 'delmouvconfirm')) && !GETPOSTISSET('begin') && !GETPOSTISSET('formfilteraction') && GETPOST('page', 'int') == '' && !GETPOST('noreset', 'int') && $user->hasRight('accounting', 'mouvements', 'export')) {
+if (!in_array($action, array('delmouv', 'delmouvconfirm')) && !GETPOSTISSET('begin') && !GETPOSTISSET('formfilteraction') && GETPOST('page', 'int') == '' && !GETPOST('noreset', 'int') && $user->hasRight('accounting', 'mouvements', 'export')) {
 	if (empty($search_date_start) && empty($search_date_end) && !GETPOSTISSET('restore_lastsearch_values') && !GETPOST('search_accountancy_code_start')) {
 		$query = "SELECT date_start, date_end from ".MAIN_DB_PREFIX."accounting_fiscalyear ";
 		$query .= " where date_start < '".$db->idate(dol_now())."' and date_end > '".$db->idate(dol_now())."' limit 1";
@@ -201,8 +192,8 @@ $arrayfields = array(
 	't.lettering_code'=>array('label'=>$langs->trans("LetteringCode"), 'checked'=>1),
 	't.date_creation'=>array('label'=>$langs->trans("DateCreation"), 'checked'=>0),
 	'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, 'enabled'=>!getDolGlobalString("ACCOUNTANCY_DISABLE_CLOSURE_LINE_BY_LINE")),
+	't.date_export'=>array('label'=>$langs->trans("DateExport"), 'checked'=>0),
+	't.date_validated'=>array('label'=>$langs->trans("DateValidationAndLock"), 'checked'=>0, 'enabled'=>!getDolGlobalString("ACCOUNTANCY_DISABLE_CLOSURE_LINE_BY_LINE")),
 	't.import_key'=>array('label'=>$langs->trans("ImportId"), 'checked'=>0, 'position'=>1100),
 );
 
@@ -210,13 +201,6 @@ if (empty($conf->global->ACCOUNTING_ENABLE_LETTERING)) {
 	unset($arrayfields['t.lettering_code']);
 }
 
-$accountancyexport = new AccountancyExport($db);
-$listofformat = $accountancyexport->getType();
-$formatexportset = getDolGlobalString('ACCOUNTING_EXPORT_MODELCSV');
-if (empty($listofformat[$formatexportset])) {
-	$formatexportset = 1;
-}
-
 $error = 0;
 
 if (!isModEnabled('accounting')) {
@@ -457,55 +441,12 @@ if (empty($reshook)) {
 		$param .= '&search_import_key='.urlencode($search_import_key);
 	}
 
-	//if ($action == 'delbookkeepingyearconfirm' && !$user->hasRight('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++;
-		}
-
-		if (!$error) {
-			if ($conf->global->ACCOUNTING_REEXPORT == 1) {
-				setEventMessages($langs->trans("ExportOfPiecesAlreadyExportedIsEnable"), null, 'mesgs');
-			} else {
-				setEventMessages($langs->trans("ExportOfPiecesAlreadyExportedIsDisable"), null, 'warnings');
-			}
-		} else {
-			setEventMessages($langs->trans("Error"), null, 'errors');
-		}
-	}
-
 	// Mass actions
 	$objectclass = 'Bookkeeping';
 	$objectlabel = 'Bookkeeping';
 	$permissiontoread = $user->hasRight('societe', 'lire');
 	$permissiontodelete = $user->hasRight('societe', 'supprimer');
-	$permissiontoadd = $user->rights->societe->creer;
+	$permissiontoadd = $user->hasRight('societe', 'creer');
 	$uploaddir = $conf->societe->dir_output;
 	include DOL_DOCUMENT_ROOT.'/core/actions_massactions.inc.php';
 
@@ -707,99 +648,12 @@ if (count($filter) > 0) {
 	}
 }
 $sql .= ' WHERE t.entity IN ('.getEntity('accountancy').')';
-if (empty($conf->global->ACCOUNTING_REEXPORT)) {
-	$sql .= " AND t.date_export IS NULL";
-}
+
 if (count($sqlwhere) > 0) {
 	$sql .= ' AND '.implode(' AND ', $sqlwhere);
 }
 //print $sql;
 
-
-// Export into a file with format defined into setup (FEC, CSV, ...)
-// Must be after definition of $sql
-if ($action == 'export_fileconfirm' && $user->hasRight('accounting', 'mouvements', 'export')) {
-	// TODO Replace the fetchAll to get all ->line followed by call to ->export(). It consumes too much memory on large export.
-	// Replace this with the query($sql) and loop on each line to export them.
-	$result = $object->fetchAll($sortorder, $sortfield, 0, 0, $filter, 'AND', (empty($conf->global->ACCOUNTING_REEXPORT) ? 0 : 1));
-
-	if ($result < 0) {
-		setEventMessages($object->error, $object->errors, 'errors');
-	} else {
-		// Export files then exit
-		$accountancyexport = new AccountancyExport($db);
-
-		$notexportlettering = GETPOST('notexportlettering', 'alpha');
-
-		if (!empty($notexportlettering)) {
-			if (is_array($object->lines)) {
-				foreach ($object->lines as $k => $movement) {
-					unset($object->lines[$k]->lettering_code);
-					unset($object->lines[$k]->date_lettering);
-				}
-			}
-		}
-
-		$notifiedexportdate = GETPOST('notifiedexportdate', 'alpha');
-		$notifiedvalidationdate = GETPOST('notifiedvalidationdate', 'alpha');
-		$withAttachment = !empty(trim(GETPOST('notifiedexportfull', 'alphanohtml'))) ? 1 : 0;
-
-		// Output data on screen or download
-		$result = $accountancyexport->export($object->lines, $formatexportset, $withAttachment);
-
-		$error = 0;
-		if ($result < 0) {
-			$error++;
-		} else {
-			if (!empty($notifiedexportdate) || !empty($notifiedvalidationdate)) {
-				if (is_array($object->lines)) {
-					// Specify as export : update field date_export or date_validated
-					$db->begin();
-
-					foreach ($object->lines as $movement) {
-						$now = dol_now();
-
-						$sql = " UPDATE ".MAIN_DB_PREFIX."accounting_bookkeeping";
-						$sql .= " SET";
-						if (!empty($notifiedexportdate) && !empty($notifiedvalidationdate)) {
-							$sql .= " date_export = '".$db->idate($now)."'";
-							$sql .= ", date_validated = '".$db->idate($now)."'";
-						} elseif (!empty($notifiedexportdate)) {
-							$sql .= " date_export = '".$db->idate($now)."'";
-						} elseif (!empty($notifiedvalidationdate)) {
-							$sql .= " date_validated = '".$db->idate($now)."'";
-						}
-						$sql .= " WHERE rowid = ".((int) $movement->id);
-
-						dol_syslog("/accountancy/bookkeeping/list.php Function export_file Specify movements as exported", LOG_DEBUG);
-
-						$result = $db->query($sql);
-						if (!$result) {
-							$error++;
-							break;
-						}
-					}
-
-					if (!$error) {
-						$db->commit();
-					} else {
-						$error++;
-						$accountancyexport->errors[] = $langs->trans('NotAllExportedMovementsCouldBeRecordedAsExportedOrValidated');
-						$db->rollback();
-					}
-				}
-			}
-		}
-
-		if ($error) {
-			setEventMessages('', $accountancyexport->errors, 'errors');
-			header('Location: '.$_SERVER['PHP_SELF']);
-		}
-		exit(); // download or show errors
-	}
-}
-
-
 /*
  * View
  */
@@ -811,7 +665,7 @@ $title_page = $langs->trans("Operations").' - '.$langs->trans("Journals");
 
 // Count total nb of records
 $nbtotalofrecords = '';
-if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
+if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
 	/* The fast and low memory method to get and count full list converts the sql into a sql count */
 	$sqlforcount = preg_replace('/^'.preg_quote($sqlfields, '/').'/', 'SELECT COUNT(*) as nbtotalofrecords', $sql);
 	$sqlforcount = preg_replace('/GROUP BY .*$/', '', $sqlforcount);
@@ -853,100 +707,6 @@ llxHeader('', $title_page);
 
 $formconfirm = '';
 
-if ($action == 'export_file') {
-	$form_question = array();
-
-	if (getDolGlobalInt("ACCOUNTING_ENABLE_LETTERING")) {
-		// If 1, we check by default.
-		$checked = !empty($conf->global->ACCOUNTING_DEFAULT_NOT_EXPORT_LETTERING) ? 'true' : 'false';
-		$form_question['notexportlettering'] = array(
-			'name' => 'notexportlettering',
-			'type' => 'checkbox',
-			'label' => $langs->trans('NotExportLettering'),
-			'value' => $checked,
-		);
-
-		$form_question['separator'] = array('name'=>'separator', 'type'=>'separator');
-	}
-
-	// If 1 or not set, we check by default.
-	$checked = (!isset($conf->global->ACCOUNTING_DEFAULT_NOT_NOTIFIED_EXPORT_DATE) || !empty($conf->global->ACCOUNTING_DEFAULT_NOT_NOTIFIED_EXPORT_DATE));
-	$form_question['notifiedexportdate'] = array(
-		'name' => 'notifiedexportdate',
-		'type' => 'checkbox',
-		'label' => $langs->trans('NotifiedExportDate'),
-		'value' => (!empty($conf->global->ACCOUNTING_DEFAULT_NOT_NOTIFIED_EXPORT_DATE) ? 'false' : 'true'),
-	);
-
-	$form_question['separator2'] = array('name'=>'separator2', 'type'=>'separator');
-
-	if (!getDolGlobalString("ACCOUNTANCY_DISABLE_CLOSURE_LINE_BY_LINE")) {
-		// If 0 or not set, we NOT check by default.
-		$checked = (isset($conf->global->ACCOUNTING_DEFAULT_NOT_NOTIFIED_VALIDATION_DATE) || !empty($conf->global->ACCOUNTING_DEFAULT_NOT_NOTIFIED_VALIDATION_DATE));
-		$form_question['notifiedvalidationdate'] = array(
-			'name' => 'notifiedvalidationdate',
-			'type' => 'checkbox',
-			'label' => $langs->trans('NotifiedValidationDate', $langs->transnoentitiesnoconv("MenuAccountancyClosure")),
-			'value' => $checked,
-		);
-
-		$form_question['separator3'] = array('name'=>'separator3', 'type'=>'separator');
-	}
-
-	// add documents in an archive for accountancy export (Quadratus)
-	if (getDolGlobalString('ACCOUNTING_EXPORT_MODELCSV') == AccountancyExport::$EXPORT_TYPE_QUADRATUS) {
-		$form_question['notifiedexportfull'] = array(
-			'name' => 'notifiedexportfull',
-			'type' => 'checkbox',
-			'label' => $langs->trans('NotifiedExportFull'),
-			'value' => 'false',
-		);
-	}
-
-	$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?'.$param, $langs->trans("ExportFilteredList").' ('.$listofformat[$formatexportset].')', $langs->trans('ConfirmExportFile'), 'export_fileconfirm', $form_question, '', 1, 400, 600);
-}
-
-//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;
 
@@ -955,7 +715,7 @@ if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) {
 	$param .= '&contextpage='.urlencode($contextpage);
 }
 if ($limit > 0 && $limit != $conf->liste_limit) {
-	$param .= '&limit='.urlencode($limit);
+	$param .= '&limit='.((int) $limit);
 }
 
 // List of mass actions available
@@ -1000,21 +760,6 @@ if ($reshook < 0) {
 $newcardbutton = empty($hookmanager->resPrint) ? '' : $hookmanager->resPrint;
 
 if (empty($reshook)) {
-	// Button re-export
-	if (!empty($conf->global->ACCOUNTING_REEXPORT)) {
-		$newcardbutton .= '<a class="valignmiddle" href="'.$_SERVER['PHP_SELF'].'?action=setreexport&token='.newToken().'&value=0'.($param ? '&'.$param : '').'">'.img_picto($langs->trans("ClickToHideAlreadyExportedLines"), 'switch_off', 'class="small size15x valignmiddle"');
-		$newcardbutton .= '<span class="valignmiddle marginrightonly paddingleft">'.$langs->trans("ClickToHideAlreadyExportedLines").'</span>';
-		$newcardbutton .= '</a>';
-	} else {
-		$newcardbutton .= '<a class="valignmiddle" href="'.$_SERVER['PHP_SELF'].'?action=setreexport&token='.newToken().'&value=1'.($param ? '&'.$param : '').'">'.img_picto($langs->trans("DocsAlreadyExportedAreExcluded"), 'switch_on', 'class="warning size15x valignmiddle"').'';
-		$newcardbutton .= '<span class="valignmiddle marginrightonly paddingleft">'.$langs->trans("DocsAlreadyExportedAreExcluded").'</span>';
-		$newcardbutton .= '</a>';
-	}
-
-	if ($user->hasRight('accounting', 'mouvements', 'export')) {
-		$newcardbutton .= dolGetButtonTitle($buttonLabel, $langs->trans("ExportFilteredList").' ('.$listofformat[$formatexportset].')', 'fa fa-file-export paddingleft', $_SERVER["PHP_SELF"].'?action=export_file&token='.newToken().($param ? '&'.$param : ''), $user->hasRight('accounting', 'mouvements', 'export'));
-	}
-
 	$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/listbyaccount.php?type=sub'.$param, '', 1, array('morecss' => 'marginleftonly'));
@@ -1343,6 +1088,9 @@ while ($i < min($num, $limit)) {
 			print '<input id="cb'.$line->id.'" class="flat checkforselect" type="checkbox" name="toselect[]" value="'.$line->id.'"'.($selected ? ' checked="checked"' : '').' />';
 		}
 		print '</td>';
+		if (!$i) {
+			$totalarray['nbfield']++;
+		}
 	}
 
 	// Piece number
@@ -1362,7 +1110,7 @@ while ($i < min($num, $limit)) {
 		$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>';
+		print '<td class="center tdoverflowmax150">'.$journaltoshow.'</td>';
 		if (!$i) {
 			$totalarray['nbfield']++;
 		}
@@ -1510,7 +1258,7 @@ while ($i < min($num, $limit)) {
 
 	// Creation operation date
 	if (!empty($arrayfields['t.date_creation']['checked'])) {
-		print '<td class="center">'.dol_print_date($line->date_creation, 'dayhour').'</td>';
+		print '<td class="center">'.dol_print_date($line->date_creation, 'dayhour', 'tzuserrel').'</td>';
 		if (!$i) {
 			$totalarray['nbfield']++;
 		}
@@ -1518,7 +1266,7 @@ while ($i < min($num, $limit)) {
 
 	// Modification operation date
 	if (!empty($arrayfields['t.tms']['checked'])) {
-		print '<td class="center">'.dol_print_date($line->date_modification, 'dayhour').'</td>';
+		print '<td class="center">'.dol_print_date($line->date_modification, 'dayhour', 'tzuserrel').'</td>';
 		if (!$i) {
 			$totalarray['nbfield']++;
 		}
@@ -1526,7 +1274,7 @@ while ($i < min($num, $limit)) {
 
 	// Exported operation date
 	if (!empty($arrayfields['t.date_export']['checked'])) {
-		print '<td class="center nowraponall">'.dol_print_date($line->date_export, 'dayhour').'</td>';
+		print '<td class="center nowraponall">'.dol_print_date($line->date_export, 'dayhour', 'tzuserrel').'</td>';
 		if (!$i) {
 			$totalarray['nbfield']++;
 		}
@@ -1534,7 +1282,7 @@ while ($i < min($num, $limit)) {
 
 	// Validated operation date
 	if (!empty($arrayfields['t.date_validated']['checked'])) {
-		print '<td class="center nowraponall">'.dol_print_date($line->date_validation, 'dayhour').'</td>';
+		print '<td class="center nowraponall">'.dol_print_date($line->date_validation, 'dayhour', 'tzuserrel').'</td>';
 		if (!$i) {
 			$totalarray['nbfield']++;
 		}
@@ -1558,11 +1306,11 @@ while ($i < min($num, $limit)) {
 			print '<input id="cb'.$line->id.'" class="flat checkforselect" type="checkbox" name="toselect[]" value="'.$line->id.'"'.($selected ? ' checked="checked"' : '').' />';
 		}
 		print '</td>';
+		if (!$i) {
+			$totalarray['nbfield']++;
+		}
 	}
 
-	if (!$i) {
-		$totalarray['nbfield']++;
-	}
 
 	print "</tr>\n";
 
@@ -1590,13 +1338,6 @@ print $hookmanager->resPrint;
 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

+ 16 - 7
htdocs/accountancy/bookkeeping/listbyaccount.php

@@ -413,7 +413,7 @@ if (empty($reshook)) {
 	$objectlabel = 'Bookkeeping';
 	$permissiontoread = $user->hasRight('societe', 'lire');
 	$permissiontodelete = $user->hasRight('societe', 'supprimer');
-	$permissiontoadd = $user->rights->societe->creer;
+	$permissiontoadd = $user->hasRight('societe', 'creer');
 	$uploaddir = $conf->societe->dir_output;
 	include DOL_DOCUMENT_ROOT.'/core/actions_massactions.inc.php';
 
@@ -559,7 +559,7 @@ llxHeader('', $title_page);
 
 // List
 $nbtotalofrecords = '';
-if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
+if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
 	// TODO Perf Replace this by a count
 	if ($type == 'sub') {
 		$nbtotalofrecords = $object->fetchAllByAccount($sortorder, $sortfield, 0, 0, $filter, 'AND', 1, 1);
@@ -686,7 +686,7 @@ if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) {
 	$param .= '&contextpage='.urlencode($contextpage);
 }
 if ($limit > 0 && $limit != $conf->liste_limit) {
-	$param .= '&limit='.urlencode($limit);
+	$param .= '&limit='.((int) $limit);
 }
 
 print_barre_liste($title_page, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $result, $nbtotalofrecords, 'title_accountancy', 0, $newcardbutton, '', $limit, 0, 0, 1);
@@ -951,6 +951,10 @@ while ($i < min($num, $limit)) {
 	if (!empty($arrayfields['t.date_export']['checked'])) { $colspanend++; }
 	if (!empty($arrayfields['t.date_validating']['checked'])) { $colspanend++; }
 	if (!empty($arrayfields['t.lettering_code']['checked'])) { $colspanend++; }
+	if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		$colspan++;
+		$colspanend--;
+	}
 
 	// Is it a break ?
 	if ($accountg != $displayed_account_number || !isset($displayed_account_number)) {
@@ -990,7 +994,9 @@ while ($i < min($num, $limit)) {
 		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);
+				print empty($line->subledger_label) ? '<span class="error">'.$langs->trans("Unknown").'</span>' : $line->subledger_label;
+				print ' : ';
+				print 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");
@@ -1033,6 +1039,9 @@ while ($i < min($num, $limit)) {
 			print '<input id="cb' . $line->id . '" class="flat checkforselect" type="checkbox" name="toselect[]" value="' . $line->id . '"' . ($selected ? ' checked="checked"' : '') . ' />';
 		}
 		print '</td>';
+		if (!$i) {
+			$totalarray['nbfield']++;
+		}
 	}
 	// Piece number
 	if (!empty($arrayfields['t.piece_num']['checked'])) {
@@ -1216,9 +1225,9 @@ while ($i < min($num, $limit)) {
 			print '<input id="cb' . $line->id . '" class="flat checkforselect" type="checkbox" name="toselect[]" value="' . $line->id . '"' . ($selected ? ' checked="checked"' : '') . ' />';
 		}
 		print '</td>';
-	}
-	if (!$i) {
-		$totalarray['nbfield']++;
+		if (!$i) {
+			$totalarray['nbfield']++;
+		}
 	}
 
 	// Comptabilise le sous-total

+ 13 - 4
htdocs/accountancy/class/accountancycategory.class.php

@@ -466,7 +466,7 @@ class AccountancyCategory // extends CommonObject
 		$sql .= " FROM ".MAIN_DB_PREFIX."accounting_account as aa";
 		$sql .= " INNER JOIN ".MAIN_DB_PREFIX."accounting_system as asy ON aa.fk_pcg_version = asy.pcg_version";
 		$sql .= " WHERE (aa.fk_accounting_category <> ".((int) $id)." OR aa.fk_accounting_category IS NULL)";
-		$sql .= " AND asy.rowid = ".((int) $conf->global->CHARTOFACCOUNTS);
+		$sql .= " AND asy.rowid = ".((int) getDolGlobalInt('CHARTOFACCOUNTS'));
 		$sql .= " AND aa.active = 1";
 		$sql .= " AND aa.entity = ".$conf->entity;
 		$sql .= " GROUP BY aa.account_number, aa.label";
@@ -512,7 +512,7 @@ class AccountancyCategory // extends CommonObject
 		$sql = "SELECT aa.rowid, aa.account_number";
 		$sql .= " FROM ".MAIN_DB_PREFIX."accounting_account as aa";
 		$sql .= " INNER JOIN ".MAIN_DB_PREFIX."accounting_system as asy ON aa.fk_pcg_version = asy.pcg_version";
-		$sql .= " AND asy.rowid = ".((int) $conf->global->CHARTOFACCOUNTS);
+		$sql .= " AND asy.rowid = ".((int) getDolGlobalInt('CHARTOFACCOUNTS'));
 		$sql .= " AND aa.active = 1";
 		$sql .= " AND aa.entity = ".$conf->entity;
 		$sql .= " ORDER BY LENGTH(aa.account_number) DESC;"; // LENGTH is ok with mysql and postgresql
@@ -646,7 +646,7 @@ class AccountancyCategory // extends CommonObject
 		}
 		$sql .= " FROM ".MAIN_DB_PREFIX."accounting_bookkeeping as t";
 		//if (in_array($this->db->type, array('mysql', 'mysqli'))) $sql.=' USE INDEX idx_accounting_bookkeeping_doc_date';
-		$sql .= " WHERE t.entity = ".$conf->entity;
+		$sql .= " WHERE t.entity = ".((int) $conf->entity);
 		if (is_array($cpt)) {
 			$sql .= " AND t.numero_compte IN (".$this->db->sanitize($listofaccount, 1).")";
 		} else {
@@ -839,20 +839,29 @@ class AccountancyCategory // extends CommonObject
 			exit();
 		}
 
+		$pcgverid = getDolGlobalInt('CHARTOFACCOUNTS');
+		$pcgvercode = dol_getIdFromCode($this->db, $pcgverid, 'accounting_system', 'rowid', 'pcg_version');
+		if (empty($pcgvercode)) {
+			$pcgvercode = $pcgverid;
+		}
+
 		if (!empty($cat_id)) {
 			$sql = "SELECT t.rowid, t.account_number, t.label as account_label";
 			$sql .= " FROM ".MAIN_DB_PREFIX."accounting_account as t";
 			$sql .= " WHERE t.fk_accounting_category = ".((int) $cat_id);
 			$sql .= " AND t.entity = ".$conf->entity;
+			$sql .= " AND t.active = 1";
+			$sql .= " AND t.fk_pcg_version = '".$this->db->escape($pcgvercode)."'";
 			$sql .= " ORDER BY t.account_number";
 		} else {
 			$sql = "SELECT t.rowid, t.account_number, t.label as account_label";
 			$sql .= " FROM ".MAIN_DB_PREFIX."accounting_account as t";
 			$sql .= " WHERE ".$predefinedgroupwhere;
 			$sql .= " AND t.entity = ".$conf->entity;
+			$sql .= ' AND t.active = 1';
+			$sql .= " AND t.fk_pcg_version = '".$this->db->escape($pcgvercode)."'";
 			$sql .= " ORDER BY t.account_number";
 		}
-		//echo $sql;
 
 		$resql = $this->db->query($sql);
 		if ($resql) {

ファイルの差分が大きいため隠しています
+ 452 - 310
htdocs/accountancy/class/accountancyexport.class.php


+ 5 - 0
htdocs/accountancy/class/accountancysystem.class.php

@@ -38,6 +38,11 @@ class AccountancySystem
 	 */
 	public $error = '';
 
+	/**
+	 * @var string[] Array of Errors code (or messages)
+	 */
+	public $errors = array();
+
 	/**
 	 * @var int ID
 	 */

+ 9 - 9
htdocs/accountancy/class/accountingaccount.class.php

@@ -169,7 +169,7 @@ class AccountingAccount extends CommonObject
 		global $conf;
 
 		$this->db = $db;
-		$this->next_prev_filter = "fk_pcg_version IN (SELECT pcg_version FROM ".MAIN_DB_PREFIX."accounting_system WHERE rowid = ".((int) $conf->global->CHARTOFACCOUNTS).")"; // Used to add a filter in Form::showrefnav method
+		$this->next_prev_filter = "fk_pcg_version IN (SELECT pcg_version FROM ".MAIN_DB_PREFIX."accounting_system WHERE rowid = ".((int) getDolGlobalInt('CHARTOFACCOUNTS')).")"; // Used to add a filter in Form::showrefnav method
 	}
 
 	/**
@@ -198,7 +198,7 @@ class AccountingAccount extends CommonObject
 				$sql .= " AND a.entity = ".$conf->entity;
 			}
 			if (!empty($limittocurrentchart)) {
-				$sql .= ' AND a.fk_pcg_version IN (SELECT pcg_version FROM '.MAIN_DB_PREFIX.'accounting_system WHERE rowid = '.((int) $conf->global->CHARTOFACCOUNTS).')';
+				$sql .= ' AND a.fk_pcg_version IN (SELECT pcg_version FROM '.MAIN_DB_PREFIX.'accounting_system WHERE rowid = '.((int) getDolGlobalInt('CHARTOFACCOUNTS')).')';
 			}
 			if (!empty($limittoachartaccount)) {
 				$sql .= " AND a.fk_pcg_version = '".$this->db->escape($limittoachartaccount)."'";
@@ -684,10 +684,10 @@ class AccountingAccount extends CommonObject
 	}
 
 	/**
-	 *  Retourne le libelle du statut d'un user (actif, inactif)
+	 *  Return the label of the status
 	 *
-	 * @param int $mode 0=libelle long, 1=libelle court, 2=Picto + Libelle court, 3=Picto, 4=Picto + Libelle long, 5=Libelle court + Picto
-	 * @return string              Label of status
+	 *  @param  int		$mode          0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
+	 *  @return	string 			       Label of status
 	 */
 	public function getLibStatut($mode = 0)
 	{
@@ -696,11 +696,11 @@ class AccountingAccount extends CommonObject
 
 	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
 	/**
-	 *  Renvoi le libelle d'un statut donne
+	 *  Return the label of a given status
 	 *
-	 * @param int $status Id status
-	 * @param int $mode 0=libelle long, 1=libelle court, 2=Picto + Libelle court, 3=Picto, 4=Picto + Libelle long, 5=Libelle court + Picto
-	 * @return string              Label of status
+	 *  @param	int		$status        Id status
+	 *  @param  int		$mode          0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
+	 *  @return string 			       Label of status
 	 */
 	public function LibStatut($status, $mode = 0)
 	{

+ 4 - 21
htdocs/accountancy/class/accountingjournal.class.php

@@ -312,10 +312,10 @@ class AccountingJournal extends CommonObject
 	}
 
 	/**
-	 *  Retourne le libelle du statut d'un user (actif, inactif)
+	 *  Return the label of the status
 	 *
-	 *  @param	int		$mode		  0=libelle long, 1=libelle court
-	 *  @return	string 				   Label of type
+	 *  @param  int		$mode          0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
+	 *  @return	string 			       Label of status
 	 */
 	public function getLibType($mode = 0)
 	{
@@ -327,7 +327,7 @@ class AccountingJournal extends CommonObject
 	 *  Return type of an accounting journal
 	 *
 	 *  @param	int		$nature			Id type
-	 *  @param  int		$mode		  	0=libelle long, 1=libelle court
+	 *  @param  int		$mode		  	0=label long, 1=label short
 	 *  @return string 				   	Label of type
 	 */
 	public function LibType($nature, $mode = 0)
@@ -388,12 +388,6 @@ class AccountingJournal extends CommonObject
 		if (empty($type)) $type = 'view';
 		if (empty($in_bookkeeping)) $in_bookkeeping = 'notyet';
 
-		// Hook
-		if (!is_object($hookmanager)) {
-			include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
-			$hookmanager = new HookManager($this->db);
-		}
-
 		$data = array();
 
 		$hookmanager->initHooks(array('accountingjournaldao'));
@@ -790,12 +784,6 @@ class AccountingJournal extends CommonObject
 		global $conf, $langs, $hookmanager;
 		require_once DOL_DOCUMENT_ROOT . '/accountancy/class/bookkeeping.class.php';
 
-		// Hook
-		if (!is_object($hookmanager)) {
-			include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
-			$hookmanager = new HookManager($this->db);
-		}
-
 		$error = 0;
 
 		$hookmanager->initHooks(array('accountingjournaldao'));
@@ -940,11 +928,6 @@ class AccountingJournal extends CommonObject
 		$out = '';
 
 		// Hook
-		if (!is_object($hookmanager)) {
-			include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
-			$hookmanager = new HookManager($this->db);
-		}
-
 		$hookmanager->initHooks(array('accountingjournaldao'));
 		$parameters = array('journal_data' => &$journal_data, 'search_date_end' => &$search_date_end, 'sep' => &$sep, 'out' => &$out);
 		$reshook = $hookmanager->executeHooks('exportCsv', $parameters, $this); // Note that $action and $object may have been

+ 276 - 0
htdocs/accountancy/class/api_accountancy.class.php

@@ -0,0 +1,276 @@
+<?php
+/* Copyright (C) 2015   Jean-François Ferry     <jfefe@aternatik.fr>
+ * Copyright (C) 2019   Cedric Ancelin          <icedo.anc@gmail.com>
+ * Copyright (C) 2023   Lionel Vessiller     	<lvessiller@open-dsi.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/>.
+ */
+
+use Luracast\Restler\RestException;
+
+/**
+ * API class for accountancy
+ *
+ * @access protected
+ * @class  DolibarrApiAccess {@requires user,external}
+ *
+ */
+class Accountancy extends DolibarrApi
+{
+	/**
+	 *
+	 * @var array $FIELDS Mandatory fields, checked when create and update object
+	 */
+	public static $FIELDS = array();
+
+	/**
+	 * @var BookKeeping $bookkeeping {@type BookKeeping}
+	 */
+	public $bookkeeping;
+
+	/**
+	 * @var AccountancyExport $accountancy_export {@type AccountancyExport}
+	 */
+	public $accountancyexport;
+
+	/**
+	 * Constructor
+	 */
+	public function __construct()
+	{
+		global $db, $langs;
+		$this->db = $db;
+
+		require_once DOL_DOCUMENT_ROOT.'/accountancy/class/bookkeeping.class.php';
+		require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountancyexport.class.php';
+
+		$langs->load('accountancy');
+
+		$this->bookkeeping = new BookKeeping($this->db);
+		$this->accountancyexport = new AccountancyExport($this->db);
+	}
+
+	/**
+	 * Accountancy export data
+	 *
+	 * @param       string 		$period					Period : 'lastmonth', 'currentmonth', 'last3months', 'last6months', 'currentyear', 'lastyear', 'fiscalyear', 'lastfiscalyear', 'actualandlastfiscalyear' or 'custom' (see above)
+	 * @param 		string		$date_min				[=''] Start date of period if 'custom' is set in period parameter
+	 * 													Date format is 'YYYY-MM-DD'
+	 * @param 		string		$date_max				[=''] End date of period if 'custom' is set in period parameter
+	 * 													Date format is 'YYYY-MM-DD'
+	 * @param 		string		$format					[=''] by default uses '1' for 'Configurable (CSV)' for format number
+	 *													or '1000' for FEC
+	 * 													or '1010' for FEC2
+	 * 													(see AccountancyExport class)
+	 * @param		int			$lettering				[=0] by default don't export or 1 to export lettering data (columns 'letterring_code' and 'date_lettering' returns empty or not)
+	 * @param 		int			$alreadyexport			[=0] by default export data only if it's not yet exported or 1 already exported (always export data even if 'date_export" is set)
+	 * @param 		int			$notnotifiedasexport	[=0] by default notified as exported or 1 not notified as exported (when the export is done, notified or not the column 'date_export')
+	 *
+	 * @return	string
+	 *
+	 * @url		GET exportdata
+	 *
+	 * @throws	RestException	401		Insufficient rights
+	 * @throws	RestException	404		Accountancy export period not found
+	 * @throws	RestException	404		Accountancy export start or end date not defined
+	 * @throws	RestException	404		Accountancy export format not found
+	 * @throws	RestException	500		Error on accountancy export
+	 */
+	public function exportData($period, $date_min = '', $date_max = '', $format = '', $lettering = 0, $alreadyexport = 0, $notnotifiedasexport = 0)
+	{
+		global $conf, $langs;
+
+		// check rights
+		if (!DolibarrApiAccess::$user->rights->accounting->mouvements->export) {
+			throw new RestException(401, 'No permission to export accounting');
+		}
+
+		// check parameters
+		$period_available_list = array('lastmonth', 'currentmonth', 'last3months', 'last6months', 'currentyear', 'lastyear', 'fiscalyear', 'lastfiscalyear', 'actualandlastfiscalyear', 'custom');
+		if (!in_array($period, $period_available_list)) {
+			throw new RestException(404, 'Accountancy export period not found');
+		}
+		if ($period == 'custom') {
+			if ($date_min == '' && $date_max == '') {
+				throw new RestException(404, 'Accountancy export start and end date for custom period not defined');
+			}
+		}
+		if ($format == '') {
+			$format = AccountancyExport::$EXPORT_TYPE_CONFIGURABLE; // uses default
+		}
+
+		// get objects
+		$bookkeeping = $this->bookkeeping;
+		$accountancyexport = $this->accountancyexport;
+
+		// find export format code from format number
+		$format_number_available_list = $accountancyexport->getType();
+		if (is_numeric($format)) {
+			$format_number = (int) $format;
+		} else {
+			$format_number = 0;
+			$format_label_available_list = array_flip($format_number_available_list);
+			if (isset($format_label_available_list[$format])) {
+				$format_number = $format_label_available_list[$format];
+			}
+		}
+
+		// get all format available and check if exists
+		if (!array_key_exists($format_number, $format_number_available_list)) {
+			throw new RestException(404, 'Accountancy export format not found');
+		}
+
+		$sortorder = 'ASC'; // by default
+		$sortfield = 't.piece_num, t.rowid'; // by default
+
+		// set filter for each period available
+		$filter = array();
+		$doc_date_start = null;
+		$doc_date_end= null;
+		$now = dol_now();
+		$now_arr = dol_getdate($now);
+		$now_month = $now_arr['mon'];
+		$now_year = $now_arr['year'];
+		if ($period == 'custom') {
+			if ($date_min != '') {
+				$time_min = strtotime($date_min);
+				if ($time_min !== false) {
+					$doc_date_start = $time_min;
+				}
+			}
+			if ($date_max != '') {
+				$time_max = strtotime($date_max);
+				if ($time_max !== false) {
+					$doc_date_end = $time_max;
+				}
+			}
+		} elseif ($period == 'lastmonth') {
+			$prev_date_arr = dol_get_prev_month($now_month, $now_year); // get previous month and year if month is january
+			$doc_date_start = dol_mktime(0, 0, 0, $prev_date_arr['month'], 1, $prev_date_arr['year']); // first day of previous month
+			$doc_date_end = dol_get_last_day($prev_date_arr['year'], $prev_date_arr['month']); // last day of previous month
+		} elseif ($period == 'currentmonth') {
+			$doc_date_start = dol_mktime(0, 0, 0, $now_month, 1, $now_year); // first day of current month
+			$doc_date_end = dol_get_last_day($now_year, $now_month); // last day of current month
+		} elseif ($period == 'last3months' || $period == 'last6months') {
+			if ($period == 'last3months') {
+				// last 3 months
+				$nb_prev_month = 3;
+			} else {
+				// last 6 months
+				$nb_prev_month = 6;
+			}
+			$prev_month_date_list = array();
+			$prev_month_date_list[] = dol_get_prev_month($now_month, $now_year); // get previous month for index = 0
+			for ($i = 1; $i < $nb_prev_month; $i++) {
+				$prev_month_date_list[] = dol_get_prev_month($prev_month_date_list[$i-1]['month'], $prev_month_date_list[$i-1]['year']); // get i+1 previous month for index=i
+			}
+			$doc_date_start = dol_mktime(0, 0, 0, $prev_month_date_list[$nb_prev_month-1]['month'], 1, $prev_month_date_list[$nb_prev_month-1]['year']); // first day of n previous month for index=n-1
+			$doc_date_end = dol_get_last_day($prev_month_date_list[0]['year'], $prev_month_date_list[0]['month']); // last day of previous month for index = 0
+		} elseif ($period == 'currentyear' || $period == 'lastyear') {
+			$period_year = $now_year;
+			if ($period == 'lastyear') {
+				$period_year--;
+			}
+			$doc_date_start = dol_mktime(0, 0, 0, 1, 1, $period_year); // first day of year
+			$doc_date_end = dol_mktime(23, 59, 59, 12, 31, $period_year); // last day of year
+		} elseif ($period == 'fiscalyear' || $period == 'lastfiscalyear' || $period == 'actualandlastfiscalyear') {
+			// find actual fiscal year
+			$cur_fiscal_period = getCurrentPeriodOfFiscalYear($this->db, $conf);
+			$cur_fiscal_date_start = $cur_fiscal_period['date_start'];
+			$cur_fiscal_date_end = $cur_fiscal_period['date_end'];
+
+			if ($period == 'fiscalyear') {
+				$doc_date_start = $cur_fiscal_date_start;
+				$doc_date_end = $cur_fiscal_date_end;
+			} else {
+				// get one day before current fiscal date start (to find previous fiscal period)
+				$prev_fiscal_date_search = dol_time_plus_duree($cur_fiscal_date_start, -1, 'd');
+
+				// find previous fiscal year from current fiscal year
+				$prev_fiscal_period = getCurrentPeriodOfFiscalYear($this->db, $conf, $prev_fiscal_date_search);
+				$prev_fiscal_date_start = $prev_fiscal_period['date_start'];
+				$prev_fiscal_date_end = $prev_fiscal_period['date_end'];
+
+				if ($period == 'lastfiscalyear') {
+					$doc_date_start = $prev_fiscal_date_start;
+					$doc_date_end = $prev_fiscal_date_end;
+				} else {
+					// period == 'actualandlastfiscalyear'
+					$doc_date_start = $prev_fiscal_date_start;
+					$doc_date_end = $cur_fiscal_date_end;
+				}
+			}
+		}
+		if (is_numeric($doc_date_start)) {
+			$filter['t.doc_date>='] = $doc_date_start;
+		}
+		if (is_numeric($doc_date_end)) {
+			$filter['t.doc_date<='] = $doc_date_end;
+		}
+
+		$result = $bookkeeping->fetchAll($sortorder, $sortfield, 0, 0, $filter, 'AND', $alreadyexport);
+
+		if ($result < 0) {
+			throw new RestException(500, 'Error bookkeeping fetch all : '.$bookkeeping->errorsToString());
+		} else {
+			// export files then exit
+			if (empty($lettering)) {
+				if (is_array($bookkeeping->lines)) {
+					foreach ($bookkeeping->lines as $k => $movement) {
+						unset($bookkeeping->lines[$k]->lettering_code);
+						unset($bookkeeping->lines[$k]->date_lettering);
+					}
+				}
+			}
+
+			$error = 0;
+			$this->db->begin();
+
+			if (empty($notnotifiedasexport)) {
+				if (is_array($bookkeeping->lines)) {
+					foreach ($bookkeeping->lines as $movement) {
+						$now = dol_now();
+
+						$sql = " UPDATE " . MAIN_DB_PREFIX . "accounting_bookkeeping";
+						$sql .= " SET date_export = '" . $this->db->idate($now) . "'";
+						$sql .= " WHERE rowid = " . ((int) $movement->id);
+
+						$result = $this->db->query($sql);
+						if (!$result) {
+							$accountancyexport->errors[] = $langs->trans('NotAllExportedMovementsCouldBeRecordedAsExportedOrValidated');
+							$error++;
+							break;
+						}
+					}
+				}
+			}
+
+			// export and only write file without downloading
+			if (!$error) {
+				$result = $accountancyexport->export($bookkeeping->lines, $format_number, 0, 1, 2);
+				if ($result < 0) {
+					$error++;
+				}
+			}
+
+			if ($error) {
+				$this->db->rollback();
+				throw new RestException(500, 'Error accountancy export : '.implode(',', $accountancyexport->errors));
+			} else {
+				$this->db->commit();
+				exit();
+			}
+		}
+	}
+}

+ 4 - 4
htdocs/accountancy/class/bookkeeping.class.php

@@ -2031,7 +2031,7 @@ class BookKeeping extends CommonObject
 
 		require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
 
-		$pcgver = $conf->global->CHARTOFACCOUNTS;
+		$pcgver = getDolGlobalInt('CHARTOFACCOUNTS');
 
 		$sql = "SELECT DISTINCT ab.numero_compte as account_number, aa.label as label, aa.rowid as rowid, aa.fk_pcg_version";
 		$sql .= " FROM ".MAIN_DB_PREFIX."accounting_bookkeeping as ab";
@@ -2088,12 +2088,12 @@ class BookKeeping extends CommonObject
 	 * FIXME: This function takes the parent of parent to get the root account !
 	 *
 	 * @param 	string 	$account	Accounting account
-	 * @return 	array 				Array with root account information (max 2 upper level)
+	 * @return 	array|int 			Array with root account information (max 2 upper level), <0 if KO
 	 */
 	public function getRootAccount($account = null)
 	{
 		global $conf;
-		$pcgver = $conf->global->CHARTOFACCOUNTS;
+		$pcgver = getDolGlobalInt('CHARTOFACCOUNTS');
 
 		$sql  = "SELECT root.rowid, root.account_number, root.label as label,";
 		$sql .= " parent.rowid as parent_rowid, parent.account_number as parent_account_number, parent.label as parent_label";
@@ -2135,7 +2135,7 @@ class BookKeeping extends CommonObject
 		// phpcs:enable
 		global $conf;
 
-		$pcgver = $conf->global->CHARTOFACCOUNTS;
+		$pcgver = getDolGlobalInt('CHARTOFACCOUNTS');
 		$sql  = "SELECT aa.account_number, aa.label, aa.rowid, aa.fk_pcg_version, cat.label as category";
 		$sql .= " FROM ".MAIN_DB_PREFIX."accounting_account as aa ";
 		$sql .= " INNER JOIN ".MAIN_DB_PREFIX."accounting_system as asy ON aa.fk_pcg_version = asy.pcg_version";

+ 6 - 3
htdocs/accountancy/class/lettering.class.php

@@ -329,6 +329,7 @@ class Lettering extends BookKeeping
 		// Update request
 
 		$now = dol_now();
+		$affected_rows = 0;
 
 		if (!$error) {
 			$sql = "UPDATE ".MAIN_DB_PREFIX."accounting_bookkeeping SET";
@@ -341,6 +342,8 @@ class Lettering extends BookKeeping
 			if (!$resql) {
 				$error++;
 				$this->errors[] = "Error ".$this->db->lasterror();
+			} else {
+				$affected_rows = $this->db->affected_rows($resql);
 			}
 		}
 
@@ -352,7 +355,7 @@ class Lettering extends BookKeeping
 			}
 			return -1 * $error;
 		} else {
-			return 1;
+			return $affected_rows;
 		}
 	}
 
@@ -387,7 +390,7 @@ class Lettering extends BookKeeping
 			}
 			return -1 * $error;
 		} else {
-			return 1;
+			return $this->db->affected_rows($resql);
 		}
 	}
 
@@ -482,7 +485,7 @@ class Lettering extends BookKeeping
 				else $result = $this->updateLettering($bookkeeping_lines);
 				if ($result < 0) {
 					$group_error++;
-				} else {
+				} elseif ($result > 0) {
 					$nb_lettering++;
 				}
 			}

+ 67 - 35
htdocs/accountancy/customer/index.php

@@ -75,7 +75,7 @@ $year_current = $year_start;
 // Validate History
 $action = GETPOST('action', 'aZ09');
 
-$chartaccountcode = dol_getIdFromCode($db, $conf->global->CHARTOFACCOUNTS, 'accounting_system', 'rowid', 'pcg_version');
+$chartaccountcode = dol_getIdFromCode($db, getDolGlobalInt('CHARTOFACCOUNTS'), 'accounting_system', 'rowid', 'pcg_version');
 
 // Security check
 if (!isModEnabled('accounting')) {
@@ -96,15 +96,15 @@ if (!$user->hasRight('accounting', 'mouvements', 'lire')) {
 if (($action == 'clean' || $action == 'validatehistory') && $user->hasRight('accounting', 'bind', 'write')) {
 	// Clean database by removing binding done on non existing or no more existing accounts
 	$db->begin();
-	$sql1 = "UPDATE ".MAIN_DB_PREFIX."facturedet as fd";
+	$sql1 = "UPDATE ".$db->prefix()."facturedet as fd";
 	$sql1 .= " SET fk_code_ventilation = 0";
 	$sql1 .= ' WHERE fd.fk_code_ventilation NOT IN';
 	$sql1 .= '	(SELECT accnt.rowid ';
-	$sql1 .= '	FROM '.MAIN_DB_PREFIX.'accounting_account as accnt';
-	$sql1 .= '	INNER JOIN '.MAIN_DB_PREFIX.'accounting_system as syst';
-	$sql1 .= '	ON accnt.fk_pcg_version = syst.pcg_version AND syst.rowid='.((int) $conf->global->CHARTOFACCOUNTS).' AND accnt.entity = '.((int) $conf->entity).')';
-	$sql1 .= ' AND fd.fk_facture IN (SELECT rowid FROM '.MAIN_DB_PREFIX.'facture WHERE entity = '.((int) $conf->entity).')';
-	$sql1 .= ' AND fk_code_ventilation <> 0';
+	$sql1 .= '	FROM '.$db->prefix().'accounting_account as accnt';
+	$sql1 .= '	INNER JOIN '.$db->prefix().'accounting_system as syst';
+	$sql1 .= "	ON accnt.fk_pcg_version = syst.pcg_version AND syst.rowid = ".((int) getDolGlobalInt('CHARTOFACCOUNTS'))." AND accnt.entity = ".((int) $conf->entity).")";
+	$sql1 .= " AND fd.fk_facture IN (SELECT rowid FROM ".$db->prefix()."facture WHERE entity = ".((int) $conf->entity).")";
+	$sql1 .= " AND fk_code_ventilation <> 0";
 
 	dol_syslog("htdocs/accountancy/customer/index.php fixaccountancycode", LOG_DEBUG);
 	$resql1 = $db->query($sql1);
@@ -128,8 +128,8 @@ if ($action == 'validatehistory') {
 
 	// Now make the binding. Bind automatically only for product with a dedicated account that exists into chart of account, others need a manual bind
 	// Customer Invoice lines (must be same request than into page list.php for manual binding)
-	$sql = "SELECT f.rowid as facid, f.ref as ref, f.datef, f.type as ftype, f.fk_facture_source,";
-	$sql .= " l.rowid, l.fk_product, l.description, l.total_ht, l.fk_code_ventilation, l.product_type as type_l, l.tva_tx as tva_tx_line, l.vat_src_code,";
+	$sql = "SELECT f.rowid as facid, f.ref as ref, f.datef, f.type as ftype, f.situation_cycle_ref, f.fk_facture_source,";
+	$sql .= " l.rowid, l.fk_product, l.description, l.total_ht, l.fk_code_ventilation, l.product_type as type_l, l.situation_percent, l.tva_tx as tva_tx_line, l.vat_src_code,";
 	$sql .= " p.rowid as product_id, p.ref as product_ref, p.label as product_label, p.fk_product_type as type, p.tva_tx as tva_tx_prod,";
 	if (!empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
 		$sql .= " ppe.accountancy_code_sell as code_sell, ppe.accountancy_code_sell_intra as code_sell_intra, ppe.accountancy_code_sell_export as code_sell_export,";
@@ -144,23 +144,23 @@ if ($action == 'validatehistory') {
 	} else {
 		$sql .= " s.accountancy_code_sell as company_code_sell";	// accounting code for product but stored on thirdparty
 	}
-	$sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
-	$sql .= " INNER JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = f.fk_soc";
+	$sql .= " FROM ".$db->prefix()."facture as f";
+	$sql .= " INNER JOIN ".$db->prefix()."societe as s ON s.rowid = f.fk_soc";
 	if (!empty($conf->global->MAIN_COMPANY_PERENTITY_SHARED)) {
-		$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_perentity as spe ON spe.fk_soc = s.rowid AND spe.entity = " . ((int) $conf->entity);
+		$sql .= " LEFT JOIN " . $db->prefix() . "societe_perentity as spe ON spe.fk_soc = s.rowid AND spe.entity = " . ((int) $conf->entity);
 	}
-	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_country as co ON co.rowid = s.fk_pays ";
-	$sql .= " INNER JOIN ".MAIN_DB_PREFIX."facturedet as l ON f.rowid = l.fk_facture";	// the main table
-	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = l.fk_product";
+	$sql .= " LEFT JOIN ".$db->prefix()."c_country as co ON co.rowid = s.fk_pays ";
+	$sql .= " INNER JOIN ".$db->prefix()."facturedet as l ON f.rowid = l.fk_facture";	// the main table
+	$sql .= " LEFT JOIN ".$db->prefix()."product as p ON p.rowid = l.fk_product";
 	if (!empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
-		$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "product_perentity as ppe ON ppe.fk_product = p.rowid AND ppe.entity = " . ((int) $conf->entity);
+		$sql .= " LEFT JOIN " . $db->prefix() . "product_perentity as ppe ON ppe.fk_product = p.rowid AND ppe.entity = " . ((int) $conf->entity);
 	}
 	$alias_societe_perentity = empty($conf->global->MAIN_COMPANY_PERENTITY_SHARED) ? "s" : "spe";
 	$alias_product_perentity = empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED) ? "p" : "ppe";
-	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa  ON " . $alias_product_perentity . ".accountancy_code_sell = aa.account_number         AND aa.active = 1  AND aa.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa.entity = ".$conf->entity;
-	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa2 ON " . $alias_product_perentity . ".accountancy_code_sell_intra = aa2.account_number  AND aa2.active = 1 AND aa2.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa2.entity = ".$conf->entity;
-	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa3 ON " . $alias_product_perentity . ".accountancy_code_sell_export = aa3.account_number AND aa3.active = 1 AND aa3.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa3.entity = ".$conf->entity;
-	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa4 ON " . $alias_societe_perentity . ".accountancy_code_sell = aa4.account_number        AND aa4.active = 1 AND aa4.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa4.entity = ".$conf->entity;
+	$sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa  ON " . $alias_product_perentity . ".accountancy_code_sell = aa.account_number         AND aa.active = 1  AND aa.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa.entity = ".$conf->entity;
+	$sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa2 ON " . $alias_product_perentity . ".accountancy_code_sell_intra = aa2.account_number  AND aa2.active = 1 AND aa2.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa2.entity = ".$conf->entity;
+	$sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa3 ON " . $alias_product_perentity . ".accountancy_code_sell_export = aa3.account_number AND aa3.active = 1 AND aa3.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa3.entity = ".$conf->entity;
+	$sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa4 ON " . $alias_societe_perentity . ".accountancy_code_sell = aa4.account_number        AND aa4.active = 1 AND aa4.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa4.entity = ".$conf->entity;
 	$sql .= " WHERE f.fk_statut > 0 AND l.fk_code_ventilation <= 0";
 	$sql .= " AND l.product_type <= 2";
 	$sql .= " AND f.entity IN (".getEntity('invoice', 0).")"; // We don't share object for accountancy
@@ -312,6 +312,10 @@ print '<span class="opacitymedium">'.$langs->trans("DescVentilCustomer").'</span
 print '<span class="opacitymedium hideonsmartphone">'.$langs->trans("DescVentilMore", $langs->transnoentitiesnoconv("ValidateHistory"), $langs->transnoentitiesnoconv("ToBind")).'<br>';
 print '</span><br>';
 
+if (getDolGlobalInt('INVOICE_USE_SITUATION') == 1) {
+	print info_admin($langs->trans("SorryThisModuleIsNotCompatibleWithTheExperimentalFeatureOfSituationInvoices"));
+	print "<br>";
+}
 
 $y = $year_current;
 
@@ -386,6 +390,11 @@ if ($resql) {
 	$num = $db->num_rows($resql);
 
 	while ($row = $db->fetch_row($resql)) {
+		// TODO When INVOICE_USE_SITUATION = 1, values here are wrong. There is no compensation on bad stored amounts
+		//$situation_ratio = 1;
+		//if (getDolGlobalInt('INVOICE_USE_SITUATION') == 1) {
+		//}
+
 		print '<tr class="oddeven">';
 		print '<td>';
 		if ($row[0] == 'tobind') {
@@ -519,6 +528,11 @@ if ($resql) {
 	$num = $db->num_rows($resql);
 
 	while ($row = $db->fetch_row($resql)) {
+		// TODO When INVOICE_USE_SITUATION = 1, values here are wrong. There is no compensation on bad stored amounts
+		//$situation_ratio = 1;
+		//if (getDolGlobalInt('INVOICE_USE_SITUATION') == 1) {
+		//}
+
 		print '<tr class="oddeven">';
 		print '<td>';
 		if ($row[0] == 'tobind') {
@@ -630,7 +644,6 @@ if (getDolGlobalString('SHOW_TOTAL_OF_PREVIOUS_LISTS_IN_LIN_PAGE')) { // This pa
 	print "</table>\n";
 	print '</div>';
 
-
 	if (isModEnabled('margin')) {
 		print "<br>\n";
 		print '<div class="div-table-responsive-no-min">';
@@ -644,22 +657,41 @@ if (getDolGlobalString('SHOW_TOTAL_OF_PREVIOUS_LISTS_IN_LIN_PAGE')) { // This pa
 			print '<td width="60" class="right">'.$langs->trans('MonthShort'.str_pad($j, 2, '0', STR_PAD_LEFT)).'</td>';
 		}
 		print '<td width="60" class="right"><b>'.$langs->trans("Total").'</b></td></tr>';
-		$sql = "SELECT '".$db->escape($langs->trans("Vide"))."' AS marge,";
-		for ($i = 1; $i <= 12; $i++) {
-			$j = $i + ($conf->global->SOCIETE_FISCAL_MONTH_START ? $conf->global->SOCIETE_FISCAL_MONTH_START : 1) - 1;
-			if ($j > 12) {
-				$j -= 12;
+
+		if (getDolGlobalInt('INVOICE_USE_SITUATION') == 1) {
+			// With old situation invoice setup
+			$sql = "SELECT '".$db->escape($langs->trans("Vide"))."' AS marge,";
+			for ($i = 1; $i <= 12; $i++) {
+				$j = $i + ($conf->global->SOCIETE_FISCAL_MONTH_START ? $conf->global->SOCIETE_FISCAL_MONTH_START : 1) - 1;
+				if ($j > 12) {
+					$j -= 12;
+				}
+				$sql .= " SUM(".$db->ifsql("MONTH(f.datef)=".$j,
+							" (".$db->ifsql("fd.total_ht < 0",
+								" (-1 * (abs(fd.total_ht) - (fd.buy_price_ht * fd.qty * (fd.situation_percent / 100))))",	// TODO This is bugged, we must use the percent for the invoice and fd.situation_percent is cumulated percent !
+								"  (fd.total_ht - (fd.buy_price_ht * fd.qty * (fd.situation_percent / 100)))").")",
+							 0).") AS month".str_pad($j, 2, '0', STR_PAD_LEFT).",";
+			}
+			$sql .= "  SUM(".$db->ifsql("fd.total_ht < 0",
+				" (-1 * (abs(fd.total_ht) - (fd.buy_price_ht * fd.qty * (fd.situation_percent / 100))))",	// TODO This is bugged, we must use the percent for the invoice and fd.situation_percent is cumulated percent !
+								"  (fd.total_ht - (fd.buy_price_ht * fd.qty * (fd.situation_percent / 100)))").") as total";
+		} else {
+			$sql = "SELECT '".$db->escape($langs->trans("Vide"))."' AS marge,";
+			for ($i = 1; $i <= 12; $i++) {
+				$j = $i + ($conf->global->SOCIETE_FISCAL_MONTH_START ? $conf->global->SOCIETE_FISCAL_MONTH_START : 1) - 1;
+				if ($j > 12) {
+					$j -= 12;
+				}
+				$sql .= " SUM(".$db->ifsql("MONTH(f.datef)=".$j,
+					" (".$db->ifsql("fd.total_ht < 0",
+						" (-1 * (abs(fd.total_ht) - (fd.buy_price_ht * fd.qty)))",
+						"  (fd.total_ht - (fd.buy_price_ht * fd.qty))").")",
+					0).") AS month".str_pad($j, 2, '0', STR_PAD_LEFT).",";
 			}
-			$sql .= " SUM(".$db->ifsql("MONTH(f.datef)=".$j,
-						" (".$db->ifsql("fd.total_ht < 0",
-							" (-1 * (abs(fd.total_ht) - (fd.buy_price_ht * fd.qty * (fd.situation_percent / 100))))",
-							"  (fd.total_ht - (fd.buy_price_ht * fd.qty * (fd.situation_percent / 100)))").")",
-						 0).") AS month".str_pad($j, 2, '0', STR_PAD_LEFT).",";
+			$sql .= "  SUM(".$db->ifsql("fd.total_ht < 0",
+				" (-1 * (abs(fd.total_ht) - (fd.buy_price_ht * fd.qty)))",
+				"  (fd.total_ht - (fd.buy_price_ht * fd.qty))").") as total";
 		}
-		$sql .= "  SUM(".$db->ifsql("fd.total_ht < 0",
-							" (-1 * (abs(fd.total_ht) - (fd.buy_price_ht * fd.qty * (fd.situation_percent / 100))))",
-							"  (fd.total_ht - (fd.buy_price_ht * fd.qty * (fd.situation_percent / 100)))").") as total";
-
 		$sql .= " FROM ".MAIN_DB_PREFIX."facturedet as fd";
 		$sql .= "  LEFT JOIN ".MAIN_DB_PREFIX."facture as f ON f.rowid = fd.fk_facture";
 		$sql .= " WHERE f.datef >= '".$db->idate($search_date_start)."'";

+ 40 - 7
htdocs/accountancy/customer/lines.php

@@ -65,10 +65,10 @@ $search_country = GETPOST('search_country', 'alpha');
 $search_tvaintra = GETPOST('search_tvaintra', 'alpha');
 
 // 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);
+$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('pageplusonPour le détail de la facture ref…e') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int');
+$page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int');
 if (empty($page) || $page < 0) {
 	$page = 0;
 }
@@ -81,6 +81,8 @@ if (!$sortfield) {
 if (!$sortorder) {
 	if ($conf->global->ACCOUNTING_LIST_SORT_VENTILATION_DONE > 0) {
 		$sortorder = "DESC";
+	} else {
+		$sortorder = "ASC";
 	}
 }
 
@@ -159,6 +161,12 @@ if (is_array($changeaccount) && count($changeaccount) > 0 && $user->hasRight('ac
 	}
 }
 
+if (GETPOST('sortfield') == 'f.datef, f.ref, fd.rowid') {
+	$value = (GETPOST('sortorder') == 'asc,asc,asc' ? 0 : 1);
+	require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
+	$res = dolibarr_set_const($db, "ACCOUNTING_LIST_SORT_VENTILATION_DONE", $value, 'yesno', 0, '', $conf->entity);
+}
+
 
 /*
  * View
@@ -189,8 +197,8 @@ print '<script type="text/javascript">
 /*
  * Customer Invoice lines
  */
-$sql = "SELECT f.rowid as facid, f.ref as ref, f.type as ftype, f.datef, f.ref_client,";
-$sql .= " fd.rowid, fd.description, fd.product_type as line_type, fd.total_ht, fd.total_tva, fd.tva_tx, fd.vat_src_code, fd.total_ttc,";
+$sql = "SELECT f.rowid as facid, f.ref as ref, f.type as ftype, f.situation_cycle_ref, f.datef, f.ref_client,";
+$sql .= " fd.rowid, fd.description, fd.product_type as line_type, fd.total_ht, fd.total_tva, fd.tva_tx, fd.vat_src_code, fd.total_ttc, fd.situation_percent,";
 $sql .= " s.rowid as socid, s.nom as name, s.code_client,";
 if (!empty($conf->global->MAIN_COMPANY_PERENTITY_SHARED)) {
 	$sql .= " spe.accountancy_code_customer as code_compta_client,";
@@ -295,7 +303,7 @@ $sql .= $db->order($sortfield, $sortorder);
 
 // Count total nb of records
 $nbtotalofrecords = '';
-if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
+if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
 	$result = $db->query($sql);
 	$nbtotalofrecords = $db->num_rows($result);
 	if (($page * $limit) > $nbtotalofrecords) {	// if total resultset is smaller then paging size (filtering), goto and load page 0
@@ -317,7 +325,7 @@ if ($result) {
 		$param .= '&contextpage='.urlencode($contextpage);
 	}
 	if ($limit > 0 && $limit != $conf->liste_limit) {
-		$param .= '&limit='.urlencode($limit);
+		$param .= '&limit='.((int) $limit);
 	}
 	if ($search_societe) {
 		$param .= "&search_societe=".urlencode($search_societe);
@@ -502,8 +510,33 @@ if ($result) {
 		print $form->textwithtooltip(dol_trunc($text, $trunclength), $objp->description);
 		print '</td>';
 
-		print '<td class="right nowraponall amount">'.price($objp->total_ht).'</td>';
+		// Amount
+		print '<td class="right nowraponall amount">';
+
+		// Create a compensation rate for old situation invoice feature.
+		$situation_ratio = 1;
+		if (getDolGlobalInt('INVOICE_USE_SITUATION') == 1) {
+			if ($objp->situation_cycle_ref) {
+				// Avoid divide by 0
+				if ($objp->situation_percent == 0) {
+					$situation_ratio = 0;
+				} else {
+					$line = new FactureLigne($db);
+					$line->fetch($objp->rowid);
+
+					// Situation invoices handling
+					$prev_progress = $line->get_prev_progress($objp->facid);
+
+					$situation_ratio = ($objp->situation_percent - $prev_progress) / $objp->situation_percent;
+				}
+			}
+			print price($objp->total_ht * $situation_ratio);
+		} else {
+			print price($objp->total_ht);
+		}
+		print '</td>';
 
+		// Vat rate
 		print '<td class="right">'.vatrate($objp->tva_tx.($objp->vat_src_code ? ' ('.$objp->vat_src_code.')' : '')).'</td>';
 
 		// Thirdparty

+ 35 - 6
htdocs/accountancy/customer/list.php

@@ -91,6 +91,8 @@ if (!$sortfield) {
 if (!$sortorder) {
 	if ($conf->global->ACCOUNTING_LIST_SORT_VENTILATION_TODO > 0) {
 		$sortorder = "DESC";
+	} else {
+		$sortorder = "ASC";
 	}
 }
 
@@ -100,7 +102,7 @@ $hookmanager->initHooks(array('accountancycustomerlist'));
 $formaccounting = new FormAccounting($db);
 $accountingAccount = new AccountingAccount($db);
 
-$chartaccountcode = dol_getIdFromCode($db, $conf->global->CHARTOFACCOUNTS, 'accounting_system', 'rowid', 'pcg_version');
+$chartaccountcode = dol_getIdFromCode($db, getDolGlobalInt('CHARTOFACCOUNTS'), 'accounting_system', 'rowid', 'pcg_version');
 
 // Security check
 if (!isModEnabled('accounting')) {
@@ -208,6 +210,11 @@ if ($massaction == 'ventil' && $user->rights->accounting->bind->write) {
 	}
 }
 
+if (GETPOST('sortfield') == 'f.datef, f.ref, l.rowid') {
+	$value = (GETPOST('sortorder') == 'asc,asc,asc' ? 0 : 1);
+	require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
+	$res = dolibarr_set_const($db, "ACCOUNTING_LIST_SORT_VENTILATION_TODO", $value, 'yesno', 0, '', $conf->entity);
+}
 
 
 /*
@@ -228,8 +235,8 @@ if (empty($chartaccountcode)) {
 }
 
 // Customer Invoice lines
-$sql = "SELECT f.rowid as facid, f.ref, f.datef, f.type as ftype, f.fk_facture_source,";
-$sql .= " l.rowid, l.fk_product, l.description, l.total_ht, l.fk_code_ventilation, l.product_type as type_l, l.tva_tx as tva_tx_line, l.vat_src_code,";
+$sql = "SELECT f.rowid as facid, f.ref, f.datef, f.type as ftype, f.situation_cycle_ref, f.fk_facture_source,";
+$sql .= " l.rowid, l.fk_product, l.description, l.total_ht, l.situation_percent, l.fk_code_ventilation, l.product_type as type_l, l.tva_tx as tva_tx_line, l.vat_src_code,";
 $sql .= " p.rowid as product_id, p.ref as product_ref, p.label as product_label, p.fk_product_type as type, p.tva_tx as tva_tx_prod,";
 if (!empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
 	$sql .= " ppe.accountancy_code_sell as code_sell, ppe.accountancy_code_sell_intra as code_sell_intra, ppe.accountancy_code_sell_export as code_sell_export,";
@@ -352,7 +359,7 @@ $sql .= $db->order($sortfield, $sortorder);
 
 // Count total nb of records
 $nbtotalofrecords = '';
-if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
+if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
 	$result = $db->query($sql);
 	$nbtotalofrecords = $db->num_rows($result);
 	if (($page * $limit) > $nbtotalofrecords) {	// if total resultset is smaller then paging size (filtering), goto and load page 0
@@ -382,7 +389,7 @@ if ($result) {
 		$param .= '&contextpage='.urlencode($contextpage);
 	}
 	if ($limit > 0 && $limit != $conf->liste_limit) {
-		$param .= '&limit='.urlencode($limit);
+		$param .= '&limit='.((int) $limit);
 	}
 	if ($search_societe) {
 		$param .= '&search_societe='.urlencode($search_societe);
@@ -655,8 +662,30 @@ if ($result) {
 		print $form->textwithtooltip(dol_trunc($text, $trunclength), $facture_static_det->desc);
 		print '</td>';
 
+		// Amount
 		print '<td class="right nowraponall amount">';
-		print price($objp->total_ht);
+
+		// Create a compensation rate for old situation invoice feature.
+		$situation_ratio = 1;
+		if (getDolGlobalInt('INVOICE_USE_SITUATION') == 1) {
+			if ($objp->situation_cycle_ref) {
+				// Avoid divide by 0
+				if ($objp->situation_percent == 0) {
+					$situation_ratio = 0;
+				} else {
+					$line = new FactureLigne($db);
+					$line->fetch($objp->rowid);
+
+					// Situation invoices handling
+					$prev_progress = $line->get_prev_progress($objp->facid);
+
+					$situation_ratio = ($objp->situation_percent - $prev_progress) / $objp->situation_percent;
+				}
+			}
+			print price($objp->total_ht * $situation_ratio);
+		} else {
+			print price($objp->total_ht);
+		}
 		print '</td>';
 
 		// Vat rate

+ 3 - 3
htdocs/accountancy/expensereport/index.php

@@ -58,7 +58,7 @@ $year_current = $year_start;
 // Validate History
 $action = GETPOST('action', 'aZ09');
 
-$chartaccountcode = dol_getIdFromCode($db, $conf->global->CHARTOFACCOUNTS, 'accounting_system', 'rowid', 'pcg_version');
+$chartaccountcode = dol_getIdFromCode($db, getDolGlobalInt('CHARTOFACCOUNTS'), 'accounting_system', 'rowid', 'pcg_version');
 
 // Security check
 if (!isModEnabled('accounting')) {
@@ -85,7 +85,7 @@ if (($action == 'clean' || $action == 'validatehistory') && $user->hasRight('acc
 	$sql1 .= '	(SELECT accnt.rowid ';
 	$sql1 .= '	FROM '.MAIN_DB_PREFIX.'accounting_account as accnt';
 	$sql1 .= '	INNER JOIN '.MAIN_DB_PREFIX.'accounting_system as syst';
-	$sql1 .= '	ON accnt.fk_pcg_version = syst.pcg_version AND syst.rowid='.((int) $conf->global->CHARTOFACCOUNTS).' AND accnt.entity = '.((int) $conf->entity).')';
+	$sql1 .= '	ON accnt.fk_pcg_version = syst.pcg_version AND syst.rowid='.((int) getDolGlobalInt('CHARTOFACCOUNTS')).' AND accnt.entity = '.((int) $conf->entity).')';
 	$sql1 .= ' AND erd.fk_expensereport IN (SELECT rowid FROM '.MAIN_DB_PREFIX.'expensereport WHERE entity = '.((int) $conf->entity).')';
 	$sql1 .= ' AND fk_code_ventilation <> 0';
 	dol_syslog("htdocs/accountancy/customer/index.php fixaccountancycode", LOG_DEBUG);
@@ -112,7 +112,7 @@ if ($action == 'validatehistory') {
 	$sql1 = "SELECT erd.rowid, accnt.rowid as suggestedid";
 	$sql1 .= " FROM ".MAIN_DB_PREFIX."expensereport_det as erd";
 	$sql1 .= " LEFT JOIN ".MAIN_DB_PREFIX."c_type_fees as t ON erd.fk_c_type_fees = t.id";
-	$sql1 .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as accnt ON t.accountancy_code = accnt.account_number AND accnt.active = 1 AND accnt.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND accnt.entity =".((int) $conf->entity);
+	$sql1 .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as accnt ON t.accountancy_code = accnt.account_number AND accnt.active = 1 AND accnt.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND accnt.entity =".((int) $conf->entity).",";
 	$sql1 .= " ".MAIN_DB_PREFIX."expensereport as er";
 	$sql1 .= " WHERE erd.fk_expensereport = er.rowid AND er.entity = ".((int) $conf->entity);
 	$sql1 .= " AND er.fk_statut IN (".ExpenseReport::STATUS_APPROVED.", ".ExpenseReport::STATUS_CLOSED.") AND erd.fk_code_ventilation <= 0";

+ 10 - 2
htdocs/accountancy/expensereport/lines.php

@@ -75,6 +75,8 @@ if (!$sortfield) {
 if (!$sortorder) {
 	if ($conf->global->ACCOUNTING_LIST_SORT_VENTILATION_DONE > 0) {
 		$sortorder = "DESC";
+	} else {
+		$sortorder = "ASC";
 	}
 }
 
@@ -149,6 +151,12 @@ if (is_array($changeaccount) && count($changeaccount) > 0 && $user->rights->acco
 	}
 }
 
+if (GETPOST('sortfield') == 'erd.date, erd.rowid') {
+	$value = (GETPOST('sortorder') == 'asc,asc' ? 0 : 1);
+	require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
+	$res = dolibarr_set_const($db, "ACCOUNTING_LIST_SORT_VENTILATION_DONE", $value, 'yesno', 0, '', $conf->entity);
+}
+
 
 /*
  * View
@@ -226,7 +234,7 @@ $sql .= $db->order($sortfield, $sortorder);
 
 // Count total nb of records
 $nbtotalofrecords = '';
-if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
+if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
 	$result = $db->query($sql);
 	$nbtotalofrecords = $db->num_rows($result);
 	if (($page * $limit) > $nbtotalofrecords) {	// if total resultset is smaller then paging size (filtering), goto and load page 0
@@ -248,7 +256,7 @@ if ($result) {
 		$param .= '&contextpage='.urlencode($contextpage);
 	}
 	if ($limit > 0 && $limit != $conf->liste_limit) {
-		$param .= '&limit='.urlencode($limit);
+		$param .= '&limit='.((int) $limit);
 	}
 	if ($search_login) {
 		$param .= '&search_login='.urlencode($search_login);

+ 13 - 7
htdocs/accountancy/expensereport/list.php

@@ -84,6 +84,8 @@ if (!$sortfield) {
 if (!$sortorder) {
 	if ($conf->global->ACCOUNTING_LIST_SORT_VENTILATION_TODO > 0) {
 		$sortorder = "DESC";
+	} else {
+		$sortorder = "ASC";
 	}
 }
 
@@ -93,7 +95,7 @@ $hookmanager->initHooks(array('accountancyexpensereportlist'));
 $formaccounting = new FormAccounting($db);
 $accounting = new AccountingAccount($db);
 
-$chartaccountcode = dol_getIdFromCode($db, $conf->global->CHARTOFACCOUNTS, 'accounting_system', 'rowid', 'pcg_version');
+$chartaccountcode = dol_getIdFromCode($db, getDolGlobalInt('CHARTOFACCOUNTS'), 'accounting_system', 'rowid', 'pcg_version');
 
 // Security check
 if (!isModEnabled('accounting')) {
@@ -159,7 +161,6 @@ if (empty($reshook)) {
 if ($massaction == 'ventil' && $user->rights->accounting->bind->write) {
 	$msg = '';
 
-	//print '<div><span style="color:red">' . $langs->trans("Processing") . '...</span></div>';
 	if (!empty($mesCasesCochees)) {
 		$msg = '<div>'.$langs->trans("SelectedLines").': '.count($mesCasesCochees).'</div>';
 		$msg .= '<div class="detail">';
@@ -173,7 +174,7 @@ if ($massaction == 'ventil' && $user->rights->accounting->bind->write) {
 			$monCompte = GETPOST('codeventil'.$monId);
 
 			if ($monCompte <= 0) {
-				$msg .= '<div><span style="color:red">'.$langs->trans("Lineofinvoice").' '.$monId.' - '.$langs->trans("NoAccountSelected").'</span></div>';
+				$msg .= '<div><span class="error">'.$langs->trans("Lineofinvoice").' '.$monId.' - '.$langs->trans("NoAccountSelected").'</span></div>';
 				$ko++;
 			} else {
 				$sql = " UPDATE ".MAIN_DB_PREFIX."expensereport_det";
@@ -185,10 +186,10 @@ if ($massaction == 'ventil' && $user->rights->accounting->bind->write) {
 
 				dol_syslog('accountancy/expensereport/list.php:: sql='.$sql, LOG_DEBUG);
 				if ($db->query($sql)) {
-					$msg .= '<div><span style="color:green">'.$langs->trans("LineOfExpenseReport").' '.$monId.' - '.$langs->trans("VentilatedinAccount").' : '.length_accountg($accountventilated->account_number).'</span></div>';
+					$msg .= '<div><span class="green">'.$langs->trans("LineOfExpenseReport").' '.$monId.' - '.$langs->trans("VentilatedinAccount").' : '.length_accountg($accountventilated->account_number).'</span></div>';
 					$ok++;
 				} else {
-					$msg .= '<div><span style="color:red">'.$langs->trans("ErrorDB").' : '.$langs->trans("Lineofinvoice").' '.$monId.' - '.$langs->trans("NotVentilatedinAccount").' : '.length_accountg($accountventilated->account_number).'<br> <pre>'.$sql.'</pre></span></div>';
+					$msg .= '<div><span class="error">'.$langs->trans("ErrorDB").' : '.$langs->trans("Lineofinvoice").' '.$monId.' - '.$langs->trans("NotVentilatedinAccount").' : '.length_accountg($accountventilated->account_number).'<br> <pre>'.$sql.'</pre></span></div>';
 					$ko++;
 				}
 			}
@@ -200,6 +201,11 @@ if ($massaction == 'ventil' && $user->rights->accounting->bind->write) {
 	}
 }
 
+if (GETPOST('sortfield') == 'erd.date, erd.rowid') {
+	$value = (GETPOST('sortorder') == 'asc,asc' ? 0 : 1);
+	require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
+	$res = dolibarr_set_const($db, "ACCOUNTING_LIST_SORT_VENTILATION_TODO", $value, 'yesno', 0, '', $conf->entity);
+}
 
 
 /*
@@ -277,7 +283,7 @@ $sql .= $db->order($sortfield, $sortorder);
 
 // Count total nb of records
 $nbtotalofrecords = '';
-if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
+if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
 	$result = $db->query($sql);
 	$nbtotalofrecords = $db->num_rows($result);
 	if (($page * $limit) > $nbtotalofrecords) {	// if total resultset is smaller then paging size (filtering), goto and load page 0
@@ -307,7 +313,7 @@ if ($result) {
 		$param .= '&contextpage='.urlencode($contextpage);
 	}
 	if ($limit > 0 && $limit != $conf->liste_limit) {
-		$param .= '&limit='.urlencode($limit);
+		$param .= '&limit='.((int) $limit);
 	}
 	if ($search_login) {
 		$param .= '&search_login='.urlencode($search_login);

+ 2 - 6
htdocs/accountancy/index.php

@@ -83,12 +83,7 @@ $help_url = 'EN:Module_Double_Entry_Accounting#Setup';
 
 llxHeader('', $langs->trans("AccountancyArea"), $help_url);
 
-if (!empty($conf->global->INVOICE_USE_SITUATION) && $conf->global->INVOICE_USE_SITUATION == 1) {
-	print load_fiche_titre($langs->trans("AccountancyArea"), '', 'accountancy');
-
-	print '<span class="opacitymedium">'.$langs->trans("SorryThisModuleIsNotCompatibleWithTheExperimentalFeatureOfSituationInvoices")."</span>\n";
-	print "<br>";
-} elseif (isModEnabled('accounting')) {
+if (isModEnabled('accounting')) {
 	$step = 0;
 
 	$resultboxes = FormOther::getBoxesArea($user, "27"); // Load $resultboxes (selectboxlist + boxactivated + boxlista + boxlistb)
@@ -124,6 +119,7 @@ if (!empty($conf->global->INVOICE_USE_SITUATION) && $conf->global->INVOICE_USE_S
 	print "<br>\n";
 	print '<span class="opacitymedium">'.$langs->trans("AccountancyAreaDescIntro")."</span><br>\n";
 	if ($user->hasRight('accounting', 'chartofaccount')) {
+		print '<br>';
 		print load_fiche_titre('<span class="fa fa-calendar-check-o"></span> '.$langs->trans("AccountancyAreaDescActionOnce"), '', '')."\n";
 		print '<hr>';
 		print "<br>\n";

+ 40 - 23
htdocs/accountancy/journal/bankjournal.php

@@ -7,7 +7,7 @@
  * Copyright (C) 2013-2022  Open-DSI      			<support@open-dsi.fr>
  * Copyright (C) 2013-2014  Florian Henry           <florian.henry@open-concept.pro>
  * Copyright (C) 2013-2014  Olivier Geffroy         <jeff@jeffinfo.com>
- * Copyright (C) 2017-2021  Frédéric France         <frederic.france@netlogic.fr>
+ * Copyright (C) 2017-2023  Frédéric France         <frederic.france@netlogic.fr>
  * Copyright (C) 2018		Ferran Marcet	        <fmarcet@2byte.es>
  * Copyright (C) 2018		Eric Seigne	            <eric.seigne@cap-rel.fr>
  * Copyright (C) 2021		Gauthier VERDOL         <gauthier.verdol@atm-consulting.fr>
@@ -147,8 +147,8 @@ if ($date_start && $date_end) {
 	$sql .= " AND b.dateo >= '".$db->idate($date_start)."' AND b.dateo <= '".$db->idate($date_end)."'";
 }
 // Define begin binding date
-if (!empty($conf->global->ACCOUNTING_DATE_START_BINDING)) {
-	$sql .= " AND b.dateo >= '".$db->idate($conf->global->ACCOUNTING_DATE_START_BINDING)."'";
+if (getDolGlobalInt('ACCOUNTING_DATE_START_BINDING')) {
+	$sql .= " AND b.dateo >= '".$db->idate(getDolGlobalInt('ACCOUNTING_DATE_START_BINDING'))."'";
 }
 // Already in bookkeeping or not
 if ($in_bookkeeping == 'already') {
@@ -195,13 +195,13 @@ if ($result) {
 	//print $sql;
 
 	// Variables
-	$account_supplier = (($conf->global->ACCOUNTING_ACCOUNT_SUPPLIER != "") ? $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER : 'NotDefined'); // NotDefined is a reserved word
-	$account_customer = (($conf->global->ACCOUNTING_ACCOUNT_CUSTOMER != "") ? $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER : 'NotDefined'); // NotDefined is a reserved word
-	$account_employee = (!empty($conf->global->SALARIES_ACCOUNTING_ACCOUNT_PAYMENT) ? $conf->global->SALARIES_ACCOUNTING_ACCOUNT_PAYMENT : 'NotDefined'); // NotDefined is a reserved word
-	$account_pay_vat = (!empty($conf->global->ACCOUNTING_VAT_PAY_ACCOUNT) ? $conf->global->ACCOUNTING_VAT_PAY_ACCOUNT : 'NotDefined'); // NotDefined is a reserved word
-	$account_pay_donation = (!empty($conf->global->DONATION_ACCOUNTINGACCOUNT) ? $conf->global->DONATION_ACCOUNTINGACCOUNT : 'NotDefined'); // NotDefined is a reserved word
-	$account_pay_subscription = (!empty($conf->global->ADHERENT_SUBSCRIPTION_ACCOUNTINGACCOUNT) ? $conf->global->ADHERENT_SUBSCRIPTION_ACCOUNTINGACCOUNT : 'NotDefined'); // NotDefined is a reserved word
-	$account_transfer = (!empty($conf->global->ACCOUNTING_ACCOUNT_TRANSFER_CASH) ? $conf->global->ACCOUNTING_ACCOUNT_TRANSFER_CASH : 'NotDefined'); // NotDefined is a reserved word
+	$account_supplier = getDolGlobalString('ACCOUNTING_ACCOUNT_SUPPLIER', 'NotDefined'); // NotDefined is a reserved word
+	$account_customer = getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER', 'NotDefined'); // NotDefined is a reserved word
+	$account_employee = getDolGlobalString('SALARIES_ACCOUNTING_ACCOUNT_PAYMENT', 'NotDefined'); // NotDefined is a reserved word
+	$account_pay_vat = getDolGlobalString('ACCOUNTING_VAT_PAY_ACCOUNT', 'NotDefined'); // NotDefined is a reserved word
+	$account_pay_donation = getDolGlobalString('DONATION_ACCOUNTINGACCOUNT', 'NotDefined'); // NotDefined is a reserved word
+	$account_pay_subscription = getDolGlobalString('ADHERENT_SUBSCRIPTION_ACCOUNTINGACCOUNT', 'NotDefined'); // NotDefined is a reserved word
+	$account_transfer = getDolGlobalString('ACCOUNTING_ACCOUNT_TRANSFER_CASH', 'NotDefined'); // NotDefined is a reserved word
 
 	$tabcompany = array();
 	$tabuser = array();
@@ -434,7 +434,7 @@ if ($result) {
 					$tabpay[$obj->rowid]["paymentsalid"] = $paymentsalstatic->id;
 
 					// This part of code is no more required. it is here to solve case where a link were missing (ith v14.0.0) and keep writing in accountancy complete.
-					// Note: A better way to fix this is to delete payement of salary and recreate it, or to fix the bookkeeping table manually after.
+					// Note: A better way to fix this is to delete payment of salary and recreate it, or to fix the bookkeeping table manually after.
 					if (!empty($conf->global->ACCOUNTANCY_AUTOFIX_MISSING_LINK_TO_USEr_ON_SALARY_BANK_PAYMENT)) {
 						$tmpsalary = new Salary($db);
 						$tmpsalary->fetch($paymentsalstatic->id);
@@ -805,6 +805,12 @@ if (!$error && $action == 'writebookkeeping') {
 								require_once DOL_DOCUMENT_ROOT . '/accountancy/class/lettering.class.php';
 								$lettering_static = new Lettering($db);
 								$nb_lettering = $lettering_static->bookkeepingLetteringAll(array($bookkeeping->id));
+
+								if ($nb_lettering < 0) {
+									$error++;
+									$errorforline++;
+									setEventMessages($lettering_static->error, $lettering_static->errors, 'errors');
+								}
 							}
 						}
 					}
@@ -906,7 +912,7 @@ if (!$error && $action == 'writebookkeeping') {
 
 // Export
 if ($action == 'exportcsv') {		// ISO and not UTF8 !
-	$sep = $conf->global->ACCOUNTING_EXPORT_SEPARATORCSV;
+	$sep = getDolGlobalString('ACCOUNTING_EXPORT_SEPARATORCSV');
 
 	$filename = 'journal';
 	$type_export = 'journal';
@@ -977,13 +983,13 @@ if ($action == 'exportcsv') {		// ISO and not UTF8 !
 					print '"'.$val["type_payment"].'"'.$sep;
 					print '"'.length_accountg(html_entity_decode($k)).'"'.$sep;
 					if ($tabtype[$key] == 'payment_supplier') {
-						print '"'.$conf->global->ACCOUNTING_ACCOUNT_SUPPLIER.'"'.$sep;
+						print '"'.getDolGlobalString('ACCOUNTING_ACCOUNT_SUPPLIER').'"'.$sep;
 					} elseif ($tabtype[$key] == 'payment') {
-						print '"'.$conf->global->ACCOUNTING_ACCOUNT_CUSTOMER.'"'.$sep;
+						print '"'.getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER').'"'.$sep;
 					} elseif ($tabtype[$key] == 'payment_expensereport') {
-						print '"'.$conf->global->SALARIES_ACCOUNTING_ACCOUNT_PAYMENT.'"'.$sep;
+						print '"'.getDolGlobalString('SALARIES_ACCOUNTING_ACCOUNT_PAYMENT').'"'.$sep;
 					} elseif ($tabtype[$key] == 'payment_salary') {
-						print '"'.$conf->global->SALARIES_ACCOUNTING_ACCOUNT_PAYMENT.'"'.$sep;
+						print '"'.getDolGlobalString('SALARIES_ACCOUNTING_ACCOUNT_PAYMENT').'"'.$sep;
 					} else {
 						print '"'.length_accountg(html_entity_decode($k)).'"'.$sep;
 					}
@@ -1008,8 +1014,8 @@ if ($action == 'exportcsv') {		// ISO and not UTF8 !
 					print '"'.$key.'"'.$sep;
 					print '"'.$date.'"'.$sep;
 					print '"'.$val["type_payment"].'"'.$sep;
-					print '"'.length_accountg($conf->global->ACCOUNTING_ACCOUNT_SUSPENSE).'"'.$sep;
-					print '"'.length_accounta($conf->global->ACCOUNTING_ACCOUNT_SUSPENSE).'"'.$sep;
+					print '"'.length_accountg(getDolGlobalString('ACCOUNTING_ACCOUNT_SUSPENSE')).'"'.$sep;
+					print '"'.length_accounta(getDolGlobalString('ACCOUNTING_ACCOUNT_SUSPENSE')).'"'.$sep;
 					print "".$sep;
 					print '"'.$reflabel.'"'.$sep;
 					print '"'.($mt < 0 ? price(-$mt) : '').'"'.$sep;
@@ -1082,9 +1088,9 @@ if (empty($action) || $action == 'view') {
 
 
 	// Button to write into Ledger
-	if (($conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == "") || $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == '-1'
-		|| ($conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == "") || $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == '-1'
-		|| empty($conf->global->SALARIES_ACCOUNTING_ACCOUNT_PAYMENT) || $conf->global->SALARIES_ACCOUNTING_ACCOUNT_PAYMENT == '-1') {
+	if (getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER') == "" || getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER') == '-1'
+		|| getDolGlobalString('ACCOUNTING_ACCOUNT_SUPPLIER') == "" || getDolGlobalString('ACCOUNTING_ACCOUNT_SUPPLIER') == '-1'
+		|| getDolGlobalString('SALARIES_ACCOUNTING_ACCOUNT_PAYMENT') == "" || getDolGlobalString('SALARIES_ACCOUNTING_ACCOUNT_PAYMENT') == '-1') {
 		print ($desc ? '' : '<br>').'<div class="warning">'.img_warning().' '.$langs->trans("SomeMandatoryStepsOfSetupWereNotDone");
 		$desc = ' : '.$langs->trans("AccountancyAreaDescMisc", 4, '{link}');
 		$desc = str_replace('{link}', '<strong>'.$langs->transnoentitiesnoconv("MenuAccountancy").'-'.$langs->transnoentitiesnoconv("Setup")."-".$langs->transnoentitiesnoconv("MenuDefaultAccounts").'</strong>', $desc);
@@ -1099,8 +1105,8 @@ if (empty($action) || $action == 'view') {
 		print '<input type="button" class="butAction" name="exportcsv" value="'.$langs->trans("ExportDraftJournal").'" onclick="launch_export();" />';
 	}
 
-	if (($conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == "") || $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == '-1'
-		|| ($conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == "") || $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == '-1') {
+	if (getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER') == "" || getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER') == '-1'
+		|| getDolGlobalString('ACCOUNTING_ACCOUNT_SUPPLIER') == "" || getDolGlobalString('ACCOUNTING_ACCOUNT_SUPPLIER') == '-1') {
 		print '<input type="button" class="butActionRefused classfortooltip" title="'.dol_escape_htmltag($langs->trans("SomeMandatoryStepsOfSetupWereNotDone")).'" value="'.$langs->trans("WriteBookKeeping").'" />';
 	} else {
 		if ($in_bookkeeping == 'notyet') {
@@ -1205,6 +1211,8 @@ if (empty($action) || $action == 'view') {
 				print '<td class="right nowraponall amount">'.($mt >= 0 ? price($mt) : '')."</td>";
 				print '<td class="right nowraponall amount">'.($mt < 0 ? price(-$mt) : '')."</td>";
 				print "</tr>";
+
+				$i++;
 			}
 		}
 
@@ -1330,6 +1338,8 @@ if (empty($action) || $action == 'view') {
 					print '<td class="right nowraponall amount">'.($mt >= 0 ? price($mt) : '')."</td>";
 
 					print "</tr>";
+
+					$i++;
 				}
 			}
 		} else {	// Waiting account
@@ -1367,11 +1377,18 @@ if (empty($action) || $action == 'view') {
 					print '<td class="right nowraponall amount">'.($mt < 0 ? price(-$mt) : '')."</td>";
 					print '<td class="right nowraponall amount">'.($mt >= 0 ? price($mt) : '')."</td>";
 					print "</tr>";
+
+					$i++;
 				}
 			}
 		}
 	}
 
+	if (!$i) {
+		$colspan = 8;
+		print '<tr class="oddeven"><td colspan="'.$colspan.'"><span class="opacitymedium">'.$langs->trans("NoRecordFound").'</span></td></tr>';
+	}
+
 	print "</table>";
 	print '</div>';
 

+ 11 - 0
htdocs/accountancy/journal/expensereportsjournal.php

@@ -615,6 +615,8 @@ if (empty($action) || $action == 'view') {
 				print '<td class="right nowraponall amount">'.($mt >= 0 ? price($mt) : '')."</td>";
 				print '<td class="right nowraponall amount">'.($mt < 0 ? price(-$mt) : '')."</td>";
 				print "</tr>";
+
+				$i++;
 			}
 		}
 
@@ -649,6 +651,8 @@ if (empty($action) || $action == 'view') {
 			print '<td class="right nowraponall amount">'.($mt < 0 ? price(-$mt) : '')."</td>";
 			print '<td class="right nowraponall amount">'.($mt >= 0 ? price($mt) : '')."</td>";
 			print "</tr>";
+
+			$i++;
 		}
 
 		// VAT
@@ -685,11 +689,18 @@ if (empty($action) || $action == 'view') {
 					print '<td class="right nowraponall amount">'.($mt >= 0 ? price($mt) : '')."</td>";
 					print '<td class="right nowraponall amount">'.($mt < 0 ? price(-$mt) : '')."</td>";
 					print "</tr>";
+
+					$i++;
 				}
 			}
 		}
 	}
 
+	if (!$i) {
+		$colspan = 7;
+		print '<tr class="oddeven"><td colspan="'.$colspan.'"><span class="opacitymedium">'.$langs->trans("NoRecordFound").'</span></td></tr>';
+	}
+
 	print "</table>";
 	print '</div>';
 

+ 156 - 9
htdocs/accountancy/journal/purchasesjournal.php

@@ -3,7 +3,7 @@
  * Copyright (C) 2007-2010  Jean Heimburger         <jean@tiaris.info>
  * Copyright (C) 2011       Juanjo Menent           <jmenent@2byte.es>
  * Copyright (C) 2012       Regis Houssin           <regis.houssin@inodbox.com>
- * Copyright (C) 2013-2021  Alexandre Spangaro      <aspangaro@open-dsi.fr>
+ * Copyright (C) 2013-2023  Alexandre Spangaro      <aspangaro@open-dsi.fr>
  * Copyright (C) 2013-2016  Olivier Geffroy         <jeff@jeffinfo.com>
  * Copyright (C) 2013-2016  Florian Henry           <florian.henry@open-concept.pro>
  * Copyright (C) 2018       Frédéric France         <frederic.france@netlogic.fr>
@@ -32,6 +32,7 @@ require '../../main.inc.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/report.lib.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
 require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingjournal.class.php';
 require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
@@ -103,9 +104,11 @@ if (!GETPOSTISSET('date_startmonth') && (empty($date_start) || empty($date_end))
 	$date_end = dol_get_last_day($pastmonthyear, $pastmonth, false);
 }
 
-$sql = "SELECT f.rowid, f.ref as ref, f.type, f.datef as df, f.libelle,f.ref_supplier, f.date_lim_reglement as dlr, f.close_code,";
-$sql .= " fd.rowid as fdid, fd.description, fd.product_type, fd.total_ht, fd.tva as total_tva, fd.total_localtax1, fd.total_localtax2, fd.tva_tx, fd.total_ttc, fd.vat_src_code,";
-$sql .= " s.rowid as socid, s.nom as name, s.fournisseur, s.code_client, s.code_fournisseur,";
+$sql = "SELECT f.rowid, f.ref as ref, f.type, f.datef as df, f.libelle as label, f.ref_supplier, f.date_lim_reglement as dlr, f.close_code, f.vat_reverse_charge,";
+$sql .= " fd.rowid as fdid, fd.description, fd.product_type, fd.total_ht, fd.tva as total_tva, fd.total_localtax1, fd.total_localtax2, fd.tva_tx, fd.total_ttc, fd.vat_src_code, fd.info_bits,";
+$sql .= " p.default_vat_code AS product_buy_default_vat_code, p.tva_tx as product_buy_vat, p.localtax1_tx as product_buy_localvat1, p.localtax2_tx as product_buy_localvat2,";
+$sql .= " co.code as country_code, co.label as country_label,";
+$sql .= " s.rowid as socid, s.nom as name, s.fournisseur, s.code_client, s.code_fournisseur, s.fk_pays,";
 if (!empty($conf->global->MAIN_COMPANY_PERENTITY_SHARED)) {
 	$sql .= " spe.accountancy_code_customer as code_compta,";
 	$sql .= " spe.accountancy_code_supplier as code_compta_fournisseur,";
@@ -127,6 +130,7 @@ if (!empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa ON aa.rowid = fd.fk_code_ventilation";
 $sql .= " JOIN ".MAIN_DB_PREFIX."facture_fourn as f ON f.rowid = fd.fk_facture_fourn";
 $sql .= " JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = f.fk_soc";
+$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_country as co ON co.rowid = s.fk_pays ";
 if (!empty($conf->global->MAIN_COMPANY_PERENTITY_SHARED)) {
 	$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_perentity as spe ON spe.fk_soc = s.rowid AND spe.entity = " . ((int) $conf->entity);
 }
@@ -166,12 +170,18 @@ if ($result) {
 	$tablocaltax2 = array();
 	$tabcompany = array();
 	$tabother = array();
+	$tabrctva = array();
+	$tabrclocaltax1 = array();
+	$tabrclocaltax2 = array();
 
 	$num = $db->num_rows($result);
 
 	// Variables
 	$cptfour = ($conf->global->ACCOUNTING_ACCOUNT_SUPPLIER != "") ? $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER : 'NotDefined';
 	$cpttva = (!empty($conf->global->ACCOUNTING_VAT_BUY_ACCOUNT)) ? $conf->global->ACCOUNTING_VAT_BUY_ACCOUNT : 'NotDefined';
+	$rcctva = (!empty($conf->global->ACCOUNTING_VAT_BUY_REVERSE_CHARGES_CREDIT)) ? $conf->global->ACCOUNTING_VAT_BUY_REVERSE_CHARGES_CREDIT : 'NotDefined';
+	$rcdtva = (!empty($conf->global->ACCOUNTING_VAT_BUY_REVERSE_CHARGES_DEBIT)) ? $conf->global->ACCOUNTING_VAT_BUY_REVERSE_CHARGES_DEBIT : 'NotDefined';
+	$country_code_in_EEC = getCountriesInEEC();		// This make a database call but there is a cache done into $conf->cache['country_code_in_EEC']
 
 	$i = 0;
 	while ($i < $num) {
@@ -200,8 +210,8 @@ if ($result) {
 			$def_tva[$obj->rowid][$compta_tva][vatrate($obj->tva_tx).($obj->vat_src_code ? ' ('.$obj->vat_src_code.')' : '')] = (vatrate($obj->tva_tx).($obj->vat_src_code ? ' ('.$obj->vat_src_code.')' : ''));
 		}
 
-		$line = new SupplierInvoiceLine($db);
-		$line->fetch($obj->fdid);
+		//$line = new SupplierInvoiceLine($db);
+		//$line->fetch($obj->fdid);
 
 		$tabfac[$obj->rowid]["date"] = $db->jdate($obj->df);
 		$tabfac[$obj->rowid]["datereg"] = $db->jdate($obj->dlr);
@@ -230,10 +240,58 @@ if ($result) {
 			$tablocaltax2[$obj->rowid][$compta_localtax2] = 0;
 		}
 
+		// VAT Reverse charge
+		if (($mysoc->country_code == 'FR' || !empty($conf->global->ACCOUNTING_FORCE_ENABLE_VAT_REVERSE_CHARGE)) && $obj->vat_reverse_charge == 1 && in_array($obj->country_code, $country_code_in_EEC)) {
+			$rcvatdata = getTaxesFromId($obj->product_buy_vat . ($obj->product_buy_default_vat_code ? ' (' . $obj->product_buy_default_vat_code . ')' : ''), $mysoc, $mysoc, 0);
+			$rcc_compta_tva = (!empty($vatdata['accountancy_code_vat_reverse_charge_credit']) ? $vatdata['accountancy_code_vat_reverse_charge_credit'] : $rcctva);
+			$rcd_compta_tva = (!empty($vatdata['accountancy_code_vat_reverse_charge_debit']) ? $vatdata['accountancy_code_vat_reverse_charge_debit'] : $rcdtva);
+			$rcc_compta_localtax1 = (!empty($vatdata['accountancy_code_vat_reverse_charge_credit']) ? $vatdata['accountancy_code_vat_reverse_charge_credit'] : $rcctva);
+			$rcd_compta_localtax1 = (!empty($vatdata['accountancy_code_vat_reverse_charge_debit']) ? $vatdata['accountancy_code_vat_reverse_charge_debit'] : $rcdtva);
+			$rcc_compta_localtax2 = (!empty($vatdata['accountancy_code_vat_reverse_charge_credit']) ? $vatdata['accountancy_code_vat_reverse_charge_credit'] : $rcctva);
+			$rcd_compta_localtax2 = (!empty($vatdata['accountancy_code_vat_reverse_charge_debit']) ? $vatdata['accountancy_code_vat_reverse_charge_debit'] : $rcdtva);
+			if (price2num($obj->product_buy_vat) || !empty($obj->product_buy_default_vat_code)) {
+				$vat_key = vatrate($obj->product_buy_vat) . ($obj->product_buy_default_vat_code ? ' (' . $obj->product_buy_default_vat_code . ')' : '');
+				$val_value = $vat_key;
+				$def_tva[$obj->rowid][$rcc_compta_tva][$vat_key] = $val_value;
+				$def_tva[$obj->rowid][$rcd_compta_tva][$vat_key] = $val_value;
+			}
+
+			if (!isset($tabrctva[$obj->rowid][$rcc_compta_tva])) {
+				$tabrctva[$obj->rowid][$rcc_compta_tva] = 0;
+			}
+			if (!isset($tabrctva[$obj->rowid][$rcd_compta_tva])) {
+				$tabrctva[$obj->rowid][$rcd_compta_tva] = 0;
+			}
+			if (!isset($tabrclocaltax1[$obj->rowid][$rcc_compta_localtax1])) {
+				$tabrclocaltax1[$obj->rowid][$rcc_compta_localtax1] = 0;
+			}
+			if (!isset($tabrclocaltax1[$obj->rowid][$rcd_compta_localtax1])) {
+				$tabrclocaltax1[$obj->rowid][$rcd_compta_localtax1] = 0;
+			}
+			if (!isset($tabrclocaltax2[$obj->rowid][$rcc_compta_localtax2])) {
+				$tabrclocaltax2[$obj->rowid][$rcc_compta_localtax2] = 0;
+			}
+			if (!isset($tabrclocaltax2[$obj->rowid][$rcd_compta_localtax2])) {
+				$tabrclocaltax2[$obj->rowid][$rcd_compta_localtax2] = 0;
+			}
+
+			$rcvat = (double) price2num($obj->total_ttc * $obj->product_buy_vat / 100, 'MT');
+			$rclocalvat1 = (double) price2num($obj->total_ttc * $obj->product_buy_localvat1 / 100, 'MT');
+			$rclocalvat2 = (double) price2num($obj->total_ttc * $obj->product_buy_localvat2 / 100, 'MT');
+
+			$tabrctva[$obj->rowid][$rcd_compta_tva] += $rcvat;
+			$tabrctva[$obj->rowid][$rcc_compta_tva] -= $rcvat;
+			$tabrclocaltax1[$obj->rowid][$rcd_compta_localtax1] += $rclocalvat1;
+			$tabrclocaltax1[$obj->rowid][$rcc_compta_localtax1] -= $rclocalvat1;
+			$tabrclocaltax2[$obj->rowid][$rcd_compta_localtax2] += $rclocalvat2;
+			$tabrclocaltax2[$obj->rowid][$rcc_compta_localtax2] -= $rclocalvat2;
+		}
+
 		$tabttc[$obj->rowid][$compta_soc] += $obj->total_ttc;
 		$tabht[$obj->rowid][$compta_prod] += $obj->total_ht;
 		$tabtva[$obj->rowid][$compta_tva] += $obj->total_tva;
-		if (!empty($line->tva_npr)) {	// Add an entry for counterpart
+		$tva_npr = (($obj->info_bits & 1 == 1) ? 1 : 0);
+		if ($tva_npr) { // If NPR, we add an entry for counterpartWe into tabother
 			$tabother[$obj->rowid][$compta_counterpart_tva_npr] += $obj->total_tva;
 		}
 		$tablocaltax1[$obj->rowid][$compta_localtax1] += $obj->total_localtax1;
@@ -380,6 +438,12 @@ if ($action == 'writebookkeeping') {
 						require_once DOL_DOCUMENT_ROOT . '/accountancy/class/lettering.class.php';
 						$lettering_static = new Lettering($db);
 						$nb_lettering = $lettering_static->bookkeepingLettering(array($bookkeeping->id));
+
+						if ($nb_lettering < 0) {
+							$error++;
+							$errorforline++;
+							setEventMessages($lettering_static->error, $lettering_static->errors, 'errors');
+						}
 					}
 				}
 			}
@@ -463,6 +527,29 @@ if ($action == 'writebookkeeping') {
 					$arrayofvat = $tablocaltax2;
 				}
 
+				// VAT Reverse charge
+				if ($mysoc->country_code == 'FR' || !empty($conf->global->ACCOUNTING_FORCE_ENABLE_VAT_REVERSE_CHARGE)) {
+					$has_vat = false;
+					foreach ($arrayofvat[$key] as $k => $mt) {
+						if ($mt) {
+							$has_vat = true;
+						}
+					}
+
+					if (!$has_vat) {
+						$arrayofvat = $tabrctva;
+						if ($numtax == 1) {
+							$arrayofvat = $tabrclocaltax1;
+						}
+						if ($numtax == 2) {
+							$arrayofvat = $tabrclocaltax2;
+						}
+						if (!is_array($arrayofvat[$key])) {
+							$arrayofvat[$key] = array();
+						}
+					}
+				}
+
 				foreach ($arrayofvat[$key] as $k => $mt) {
 					if ($mt) {
 						$accountingaccount->fetch(null, $k, true);		// TODO Use a cache for label
@@ -713,6 +800,29 @@ if ($action == 'exportcsv') {		// ISO and not UTF8 !
 				$arrayofvat = $tablocaltax2;
 			}
 
+			// VAT Reverse charge
+			if ($mysoc->country_code == 'FR' || !empty($conf->global->ACCOUNTING_FORCE_ENABLE_VAT_REVERSE_CHARGE)) {
+				$has_vat = false;
+				foreach ($arrayofvat[$key] as $k => $mt) {
+					if ($mt) {
+						$has_vat = true;
+					}
+				}
+
+				if (!$has_vat) {
+					$arrayofvat = $tabrctva;
+					if ($numtax == 1) {
+						$arrayofvat = $tabrclocaltax1;
+					}
+					if ($numtax == 2) {
+						$arrayofvat = $tabrclocaltax2;
+					}
+					if (!is_array($arrayofvat[$key])) {
+						$arrayofvat[$key] = array();
+					}
+				}
+			}
+
 			foreach ($arrayofvat[$key] as $k => $mt) {
 				if ($mt) {
 					print '"'.$key.'"'.$sep;
@@ -824,7 +934,6 @@ if (empty($action) || $action == 'view') {
 	 */
 	print '<br>';
 
-	$i = 0;
 	print '<div class="div-table-responsive">';
 	print "<table class=\"noborder\" width=\"100%\">";
 	print "<tr class=\"liste_titre\">";
@@ -837,7 +946,7 @@ if (empty($action) || $action == 'view') {
 	print '<td class="center">'.$langs->trans("AccountingCredit")."</td>";
 	print "</tr>\n";
 
-	$r = '';
+	$i = 0;
 
 	$invoicestatic = new FactureFournisseur($db);
 	$companystatic = new Fournisseur($db);
@@ -887,6 +996,7 @@ if (empty($action) || $action == 'view') {
 			print '<td class="right"></td>';
 			print "</tr>";
 
+			$i++;
 			continue;
 		}
 		if ($errorforinvoice[$key] == 'somelinesarenotbound') {
@@ -906,6 +1016,8 @@ if (empty($action) || $action == 'view') {
 			print '<td class="right"></td>';
 			print '<td class="right"></td>';
 			print "</tr>";
+
+			$i++;
 		}
 
 		// Third party
@@ -936,6 +1048,8 @@ if (empty($action) || $action == 'view') {
 			print '<td class="right nowraponall amount">'.($mt < 0 ? price(-$mt) : '')."</td>";
 			print '<td class="right nowraponall amount">'.($mt >= 0 ? price($mt) : '')."</td>";
 			print "</tr>";
+
+			$i++;
 		}
 
 		// Product / Service
@@ -972,6 +1086,8 @@ if (empty($action) || $action == 'view') {
 			print '<td class="right nowraponall amount">'.($mt >= 0 ? price($mt) : '')."</td>";
 			print '<td class="right nowraponall amount">'.($mt < 0 ? price(-$mt) : '')."</td>";
 			print "</tr>";
+
+			$i++;
 		}
 
 		// VAT
@@ -985,6 +1101,29 @@ if (empty($action) || $action == 'view') {
 				$arrayofvat = $tablocaltax2;
 			}
 
+			// VAT Reverse charge
+			if ($mysoc->country_code == 'FR' || !empty($conf->global->ACCOUNTING_FORCE_ENABLE_VAT_REVERSE_CHARGE)) {
+				$has_vat = false;
+				foreach ($arrayofvat[$key] as $k => $mt) {
+					if ($mt) {
+						$has_vat = true;
+					}
+				}
+
+				if (!$has_vat) {
+					$arrayofvat = $tabrctva;
+					if ($numtax == 1) {
+						$arrayofvat = $tabrclocaltax1;
+					}
+					if ($numtax == 2) {
+						$arrayofvat = $tabrclocaltax2;
+					}
+					if (!is_array($arrayofvat[$key])) {
+						$arrayofvat[$key] = array();
+					}
+				}
+			}
+
 			foreach ($arrayofvat[$key] as $k => $mt) {
 				if ($mt) {
 					print '<tr class="oddeven">';
@@ -1009,6 +1148,8 @@ if (empty($action) || $action == 'view') {
 					print '<td class="right nowraponall amount">'.($mt >= 0 ? price($mt) : '')."</td>";
 					print '<td class="right nowraponall amount">'.($mt < 0 ? price(-$mt) : '')."</td>";
 					print "</tr>";
+
+					$i++;
 				}
 			}
 		}
@@ -1037,11 +1178,17 @@ if (empty($action) || $action == 'view') {
 					print '<td class="right nowraponall amount">'.($mt < 0 ? price(-$mt) : '')."</td>";
 					print '<td class="right nowraponall amount">'.($mt >= 0 ? price($mt) : '')."</td>";
 					print "</tr>";
+
+					$i++;
 				}
 			}
 		}
 	}
 
+	if (!$i) {
+		print '<tr class="oddeven"><td colspan="7"><span class="opacitymedium">'.$langs->trans("NoRecordFound").'</span></td></tr>';
+	}
+
 	print "</table>";
 	print '</div>';
 

+ 51 - 20
htdocs/accountancy/journal/sellsjournal.php

@@ -106,8 +106,8 @@ if (!GETPOSTISSET('date_startmonth') && (empty($date_start) || empty($date_end))
 	$date_end = dol_get_last_day($pastmonthyear, $pastmonth, false);
 }
 
-$sql = "SELECT f.rowid, f.ref, f.type, f.datef as df, f.ref_client, f.date_lim_reglement as dlr, f.close_code, f.retained_warranty,";
-$sql .= " fd.rowid as fdid, fd.description, fd.product_type, fd.total_ht, fd.total_tva, fd.total_localtax1, fd.total_localtax2, fd.tva_tx, fd.total_ttc, fd.situation_percent, fd.vat_src_code,";
+$sql = "SELECT f.rowid, f.ref, f.type, f.situation_cycle_ref, f.datef as df, f.ref_client, f.date_lim_reglement as dlr, f.close_code, f.retained_warranty,";
+$sql .= " fd.rowid as fdid, fd.description, fd.product_type, fd.total_ht, fd.total_tva, fd.total_localtax1, fd.total_localtax2, fd.tva_tx, fd.total_ttc, fd.situation_percent, fd.vat_src_code, fd.info_bits,";
 $sql .= " s.rowid as socid, s.nom as name, s.code_client, s.code_fournisseur,";
 if (!empty($conf->global->MAIN_COMPANY_PERENTITY_SHARED)) {
 	$sql .= " spe.accountancy_code_customer as code_compta,";
@@ -158,7 +158,7 @@ if ($in_bookkeeping == 'notyet') {
 	$sql .= " AND f.rowid NOT IN (SELECT fk_doc FROM ".MAIN_DB_PREFIX."accounting_bookkeeping as ab WHERE ab.doc_type='customer_invoice')";
 	// $sql .= " AND fd.rowid NOT IN (SELECT fk_docdet FROM " . MAIN_DB_PREFIX . "accounting_bookkeeping as ab WHERE ab.doc_type='customer_invoice')";		// Useless, we save one line for all products with same account
 }
-$sql .= " ORDER BY f.datef";
+$sql .= " ORDER BY f.datef, f.ref";
 //print $sql; exit;
 
 dol_syslog('accountancy/journal/sellsjournal.php', LOG_DEBUG);
@@ -206,21 +206,23 @@ if ($result) {
 			$def_tva[$obj->rowid][$compta_tva][vatrate($obj->tva_tx).($obj->vat_src_code ? ' ('.$obj->vat_src_code.')' : '')] = (vatrate($obj->tva_tx).($obj->vat_src_code ? ' ('.$obj->vat_src_code.')' : ''));
 		}
 
-		$line = new FactureLigne($db);
-		$line->fetch($obj->fdid);
+		// Create a compensation rate.
+		$situation_ratio = 1;
+		if (getDolGlobalInt('INVOICE_USE_SITUATION') == 1) {
+			if ($obj->situation_cycle_ref) {
+				// Avoid divide by 0
+				if ($obj->situation_percent == 0) {
+					$situation_ratio = 0;
+				} else {
+					$line = new FactureLigne($db);
+					$line->fetch($obj->fdid);
 
-		// Situation invoices handling
-		$prev_progress = $line->get_prev_progress($obj->rowid);
+					// Situation invoices handling
+					$prev_progress = $line->get_prev_progress($obj->rowid);
 
-		if ($obj->type == Facture::TYPE_SITUATION) {
-			// Avoid divide by 0
-			if ($obj->situation_percent == 0) {
-				$situation_ratio = 0;
-			} else {
-				$situation_ratio = ($obj->situation_percent - $prev_progress) / $obj->situation_percent;
+					$situation_ratio = ($obj->situation_percent - $prev_progress) / $obj->situation_percent;
+				}
 			}
-		} else {
-			$situation_ratio = 1;
 		}
 
 		// Invoice lines
@@ -249,6 +251,11 @@ if ($result) {
 			$tablocaltax2[$obj->rowid][$compta_localtax2] = 0;
 		}
 
+		// Compensation of data for invoice situation by using $situation_ratio. This works (nearly) for invoice that was not correctly recorded
+		// but it may introduces an error for situation invoices that were correctly saved. There is still rounding problem that differs between
+		// real data we should have stored and result obtained with a compensation.
+		// It also seems that credit notes on situation invoices are correctly saved (but it depends on the version used in fact).
+		// For credit notes, we hope to have situation_ratio = 1 so the compensation has no effect to avoid introducing troubles with credit notes.
 		$total_ttc = $obj->total_ttc * $situation_ratio;
 		if (!empty($conf->global->INVOICE_USE_RETAINED_WARRANTY) && $obj->retained_warranty > 0) {
 			$retained_warranty = (double) price2num($total_ttc * $obj->retained_warranty / 100, 'MT');
@@ -257,11 +264,14 @@ if ($result) {
 		}
 		$tabttc[$obj->rowid][$compta_soc] += $total_ttc;
 		$tabht[$obj->rowid][$compta_prod] += $obj->total_ht * $situation_ratio;
-		if (empty($line->tva_npr)) {
-			$tabtva[$obj->rowid][$compta_tva] += $obj->total_tva * $situation_ratio; // We ignore line if VAT is a NPR
+		$tva_npr = (($obj->info_bits & 1 == 1) ? 1 : 0);
+		if (!$tva_npr) { // We ignore line if VAT is a NPR
+			$tabtva[$obj->rowid][$compta_tva] += $obj->total_tva * $situation_ratio;
 		}
 		$tablocaltax1[$obj->rowid][$compta_localtax1] += $obj->total_localtax1 * $situation_ratio;
 		$tablocaltax2[$obj->rowid][$compta_localtax2] += $obj->total_localtax2 * $situation_ratio;
+
+
 		$tabcompany[$obj->rowid] = array(
 			'id' => $obj->socid,
 			'name' => $obj->name,
@@ -453,7 +463,15 @@ if ($action == 'writebookkeeping') {
 					if (getDolGlobalInt('ACCOUNTING_ENABLE_LETTERING') && getDolGlobalInt('ACCOUNTING_ENABLE_AUTOLETTERING')) {
 						require_once DOL_DOCUMENT_ROOT . '/accountancy/class/lettering.class.php';
 						$lettering_static = new Lettering($db);
+
 						$nb_lettering = $lettering_static->bookkeepingLettering(array($bookkeeping->id));
+
+						if ($nb_lettering < 0) {
+							$error++;
+							$errorforline++;
+							$errorforinvoice[$key] = 'other';
+							setEventMessages($lettering_static->error, $lettering_static->errors, 'errors');
+						}
 					}
 				}
 			}
@@ -658,7 +676,8 @@ if ($action == 'exportcsv') {		// ISO and not UTF8 !
 	foreach ($tabfac as $key => $val) {
 		$companystatic->id = $tabcompany[$key]['id'];
 		$companystatic->name = $tabcompany[$key]['name'];
-		$companystatic->code_compta = $tabcompany[$key]['code_compta'];
+		$companystatic->code_compta = $tabcompany[$key]['code_compta'];				// deprecated
+		$companystatic->code_compta_client = $tabcompany[$key]['code_compta'];
 		$companystatic->code_client = $tabcompany[$key]['code_client'];
 		$companystatic->client = 3;
 
@@ -846,7 +865,6 @@ if (empty($action) || $action == 'view') {
 	 */
 	print '<br>';
 
-	$i = 0;
 	print '<div class="div-table-responsive">';
 	print "<table class=\"noborder\" width=\"100%\">";
 	print "<tr class=\"liste_titre\">";
@@ -859,7 +877,7 @@ if (empty($action) || $action == 'view') {
 	print '<td class="center">'.$langs->trans("AccountingCredit")."</td>";
 	print "</tr>\n";
 
-	$r = '';
+	$i = 0;
 
 	$companystatic = new Client($db);
 	$invoicestatic = new Facture($db);
@@ -907,6 +925,7 @@ if (empty($action) || $action == 'view') {
 			print '<td class="right"></td>';
 			print "</tr>";
 
+			$i++;
 			continue;
 		}
 		if ($errorforinvoice[$key] == 'somelinesarenotbound') {
@@ -926,6 +945,8 @@ if (empty($action) || $action == 'view') {
 			print '<td class="right"></td>';
 			print '<td class="right"></td>';
 			print "</tr>";
+
+			$i++;
 		}
 
 		// Warranty
@@ -986,6 +1007,8 @@ if (empty($action) || $action == 'view') {
 			print '<td class="right nowraponall amount">'.($mt >= 0 ? price($mt) : '')."</td>";
 			print '<td class="right nowraponall amount">'.($mt < 0 ? price(-$mt) : '')."</td>";
 			print "</tr>";
+
+			$i++;
 		}
 
 		// Product / Service
@@ -1022,6 +1045,8 @@ if (empty($action) || $action == 'view') {
 			print '<td class="right nowraponall amount">'.($mt < 0 ? price(-$mt) : '')."</td>";
 			print '<td class="right nowraponall amount">'.($mt >= 0 ? price($mt) : '')."</td>";
 			print "</tr>";
+
+			$i++;
 		}
 
 		// VAT
@@ -1058,11 +1083,17 @@ if (empty($action) || $action == 'view') {
 					print '<td class="right nowraponall amount">'.($mt < 0 ? price(-$mt) : '')."</td>";
 					print '<td class="right nowraponall amount">'.($mt >= 0 ? price($mt) : '')."</td>";
 					print "</tr>";
+
+					$i++;
 				}
 			}
 		}
 	}
 
+	if (!$i) {
+		print '<tr class="oddeven"><td colspan="6"><span class="opacitymedium">'.$langs->trans("NoRecordFound").'</span></td></tr>';
+	}
+
 	print "</table>";
 	print '</div>';
 

+ 14 - 3
htdocs/accountancy/journal/variousjournal.php

@@ -272,9 +272,10 @@ $object_label = $langs->trans("ObjectsRef");
 if ($object->nature == 2 || $object->nature == 3) $object_label = $langs->trans("InvoiceRef");
 if ($object->nature == 5) $object_label = $langs->trans("ExpenseReportRef");
 
-/*
- * Show result array
- */
+
+// Show result array
+$i = 0;
+
 print '<br>';
 
 print '<div class="div-table-responsive">';
@@ -304,11 +305,21 @@ if (is_array($journal_data) && !empty($journal_data)) {
 				print '<td class="right nowraponall">' . $line['debit'] . '</td>';
 				print '<td class="right nowraponall">' . $line['credit'] . '</td>';
 				print '</tr>';
+
+				$i++;
 			}
 		}
 	}
 }
 
+if (!$i) {
+	$colspan = 7;
+	if ($object->nature == 4) {
+		$colspan++;
+	}
+	print '<tr class="oddeven"><td colspan="'.$colspan.'"><span class="opacitymedium">'.$langs->trans("NoRecordFound").'</span></td></tr>';
+}
+
 print '</table>';
 print '</div>';
 

+ 29 - 29
htdocs/accountancy/supplier/index.php

@@ -73,7 +73,7 @@ $year_current = $year_start;
 // Validate History
 $action = GETPOST('action', 'aZ09');
 
-$chartaccountcode = dol_getIdFromCode($db, $conf->global->CHARTOFACCOUNTS, 'accounting_system', 'rowid', 'pcg_version');
+$chartaccountcode = dol_getIdFromCode($db, getDolGlobalInt('CHARTOFACCOUNTS'), 'accounting_system', 'rowid', 'pcg_version');
 
 // Security check
 if (!isModEnabled('accounting')) {
@@ -94,15 +94,15 @@ if (empty($user->rights->accounting->mouvements->lire)) {
 if (($action == 'clean' || $action == 'validatehistory') && $user->rights->accounting->bind->write) {
 	// Clean database
 	$db->begin();
-	$sql1 = "UPDATE ".MAIN_DB_PREFIX."facture_fourn_det as fd";
+	$sql1 = "UPDATE ".$db->prefix()."facture_fourn_det as fd";
 	$sql1 .= " SET fk_code_ventilation = 0";
 	$sql1 .= ' WHERE fd.fk_code_ventilation NOT IN';
 	$sql1 .= '	(SELECT accnt.rowid ';
-	$sql1 .= '	FROM '.MAIN_DB_PREFIX.'accounting_account as accnt';
-	$sql1 .= '	INNER JOIN '.MAIN_DB_PREFIX.'accounting_system as syst';
-	$sql1 .= '	ON accnt.fk_pcg_version = syst.pcg_version AND syst.rowid='.$conf->global->CHARTOFACCOUNTS.' AND accnt.entity = '.$conf->entity.')';
-	$sql1 .= ' AND fd.fk_facture_fourn IN (SELECT rowid FROM '.MAIN_DB_PREFIX.'facture_fourn WHERE entity = '.$conf->entity.')';
-	$sql1 .= ' AND fk_code_ventilation <> 0';
+	$sql1 .= "	FROM ".$db->prefix()."accounting_account as accnt";
+	$sql1 .= "	INNER JOIN ".$db->prefix()."accounting_system as syst";
+	$sql1 .= "	ON accnt.fk_pcg_version = syst.pcg_version AND syst.rowid = ".getDolGlobalInt('CHARTOFACCOUNTS')." AND accnt.entity = ".((int) $conf->entity).")";
+	$sql1 .= " AND fd.fk_facture_fourn IN (SELECT rowid FROM ".$db->prefix()."facture_fourn WHERE entity = ".((int) $conf->entity).")";
+	$sql1 .= " AND fk_code_ventilation <> 0";
 
 	dol_syslog("htdocs/accountancy/customer/index.php fixaccountancycode", LOG_DEBUG);
 	$resql1 = $db->query($sql1);
@@ -142,23 +142,23 @@ if ($action == 'validatehistory') {
 	} else {
 		$sql .= " s.accountancy_code_buy as company_code_buy";
 	}
-	$sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f";
-	$sql .= " INNER JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = f.fk_soc";
+	$sql .= " FROM ".$db->prefix()."facture_fourn as f";
+	$sql .= " INNER JOIN ".$db->prefix()."societe as s ON s.rowid = f.fk_soc";
 	if (!empty($conf->global->MAIN_COMPANY_PERENTITY_SHARED)) {
-		$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_perentity as spe ON spe.fk_soc = s.rowid AND spe.entity = " . ((int) $conf->entity);
+		$sql .= " LEFT JOIN " . $db->prefix() . "societe_perentity as spe ON spe.fk_soc = s.rowid AND spe.entity = " . ((int) $conf->entity);
 	}
-	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_country as co ON co.rowid = s.fk_pays ";
-	$sql .= " INNER JOIN ".MAIN_DB_PREFIX."facture_fourn_det as l ON f.rowid = l.fk_facture_fourn";
-	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = l.fk_product";
+	$sql .= " LEFT JOIN ".$db->prefix()."c_country as co ON co.rowid = s.fk_pays ";
+	$sql .= " INNER JOIN ".$db->prefix()."facture_fourn_det as l ON f.rowid = l.fk_facture_fourn";
+	$sql .= " LEFT JOIN ".$db->prefix()."product as p ON p.rowid = l.fk_product";
 	if (!empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
-		$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "product_perentity as ppe ON ppe.fk_product = p.rowid AND ppe.entity = " . ((int) $conf->entity);
+		$sql .= " LEFT JOIN " . $db->prefix() . "product_perentity as ppe ON ppe.fk_product = p.rowid AND ppe.entity = " . ((int) $conf->entity);
 	}
 	$alias_societe_perentity = empty($conf->global->MAIN_COMPANY_PERENTITY_SHARED) ? "s" : "spe";
 	$alias_product_perentity = empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED) ? "p" : "ppe";
-	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa  ON " . $alias_product_perentity . ".accountancy_code_buy = aa.account_number         AND aa.active = 1  AND aa.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa.entity = ".$conf->entity;
-	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa2 ON " . $alias_product_perentity . ".accountancy_code_buy_intra = aa2.account_number  AND aa2.active = 1 AND aa2.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa2.entity = ".$conf->entity;
-	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa3 ON " . $alias_product_perentity . ".accountancy_code_buy_export = aa3.account_number AND aa3.active = 1 AND aa3.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa3.entity = ".$conf->entity;
-	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa4 ON " . $alias_product_perentity . ".accountancy_code_buy = aa4.account_number        AND aa4.active = 1 AND aa4.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa4.entity = ".$conf->entity;
+	$sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa  ON " . $alias_product_perentity . ".accountancy_code_buy = aa.account_number         AND aa.active = 1  AND aa.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa.entity = ".$conf->entity;
+	$sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa2 ON " . $alias_product_perentity . ".accountancy_code_buy_intra = aa2.account_number  AND aa2.active = 1 AND aa2.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa2.entity = ".$conf->entity;
+	$sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa3 ON " . $alias_product_perentity . ".accountancy_code_buy_export = aa3.account_number AND aa3.active = 1 AND aa3.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa3.entity = ".$conf->entity;
+	$sql .= " LEFT JOIN ".$db->prefix()."accounting_account as aa4 ON " . $alias_product_perentity . ".accountancy_code_buy = aa4.account_number        AND aa4.active = 1 AND aa4.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa4.entity = ".$conf->entity;
 	$sql .= " WHERE f.fk_statut > 0 AND l.fk_code_ventilation <= 0";
 	$sql .= " AND l.product_type <= 2";
 	$sql .= " AND f.entity IN (".getEntity('facture_fourn', 0).")"; // We don't share object for accountancy
@@ -257,7 +257,7 @@ if ($action == 'validatehistory') {
 			}
 
 			if ($suggestedid > 0) {
-				$sqlupdate = "UPDATE ".MAIN_DB_PREFIX."facture_fourn_det";
+				$sqlupdate = "UPDATE ".$db->prefix()."facture_fourn_det";
 				$sqlupdate .= " SET fk_code_ventilation = ".((int) $suggestedid);
 				$sqlupdate .= " WHERE fk_code_ventilation <= 0 AND product_type <= 2 AND rowid = ".((int) $facture_static_det->id);
 
@@ -357,9 +357,9 @@ for ($i = 1; $i <= 12; $i++) {
 	$sql .= "  SUM(".$db->ifsql("MONTH(ff.datef)=".$j, "ffd.total_ht", "0").") AS month".str_pad($j, 2, "0", STR_PAD_LEFT).",";
 }
 $sql .= "  SUM(ffd.total_ht) as total";
-$sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn_det as ffd";
-$sql .= "  LEFT JOIN ".MAIN_DB_PREFIX."facture_fourn as ff ON ff.rowid = ffd.fk_facture_fourn";
-$sql .= "  LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa ON aa.rowid = ffd.fk_code_ventilation";
+$sql .= " FROM ".$db->prefix()."facture_fourn_det as ffd";
+$sql .= "  LEFT JOIN ".$db->prefix()."facture_fourn as ff ON ff.rowid = ffd.fk_facture_fourn";
+$sql .= "  LEFT JOIN ".$db->prefix()."accounting_account as aa ON aa.rowid = ffd.fk_code_ventilation";
 $sql .= " WHERE ff.datef >= '".$db->idate($search_date_start)."'";
 $sql .= "  AND ff.datef <= '".$db->idate($search_date_end)."'";
 // Define begin binding date
@@ -489,9 +489,9 @@ for ($i = 1; $i <= 12; $i++) {
 	$sql .= "  SUM(".$db->ifsql("MONTH(ff.datef)=".$j, "ffd.total_ht", "0").") AS month".str_pad($j, 2, "0", STR_PAD_LEFT).",";
 }
 $sql .= "  SUM(ffd.total_ht) as total";
-$sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn_det as ffd";
-$sql .= "  LEFT JOIN ".MAIN_DB_PREFIX."facture_fourn as ff ON ff.rowid = ffd.fk_facture_fourn";
-$sql .= "  LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa ON aa.rowid = ffd.fk_code_ventilation";
+$sql .= " FROM ".$db->prefix()."facture_fourn_det as ffd";
+$sql .= "  LEFT JOIN ".$db->prefix()."facture_fourn as ff ON ff.rowid = ffd.fk_facture_fourn";
+$sql .= "  LEFT JOIN ".$db->prefix()."accounting_account as aa ON aa.rowid = ffd.fk_code_ventilation";
 $sql .= " WHERE ff.datef >= '".$db->idate($search_date_start)."'";
 $sql .= "  AND ff.datef <= '".$db->idate($search_date_end)."'";
 // Define begin binding date
@@ -525,11 +525,11 @@ if ($resql) {
 		}
 		print '</td>';
 
-		print '<td class="left">';
+		print '<td class="tdoverflowmax300"'.(empty($row[1]) ? '' : ' title="'.dol_escape_htmltag($row[1]).'"').'>';
 		if ($row[0] == 'tobind') {
 			print $langs->trans("UseMenuToSetBindindManualy", DOL_URL_ROOT.'/accountancy/supplier/list.php?search_year='.((int) $y), $langs->transnoentitiesnoconv("ToBind"));
 		} else {
-			print $row[1];
+			print dol_escape_htmltag($row[1]);
 		}
 		print '</td>';
 
@@ -583,8 +583,8 @@ if (getDolGlobalString('SHOW_TOTAL_OF_PREVIOUS_LISTS_IN_LIN_PAGE')) { // This pa
 		$sql .= "  SUM(".$db->ifsql("MONTH(ff.datef)=".$j, "ffd.total_ht", "0").") AS month".str_pad($j, 2, "0", STR_PAD_LEFT).",";
 	}
 	$sql .= "  SUM(ffd.total_ht) as total";
-	$sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn_det as ffd";
-	$sql .= "  LEFT JOIN ".MAIN_DB_PREFIX."facture_fourn as ff ON ff.rowid = ffd.fk_facture_fourn";
+	$sql .= " FROM ".$db->prefix()."facture_fourn_det as ffd";
+	$sql .= "  LEFT JOIN ".$db->prefix()."facture_fourn as ff ON ff.rowid = ffd.fk_facture_fourn";
 	$sql .= " WHERE ff.datef >= '".$db->idate($search_date_start)."'";
 	$sql .= "  AND ff.datef <= '".$db->idate($search_date_end)."'";
 	// Define begin binding date

+ 10 - 2
htdocs/accountancy/supplier/lines.php

@@ -83,6 +83,8 @@ if (!$sortfield) {
 if (!$sortorder) {
 	if ($conf->global->ACCOUNTING_LIST_SORT_VENTILATION_DONE > 0) {
 		$sortorder = "DESC";
+	} else {
+		$sortorder = "ASC";
 	}
 }
 
@@ -164,6 +166,12 @@ if (is_array($changeaccount) && count($changeaccount) > 0 && $user->rights->acco
 	}
 }
 
+if (GETPOST('sortfield') == 'f.datef, f.ref, l.rowid') {
+	$value = (GETPOST('sortorder') == 'asc,asc,asc' ? 0 : 1);
+	require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
+	$res = dolibarr_set_const($db, "ACCOUNTING_LIST_SORT_VENTILATION_DONE", $value, 'yesno', 0, '', $conf->entity);
+}
+
 
 /*
  * View
@@ -300,7 +308,7 @@ $sql .= $db->order($sortfield, $sortorder);
 
 // Count total nb of records
 $nbtotalofrecords = '';
-if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
+if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
 	$result = $db->query($sql);
 	$nbtotalofrecords = $db->num_rows($result);
 	if (($page * $limit) > $nbtotalofrecords) {	// if total resultset is smaller then paging size (filtering), goto and load page 0
@@ -322,7 +330,7 @@ if ($result) {
 		$param .= '&contextpage='.urlencode($contextpage);
 	}
 	if ($limit > 0 && $limit != $conf->liste_limit) {
-		$param .= '&limit='.urlencode($limit);
+		$param .= '&limit='.((int) $limit);
 	}
 	if ($search_societe) {
 		$param .= "&search_societe=".urlencode($search_societe);

+ 13 - 7
htdocs/accountancy/supplier/list.php

@@ -93,6 +93,8 @@ if (!$sortfield) {
 if (!$sortorder) {
 	if ($conf->global->ACCOUNTING_LIST_SORT_VENTILATION_TODO > 0) {
 		$sortorder = "DESC";
+	} else {
+		$sortorder = "ASC";
 	}
 }
 
@@ -102,7 +104,7 @@ $hookmanager->initHooks(array('accountancysupplierlist'));
 $formaccounting = new FormAccounting($db);
 $accountingAccount = new AccountingAccount($db);
 
-$chartaccountcode = dol_getIdFromCode($db, $conf->global->CHARTOFACCOUNTS, 'accounting_system', 'rowid', 'pcg_version');
+$chartaccountcode = dol_getIdFromCode($db, getDolGlobalInt('CHARTOFACCOUNTS'), 'accounting_system', 'rowid', 'pcg_version');
 
 // Security check
 if (!isModEnabled('accounting')) {
@@ -170,7 +172,6 @@ if (empty($reshook)) {
 if ($massaction == 'ventil' && $user->rights->accounting->bind->write) {
 	$msg = '';
 
-	//print '<div><span style="color:red">' . $langs->trans("Processing") . '...</span></div>';
 	if (!empty($mesCasesCochees)) {
 		$msg = '<div>'.$langs->trans("SelectedLines").': '.count($mesCasesCochees).'</div>';
 		$msg .= '<div class="detail">';
@@ -184,7 +185,7 @@ if ($massaction == 'ventil' && $user->rights->accounting->bind->write) {
 			$monCompte = GETPOST('codeventil'.$monId);
 
 			if ($monCompte <= 0) {
-				$msg .= '<div><span style="color:red">'.$langs->trans("Lineofinvoice").' '.$monId.' - '.$langs->trans("NoAccountSelected").'</span></div>';
+				$msg .= '<div><span class="error">'.$langs->trans("Lineofinvoice").' '.$monId.' - '.$langs->trans("NoAccountSelected").'</span></div>';
 				$ko++;
 			} else {
 				$sql = " UPDATE ".MAIN_DB_PREFIX."facture_fourn_det";
@@ -196,10 +197,10 @@ if ($massaction == 'ventil' && $user->rights->accounting->bind->write) {
 
 				dol_syslog('accountancy/supplier/list.php', LOG_DEBUG);
 				if ($db->query($sql)) {
-					$msg .= '<div><span style="color:green">'.$langs->trans("Lineofinvoice").' '.$monId.' - '.$langs->trans("VentilatedinAccount").' : '.length_accountg($accountventilated->account_number).'</span></div>';
+					$msg .= '<div><span class="green">'.$langs->trans("Lineofinvoice").' '.$monId.' - '.$langs->trans("VentilatedinAccount").' : '.length_accountg($accountventilated->account_number).'</span></div>';
 					$ok++;
 				} else {
-					$msg .= '<div><span style="color:red">'.$langs->trans("ErrorDB").' : '.$langs->trans("Lineofinvoice").' '.$monId.' - '.$langs->trans("NotVentilatedinAccount").' : '.length_accountg($accountventilated->account_number).'<br> <pre>'.$sql.'</pre></span></div>';
+					$msg .= '<div><span class="error">'.$langs->trans("ErrorDB").' : '.$langs->trans("Lineofinvoice").' '.$monId.' - '.$langs->trans("NotVentilatedinAccount").' : '.length_accountg($accountventilated->account_number).'<br> <pre>'.$sql.'</pre></span></div>';
 					$ko++;
 				}
 			}
@@ -211,6 +212,11 @@ if ($massaction == 'ventil' && $user->rights->accounting->bind->write) {
 	}
 }
 
+if (GETPOST('sortfield') == 'f.datef, f.ref, l.rowid') {
+	$value = (GETPOST('sortorder') == 'asc,asc,asc' ? 0 : 1);
+	require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
+	$res = dolibarr_set_const($db, "ACCOUNTING_LIST_SORT_VENTILATION_TODO", $value, 'yesno', 0, '', $conf->entity);
+}
 
 
 /*
@@ -357,7 +363,7 @@ $sql .= $db->order($sortfield, $sortorder);
 
 // Count total nb of records
 $nbtotalofrecords = '';
-if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
+if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
 	$result = $db->query($sql);
 	$nbtotalofrecords = $db->num_rows($result);
 	if (($page * $limit) > $nbtotalofrecords) {	// if total resultset is smaller then paging size (filtering), goto and load page 0
@@ -387,7 +393,7 @@ if ($result) {
 		$param .= '&contextpage='.urlencode($contextpage);
 	}
 	if ($limit > 0 && $limit != $conf->liste_limit) {
-		$param .= '&limit='.urlencode($limit);
+		$param .= '&limit='.((int) $limit);
 	}
 	if ($search_societe) {
 		$param .= '&search_societe='.urlencode($search_societe);

+ 3 - 2
htdocs/accountancy/tpl/export_journal.tpl.php

@@ -19,6 +19,7 @@
  */
 
 // $formatexportset must be defined
+// $downloadMode 	=0 for direct download or =1 to download after writing files or =-1 not to download files
 
 // Protection to avoid direct call of template
 if (empty($conf) || !is_object($conf)) {
@@ -35,7 +36,7 @@ $siren = getDolGlobalString('MAIN_INFO_SIREN');
 $date_export = "_".dol_print_date(dol_now(), '%Y%m%d%H%M%S');
 $endaccountingperiod = dol_print_date(dol_now(), '%Y%m%d');
 
-if (empty($withAttachment)) {
+if (empty($downloadMode)) {
 	header('Content-Type: text/csv');
 }
 
@@ -70,6 +71,6 @@ if (($accountancyexport->getFormatCode($formatexportset) == 'fec' || $accountanc
 	$completefilename = ($code ? $code."_" : "").($prefix ? $prefix."_" : "").$filename.($nodateexport ? "" : $date_export).".".$format;
 }
 
-if (empty($withAttachment)) {
+if (empty($downloadMode)) {
 	header('Content-Disposition: attachment;filename=' . $completefilename);
 }

+ 1 - 1
htdocs/adherents/admin/member.php

@@ -511,7 +511,7 @@ foreach ($dirmodels as $reldir) {
 
 								// Defaut
 								print '<td class="center">';
-								if (getDolGlobalString('MEMBER_ADDON_PDF') == $name) {
+								if (getDolGlobalString('MEMBER_ADDON_PDF_ODT') == $name) {
 									print img_picto($langs->trans("Default"), 'on');
 								} else {
 									print '<a href="'.$_SERVER["PHP_SELF"].'?action=setdoc&token='.newToken().'&value='.$name.'&scandir='.(!empty($module->scandir) ? $module->scandir : '').'&label='.urlencode($module->name).'" alt="'.$langs->trans("Default").'">'.img_picto($langs->trans("Disabled"), 'off').'</a>';

+ 15 - 15
htdocs/adherents/admin/member_emails.php

@@ -48,18 +48,23 @@ $action = GETPOST('action', 'aZ09');
 
 $error = 0;
 
+$helptext = '*'.$langs->trans("FollowingConstantsWillBeSubstituted").'<br>';
+$helptext .= '__DOL_MAIN_URL_ROOT__, __ID__, __FIRSTNAME__, __LASTNAME__, __FULLNAME__, __LOGIN__, __PASSWORD__, ';
+$helptext .= '__COMPANY__, __ADDRESS__, __ZIP__, __TOWN__, __COUNTRY__, __EMAIL__, __BIRTH__, __PHOTO__, __TYPE__, ';
+//$helptext.='__YEAR__, __MONTH__, __DAY__';	// Not supported
+
 // Editing global variables not related to a specific theme
 $constantes = array(
 	'MEMBER_REMINDER_EMAIL'=>array('type'=>'yesno', 'label'=>$langs->trans('MEMBER_REMINDER_EMAIL', $langs->transnoentities("Module2300Name"))),
-	'ADHERENT_EMAIL_TEMPLATE_REMIND_EXPIRATION' 	=>'emailtemplate:member',
-	'ADHERENT_EMAIL_TEMPLATE_AUTOREGISTER'			=>'emailtemplate:member',	// until Dolibarr 7 it was ADHERENT_AUTOREGISTER_MAIL
-	'ADHERENT_EMAIL_TEMPLATE_MEMBER_VALIDATION'		=>'emailtemplate:member',	// until Dolibarr 7 it was ADHERENT_MAIL_VALID
-	'ADHERENT_EMAIL_TEMPLATE_SUBSCRIPTION'			=>'emailtemplate:member',	// until Dolibarr 7 it was ADHERENT_MAIL_COTIS
-	'ADHERENT_EMAIL_TEMPLATE_CANCELATION'			=>'emailtemplate:member',	// until Dolibarr 7 it was ADHERENT_MAIL_RESIL
-	'ADHERENT_EMAIL_TEMPLATE_EXCLUSION'				=>'emailtemplate:member',
-	'ADHERENT_MAIL_FROM'							=>'string',
-	'ADHERENT_AUTOREGISTER_NOTIF_MAIL_SUBJECT'		=>'string',
-	'ADHERENT_AUTOREGISTER_NOTIF_MAIL'				=>'html',
+	'ADHERENT_EMAIL_TEMPLATE_REMIND_EXPIRATION' 	=>array('type'=>'emailtemplate:member'),
+	'ADHERENT_EMAIL_TEMPLATE_AUTOREGISTER'			=>array('type'=>'emailtemplate:member'),
+	'ADHERENT_EMAIL_TEMPLATE_MEMBER_VALIDATION'		=>array('type'=>'emailtemplate:member'),
+	'ADHERENT_EMAIL_TEMPLATE_SUBSCRIPTION'			=>array('type'=>'emailtemplate:member'),
+	'ADHERENT_EMAIL_TEMPLATE_CANCELATION'			=>array('type'=>'emailtemplate:member'),
+	'ADHERENT_EMAIL_TEMPLATE_EXCLUSION'				=>array('type'=>'emailtemplate:member'),
+	'ADHERENT_MAIL_FROM'							=>array('type'=>'string'),
+	'ADHERENT_AUTOREGISTER_NOTIF_MAIL_SUBJECT'		=>array('type'=>'string'),
+	'ADHERENT_AUTOREGISTER_NOTIF_MAIL'				=>array('type'=>'html', 'tooltip'=>$helptext)
 );
 
 
@@ -147,12 +152,7 @@ print '<form action="'.$_SERVER["PHP_SELF"].'" method="POST">';
 print '<input type="hidden" name="token" value="'.newToken().'">';
 print '<input type="hidden" name="action" value="updateall">';
 
-$helptext = '*'.$langs->trans("FollowingConstantsWillBeSubstituted").'<br>';
-$helptext .= '__DOL_MAIN_URL_ROOT__, __ID__, __FIRSTNAME__, __LASTNAME__, __FULLNAME__, __LOGIN__, __PASSWORD__, ';
-$helptext .= '__COMPANY__, __ADDRESS__, __ZIP__, __TOWN__, __COUNTRY__, __EMAIL__, __BIRTH__, __PHOTO__, __TYPE__, ';
-//$helptext.='__YEAR__, __MONTH__, __DAY__';	// Not supported
-
-form_constantes($constantes, 3, $helptext);
+form_constantes($constantes, 3, '');
 
 print '<div class="center"><input type="submit" class="button" value="'.$langs->trans("Update").'" name="update"></div>';
 print '</form>';

+ 1 - 1
htdocs/adherents/admin/member_extrafields.php

@@ -65,7 +65,7 @@ require DOL_DOCUMENT_ROOT.'/core/actions_extrafields.inc.php';
 
 $textobject = $langs->transnoentitiesnoconv("Members");
 
-$help_url = 'EN:Module_Foundations|FR:Module_Adh&eacute;rents|ES:M&oacute;dulo_Miembros';
+$help_url = 'EN:Module_Foundations|FR:Module_Adh&eacute;rents|ES:M&oacute;dulo_Miembros|DE:Modul_Mitglieder';
 llxHeader('', $langs->trans("MembersSetup"), $help_url);
 
 

+ 26 - 6
htdocs/adherents/admin/website.php

@@ -58,16 +58,20 @@ if ($action == 'setMEMBER_ENABLE_PUBLIC') {
 if ($action == 'update') {
 	$public = GETPOST('MEMBER_ENABLE_PUBLIC');
 	$amount = price2num(GETPOST('MEMBER_NEWFORM_AMOUNT'), 'MT', 2);
-	$editamount = GETPOST('MEMBER_NEWFORM_EDITAMOUNT');
+	$minamount = GETPOST('MEMBER_MIN_AMOUNT');
 	$publiccounters = GETPOST('MEMBER_COUNTERS_ARE_PUBLIC');
+	$showtable = GETPOST('MEMBER_SHOW_TABLE');
+	$showvoteallowed = GETPOST('MEMBER_SHOW_VOTE_ALLOWED');
 	$payonline = GETPOST('MEMBER_NEWFORM_PAYONLINE');
 	$forcetype = GETPOST('MEMBER_NEWFORM_FORCETYPE', 'int');
 	$forcemorphy = GETPOST('MEMBER_NEWFORM_FORCEMORPHY', 'aZ09');
 
 	$res = dolibarr_set_const($db, "MEMBER_ENABLE_PUBLIC", $public, 'chaine', 0, '', $conf->entity);
 	$res = dolibarr_set_const($db, "MEMBER_NEWFORM_AMOUNT", $amount, 'chaine', 0, '', $conf->entity);
-	$res = dolibarr_set_const($db, "MEMBER_NEWFORM_EDITAMOUNT", $editamount, 'chaine', 0, '', $conf->entity);
+	$res = dolibarr_set_const($db, "MEMBER_MIN_AMOUNT", $minamount, 'chaine', 0, '', $conf->entity);
 	$res = dolibarr_set_const($db, "MEMBER_COUNTERS_ARE_PUBLIC", $publiccounters, 'chaine', 0, '', $conf->entity);
+	$res = dolibarr_set_const($db, "MEMBER_SKIP_TABLE", !$showtable, 'chaine', 0, '', $conf->entity); // Logic is reversed for retrocompatibility: "skip -> show"
+	$res = dolibarr_set_const($db, "MEMBER_HIDE_VOTE_ALLOWED", !$showvoteallowed, 'chaine', 0, '', $conf->entity); // Logic is reversed for retrocompatibility: "hide -> show"
 	$res = dolibarr_set_const($db, "MEMBER_NEWFORM_PAYONLINE", $payonline, 'chaine', 0, '', $conf->entity);
 	if ($forcetype < 0) {
 		$res = dolibarr_del_const($db, "MEMBER_NEWFORM_FORCETYPE", $conf->entity);
@@ -99,7 +103,7 @@ if ($action == 'update') {
 $form = new Form($db);
 
 $title = $langs->trans("MembersSetup");
-$help_url = 'EN:Module_Foundations|FR:Module_Adh&eacute;rents|ES:M&oacute;dulo_Miembros';
+$help_url = 'EN:Module_Foundations|FR:Module_Adh&eacute;rents|ES:M&oacute;dulo_Miembros|DE:Modul_Mitglieder';
 llxHeader('', $title, $help_url);
 
 
@@ -232,11 +236,11 @@ if (!empty($conf->global->MEMBER_ENABLE_PUBLIC)) {
 	print '<input type="text" class="right width50" id="MEMBER_NEWFORM_AMOUNT" name="MEMBER_NEWFORM_AMOUNT" value="'.(!empty($conf->global->MEMBER_NEWFORM_AMOUNT) ? $conf->global->MEMBER_NEWFORM_AMOUNT : '').'">';
 	print "</td></tr>\n";
 
-	// Can edit
+	// Min amount
 	print '<tr class="oddeven" id="tredit"><td>';
-	print $langs->trans("CanEditAmountDetail");
+	print $langs->trans("MinimumAmount");
 	print '</td><td>';
-	print $form->selectyesno("MEMBER_NEWFORM_EDITAMOUNT", (!empty($conf->global->MEMBER_NEWFORM_EDITAMOUNT) ? $conf->global->MEMBER_NEWFORM_EDITAMOUNT : 0), 1);
+	print '<input type="text" class="right width50" id="MEMBER_MIN_AMOUNT" name="MEMBER_MIN_AMOUNT" value="'.(!empty($conf->global->MEMBER_MIN_AMOUNT) ? $conf->global->MEMBER_MIN_AMOUNT : '').'">';
 	print "</td></tr>\n";
 
 	// SHow counter of validated members publicly
@@ -246,6 +250,22 @@ if (!empty($conf->global->MEMBER_ENABLE_PUBLIC)) {
 	print $form->selectyesno("MEMBER_COUNTERS_ARE_PUBLIC", (!empty($conf->global->MEMBER_COUNTERS_ARE_PUBLIC) ? $conf->global->MEMBER_COUNTERS_ARE_PUBLIC : 0), 1);
 	print "</td></tr>\n";
 
+	// Show the table of all available membership types. If not, show a form (as the default was for Dolibarr <=16.0)
+	$skiptable = (!empty($conf->global->MEMBER_SKIP_TABLE) ? $conf->global->MEMBER_SKIP_TABLE : 0);
+	print '<tr class="oddeven" id="tredit"><td>';
+	print $langs->trans("MembersShowMembershipTypesTable");
+	print '</td><td>';
+	print $form->selectyesno("MEMBER_SHOW_TABLE", !$skiptable, 1); // Reverse the logic "hide -> show" for retrocompatibility
+	print "</td></tr>\n";
+
+	// Show "vote allowed" setting for membership types
+	$hidevoteallowed = (!empty($conf->global->MEMBER_HIDE_VOTE_ALLOWED) ? $conf->global->MEMBER_HIDE_VOTE_ALLOWED : 0);
+	print '<tr class="oddeven" id="tredit"><td>';
+	print $langs->trans("MembersShowVotesAllowed");
+	print '</td><td>';
+	print $form->selectyesno("MEMBER_SHOW_VOTE_ALLOWED", !$hidevoteallowed, 1); // Reverse the logic "hide -> show" for retrocompatibility
+	print "</td></tr>\n";
+
 	// Jump to an online payment page
 	print '<tr class="oddeven" id="trpayment"><td>';
 	print $langs->trans("MEMBER_NEWFORM_PAYONLINE");

+ 6 - 0
htdocs/adherents/agenda.php

@@ -66,11 +66,15 @@ if (GETPOST('actioncode', 'array')) {
 } else {
 	$actioncode = GETPOST("actioncode", "alpha", 3) ?GETPOST("actioncode", "alpha", 3) : (GETPOST("actioncode") == '0' ? '0' : getDolGlobalString('AGENDA_DEFAULT_FILTER_TYPE_FOR_OBJECT'));
 }
+$search_rowid = GETPOST('search_rowid');
 $search_agenda_label = GETPOST('search_agenda_label');
 
 // Get object canvas (By default, this is not defined, so standard usage of dolibarr)
 $objcanvas = null;
 
+// Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context
+$hookmanager->initHooks(array('memberagenda', 'globalcard'));
+
 // Security check
 $result = restrictedArea($user, 'adherent', $id);
 
@@ -105,6 +109,7 @@ if (empty($reshook)) {
 	// 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
 		$actioncode = '';
+		$search_rowid = '';
 		$search_agenda_label = '';
 	}
 }
@@ -184,6 +189,7 @@ if ($object->id > 0) {
 		// List of all actions
 		$filters = array();
 		$filters['search_agenda_label'] = $search_agenda_label;
+		$filters['search_rowid'] = $search_rowid;
 
 		// TODO Replace this with same code than into list.php
 		show_actions_done($conf, $langs, $db, $object, null, 0, $actioncode, '', $filters, $sortfield, $sortorder);

+ 6 - 11
htdocs/adherents/canvas/actions_adherentcard_common.class.php

@@ -61,20 +61,15 @@ abstract class ActionsAdherentCardCommon
 	 */
 	public function getObject($id)
 	{
-		//$ret = $this->getInstanceDao();
+		$object = new Adherent($this->db);
 
-		/*if (is_object($this->object) && method_exists($this->object,'fetch'))
-		{
-			if (!empty($id)) $this->object->fetch($id);
-		}
-		else
-		{*/
-			$object = new Adherent($this->db);
 		if (!empty($id)) {
 			$object->fetch($id);
 		}
-			$this->object = $object;
-		//}
+
+		$this->object = $object;
+
+		return $object;
 	}
 
 	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
@@ -83,7 +78,7 @@ abstract class ActionsAdherentCardCommon
 	 *
 	 *  @param	string		$action    Type of action
 	 *  @param	int			$id			Id
-	 *  @return	string					HTML output
+	 *  @return	void
 	 */
 	public function assign_values(&$action, $id)
 	{

+ 0 - 1
htdocs/adherents/canvas/default/actions_adherentcard_default.class.php

@@ -84,7 +84,6 @@ class ActionsAdherentCardDefault extends ActionsAdherentCardCommon
 	public function assign_values(&$action, $id)
 	{
 		// phpcs:enable
-		global $limit, $offset, $sortfield, $sortorder;
 		global $conf, $db, $langs, $user;
 		global $form;
 

+ 6 - 15
htdocs/adherents/card.php

@@ -313,10 +313,6 @@ if (empty($reshook)) {
 					$object->socialnetworks[$key] = trim(GETPOST($key, 'alphanohtml'));
 				}
 			}
-			//$object->skype = trim(GETPOST("skype", 'alpha'));
-			//$object->twitter = trim(GETPOST("twitter", 'alpha'));
-			//$object->facebook = trim(GETPOST("facebook", 'alpha'));
-			//$object->linkedin = trim(GETPOST("linkedin", 'alpha'));
 			$object->birth = $birthdate;
 			$object->default_lang = GETPOST('default_lang', 'alpha');
 			$object->typeid = GETPOST("typeid", 'int');
@@ -488,11 +484,6 @@ if (empty($reshook)) {
 			}
 		}
 
-		// $object->skype       = $skype;
-		// $object->twitter     = $twitter;
-		// $object->facebook    = $facebook;
-		// $object->linkedin    = $linkedin;
-
 		$object->email       = $email;
 		$object->url       	 = $url;
 		$object->login       = $login;
@@ -683,7 +674,7 @@ if (empty($reshook)) {
 
 					$moreinheader = 'X-Dolibarr-Info: send_an_email by adherents/card.php'."\r\n";
 
-					$result = $object->send_an_email($texttosend, $subjecttosend, array(), array(), array(), "", "", 0, -1, '', $moreinheader);
+					$result = $object->sendEmail($texttosend, $subjecttosend, array(), array(), array(), "", "", 0, -1, '', $moreinheader);
 					if ($result < 0) {
 						$error++;
 						setEventMessages($object->error, $object->errors, 'errors');
@@ -754,7 +745,7 @@ if (empty($reshook)) {
 
 						$moreinheader = 'X-Dolibarr-Info: send_an_email by adherents/card.php'."\r\n";
 
-						$result = $object->send_an_email($texttosend, $subjecttosend, array(), array(), array(), "", "", 0, -1, '', $moreinheader);
+						$result = $object->sendEmail($texttosend, $subjecttosend, array(), array(), array(), "", "", 0, -1, '', $moreinheader);
 						if ($result < 0) {
 							$error++;
 							setEventMessages($object->error, $object->errors, 'errors');
@@ -825,7 +816,7 @@ if (empty($reshook)) {
 
 						$moreinheader = 'X-Dolibarr-Info: send_an_email by adherents/card.php'."\r\n";
 
-						$result = $object->send_an_email($texttosend, $subjecttosend, array(), array(), array(), "", "", 0, -1, '', $moreinheader);
+						$result = $object->sendEmail($texttosend, $subjecttosend, array(), array(), array(), "", "", 0, -1, '', $moreinheader);
 						if ($result < 0) {
 							$error++;
 							setEventMessages($object->error, $object->errors, 'errors');
@@ -1412,7 +1403,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 		print '</table>';
 		print dol_get_fiche_end();
 
-		print $form->buttonsSaveCancel("Save", '');
+		print $form->buttonsSaveCancel("Save", 'Cancel');
 
 		print '</form>';
 	}
@@ -1882,7 +1873,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 		print "</table>\n";
 
 		print "</div></div>\n";
-		print '<div style="clear:both"></div>';
+		print '<div class="clearboth"></div>';
 
 		print dol_get_fiche_end();
 
@@ -2050,7 +2041,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 			if ($useonlinepayment) {
 				print '<br>';
 				if (empty($amount)) {   // Take the maximum amount among what the member is supposed to pay / has paid in the past
-					$amount = price(max($adht->amount, $object->first_subscription_amount, $object->last_subscription_amount));
+					$amount = max($adht->amount, $object->first_subscription_amount, $object->last_subscription_amount);
 				}
 				if (empty($amount)) {
 					$amount = 0;

+ 113 - 39
htdocs/adherents/class/adherent.class.php

@@ -38,6 +38,7 @@
 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/commonpeople.class.php';
 
 
 /**
@@ -45,6 +46,8 @@ require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
  */
 class Adherent extends CommonObject
 {
+	use CommonPeople;
+
 	/**
 	 * @var string ID to identify managed object
 	 */
@@ -440,11 +443,37 @@ class Adherent extends CommonObject
 	 *  @param	int		$msgishtml			1=String IS already html, 0=String IS NOT html, -1=Unknown need autodetection
 	 *  @param	string	$errors_to			erros to
 	 *  @param	string	$moreinheader		Add more html headers
+	 *  @deprecated since V18
+	 *  @see sendEmail()
 	 *  @return	int							<0 if KO, >0 if OK
 	 */
 	public function send_an_email($text, $subject, $filename_list = array(), $mimetype_list = array(), $mimefilename_list = array(), $addr_cc = "", $addr_bcc = "", $deliveryreceipt = 0, $msgishtml = -1, $errors_to = '', $moreinheader = '')
 	{
 		// phpcs:enable
+		dol_syslog('Warning using deprecated Adherent::send_an_email', LOG_WARNING);
+
+		return $this->sendEmail($text, $subject, $filename_list, $mimetype_list, $mimefilename_list, $addr_cc, $addr_bcc, $deliveryreceipt, $msgishtml, $errors_to, $moreinheader);
+	}
+
+	/**
+	 *  Function sending an email to the current member with the text supplied in parameter.
+	 *
+	 *  @param	string	$text				Content of message (not html entities encoded)
+	 *  @param	string	$subject			Subject of message
+	 *  @param 	array	$filename_list      Array of attached files
+	 *  @param 	array	$mimetype_list      Array of mime types of attached files
+	 *  @param 	array	$mimefilename_list  Array of public names of attached files
+	 *  @param 	string	$addr_cc            Email cc
+	 *  @param 	string	$addr_bcc           Email bcc
+	 *  @param 	int		$deliveryreceipt	Ask a delivery receipt
+	 *  @param	int		$msgishtml			1=String IS already html, 0=String IS NOT html, -1=Unknown need autodetection
+	 *  @param	string	$errors_to			erros to
+	 *  @param	string	$moreinheader		Add more html headers
+	 * 	@since V18
+	 *  @return	int							<0 if KO, >0 if OK
+	 */
+	public function sendEmail($text, $subject, $filename_list = array(), $mimetype_list = array(), $mimefilename_list = array(), $addr_cc = "", $addr_bcc = "", $deliveryreceipt = 0, $msgishtml = -1, $errors_to = '', $moreinheader = '')
+	{
 		global $conf, $langs;
 
 		// Detect if message is HTML
@@ -1221,7 +1250,7 @@ class Adherent extends CommonObject
 
 					if ($result >= 0) {
 						$result = $luser->setPassword($user, $this->pass, 0, 0, 1);
-						if ($result < 0) {
+						if (is_numeric($result) && $result < 0) {
 							$this->error = $luser->error;
 							dol_syslog(get_class($this)."::setPassword ".$this->error, LOG_ERR);
 							$error++;
@@ -1700,8 +1729,7 @@ class Adherent extends CommonObject
 				return -2;
 			}
 		} else {
-			$this->error = $subscription->error;
-			$this->errors = $subscription->errors;
+			$this->setErrorsFromObject($subscription);
 			$this->db->rollback();
 			return -1;
 		}
@@ -1764,13 +1792,11 @@ class Adherent extends CommonObject
 					}
 				} else {
 					$error++;
-					$this->error = $acct->error;
-					$this->errors = $acct->errors;
+					$this->setErrorsFromObject($acct);
 				}
 			} else {
 				$error++;
-				$this->error = $acct->error;
-				$this->errors = $acct->errors;
+				$this->setErrorsFromObject($acct);
 			}
 		}
 
@@ -2250,6 +2276,64 @@ class Adherent extends CommonObject
 		return $langs->getLabelFromKey($this->db, "Civility".$code, "c_civility", "code", "label", $code);
 	}
 
+	/**
+	 * getTooltipContentArray
+	 * @param array $params params to construct tooltip data
+	 * @since v18
+	 * @return array
+	 */
+	public function getTooltipContentArray($params)
+	{
+		global $conf, $langs;
+
+		$langs->loadLangs(['members', 'companies']);
+		$nofetch = !empty($params['nofetch']);
+
+		$datas = array();
+
+		if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
+			$langs->load("users");
+			return ['optimize' => $langs->trans("ShowUser")];
+		}
+		if (!empty($this->photo)) {
+			$photo = '<div class="photointooltip floatright">';
+			$photo .= Form::showphoto('memberphoto', $this, 80, 0, 0, 'photoref photowithmargin photologintooltip', 'small', 0, 1);
+			$photo .= '</div>';
+			$datas['photo'] = $photo;
+		}
+
+		$datas['divopen'] = '<div class="centpercent">';
+		$datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Member").'</u> '.$this->getLibStatut(4);
+		if (!empty($this->morphy)) {
+			$datas['picto'] .= '&nbsp;' . $this->getmorphylib('', 1);
+		}
+		if (!empty($this->ref)) {
+			$datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
+		}
+		if (!empty($this->login)) {
+			$datas['login'] = '<br><b>'.$langs->trans('Login').':</b> '.$this->login;
+		}
+		if (!empty($this->firstname) || !empty($this->lastname)) {
+			$datas['name'] = '<br><b>'.$langs->trans('Name').':</b> '.$this->getFullName($langs);
+		}
+		if (!empty($this->company)) {
+			$datas['company'] = '<br><b>'.$langs->trans('Company').':</b> '.$this->company;
+		}
+		if (!empty($this->email)) {
+			$datas['email'] = '<br><b>'.$langs->trans("EMail").':</b> '.$this->email;
+		}
+		$datas['address'] = '<br><b>'.$langs->trans("Address").':</b> '.dol_format_address($this, 1, ' ', $langs);
+		// show categories for this record only in ajax to not overload lists
+		if (isModEnabled('categorie') && !$nofetch) {
+			require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
+			$form = new Form($this->db);
+			$datas['categories'] = '<br>' . $form->showCategories($this->id, Categorie::TYPE_MEMBER, 1);
+		}
+		$datas['divclose'] = '</div>';
+
+		return $datas;
+	}
+
 	/**
 	 *  Return clicable name (with picto eventually)
 	 *
@@ -2272,33 +2356,23 @@ class Adherent extends CommonObject
 		}
 
 		$result = '';
-		$label = '';
 		$linkstart = '';
 		$linkend = '';
-
-		if (!empty($this->photo)) {
-			$label .= '<div class="photointooltip floatright">';
-			$label .= Form::showphoto('memberphoto', $this, 80, 0, 0, 'photoref photowithmargin photologintooltip', 'small', 0, 1);
-			$label .= '</div>';
-			//$label .= '<div style="clear: both;"></div>';
-		}
-
-		$label .= '<div class="centpercent">';
-		$label .= img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Member").'</u>';
-		$label .= ' '.$this->getLibStatut(4);
-		if (!empty($this->ref)) {
-			$label .= '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
-		}
-		if (!empty($this->login)) {
-			$label .= '<br><b>'.$langs->trans('Login').':</b> '.$this->login;
-		}
-		if (!empty($this->firstname) || !empty($this->lastname)) {
-			$label .= '<br><b>'.$langs->trans('Name').':</b> '.$this->getFullName($langs);
-		}
-		if (!empty($this->company)) {
-			$label .= '<br><b>'.$langs->trans('Company').':</b> '.$this->company;
+		$classfortooltip = 'classfortooltip';
+		$dataparams = '';
+		$params = [
+			'id' => $this->id,
+			'objecttype' => $this->element,
+			'option' => $option,
+			'nofetch' => 1,
+		];
+		if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
+			$classfortooltip = 'classforajaxtooltip';
+			$dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
+			$label = '';
+		} else {
+			$label = implode($this->getTooltipContentArray($params));
 		}
-		$label .= '</div>';
 
 		$url = DOL_URL_ROOT.'/adherents/card.php?rowid='.((int) $this->id);
 		if ($option == 'subscription') {
@@ -2324,8 +2398,8 @@ class Adherent extends CommonObject
 				$label = $langs->trans("ShowUser");
 				$linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
 			}
-			$linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
-			$linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
+			$linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' :  ' title="tocomplete"');
+			$linkclose .= $dataparams.' class="'.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
 		}
 
 		$linkstart .= $linkclose.'>';
@@ -2338,12 +2412,11 @@ class Adherent extends CommonObject
 		if ($withpictoimg) {
 			$paddafterimage = '';
 			if (abs($withpictoimg) == 1) {
-				$paddafterimage = 'style="margin-right: 3px;"';
+				$morecss .= ' paddingrightonly';
 			}
 			// Only picto
 			if ($withpictoimg > 0) {
-				$picto = '<span class="nopadding'.($morecss ? ' userimg'.$morecss : '').'">'.
-					img_object('', 'user', $paddafterimage.' '.($notooltip ? '' : 'class="classfortooltip"'), 0, 0, $notooltip ? 0 : 1).'</span>';
+				$picto = '<span class="nopadding'.($morecss ? ' userimg'.$morecss : '').'">'.img_object('', 'user', $paddafterimage.' '.($notooltip ? '' : $dataparams), 0, 0, $notooltip ? 0 : 1).'</span>';
 			} else {
 				// Picto must be a photo
 				$picto = '<span class="nopadding'.($morecss ? ' userimg'.$morecss : '').'"'.($paddafterimage ? ' '.$paddafterimage : '').'>';
@@ -2991,7 +3064,7 @@ class Adherent extends CommonObject
 
 		$blockingerrormsg = '';
 
-		if (empty($conf->adherent->enabled)) { // Should not happen. If module disabled, cron job should not be visible.
+		if (!isModEnabled('adherent')) { // Should not happen. If module disabled, cron job should not be visible.
 			$langs->load("agenda");
 			$this->output = $langs->trans('ModuleNotEnabled', $langs->transnoentitiesnoconv("Adherent"));
 			return 0;
@@ -3233,11 +3306,11 @@ class Adherent extends CommonObject
 	 */
 	public function getKanbanView($option = '', $arraydata = null)
 	{
+		$selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
 
 		$return = '<div class="box-flex-item box-flex-grow-zero">';
 		$return .= '<div class="info-box info-box-sm">';
 		$return .= '<span class="info-box-icon bg-infobox-action">';
-
 		if (property_exists($this, 'photo') || !empty($this->photo)) {
 			$return.= Form::showphoto('memberphoto', $this, 0, 60, 0, 'photokanban photoref photowithmargin photologintooltip', 'small', 0, 1);
 		} else {
@@ -3246,6 +3319,7 @@ class Adherent extends CommonObject
 		$return .= '</span>';
 		$return .= '<div class="info-box-content">';
 		$return .= '<span class="info-box-ref">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
+		$return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
 		if (property_exists($this, 'type')) {
 			$return .= '<br><span class="info-box-label opacitymedium">'.$this->type.'</span>';
 		}
@@ -3253,7 +3327,7 @@ class Adherent extends CommonObject
 			$return .= '<br><span class="info-box-label">'.$this->getmorphylib('', 2).'</span>';
 		}
 		if (method_exists($this, 'getLibStatut')) {
-			$return .= '<br><div class="info-box-status margintoponly">'.$this->getLibStatut(5).'</div>';
+			$return .= '<br><div class="info-box-status margintoponly">'.$this->getLibStatut(3).'</div>';
 		}
 		$return .= '</div>';
 		$return .= '</div>';

+ 64 - 15
htdocs/adherents/class/adherent_type.class.php

@@ -639,7 +639,7 @@ class AdherentType extends CommonObject
 			$sql .= ' AND ('.$excludefilter.')';
 		}
 
-		dol_syslog(get_class($this)."::listUsersForGroup", LOG_DEBUG);
+		dol_syslog(get_class($this)."::listMembersForMemberType", LOG_DEBUG);
 		$resql = $this->db->query($sql);
 		if ($resql) {
 			while ($obj = $this->db->fetch_object($resql)) {
@@ -688,6 +688,40 @@ class AdherentType extends CommonObject
 		//return $morphy;
 	}
 
+	/**
+	 * getTooltipContentArray
+	 * @param array $params params to construct tooltip data
+	 * @since v18
+	 * @return array
+	 */
+	public function getTooltipContentArray($params)
+	{
+		global $conf, $langs, $user;
+
+		$langs->load('members');
+
+		$datas = [];
+		$datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("MemberType").'</u> '.$this->getLibStatut(4);
+		$datas['label'] = '<br>'.$langs->trans("Label").': '.$this->label;
+		if (isset($this->subscription)) {
+			$datas['subscription'] = '<br>'.$langs->trans("SubscriptionRequired").': '.yn($this->subscription);
+		}
+		if (isset($this->vote)) {
+			$datas['vote'] = '<br>'.$langs->trans("VoteAllowed").': '.yn($this->vote);
+		}
+		if (isset($this->duration)) {
+			$datas['duration'] = '<br>'.$langs->trans("Duration").': '.$this->duration_value;
+			if ($this->duration_value > 1) {
+				$dur = array("i"=>$langs->trans("Minutes"), "h"=>$langs->trans("Hours"), "d"=>$langs->trans("Days"), "w"=>$langs->trans("Weeks"), "m"=>$langs->trans("Months"), "y"=>$langs->trans("Years"));
+			} elseif ($this->duration_value > 0) {
+				$dur = array("i"=>$langs->trans("Minute"), "h"=>$langs->trans("Hour"), "d"=>$langs->trans("Day"), "w"=>$langs->trans("Week"), "m"=>$langs->trans("Month"), "y"=>$langs->trans("Year"));
+			}
+			$datas['duration'] .= "&nbsp;" . (!empty($this->duration_unit) && isset($dur[$this->duration_unit]) ? $langs->trans($dur[$this->duration_unit]) : '');
+		}
+
+		return $datas;
+	}
+
 	/**
 	 *  Return clicable name (with picto eventually)
 	 *
@@ -703,18 +737,25 @@ class AdherentType extends CommonObject
 		global $langs;
 
 		$result = '';
+		$option = '';
 
-		$label = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("MemberType").'</u>';
-		$label .= ' '.$this->getLibStatut(4);
-		$label .= '<br>'.$langs->trans("Label").': '.$this->label;
-		if (isset($this->subscription)) {
-			$label .= '<br>'.$langs->trans("SubscriptionRequired").': '.yn($this->subscription);
+		$classfortooltip = 'classfortooltip';
+		$dataparams = '';
+		$params = [
+			'id' => $this->id,
+			'objecttype' => $this->element,
+			'option' => $option,
+			'nofetch' => 1,
+		];
+		if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
+			$classfortooltip = 'classforajaxtooltip';
+			$dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
+			$label = '';
+		} else {
+			$label = implode($this->getTooltipContentArray($params));
 		}
 
-		$option = '';
-
 		$url = DOL_URL_ROOT.'/adherents/type.php?rowid='.((int) $this->id);
-
 		if ($option != 'nolink') {
 			// Add param to save lastsearch_values or not
 			$add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
@@ -725,13 +766,15 @@ class AdherentType extends CommonObject
 				$url .= '&save_lastsearch_values=1';
 			}
 		}
+		$linkstart = '<a href="'.$url.'"';
+		$linkstart .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' :  ' title="tocomplete"');
+		$linkstart .= $dataparams.' class="'.$classfortooltip.'">';
 
-		$linkstart = '<a href="'.$url.'" title="'.dol_escape_htmltag($label, 1).'" class="classfortooltip">';
 		$linkend = '</a>';
 
 		$result .= $linkstart;
 		if ($withpicto) {
-			$result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
+			$result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (' class="'.(($withpicto != 2) ? 'paddingright' : '').'"'), 0, 0, $notooltip ? 0 : 1);
 		}
 		if ($withpicto != 2) {
 			$result .= ($maxlen ?dol_trunc($this->label, $maxlen) : $this->label);
@@ -741,7 +784,6 @@ class AdherentType extends CommonObject
 		return $result;
 	}
 
-	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
 	/**
 	 *    Return label of status (activity, closed)
 	 *
@@ -753,6 +795,7 @@ class AdherentType extends CommonObject
 		return $this->LibStatut($this->status, $mode);
 	}
 
+	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
 	/**
 	 *  Return the label of a given status
 	 *
@@ -951,7 +994,10 @@ class AdherentType extends CommonObject
 	 */
 	public function getKanbanView($option = '', $arraydata = null)
 	{
-		global $langs,$user;
+		global $langs, $user;
+
+		//$selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
+
 		$return = '<div class="box-flex-item box-flex-grow-zero">';
 		$return .= '<div class="info-box info-box-sm">';
 		$return .= '<span class="info-box-icon bg-infobox-action">';
@@ -959,8 +1005,11 @@ class AdherentType extends CommonObject
 		$return .= '</span>';
 		$return .= '<div class="info-box-content">';
 		$return .= '<span class="info-box-ref">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
+
+		//$return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
+
 		if ($user->rights->adherent->configurer) {
-			$return .= '<span class="right paddingleft"><a class="editfielda" href="'.$_SERVER["PHP_SELF"].'?action=edit&rowid='.$this->ref.'">'.img_edit().'</a></span>';
+			$return .= '<span class="right paddingleft"><a class="editfielda" href="'.$_SERVER["PHP_SELF"].'?action=edit&rowid='.urlencode($this->ref).'">'.img_edit().'</a></span>';
 		} else {
 			$return .= '<span class="right">&nbsp;</span>';
 		}
@@ -976,7 +1025,7 @@ class AdherentType extends CommonObject
 			}
 		}
 		if (method_exists($this, 'getLibStatut')) {
-			$return .= '<br><div class="info-box-status margintoponly">'.$this->getLibStatut(5).'</div>';
+			$return .= '<br><div class="info-box-status margintoponly">'.$this->getLibStatut(3).'</div>';
 		}
 		$return .= '</div>';
 		$return .= '</div>';

+ 4 - 5
htdocs/adherents/class/api_members.class.php

@@ -222,7 +222,7 @@ class Members extends DolibarrApi
 		}
 
 		$sql = "SELECT t.rowid";
-		$sql .= " FROM ".MAIN_DB_PREFIX."adherent as t";
+		$sql .= " FROM ".MAIN_DB_PREFIX."adherent AS t LEFT JOIN ".MAIN_DB_PREFIX."adherent_extrafields AS ef ON (ef.fk_object = t.rowid)"; // Modification VMR Global Solutions to include extrafields as search parameters in the API GET call
 		if ($category > 0) {
 			$sql .= ", ".MAIN_DB_PREFIX."categorie_member as c";
 		}
@@ -238,11 +238,10 @@ class Members extends DolibarrApi
 		// Add sql filters
 		if ($sqlfilters) {
 			$errormessage = '';
-			if (!DolibarrApi::_checkFilters($sqlfilters, $errormessage)) {
-				throw new RestException(503, 'Error when validating parameter sqlfilters -> '.$errormessage);
+			$sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
+			if ($errormessage) {
+				throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
 			}
-			$regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^\(\)]+)\)';
-			$sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'DolibarrApi::_forge_criteria_callback', $sqlfilters).")";
 		}
 
 		$sql .= $this->db->order($sortfield, $sortorder);

+ 3 - 4
htdocs/adherents/class/api_memberstypes.class.php

@@ -97,17 +97,16 @@ class MembersTypes extends DolibarrApi
 		}
 
 		$sql = "SELECT t.rowid";
-		$sql .= " FROM ".MAIN_DB_PREFIX."adherent_type as t";
+		$sql .= " FROM ".MAIN_DB_PREFIX."adherent_type AS t LEFT JOIN ".MAIN_DB_PREFIX."adherent_type_extrafields AS ef ON (ef.fk_object = t.rowid)"; // Modification VMR Global Solutions to include extrafields as search parameters in the API GET call, so we will be able to filter on extrafields
 		$sql .= ' WHERE t.entity IN ('.getEntity('member_type').')';
 
 		// Add sql filters
 		if ($sqlfilters) {
 			$errormessage = '';
-			if (!DolibarrApi::_checkFilters($sqlfilters, $errormessage)) {
+			$sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
+			if ($errormessage) {
 				throw new RestException(503, 'Error when validating parameter sqlfilters -> '.$errormessage);
 			}
-			$regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^\(\)]+)\)';
-			$sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'DolibarrApi::_forge_criteria_callback', $sqlfilters).")";
 		}
 
 		$sql .= $this->db->order($sortfield, $sortorder);

+ 2 - 3
htdocs/adherents/class/api_subscriptions.class.php

@@ -101,11 +101,10 @@ class Subscriptions extends DolibarrApi
 		// Add sql filters
 		if ($sqlfilters) {
 			$errormessage = '';
-			if (!DolibarrApi::_checkFilters($sqlfilters, $errormessage)) {
+			$sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
+			if ($errormessage) {
 				throw new RestException(503, 'Error when validating parameter sqlfilters -> '.$errormessage);
 			}
-			$regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^\(\)]+)\)';
-			$sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'DolibarrApi::_forge_criteria_callback', $sqlfilters).")";
 		}
 
 		$sql .= $this->db->order($sortfield, $sortorder);

+ 15 - 9
htdocs/adherents/class/subscription.class.php

@@ -109,7 +109,7 @@ class Subscription extends CommonObject
 		'datef' =>array('type'=>'datetime', 'label'=>'DateEndSubscription', 'enabled'=>1, 'visible'=>-1, 'position'=>35),
 		'subscription' =>array('type'=>'double(24,8)', 'label'=>'Amount', 'enabled'=>1, 'visible'=>-1, 'position'=>40, 'isameasure'=>1),
 		'fk_bank' =>array('type'=>'integer', 'label'=>'BankId', 'enabled'=>1, 'visible'=>-1, 'position'=>45),
-		'note' =>array('type'=>'text', 'label'=>'Note', 'enabled'=>1, 'visible'=>-1, 'position'=>50),
+		'note' =>array('type'=>'html', 'label'=>'Note', 'enabled'=>1, 'visible'=>-1, 'position'=>50),
 		'fk_type' =>array('type'=>'integer', 'label'=>'MemberType', 'enabled'=>1, 'visible'=>-1, 'position'=>55),
 		'fk_user_creat' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserAuthor', 'enabled'=>1, 'visible'=>-2, 'position'=>60),
 		'fk_user_valid' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>65),
@@ -452,10 +452,10 @@ class Subscription extends CommonObject
 
 
 	/**
-	 *  Retourne le libelle du statut d'une adhesion
+	 *  Return the label of the status
 	 *
-	 *  @param	int		$mode       0=libelle long, 1=libelle court, 2=Picto + Libelle court, 3=Picto, 4=Picto + Libelle long, 5=Libelle court + Picto
-	 *  @return string				Label
+	 *  @param  int		$mode          0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
+	 *  @return	string 			       Label of status
 	 */
 	public function getLibStatut($mode = 0)
 	{
@@ -464,16 +464,19 @@ class Subscription extends CommonObject
 
 	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
 	/**
-	 *  Renvoi le libelle d'un statut donne
+	 *  Return the label of a given status
 	 *
-	 *  @param	int			$status      			Id status
-	 *  @return string      						Label
+	 *  @param	int		$status        Id status
+	 *  @param  int		$mode          0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
+	 *  @return string 			       Label of status
 	 */
-	public function LibStatut($status)
+	public function LibStatut($status, $mode = 0)
 	{
 		// phpcs:enable
 		global $langs;
-		$langs->load("members");
+
+		//$langs->load("members");
+
 		return '';
 	}
 
@@ -515,6 +518,8 @@ class Subscription extends CommonObject
 	 */
 	public function getKanbanView($option = '', $arraydata = null)
 	{
+		$selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
+
 		$return = '<div class="box-flex-item box-flex-grow-zero">';
 		$return .= '<div class="info-box info-box-sm">';
 		$return .= '<span class="info-box-icon bg-infobox-action">';
@@ -523,6 +528,7 @@ class Subscription extends CommonObject
 
 		$return .= '<div class="info-box-content">';
 		$return .= '<span class="info-box-ref">'.(property_exists($this, 'fk_adherent')? $this->fk_adherent: $this->ref ).'</span>';
+		$return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
 		if (property_exists($this, 'dateh') || property_exists($this, 'datef')) {
 			$return .= '<br><span class="info-box-status opacitymedium">'.dol_print_date($this->dateh, 'day').' - '.dol_print_date($this->datef, 'day').'</span>';
 		}

+ 1 - 1
htdocs/adherents/index.php

@@ -77,7 +77,7 @@ $form = new Form($db);
 // Load $resultboxes (selectboxlist + boxactivated + boxlista + boxlistb)
 $resultboxes = FormOther::getBoxesArea($user, "2");
 
-llxHeader('', $langs->trans("Members"), 'EN:Module_Foundations|FR:Module_Adh&eacute;rents|ES:M&oacute;dulo_Miembros');
+llxHeader('', $langs->trans("Members"), 'EN:Module_Foundations|FR:Module_Adh&eacute;rents|ES:M&oacute;dulo_Miembros|DE:Modul_Mitglieder');
 
 $staticmember = new Adherent($db);
 $statictype = new AdherentType($db);

+ 1 - 1
htdocs/adherents/ldap.php

@@ -104,7 +104,7 @@ if ($action == 'dolibarr2ldap') {
 
 $form = new Form($db);
 
-llxHeader('', $langs->trans("Member"), 'EN:Module_Foundations|FR:Module_Adh&eacute;rents|ES:M&oacute;dulo_Miembros');
+llxHeader('', $langs->trans("Member"), 'EN:Module_Foundations|FR:Module_Adh&eacute;rents|ES:M&oacute;dulo_Miembros|DE:Modul_Mitglieder');
 
 $head = member_prepare_head($object);
 

+ 37 - 27
htdocs/adherents/list.php

@@ -5,7 +5,7 @@
  * Copyright (C) 2013-2015  Raphaël Doursenaud      <rdoursenaud@gpcsolutions.fr>
  * Copyright (C) 2014-2016  Juanjo Menent           <jmenent@2byte.es>
  * Copyright (C) 2018       Alexandre Spangaro      <aspangaro@open-dsi.fr>
- * Copyright (C) 2021		Frédéric France			<frederic.france@netlogic.fr>
+ * Copyright (C) 2021-2023	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
@@ -37,7 +37,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
 
 
 // Load translation files required by the page
-$langs->loadLangs(array("members", "companies"));
+$langs->loadLangs(array("members", "companies", "categories"));
 
 
 // Get parameters
@@ -188,7 +188,8 @@ $result = restrictedArea($user, 'adherent');
  */
 
 if (GETPOST('cancel', 'alpha')) {
-	$action = 'list'; $massaction = '';
+	$action = 'list';
+	$massaction = '';
 }
 if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend') {
 	$massaction = '';
@@ -324,14 +325,17 @@ $formother = new FormOther($db);
 $membertypestatic = new AdherentType($db);
 $memberstatic = new Adherent($db);
 
+$now = dol_now();
+
 // Page Header
-$title = $langs->trans("Members")." - ".$langs->trans("List");;
+$title = $langs->trans("Members")." - ".$langs->trans("List");
 $help_url = 'EN:Module_Foundations|FR:Module_Adh&eacute;rents|ES:M&oacute;dulo_Miembros|DE:Modul_Mitglieder';
-llxHeader('', $title, $help_url);
+$morejs = array();
+$morecss = array();
 
 
-$now = dol_now();
-
+// Build and execute select
+// --------------------------------------------------------------------
 if ((!empty($search_categ) && $search_categ > 0) || !empty($catid)) {
 	$sql = "SELECT DISTINCT";
 } else {
@@ -345,19 +349,19 @@ $sql .= " d.note_private, d.note_public, d.import_key,";
 $sql .= " s.nom,";
 $sql .= " ".$db->ifsql("d.societe IS NULL", "s.nom", "d.societe")." as companyname,";
 $sql .= " t.libelle as type, t.subscription,";
-$sql .= " state.code_departement as state_code, state.nom as state_name,";
+$sql .= " state.code_departement as state_code, state.nom as state_name";
 
 // Add fields from extrafields
 if (!empty($extrafields->attributes[$object->table_element]['label'])) {
 	foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $val) {
-		$sql .= ($extrafields->attributes[$object->table_element]['type'][$key] != 'separate' ? "ef.".$key." as options_".$key.', ' : '');
+		$sql .= ($extrafields->attributes[$object->table_element]['type'][$key] != 'separate' ? ", ef.".$key." as options_".$key : '');
 	}
 }
 
 // Add fields from hooks
 $parameters = array();
-$reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters); // Note that $action and $object may have been modified by hook
-$sql .= preg_replace('/^,/', '', $hookmanager->resPrint);
+$reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+$sql .= $hookmanager->resPrint;
 $sql = preg_replace('/,\s*$/', '', $sql);
 
 $sqlfields = $sql; // $sql fields to remove for count total
@@ -424,10 +428,10 @@ if ($search_filter == 'waitingsubscription') {
 	$sql .= " AND (datefin IS NULL AND t.subscription = '1')";
 }
 if ($search_filter == 'uptodate') {
-	$sql .= " AND (datefin >= '".$db->idate($now)."' OR t.subscription = '0')";
+	$sql .= " AND (datefin >= '".$db->idate($now)."' OR (datefin IS NULL AND t.subscription = '0'))";
 }
 if ($search_filter == 'outofdate') {
-	$sql .= " AND (datefin < '".$db->idate($now)."' AND t.subscription = '1')";
+	$sql .= " AND (datefin < '".$db->idate($now)."')";
 }
 if ($search_status != '') {
 	// Peut valoir un nombre ou liste de nombre separes par virgules
@@ -487,18 +491,16 @@ if ($search_country) {
 if ($search_import_key) {
 	$sql .= natural_search("d.import_key", $search_import_key);
 }
-
 // Add where from extra fields
 include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php';
-
 // Add where from hooks
 $parameters = array();
-$reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters); // Note that $action and $object may have been modified by hook
+$reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 $sql .= $hookmanager->resPrint;
 
-// Count total nb of records with no order and no limits
+// Count total nb of records
 $nbtotalofrecords = '';
-if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
+if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
 	/* The fast and low memory method to get and count full list converts the sql into a sql count */
 	$sqlforcount = preg_replace('/^'.preg_quote($sqlfields, '/').'/', 'SELECT COUNT(*) as nbtotalofrecords', $sql);
 	$sqlforcount = preg_replace('/GROUP BY .*$/', '', $sqlforcount);
@@ -510,12 +512,13 @@ if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
 		dol_print_error($db);
 	}
 
-	if (($page * $limit) > $nbtotalofrecords) {	// if total resultset is smaller then paging size (filtering), goto and load page 0
+	if (($page * $limit) > $nbtotalofrecords) {	// if total resultset is smaller than the paging size (filtering), goto and load page 0
 		$page = 0;
 		$offset = 0;
 	}
 	$db->free($resql);
 }
+//print $sql;
 
 // Complete request and execute it with limit
 $sql .= $db->order($sortfield, $sortorder);
@@ -532,8 +535,7 @@ if (!$resql) {
 $num = $db->num_rows($resql);
 
 
-$arrayofselected = is_array($toselect) ? $toselect : array();
-
+// Direct jump if only one record found
 if ($num == 1 && !empty($conf->global->MAIN_SEARCH_DIRECT_OPEN_IF_ONLY_ONE) && $sall) {
 	$obj = $db->fetch_object($resql);
 	$id = $obj->rowid;
@@ -541,6 +543,13 @@ if ($num == 1 && !empty($conf->global->MAIN_SEARCH_DIRECT_OPEN_IF_ONLY_ONE) && $
 	exit;
 }
 
+// Output page
+// --------------------------------------------------------------------
+
+llxHeader('', $title, $help_url, '', 0, 0, $morejs, $morecss, '', 'bodyforlist');	// Can use also classforhorizontalscrolloftabs instead of bodyforlist for no horizontal scroll
+
+$arrayofselected = is_array($toselect) ? $toselect : array();
+
 
 if ($search_type > 0) {
 	$membertype = new AdherentType($db);
@@ -557,7 +566,7 @@ if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) {
 	$param .= '&contextpage='.urlencode($contextpage);
 }
 if ($limit > 0 && $limit != $conf->liste_limit) {
-	$param .= '&limit='.urlencode($limit);
+	$param .= '&limit='.((int) $limit);
 }
 if ($sall != "") {
 	$param .= "&sall=".urlencode($sall);
@@ -711,7 +720,7 @@ if (!empty($moreforfilter)) {
 }
 
 $varpage = empty($contextpage) ? $_SERVER["PHP_SELF"] : $contextpage;
-$selectedfields = $form->multiSelectArrayWithCheckbox('selectedfields', $arrayfields, $varpage, getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN', '')); // This also change content of $arrayfields
+$selectedfields = ($mode != 'kanban' ? $form->multiSelectArrayWithCheckbox('selectedfields', $arrayfields, $varpage, getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN', '')) : ''); // This also change content of $arrayfields
 if ($massactionbutton) {
 	$selectedfields .= $form->showCheckAddButtons('checkforselect', 1);
 }
@@ -1014,7 +1023,8 @@ print "</tr>\n";
 $i = 0;
 $totalarray = array();
 $totalarray['nbfield'] = 0;
-while ($i < min($num, $limit)) {
+$imaxinloop = ($limit ? min($num, $limit) : $num);
+while ($i < $imaxinloop) {
 	$obj = $db->fetch_object($resql);
 
 	$datefin = $db->jdate($obj->datefin);
@@ -1051,14 +1061,14 @@ while ($i < min($num, $limit)) {
 	if ($mode == 'kanban') {
 		if ($i == 0) {
 			print '<tr><td colspan="12">';
-			print '<div class="box-flex-container">';
+			print '<div class="box-flex-container kanban">';
 		}
 		$membertypestatic->id = $obj->type_id;
 		$membertypestatic->label = $obj->type;
 		$memberstatic->type = $membertypestatic->label;
 		$memberstatic->photo = $obj->photo;
 		// Output Kanban
-		print $memberstatic->getKanbanView('');
+		print $memberstatic->getKanbanView('', array('selected' => in_array($object->id, $arrayofselected)));
 		if ($i == (min($num, $limit) - 1)) {
 			print '</div>';
 			print '</td></tr>';
@@ -1349,7 +1359,7 @@ if ($num == 0) {
 			$colspan++;
 		}
 	}
-	print '<tr><td colspan="'.$colspan.'" class="opacitymedium">'.$langs->trans("NoRecordFound").'</td></tr>';
+	print '<tr><td colspan="'.$colspan.'"><span class="opacitymedium">'.$langs->trans("NoRecordFound").'</span></td></tr>';
 }
 
 $db->free($resql);

+ 1 - 1
htdocs/adherents/stats/geo.php

@@ -287,7 +287,7 @@ if (count($arrayjs) && $mode == 'memberbycountry') {
 	print "\tvar container = document.getElementById('".$mode."');\n";
 	print "\tvar geomap = new google.visualization.GeoMap(container);\n";
 	print "\tgeomap.draw(data, options);\n";
-	print "};\n";
+	print "}\n";
 	print "</script>\n";
 
 	// print the div tag that will contain the map

+ 1 - 1
htdocs/adherents/stats/index.php

@@ -225,7 +225,7 @@ print '</td></tr></table>';
 
 
 print '</div></div>';
-print '<div style="clear:both"></div>';
+print '<div class="clearboth"></div>';
 
 
 print dol_get_fiche_end();

+ 14 - 6
htdocs/adherents/subscription.php

@@ -4,7 +4,7 @@
  * Copyright (C) 2004-2018  Laurent Destailleur     <eldy@users.sourceforge.net>
  * Copyright (C) 2012-2017  Regis Houssin           <regis.houssin@inodbox.com>
  * Copyright (C) 2015-2016  Alexandre Spangaro      <aspangaro@open-dsi.fr>
- * Copyright (C) 2018-2021  Frédéric France         <frederic.france@netlogic.fr>
+ * Copyright (C) 2018-2023  Frédéric France         <frederic.france@netlogic.fr>
  * Copyright (C) 2019       Thibault FOUCART        <support@ptibogxiv.net>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -405,7 +405,7 @@ if ($user->hasRight('adherent', 'cotisation', 'creer') && $action == 'subscripti
 
 					$moreinheader = 'X-Dolibarr-Info: send_an_email by adherents/subscription.php'."\r\n";
 
-					$result = $object->send_an_email($texttosend, $subjecttosend, $listofpaths, $listofmimes, $listofnames, "", "", 0, -1, '', $moreinheader);
+					$result = $object->sendEmail($texttosend, $subjecttosend, $listofpaths, $listofmimes, $listofnames, "", "", 0, -1, '', $moreinheader);
 					if ($result < 0) {
 						$errmsg = $object->error;
 						setEventMessages($object->error, $object->errors, 'errors');
@@ -451,7 +451,7 @@ if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) {
 	$param .= '&contextpage='.urlencode($contextpage);
 }
 if ($limit > 0 && $limit != $conf->liste_limit) {
-	$param .= '&limit='.urlencode($limit);
+	$param .= '&limit='.((int) $limit);
 }
 $param .= '&id='.$rowid;
 if ($optioncss != '') {
@@ -664,7 +664,7 @@ if ($rowid > 0) {
 	print "</table>\n";
 
 	print "</div></div>\n";
-	print '<div style="clear:both"></div>';
+	print '<div class="clearboth"></div>';
 
 	print dol_get_fiche_end();
 
@@ -946,6 +946,8 @@ if ($rowid > 0) {
 
 		print '<tr>';
 		// Date start subscription
+		$currentyear = dol_print_date(time(), "%Y");
+		$currentmonth = dol_print_date(time(), "%m");
 		print '<td class="fieldrequired">'.$langs->trans("DateSubscription").'</td><td>';
 		if (GETPOST('reday')) {
 			$datefrom = dol_mktime(0, 0, 0, GETPOST('remonth'), GETPOST('reday'), GETPOST('reyear'));
@@ -955,7 +957,7 @@ if ($rowid > 0) {
 			if ($object->datefin > 0 && dol_time_plus_duree($object->datefin, $defaultdelay, $defaultdelayunit) > dol_now()) {
 				$datefrom = dol_time_plus_duree($object->datefin, 1, 'd');
 			} else {
-				$datefrom = dol_get_first_day(dol_print_date(time(), "%Y"));
+				$datefrom = dol_get_first_day($currentyear);
 			}
 		}
 		print $form->selectDate($datefrom, '', '', '', '', "subscription", 1, 1);
@@ -966,7 +968,13 @@ if ($rowid > 0) {
 			$dateto = dol_mktime(0, 0, 0, GETPOST('endmonth'), GETPOST('endday'), GETPOST('endyear'));
 		}
 		if (!$dateto) {
-			$dateto = -1; // By default, no date is suggested
+			if (getDolGlobalInt('MEMBER_SUBSCRIPTION_SUGGEST_END_OF_MONTH')) {
+				$dateto = dol_get_last_day($currentyear, $currentmonth);
+			} elseif (getDolGlobalInt('MEMBER_SUBSCRIPTION_SUGGEST_END_OF_YEAR')) {
+				$dateto = dol_get_last_day($currentyear);
+			} else {
+				$dateto = -1; // By default, no date is suggested
+			}
 		}
 		print '<tr><td>'.$langs->trans("DateEndSubscription").'</td><td>';
 		print $form->selectDate($dateto, 'end', '', '', '', "subscription", 1, 0);

+ 5 - 5
htdocs/adherents/subscription/card.php

@@ -160,7 +160,7 @@ if ($action == 'confirm_delete' && $confirm == 'yes' && $user->hasRight('adheren
 
 $form = new Form($db);
 
-$help_url = 'EN:Module_Foundations|FR:Module_Adh&eacute;rents|ES:M&oacute;dulo_Miembros';
+$help_url = 'EN:Module_Foundations|FR:Module_Adh&eacute;rents|ES:M&oacute;dulo_Miembros|DE:Modul_Mitglieder';
 llxHeader('', $langs->trans("SubscriptionCard"), $help_url);
 
 
@@ -352,10 +352,10 @@ if ($rowid && $action != 'edit') {
 	print '<div class="tabsAction">';
 
 	if ($user->hasRight('adherent', 'cotisation', 'creer')) {
-		if (!empty($bankline->rappro)) {
+		if (!empty($bankline->rappro) || empty($bankline)) {
 			print '<div class="inline-block divButAction"><a class="butAction" href="'.$_SERVER["PHP_SELF"]."?rowid=".$object->id.'&action=edit&token='.newToken().'">'.$langs->trans("Modify")."</a></div>";
 		} else {
-			print '<div class="inline-block divButAction"><a class="butActionRefused classfortooltip" title="'.$langs->trans("BankLineConciliated")."\" href=\"#\">".$langs->trans("Modify")."</a></div>";
+			print '<div class="inline-block divButAction"><a class="butActionRefused classfortooltip" title="'.$langs->trans("BankLineConciliated").'" href="#">'.$langs->trans("Modify")."</a></div>";
 		}
 	}
 
@@ -375,8 +375,8 @@ if ($rowid && $action != 'edit') {
 	$filename = dol_sanitizeFileName($object->ref);
 	$filedir = $conf->facture->dir_output . '/' . dol_sanitizeFileName($object->ref);
 	$urlsource = $_SERVER['PHP_SELF'] . '?facid=' . $object->id;
-	$genallowed = $user->rights->facture->lire;
-	$delallowed = $user->rights->facture->creer;
+	$genallowed = $user->hasRight('facture', 'lire');
+	$delallowed = $user->hasRight('facture', 'creer');
 
 	print $formfile->showdocuments('facture', $filename, $filedir, $urlsource, $genallowed, $delallowed, $object->model_pdf, 1, 0, 0, 28, 0, '', '', '', $soc->default_lang);
 	$somethingshown = $formfile->numoffiles;

+ 227 - 95
htdocs/adherents/subscription/list.php

@@ -32,13 +32,16 @@ require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
 
 $langs->loadLangs(array("members", "companies"));
 
-$action = GETPOST('action', 'aZ09');
-$massaction = GETPOST('massaction', 'alpha');
-$confirm = GETPOST('confirm', 'alpha');
-$toselect = GETPOST('toselect', 'array');
-$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'subscriptionlist'; // To manage different context of search
-$mode = GETPOST('mode', 'alpha');
-
+$action     = GETPOST('action', 'aZ09') ? GETPOST('action', 'aZ09') : 'view'; // The action 'create'/'add', 'edit'/'update', 'view', ...
+$massaction = GETPOST('massaction', 'alpha'); // The bulk action (combo box choice into lists)
+$show_files = GETPOST('show_files', 'int'); // Show files area generated by bulk actions ?
+$confirm    = GETPOST('confirm', 'alpha'); // Result of a confirmation
+$cancel     = GETPOST('cancel', 'alpha'); // We click on a Cancel button
+$toselect   = GETPOST('toselect', 'array'); // Array of ids of elements selected into a list
+$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : str_replace('_', '', basename(dirname(__FILE__)).basename(__FILE__, '.php')); // To manage different context of search
+$backtopage = GETPOST('backtopage', 'alpha'); // Go back to a dedicated page
+$optioncss  = GETPOST('optioncss', 'aZ'); // Option for the css output (always '' except when 'print')
+$mode       = GETPOST('mode', 'aZ'); // The output mode ('list', 'kanban', 'hierarchy', 'calendar', ...)
 
 $statut = (GETPOSTISSET("statut") ?GETPOST("statut", "alpha") : 1);
 $search_ref = GETPOST('search_ref', 'alpha');
@@ -49,18 +52,19 @@ $search_login = GETPOST('search_login', 'alpha');
 $search_note = GETPOST('search_note', 'alpha');
 $search_account = GETPOST('search_account', 'int');
 $search_amount = GETPOST('search_amount', 'alpha');
-$optioncss = GETPOST('optioncss', 'alpha');
-$sall = '';
+$search_all = '';
 
 $date_select = GETPOST("date_select", 'alpha');
 
-$limit = GETPOST('limit', 'int') ?GETPOST('limit', 'int') : $conf->liste_limit;
+// Load variable for pagination
+$limit = GETPOST('limit', 'int') ? GETPOST('limit', 'int') : $conf->liste_limit;
 $sortfield = GETPOST('sortfield', 'aZ09comma');
 $sortorder = GETPOST('sortorder', 'aZ09comma');
 $page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int');
-if (empty($page) || $page == -1) {
+if (empty($page) || $page < 0 || GETPOST('button_search', 'alpha') || GETPOST('button_removefilter', 'alpha')) {
+	// If $page is not defined, or '' or -1 or if we click on clear filters
 	$page = 0;
-}     // If $page is not defined, or '' or -1
+}
 $offset = $limit * $page;
 $pageprev = $page - 1;
 $pagenext = $page + 1;
@@ -71,13 +75,12 @@ if (!$sortfield) {
 	$sortfield = "c.dateadh";
 }
 
+// Initialize technical objects
 $object = new Subscription($db);
-
-// Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context
-$hookmanager->initHooks(array('subscriptionlist'));
 $extrafields = new ExtraFields($db);
+$hookmanager->initHooks(array('subscriptionlist'));
 
-// fetch optionals attributes and labels
+// Fetch optionals attributes and labels
 $extrafields->fetch_name_optionals_label($object->table_element);
 
 $search_array_options = $extrafields->getOptionalsFromPost($object->table_element, '', 'search_');
@@ -106,13 +109,16 @@ $arrayfields = array(
 // Security check
 $result = restrictedArea($user, 'adherent', '', '', 'cotisation');
 
+$permissiontodelete = $user->hasRight('adherent', 'cotisation', 'creer');
+
 
 /*
  * Actions
  */
 
 if (GETPOST('cancel', 'alpha')) {
-	$action = 'list'; $massaction = '';
+	$action = 'list';
+	$massaction = '';
 }
 if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend') {
 	$massaction = '';
@@ -141,6 +147,16 @@ if (empty($reshook)) {
 		$toselect = array();
 		$search_array_options = array();
 	}
+	if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')
+		|| GETPOST('button_search_x', 'alpha') || GETPOST('button_search.x', 'alpha') || GETPOST('button_search', 'alpha')) {
+		$massaction = ''; // Protection to avoid mass action if we force a new search during a mass action confirmation
+	}
+
+	// Mass actions
+	$objectclass = 'Subscription';
+	$objectlabel = 'Subscription';
+	$uploaddir = $conf->adherent->dir_output;
+	include DOL_DOCUMENT_ROOT.'/core/actions_massactions.inc.php';
 }
 
 
@@ -162,6 +178,20 @@ $sql .= " c.rowid as crowid, c.fk_type, c.subscription,";
 $sql .= " c.dateadh, c.datef, c.datec as date_creation, c.tms as date_update,";
 $sql .= " c.fk_bank as bank, c.note,";
 $sql .= " b.fk_account";
+// Add fields from extrafields
+if (!empty($extrafields->attributes[$object->table_element]['label'])) {
+	foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $val) {
+		$sql .= ($extrafields->attributes[$object->table_element]['type'][$key] != 'separate' ? ", ef.".$key." as options_".$key : '');
+	}
+}
+// Add fields from hooks
+$parameters = array();
+$reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+$sql .= $hookmanager->resPrint;
+$sql = preg_replace('/,\s*$/', '', $sql);
+
+$sqlfields = $sql; // $sql fields to remove for count total
+
 $sql .= " FROM ".MAIN_DB_PREFIX."adherent as d";
 $sql .= " JOIN ".MAIN_DB_PREFIX."subscription as c on d.rowid = c.fk_adherent";
 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."adherent_extrafields as ef on (d.rowid = ef.fk_object)";
@@ -199,60 +229,72 @@ if ($search_account > 0) {
 if ($search_amount) {
 	$sql .= natural_search('c.subscription', $search_amount, 1);
 }
-
+if ($search_all) {
+	$sql .= natural_search(array_keys($fieldstosearchall), $search_all);
+}
 // Add where from extra fields
 include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php';
-
 // Add where from hooks
 $parameters = array();
-$reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters); // Note that $action and $object may have been modified by hook
+$reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 $sql .= $hookmanager->resPrint;
 
-$sql .= $db->order($sortfield, $sortorder);
-
-// Count total nb of records with no order and no limits
+// Count total nb of records
 $nbtotalofrecords = '';
-if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
-	$resql = $db->query($sql);
+if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
+	/* The fast and low memory method to get and count full list converts the sql into a sql count */
+	$sqlforcount = preg_replace('/^'.preg_quote($sqlfields, '/').'/', 'SELECT COUNT(*) as nbtotalofrecords', $sql);
+	$sqlforcount = preg_replace('/GROUP BY .*$/', '', $sqlforcount);
+	$resql = $db->query($sqlforcount);
 	if ($resql) {
-		$nbtotalofrecords = $db->num_rows($resql);
+		$objforcount = $db->fetch_object($resql);
+		$nbtotalofrecords = $objforcount->nbtotalofrecords;
 	} else {
 		dol_print_error($db);
 	}
-	if (($page * $limit) > $nbtotalofrecords) {	// if total resultset is smaller then paging size (filtering), goto and load page 0
+
+	if (($page * $limit) > $nbtotalofrecords) {	// if total resultset is smaller than the paging size (filtering), goto and load page 0
 		$page = 0;
 		$offset = 0;
 	}
+	$db->free($resql);
+}
+
+// Complete request and execute it with limit
+$sql .= $db->order($sortfield, $sortorder);
+if ($limit) {
+	$sql .= $db->plimit($limit + 1, $offset);
 }
-// Add limit
-$sql .= $db->plimit($limit + 1, $offset);
 
-$result = $db->query($sql);
-if (!$result) {
+$resql = $db->query($sql);
+if (!$resql) {
 	dol_print_error($db);
 	exit;
 }
 
-$num = $db->num_rows($result);
+$num = $db->num_rows($resql);
 
-$arrayofselected = is_array($toselect) ? $toselect : array();
 
-if ($num == 1 && !empty($conf->global->MAIN_SEARCH_DIRECT_OPEN_IF_ONLY_ONE) && $sall) {
+// Direct jump if only one record found
+if ($num == 1 && !empty($conf->global->MAIN_SEARCH_DIRECT_OPEN_IF_ONLY_ONE) && $search_all && !$page) {
 	$obj = $db->fetch_object($resql);
 	$id = $obj->rowid;
 	header("Location: ".DOL_URL_ROOT.'/adherents/subscription/card.php?id='.$id);
 	exit;
 }
 
+// Output page
+// --------------------------------------------------------------------
+
 $title = $langs->trans("Subscriptions");
 if (!empty($date_select)) {
 	$title .= ' ('.$langs->trans("Year").' '.$date_select.')';
 }
+$help_url = 'EN:Module_Foundations|FR:Module_Adh&eacute;rents|ES:M&oacute;dulo_Miembros|DE:Modul_Mitglieder';
 
-$help_url = 'EN:Module_Foundations|FR:Module_Adh&eacute;rents|ES:M&oacute;dulo_Miembros';
 llxHeader('', $title, $help_url);
 
-$i = 0;
+$arrayofselected = is_array($toselect) ? $toselect : array();
 
 $param = '';
 if (!empty($mode)) {
@@ -262,7 +304,7 @@ if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) {
 	$param .= '&contextpage='.urlencode($contextpage);
 }
 if ($limit > 0 && $limit != $conf->liste_limit) {
-	$param .= '&limit='.urlencode($limit);
+	$param .= '&limit='.((int) $limit);
 }
 if ($statut != '') {
 	$param .= "&statut=".urlencode($statut);
@@ -290,27 +332,25 @@ if ($optioncss != '') {
 }
 // Add $param from extra fields
 include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php';
+// Add $param from hooks
+$parameters = array();
+$reshook = $hookmanager->executeHooks('printFieldListSearchParam', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+$param .= $hookmanager->resPrint;
 
 // List of mass actions available
 $arrayofmassactions = array(
 	//'presend'=>img_picto('', 'email', 'class="pictofixedwidth"').$langs->trans("SendByMail"),
 	//'builddoc'=>img_picto('', 'pdf', 'class="pictofixedwidth"').$langs->trans("PDFMerge"),
 );
-//if ($user->hasRight('adherent', 'supprimer')) $arrayofmassactions['predelete'] = img_picto('', 'delete', 'class="pictofixedwidth"').$langs->trans("Delete");
-if (in_array($massaction, array('presend', 'predelete'))) {
+if (!empty($permissiontodelete)) {
+	$arrayofmassactions['predelete'] = img_picto('', 'delete', 'class="pictofixedwidth"').$langs->trans("Delete");
+}
+if (GETPOST('nomassaction', 'int') || in_array($massaction, array('presend', 'predelete'))) {
 	$arrayofmassactions = array();
 }
 $massactionbutton = $form->selectMassAction('', $arrayofmassactions);
 
-$newcardbutton = '';
-$newcardbutton .= dolGetButtonTitle($langs->trans('ViewList'), '', 'fa fa-bars imgforviewmode', $_SERVER["PHP_SELF"].'?mode=common'.preg_replace('/(&|\?)*mode=[^&]+/', '', $param), '', ((empty($mode) || $mode == 'common') ? 2 : 1), array('morecss'=>'reposition'));
-$newcardbutton .= dolGetButtonTitle($langs->trans('ViewKanban'), '', 'fa fa-th-list imgforviewmode', $_SERVER["PHP_SELF"].'?mode=kanban'.preg_replace('/(&|\?)*mode=[^&]+/', '', $param), '', ($mode == 'kanban' ? 2 : 1), array('morecss'=>'reposition'));
-
-if ($user->hasRight('adherent', 'cotisation', 'creer')) {
-	$newcardbutton .= dolGetButtonTitle($langs->trans('NewSubscription'), '', 'fa fa-plus-circle', DOL_URL_ROOT.'/adherents/list.php?status=-1,1');
-}
-
-print '<form method="POST" id="searchFormList" action="'.$_SERVER["PHP_SELF"].'">';
+print '<form method="POST" id="searchFormList" action="'.$_SERVER["PHP_SELF"].'">'."\n";
 if ($optioncss != '') {
 	print '<input type="hidden" name="optioncss" value="'.$optioncss.'">';
 }
@@ -319,10 +359,19 @@ print '<input type="hidden" name="formfilteraction" id="formfilteraction" value=
 print '<input type="hidden" name="action" value="list">';
 print '<input type="hidden" name="sortfield" value="'.$sortfield.'">';
 print '<input type="hidden" name="sortorder" value="'.$sortorder.'">';
+print '<input type="hidden" name="page" value="'.$page.'">';
 print '<input type="hidden" name="contextpage" value="'.$contextpage.'">';
 print '<input type="hidden" name="date_select" value="'.$date_select.'">';
+print '<input type="hidden" name="page_y" value="">';
 print '<input type="hidden" name="mode" value="'.$mode.'">';
 
+$newcardbutton = '';
+$newcardbutton .= dolGetButtonTitle($langs->trans('ViewList'), '', 'fa fa-bars imgforviewmode', $_SERVER["PHP_SELF"].'?mode=common'.preg_replace('/(&|\?)*mode=[^&]+/', '', $param), '', ((empty($mode) || $mode == 'common') ? 2 : 1), array('morecss'=>'reposition'));
+$newcardbutton .= dolGetButtonTitle($langs->trans('ViewKanban'), '', 'fa fa-th-list imgforviewmode', $_SERVER["PHP_SELF"].'?mode=kanban'.preg_replace('/(&|\?)*mode=[^&]+/', '', $param), '', ($mode == 'kanban' ? 2 : 1), array('morecss'=>'reposition'));
+if ($user->hasRight('adherent', 'cotisation', 'creer')) {
+	$newcardbutton .= dolGetButtonTitleSeparator();
+	$newcardbutton .= dolGetButtonTitle($langs->trans('NewSubscription'), '', 'fa fa-plus-circle', DOL_URL_ROOT.'/adherents/list.php?status=-1,1');
+}
 
 print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, $subscription->picto, 0, $newcardbutton, '', $limit, 0, 0, 1);
 
@@ -332,28 +381,55 @@ $objecttmp = new Subscription($db);
 $trackid = 'sub'.$object->id;
 include DOL_DOCUMENT_ROOT.'/core/tpl/massactions_pre.tpl.php';
 
-if ($sall) {
+if ($search_all) {
+	$setupstring = '';
 	foreach ($fieldstosearchall as $key => $val) {
 		$fieldstosearchall[$key] = $langs->trans($val);
+		$setupstring .= $key."=".$val.";";
 	}
-	print '<div class="divsearchfieldfilter">'.$langs->trans("FilterOnInto", $sall).join(', ', $fieldstosearchall).'</div>';
+	print '<!-- Search done like if MYOBJECT_QUICKSEARCH_ON_FIELDS = '.$setupstring.' -->'."\n";
+	print '<div class="divsearchfieldfilter">'.$langs->trans("FilterOnInto", $search_all).join(', ', $fieldstosearchall).'</div>'."\n";
 }
 
 $moreforfilter = '';
+/*$moreforfilter.='<div class="divsearchfield">';
+ $moreforfilter.= $langs->trans('MyFilter') . ': <input type="text" name="search_myfield" value="'.dol_escape_htmltag($search_myfield).'">';
+ $moreforfilter.= '</div>';*/
 
-$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);
+$parameters = array();
+$reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+if (empty($reshook)) {
+	$moreforfilter .= $hookmanager->resPrint;
+} else {
+	$moreforfilter = $hookmanager->resPrint;
 }
 
-print '<div class="div-table-responsive">';
-print '<table class="tagtable nobottomiftotal liste'.($moreforfilter ? " listwithfilterbefore" : "").'">'."\n";
+if (!empty($moreforfilter)) {
+	print '<div class="liste_titre liste_titre_bydiv centpercent">';
+	print $moreforfilter;
+	$parameters = array();
+	$reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+	print $hookmanager->resPrint;
+	print '</div>';
+}
 
+$varpage = empty($contextpage) ? $_SERVER["PHP_SELF"] : $contextpage;
+$selectedfields = ($mode != 'kanban' ? $form->multiSelectArrayWithCheckbox('selectedfields', $arrayfields, $varpage, getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN', '')) : ''); // This also change content of $arrayfields
+$selectedfields .= (count($arrayofmassactions) ? $form->showCheckAddButtons('checkforselect', 1) : '');
 
-// Line for filters fields
-print '<tr class="liste_titre_filter">';
+print '<div class="div-table-responsive">'; // You can use div-table-responsive-no-min if you dont need reserved height for your table
+print '<table class="tagtable nobottomiftotal liste'.($moreforfilter ? " listwithfilterbefore" : "").'">'."\n";
 
+// Fields title search
+// --------------------------------------------------------------------
+print '<tr class="liste_titre_filter">';
+// Action column
+if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+	print '<td class="liste_titre center maxwidthsearch">';
+	$searchpicto = $form->showFilterButtons('left');
+	print $searchpicto;
+	print '</td>';
+}
 // Line numbering
 if (!empty($conf->global->MAIN_SHOW_TECHNICAL_ID)) {
 	print '<td class="liste_titre">&nbsp;</td>';
@@ -417,7 +493,7 @@ include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_input.tpl.php';
 
 // Fields from hook
 $parameters = array('arrayfields'=>$arrayfields);
-$reshook = $hookmanager->executeHooks('printFieldListOption', $parameters); // Note that $action and $object may have been modified by hook
+$reshook = $hookmanager->executeHooks('printFieldListOption', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 print $hookmanager->resPrint;
 // Date creation
 if (!empty($arrayfields['c.datec']['checked'])) {
@@ -431,52 +507,72 @@ if (!empty($arrayfields['c.tms']['checked'])) {
 }
 
 // Action column
-print '<td class="liste_titre right">';
-$searchpicto = $form->showFilterButtons();
-print $searchpicto;
-print '</td>';
-
-print "</tr>\n";
+if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+	print '<td class="liste_titre center maxwidthsearch">';
+	$searchpicto = $form->showFilterButtons();
+	print $searchpicto;
+	print '</td>';
+}
+print '</tr>'."\n";
 
+$totalarray = array();
+$totalarray['nbfield'] = 0;
 
+// Fields title label
+// --------------------------------------------------------------------
 print '<tr class="liste_titre">';
+// Action column
+if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+	print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], '', '', '', 'align="center"', $sortfield, $sortorder, 'maxwidthsearch ');
+	$totalarray['nbfield']++;
+}
 if (!empty($arrayfields['d.ref']['checked'])) {
 	print_liste_field_titre($arrayfields['d.ref']['label'], $_SERVER["PHP_SELF"], "c.rowid", $param, "", "", $sortfield, $sortorder);
+	$totalarray['nbfield']++;
 }
 if (!empty($arrayfields['d.fk_type']['checked'])) {
 	print_liste_field_titre($arrayfields['d.fk_type']['label'], $_SERVER["PHP_SELF"], "c.fk_type", $param, "", "", $sortfield, $sortorder);
+	$totalarray['nbfield']++;
 }
 if (!empty($arrayfields['d.lastname']['checked'])) {
 	print_liste_field_titre($arrayfields['d.lastname']['label'], $_SERVER["PHP_SELF"], "d.lastname", $param, "", "", $sortfield, $sortorder);
+	$totalarray['nbfield']++;
 }
 if (!empty($arrayfields['d.firstname']['checked'])) {
 	print_liste_field_titre($arrayfields['d.firstname']['label'], $_SERVER["PHP_SELF"], "d.firstname", $param, "", "", $sortfield, $sortorder);
+	$totalarray['nbfield']++;
 }
 if (!empty($arrayfields['d.login']['checked'])) {
 	print_liste_field_titre($arrayfields['d.login']['label'], $_SERVER["PHP_SELF"], "d.login", $param, "", "", $sortfield, $sortorder);
+	$totalarray['nbfield']++;
 }
 if (!empty($arrayfields['t.libelle']['checked'])) {
 	print_liste_field_titre($arrayfields['t.libelle']['label'], $_SERVER["PHP_SELF"], "c.note", $param, "", '', $sortfield, $sortorder);
+	$totalarray['nbfield']++;
 }
 if (!empty($arrayfields['d.bank']['checked'])) {
 	print_liste_field_titre($arrayfields['d.bank']['label'], $_SERVER["PHP_SELF"], "b.fk_account", $param, "", "", $sortfield, $sortorder);
+	$totalarray['nbfield']++;
 }
 if (!empty($arrayfields['c.dateadh']['checked'])) {
 	print_liste_field_titre($arrayfields['c.dateadh']['label'], $_SERVER["PHP_SELF"], "c.dateadh", $param, "", '', $sortfield, $sortorder, 'center nowraponall ');
+	$totalarray['nbfield']++;
 }
 if (!empty($arrayfields['c.datef']['checked'])) {
 	print_liste_field_titre($arrayfields['c.datef']['label'], $_SERVER["PHP_SELF"], "c.datef", $param, "", '', $sortfield, $sortorder, 'center nowraponall ');
+	$totalarray['nbfield']++;
 }
 if (!empty($arrayfields['d.amount']['checked'])) {
 	print_liste_field_titre($arrayfields['d.amount']['label'], $_SERVER["PHP_SELF"], "c.subscription", $param, "", '', $sortfield, $sortorder, 'right ');
+	$totalarray['nbfield']++;
 }
 
 // Extra fields
 include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_title.tpl.php';
 
 // 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
+$parameters = array('arrayfields'=>$arrayfields, 'param'=>$param, 'sortfield'=>$sortfield, 'sortorder'=>$sortorder, 'totalarray'=>&$totalarray);
+$reshook = $hookmanager->executeHooks('printFieldListTitle', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 print $hookmanager->resPrint;
 if (!empty($arrayfields['c.datec']['checked'])) {
 	print_liste_field_titre($arrayfields['c.datec']['label'], $_SERVER["PHP_SELF"], "c.datec", "", $param, 'align="center" class="nowrap"', $sortfield, $sortorder);
@@ -484,14 +580,25 @@ if (!empty($arrayfields['c.datec']['checked'])) {
 if (!empty($arrayfields['c.tms']['checked'])) {
 	print_liste_field_titre($arrayfields['c.tms']['label'], $_SERVER["PHP_SELF"], "c.tms", "", $param, 'align="center" class="nowrap"', $sortfield, $sortorder);
 }
-print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], '', '', '', 'align="center"', $sortfield, $sortorder, 'maxwidthsearch ');
-print "</tr>\n";
-
+// Action column
+if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+	print getTitleFieldOfList($selectedfields, 0, $_SERVER["PHP_SELF"], '', '', '', '', $sortfield, $sortorder, 'center maxwidthsearch ')."\n";
+	$totalarray['nbfield']++;
+}
+print '</tr>'."\n";
 
+// Loop on record
+// --------------------------------------------------------------------
+$i = 0;
+$savnbfield = $totalarray['nbfield'];
 $totalarray = array();
 $totalarray['nbfield'] = 0;
-while ($i < min($num, $limit)) {
-	$obj = $db->fetch_object($result);
+$imaxinloop = ($limit ? min($num, $limit) : $num);
+while ($i < $imaxinloop) {
+	$obj = $db->fetch_object($resql);
+	if (empty($obj)) {
+		break; // Should not happen
+	}
 
 	$subscription->ref = $obj->crowid;
 	$subscription->id = $obj->crowid;
@@ -519,8 +626,15 @@ while ($i < min($num, $limit)) {
 
 	if ($mode == 'kanban') {
 		if ($i == 0) {
-			print '<tr><td colspan="12">';
-			print '<div class="box-flex-container">';
+			print '<tr><td colspan="'.$savnbfield.'">';
+			print '<div class="box-flex-container kanban">';
+		}
+		// Output Kanban
+		if ($massactionbutton || $massaction) { // 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($object->id, $arrayofselected)) {
+				$selected = 1;
+			}
 		}
 
 		//fetch informations needs on this mode
@@ -533,14 +647,30 @@ while ($i < min($num, $limit)) {
 			$subscription->fk_bank = $accountstatic->getNomUrl(1);
 		}
 		// Output Kanban
-		print $subscription->getKanbanView('');
-		if ($i == (min($num, $limit) - 1)) {
+		print $subscription->getKanbanView('', array('selected' => in_array($object->id, $arrayofselected)));
+		if ($i == ($imaxinloop - 1)) {
 			print '</div>';
 			print '</td></tr>';
 		}
 	} else {
-		print '<tr class="oddeven">';
-
+		// Show here line of result
+		$j = 0;
+		print '<tr data-rowid="'.$object->id.'" class="oddeven">';
+		// Action column
+		if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+			print '<td class="nowrap center">';
+			if ($massactionbutton || $massaction) {   // 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($obj->crowid, $arrayofselected)) {
+					$selected = 1;
+				}
+				print '<input id="cb'.$obj->crowid.'" class="flat checkforselect" type="checkbox" name="toselect[]" value="'.$obj->crowid.'"'.($selected ? ' checked="checked"' : '').'>';
+			}
+			print '</td>';
+			if (!$i) {
+				$totalarray['nbfield']++;
+			}
+		}
 		// Ref
 		if (!empty($arrayfields['d.ref']['checked'])) {
 			print '<td>'.$subscription->getNomUrl(1).'</td>';
@@ -662,20 +792,22 @@ while ($i < min($num, $limit)) {
 			}
 		}
 		// Action column
-		print '<td class="center">';
-		if ($massactionbutton || $massaction) {   // 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($obj->crowid, $arrayofselected)) {
-				$selected = 1;
+		if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+			print '<td class="nowrap center">';
+			if ($massactionbutton || $massaction) {   // 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($obj->crowid, $arrayofselected)) {
+					$selected = 1;
+				}
+				print '<input id="cb'.$obj->crowid.'" class="flat checkforselect" type="checkbox" name="toselect[]" value="'.$obj->crowid.'"'.($selected ? ' checked="checked"' : '').'>';
+			}
+			print '</td>';
+			if (!$i) {
+				$totalarray['nbfield']++;
 			}
-			print '<input id="cb'.$obj->crowid.'" class="flat checkforselect" type="checkbox" name="toselect[]" value="'.$obj->crowid.'"'.($selected ? ' checked="checked"' : '').'>';
-		}
-		print '</td>';
-		if (!$i) {
-			$totalarray['nbfield']++;
 		}
 
-		print "</tr>\n";
+		print '</tr>'."\n";
 	}
 	$i++;
 }
@@ -692,19 +824,19 @@ if ($num == 0) {
 			$colspan++;
 		}
 	}
-	print '<tr><td colspan="'.$colspan.'" class="opacitymedium">'.$langs->trans("NoRecordFound").'</td></tr>';
+	print '<tr><td colspan="'.$colspan.'"><span class="opacitymedium">'.$langs->trans("NoRecordFound").'</span></td></tr>';
 }
 
 $db->free($resql);
 
-$parameters = array('sql' => $sql);
-$reshook = $hookmanager->executeHooks('printFieldListFooter', $parameters); // Note that $action and $object may have been modified by hook
+$parameters = array('arrayfields'=>$arrayfields, 'sql' => $sql);
+$reshook = $hookmanager->executeHooks('printFieldListFooter', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 print $hookmanager->resPrint;
 
-print "</table>";
-print '</div>';
-print '</form>';
+print '</table>'."\n";
+print '</div>'."\n";
 
+print '</form>'."\n";
 
 // End of page
 llxFooter();

+ 159 - 65
htdocs/adherents/type.php

@@ -37,10 +37,12 @@ require_once DOL_DOCUMENT_ROOT.'/adherents/class/adherent_type.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
 require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php';
 
+// Load translation files required by the page
 $langs->load("members");
 
 $rowid  = GETPOST('rowid', 'int');
 $action = GETPOST('action', 'aZ09');
+$massaction = GETPOST('massaction', 'alpha');
 $cancel = GETPOST('cancel', 'alpha');
 $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : str_replace('_', '', basename(dirname(__FILE__)).basename(__FILE__, '.php')); // To manage different context of search
 $backtopage = GETPOST('backtopage', 'alpha');
@@ -48,6 +50,7 @@ $mode = GETPOST('mode', 'alopha');
 
 $sall = GETPOST("sall", "alpha");
 $filter = GETPOST("filter", 'alpha');
+$search_ref = GETPOST('search_ref', 'alpha');
 $search_lastname = GETPOST('search_lastname', 'alpha');
 $search_login = GETPOST('search_login', 'alpha');
 $search_email = GETPOST('search_email', 'alpha');
@@ -55,13 +58,15 @@ $type = GETPOST('type', 'intcomma');
 $status = GETPOST('status', 'alpha');
 $optioncss = GETPOST('optioncss', 'alpha');
 
+// Load variable for pagination
 $limit = GETPOST('limit', 'int') ? GETPOST('limit', 'int') : $conf->liste_limit;
 $sortfield = GETPOST('sortfield', 'aZ09comma');
 $sortorder = GETPOST('sortorder', 'aZ09comma');
 $page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int');
-if (empty($page) || $page == -1) {
+if (empty($page) || $page < 0 || GETPOST('button_search', 'alpha') || GETPOST('button_removefilter', 'alpha')) {
+	// If $page is not defined, or '' or -1 or if we click on clear filters
 	$page = 0;
-}     // If $page is not defined, or '' or -1
+}
 $offset = $limit * $page;
 $pageprev = $page - 1;
 $pagenext = $page + 1;
@@ -84,17 +89,24 @@ $comment = GETPOST("comment", 'restricthtml');
 $mail_valid = GETPOST("mail_valid", 'restricthtml');
 $caneditamount = GETPOSTINT("caneditamount");
 
-// Security check
-$result = restrictedArea($user, 'adherent', $rowid, 'adherent_type');
-
+// Initialize technical objects
 $object = new AdherentType($db);
-
 $extrafields = new ExtraFields($db);
+$hookmanager->initHooks(array('membertypecard', 'globalcard'));
 
-// fetch optionals attributes and labels
+// Fetch optionals attributes and labels
 $extrafields->fetch_name_optionals_label($object->table_element);
 
+// Security check
+$result = restrictedArea($user, 'adherent', $rowid, 'adherent_type');
+
+
+/*
+ *	Actions
+ */
+
 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_ref = "";
 	$search_lastname = "";
 	$search_login = "";
 	$search_email = "";
@@ -102,14 +114,13 @@ if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter_x'
 	$sall = "";
 }
 
-
-// Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context
-$hookmanager->initHooks(array('membertypecard', 'globalcard'));
-
-
-/*
- *	Actions
- */
+if (GETPOST('cancel', 'alpha')) {
+	$action = 'list';
+	$massaction = '';
+}
+if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend') {
+	$massaction = '';
+}
 
 if ($cancel) {
 	$action = '';
@@ -289,6 +300,9 @@ if (!$rowid && $action != 'create' && $action != 'edit') {
 		print '<table class="tagtable liste'.($moreforfilter ? " listwithfilterbefore" : "").'">'."\n";
 
 		print '<tr class="liste_titre">';
+		if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+			print '<th>&nbsp;</th>';
+		}
 		print '<th>'.$langs->trans("Ref").'</th>';
 		print '<th>'.$langs->trans("Label").'</th>';
 		print '<th class="center">'.$langs->trans("MembersNature").'</th>';
@@ -297,7 +311,9 @@ if (!$rowid && $action != 'create' && $action != 'edit') {
 		print '<th class="center">'.$langs->trans("CanEditAmountShort").'</th>';
 		print '<th class="center">'.$langs->trans("VoteAllowed").'</th>';
 		print '<th class="center">'.$langs->trans("Status").'</th>';
-		print '<th>&nbsp;</th>';
+		if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+			print '<th>&nbsp;</th>';
+		}
 		print "</tr>\n";
 
 		$membertype = new AdherentType($db);
@@ -322,17 +338,22 @@ if (!$rowid && $action != 'create' && $action != 'edit') {
 			if ($mode == 'kanban') {
 				if ($i == 0) {
 					print '<tr><td colspan="12">';
-					print '<div class="box-flex-container">';
+					print '<div class="box-flex-container kanban">';
 				}
 				//output kanban
 				$membertype->label = $objp->label;
-				print $membertype->getKanbanView('');
+				print $membertype->getKanbanView('', array('selected' => in_array($object->id, $arrayofselected)));
 				if ($i == ($imaxinloop - 1)) {
 					print '</div>';
 					print '</td></tr>';
 				}
 			} else {
 				print '<tr class="oddeven">';
+				if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+					if ($user->rights->adherent->configurer) {
+						print '<td class="center"><a class="editfielda" href="'.$_SERVER["PHP_SELF"].'?action=edit&rowid='.$objp->rowid.'">'.img_edit().'</a></td>';
+					}
+				}
 				print '<td class="nowraponall">';
 				print $membertype->getNomUrl(1);
 				//<a href="'.$_SERVER["PHP_SELF"].'?rowid='.$objp->rowid.'">'.img_object($langs->trans("ShowType"),'group').' '.$objp->rowid.'</a>
@@ -352,10 +373,10 @@ if (!$rowid && $action != 'create' && $action != 'edit') {
 				print '<td class="center">'.yn($objp->caneditamount).'</td>';
 				print '<td class="center">'.yn($objp->vote).'</td>';
 				print '<td class="center">'.$membertype->getLibStatut(5).'</td>';
-				if ($user->rights->adherent->configurer) {
-					print '<td class="right"><a class="editfielda" href="'.$_SERVER["PHP_SELF"].'?action=edit&rowid='.$objp->rowid.'">'.img_edit().'</a></td>';
-				} else {
-					print '<td class="right">&nbsp;</td>';
+				if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+					if ($user->rights->adherent->configurer) {
+						print '<td class="right"><a class="editfielda" href="'.$_SERVER["PHP_SELF"].'?action=edit&rowid='.$objp->rowid.'">'.img_edit().'</a></td>';
+					}
 				}
 				print "</tr>";
 			}
@@ -370,7 +391,7 @@ if (!$rowid && $action != 'create' && $action != 'edit') {
 					$colspan++;
 				}
 			}*/
-			$colspan = 8;
+			$colspan = 9;
 			print '<tr><td colspan="'.$colspan.'" class="opacitymedium">'.$langs->trans("NoRecordFound").'</td></tr>';
 		}
 
@@ -413,7 +434,7 @@ if ($action == 'create') {
 	print $form->selectarray("morphy", $morphys, GETPOSTISSET("morphy") ? GETPOST("morphy", 'aZ09') : 'morphy');
 	print "</td></tr>";
 
-	print '<tr><td>'.$langs->trans("SubscriptionRequired").'</td><td>';
+	print '<tr><td>'.$form->textwithpicto($langs->trans("SubscriptionRequired"), $langs->trans("SubscriptionRequiredDesc")).'</td><td>';
 	print $form->selectyesno("subscription", 1, 1);
 	print '</td></tr>';
 
@@ -436,12 +457,12 @@ if ($action == 'create') {
 
 	print '<tr><td class="tdtop">'.$langs->trans("Description").'</td><td>';
 	require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
-	$doleditor = new DolEditor('comment', (GETPOSTISSET('comment') ? GETPOST('comment', 'restricthtml') : $object->note_public), '', 200, 'dolibarr_notes', '', false, true, empty($conf->fckeditor->enabled) ? false : $conf->fckeditor->enabled, 15, '90%');
+	$doleditor = new DolEditor('comment', (GETPOSTISSET('comment') ? GETPOST('comment', 'restricthtml') : $object->note_public), '', 200, 'dolibarr_notes', '', false, true, isModEnabled('fckeditor'), 15, '90%');
 	$doleditor->Create();
 
 	print '<tr><td class="tdtop">'.$langs->trans("WelcomeEMail").'</td><td>';
 	require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
-	$doleditor = new DolEditor('mail_valid', GETPOSTISSET('mail_valid') ? GETPOST('mail_valid') : $object->mail_valid, '', 250, 'dolibarr_notes', '', false, true, empty($conf->fckeditor->enabled) ? false : $conf->fckeditor->enabled, 15, '90%');
+	$doleditor = new DolEditor('mail_valid', GETPOSTISSET('mail_valid') ? GETPOST('mail_valid') : $object->mail_valid, '', 250, 'dolibarr_notes', '', false, true, isModEnabled('fckeditor'), 15, '90%');
 	$doleditor->Create();
 	print '</td></tr>';
 
@@ -489,7 +510,7 @@ if ($rowid > 0) {
 		print '<tr><td>'.$langs->trans("MembersNature").'</td><td class="valeur" >'.$object->getmorphylib($object->morphy).'</td>';
 		print '</tr>';
 
-		print '<tr><td class="titlefield">'.$langs->trans("SubscriptionRequired").'</td><td>';
+		print '<tr><td>'.$form->textwithpicto($langs->trans("SubscriptionRequired"), $langs->trans("SubscriptionRequiredDesc")).'</td><td>';
 		print yn($object->subscription);
 		print '</tr>';
 
@@ -508,7 +529,7 @@ if ($rowid > 0) {
 
 		print '<tr><td class="titlefield">'.$langs->trans("Duration").'</td><td colspan="2">'.$object->duration_value.'&nbsp;';
 		if ($object->duration_value > 1) {
-			$dur = array("i"=>$langs->trans("Minute"), "h"=>$langs->trans("Hours"), "d"=>$langs->trans("Days"), "w"=>$langs->trans("Weeks"), "m"=>$langs->trans("Months"), "y"=>$langs->trans("Years"));
+			$dur = array("i"=>$langs->trans("Minutes"), "h"=>$langs->trans("Hours"), "d"=>$langs->trans("Days"), "w"=>$langs->trans("Weeks"), "m"=>$langs->trans("Months"), "y"=>$langs->trans("Years"));
 		} elseif ($object->duration_value > 0) {
 			$dur = array("i"=>$langs->trans("Minute"), "h"=>$langs->trans("Hour"), "d"=>$langs->trans("Day"), "w"=>$langs->trans("Week"), "m"=>$langs->trans("Month"), "y"=>$langs->trans("Year"));
 		}
@@ -529,6 +550,7 @@ if ($rowid > 0) {
 
 		print dol_get_fiche_end();
 
+
 		/*
 		 * Buttons
 		 */
@@ -541,8 +563,16 @@ if ($rowid > 0) {
 		}
 
 		// Add
+		if ($object->morphy == 'phy') {
+			$morphy = 'phy';
+		} elseif ($object->morphy == 'mor') {
+			$morphy = 'mor';
+		} else {
+			$morphy = '';
+		}
+
 		if ($user->hasRight('adherent', 'configurer')&& !empty($object->status)) {
-			print '<div class="inline-block divButAction"><a class="butAction" href="card.php?action=create&token='.newToken().'&typeid='.$object->id.'&backtopage='.urlencode($_SERVER["PHP_SELF"].'?rowid='.$object->id).'">'.$langs->trans("AddMember").'</a></div>';
+			print '<div class="inline-block divButAction"><a class="butAction" href="card.php?action=create&token='.newToken().'&typeid='.$object->id.($morphy ? '&morphy='.urlencode($morphy) : '').'&backtopage='.urlencode($_SERVER["PHP_SELF"].'?rowid='.$object->id).'">'.$langs->trans("AddMember").'</a></div>';
 		} else {
 			print '<div class="inline-block divButAction"><a class="butActionRefused classfortooltip" href="#" title="'.dol_escape_htmltag($langs->trans("NoAddMember")).'">'.$langs->trans("AddMember").'</a></div>';
 		}
@@ -561,10 +591,13 @@ if ($rowid > 0) {
 
 		$now = dol_now();
 
-		$sql = "SELECT d.rowid, d.login, d.firstname, d.lastname, d.societe as company,";
+		$sql = "SELECT d.rowid, d.ref, d.entity, d.login, d.firstname, d.lastname, d.societe as company, d.fk_soc,";
 		$sql .= " d.datefin,";
-		$sql .= " d.email, d.fk_adherent_type as type_id, d.morphy, d.statut as status,";
+		$sql .= " d.email, d.photo, d.fk_adherent_type as type_id, d.morphy, d.statut as status,";
 		$sql .= " t.libelle as type, t.subscription, t.amount";
+
+		$sqlfields = $sql; // $sql fields to remove for count total
+
 		$sql .= " FROM ".MAIN_DB_PREFIX."adherent as d, ".MAIN_DB_PREFIX."adherent_type as t";
 		$sql .= " WHERE d.fk_adherent_type = t.rowid ";
 		$sql .= " AND d.entity IN (".getEntity('adherent').")";
@@ -580,6 +613,9 @@ if ($rowid > 0) {
 				$sql .= natural_search(array("d.firstname", "d.lastname"), GETPOST('search', 'alpha'));
 			}
 		}
+		if (!empty($search_ref)) {
+			$sql .= natural_search("d.ref", $search_ref);
+		}
 		if (!empty($search_lastname)) {
 			$sql .= natural_search(array("d.firstname", "d.lastname"), $search_lastname);
 		}
@@ -596,24 +632,32 @@ if ($rowid > 0) {
 			$sql .= " AND (datefin < '".$db->idate($now)."' AND t.subscription = 1)";
 		}
 
-		$sql .= " ".$db->order($sortfield, $sortorder);
-
 		// Count total nb of records
 		$nbtotalofrecords = '';
-		if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
-			$resql = $db->query($sql);
+		if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
+			/* The fast and low memory method to get and count full list converts the sql into a sql count */
+			$sqlforcount = preg_replace('/^'.preg_quote($sqlfields, '/').'/', 'SELECT COUNT(*) as nbtotalofrecords', $sql);
+			$sqlforcount = preg_replace('/GROUP BY .*$/', '', $sqlforcount);
+			$resql = $db->query($sqlforcount);
 			if ($resql) {
-				$nbtotalofrecords = $db->num_rows($result);
+				$objforcount = $db->fetch_object($resql);
+				$nbtotalofrecords = $objforcount->nbtotalofrecords;
 			} else {
 				dol_print_error($db);
 			}
-			if (($page * $limit) > $nbtotalofrecords) {	// if total resultset is smaller then paging size (filtering), goto and load page 0
+
+			if (($page * $limit) > $nbtotalofrecords) {	// if total resultset is smaller than the paging size (filtering), goto and load page 0
 				$page = 0;
 				$offset = 0;
 			}
+			$db->free($resql);
 		}
 
-		$sql .= " ".$db->plimit($conf->liste_limit + 1, $offset);
+		// Complete request and execute it with limit
+		$sql .= $db->order($sortfield, $sortorder);
+		if ($limit) {
+			$sql .= $db->plimit($limit + 1, $offset);
+		}
 
 		$resql = $db->query($sql);
 		if ($resql) {
@@ -648,9 +692,21 @@ if ($rowid > 0) {
 			}
 
 			$param = "&rowid=".urlencode($object->id);
+			if (!empty($mode)) {
+				$param .= '&mode='.urlencode($mode);
+			}
+			if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) {
+				$param .= '&contextpage='.urlencode($contextpage);
+			}
+			if ($limit > 0 && $limit != $conf->liste_limit) {
+				$param .= '&limit='.((int) $limit);
+			}
 			if (!empty($status)) {
 				$param .= "&status=".urlencode($status);
 			}
+			if (!empty($search_ref)) {
+				$param .= "&search_ref=".urlencode($search_ref);
+			}
 			if (!empty($search_lastname)) {
 				$param .= "&search_lastname=".urlencode($search_lastname);
 			}
@@ -671,12 +727,11 @@ if ($rowid > 0) {
 				print $langs->trans("Filter")." (".$langs->trans("Lastname").", ".$langs->trans("Firstname").", ".$langs->trans("EMail").", ".$langs->trans("Address")." ".$langs->trans("or")." ".$langs->trans("Town")."): ".$sall;
 			}
 
-			print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
+			print '<form method="POST" id="searchFormList" action="'.$_SERVER["PHP_SELF"].'" name="formfilter" autocomplete="off">';
 			print '<input type="hidden" name="token" value="'.newToken().'">';
-			print '<input class="flat" type="hidden" name="rowid" value="'.$object->id.'" size="12"></td>';
+			print '<input class="flat" type="hidden" name="rowid" value="'.$object->id.'"></td>';
 
-			print '<br>';
-			print_barre_liste('', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num, $nbtotalofrecords);
+			print_barre_liste('', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num, $nbtotalofrecords, 'generic', 0, '', '', $limit);
 
 			$moreforfilter = '';
 
@@ -686,35 +741,55 @@ if ($rowid > 0) {
 			// Fields title search
 			print '<tr class="liste_titre_filter">';
 
+			if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+				print '<td class="liste_titre center maxwidthsearch">';
+				$searchpicto = $form->showFilterButtons('left');
+				print $searchpicto;
+				print '</td>';
+			}
+
+			print '<td class="liste_titre left">';
+			print '<input class="flat maxwidth100" type="text" name="search_ref" value="'.dol_escape_htmltag($search_ref).'"></td>';
+
 			print '<td class="liste_titre left">';
-			print '<input class="flat" type="text" name="search_lastname" value="'.dol_escape_htmltag($search_lastname).'" size="12"></td>';
+			print '<input class="flat maxwidth100" type="text" name="search_lastname" value="'.dol_escape_htmltag($search_lastname).'"></td>';
 
 			print '<td class="liste_titre left">';
-			print '<input class="flat" type="text" name="search_login" value="'.dol_escape_htmltag($search_login).'" size="7"></td>';
+			print '<input class="flat maxwidth100" type="text" name="search_login" value="'.dol_escape_htmltag($search_login).'"></td>';
 
 			print '<td class="liste_titre">&nbsp;</td>';
 
 			print '<td class="liste_titre left">';
-			print '<input class="flat" type="text" name="search_email" value="'.dol_escape_htmltag($search_email).'" size="12"></td>';
+			print '<input class="flat maxwidth100" type="text" name="search_email" value="'.dol_escape_htmltag($search_email).'"></td>';
+
+			print '<td class="liste_titre">&nbsp;</td>';
 
 			print '<td class="liste_titre">&nbsp;</td>';
 
-			print '<td class="liste_titre right" colspan="2">';
-			print '<input type="image" class="liste_titre" src="'.DOL_URL_ROOT.'/theme/'.$conf->theme.'/img/search.png" name="button_search" value="'.dol_escape_htmltag($langs->trans("Search")).'" title="'.dol_escape_htmltag($langs->trans("Search")).'">';
-			print '&nbsp; ';
-			print '<input type="image" class="liste_titre" src="'.DOL_URL_ROOT.'/theme/'.$conf->theme.'/img/searchclear.png" name="button_removefilter" value="'.dol_escape_htmltag($langs->trans("RemoveFilter")).'" title="'.dol_escape_htmltag($langs->trans("RemoveFilter")).'">';
-			print '</td>';
+			if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+				print '<td class="liste_titre center nowraponall">';
+				print '<input type="image" class="liste_titre" src="'.DOL_URL_ROOT.'/theme/'.$conf->theme.'/img/search.png" name="button_search" value="'.dol_escape_htmltag($langs->trans("Search")).'" title="'.dol_escape_htmltag($langs->trans("Search")).'">';
+				print '&nbsp; ';
+				print '<input type="image" class="liste_titre" src="'.DOL_URL_ROOT.'/theme/'.$conf->theme.'/img/searchclear.png" name="button_removefilter" value="'.dol_escape_htmltag($langs->trans("RemoveFilter")).'" title="'.dol_escape_htmltag($langs->trans("RemoveFilter")).'">';
+				print '</td>';
+			}
 
 			print "</tr>\n";
 
 			print '<tr class="liste_titre">';
+			if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+				print_liste_field_titre("Action", $_SERVER["PHP_SELF"], "", $param, "", 'width="60" align="center"', $sortfield, $sortorder);
+			}
+			print_liste_field_titre("Ref", $_SERVER["PHP_SELF"], "d.ref", $param, "", "", $sortfield, $sortorder);
 			print_liste_field_titre("NameSlashCompany", $_SERVER["PHP_SELF"], "d.lastname", $param, "", "", $sortfield, $sortorder);
 			print_liste_field_titre("Login", $_SERVER["PHP_SELF"], "d.login", $param, "", "", $sortfield, $sortorder);
 			print_liste_field_titre("MemberNature", $_SERVER["PHP_SELF"], "d.morphy", $param, "", "", $sortfield, $sortorder);
 			print_liste_field_titre("EMail", $_SERVER["PHP_SELF"], "d.email", $param, "", "", $sortfield, $sortorder);
 			print_liste_field_titre("Status", $_SERVER["PHP_SELF"], "d.statut,d.datefin", $param, "", "", $sortfield, $sortorder);
 			print_liste_field_titre("EndSubscription", $_SERVER["PHP_SELF"], "d.datefin", $param, "", 'align="center"', $sortfield, $sortorder);
-			print_liste_field_titre("Action", $_SERVER["PHP_SELF"], "", $param, "", 'width="60" align="center"', $sortfield, $sortorder);
+			if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+				print_liste_field_titre("Action", $_SERVER["PHP_SELF"], "", $param, "", 'width="60" align="center"', $sortfield, $sortorder);
+			}
 			print "</tr>\n";
 
 			$adh = new Adherent($db);
@@ -725,14 +800,36 @@ if ($rowid > 0) {
 
 				$datefin = $db->jdate($objp->datefin);
 
+				$adh->id = $objp->rowid;
+				$adh->ref = $objp->ref;
+				$adh->login = $objp->login;
 				$adh->lastname = $objp->lastname;
 				$adh->firstname = $objp->firstname;
 				$adh->datefin = $datefin;
 				$adh->need_subscription = $objp->subscription;
 				$adh->statut = $objp->status;
+				$adh->email = $objp->email;
+				$adh->photo = $objp->photo;
 
 				print '<tr class="oddeven">';
 
+				// Actions
+				if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+					print '<td class="center">';
+					if ($user->hasRight('adherent', 'creer')) {
+						print '<a class="editfielda marginleftonly" href="card.php?rowid='.$objp->rowid.'&action=edit&token='.newToken().'&backtopage='.urlencode($_SERVER["PHP_SELF"].'?rowid='.$object->id).'">'.img_edit().'</a>';
+					}
+					if ($user->hasRight('adherent', 'supprimer')) {
+						print '<a class="marginleftonly" href="card.php?rowid='.$objp->rowid.'&action=resiliate&token='.newToken().'">'.img_picto($langs->trans("Resiliate"), 'disable.png').'</a>';
+					}
+					print "</td>";
+				}
+
+				// Ref
+				print "<td>";
+				print $adh->getNomUrl(-1, 0, 'card', 'ref', '', -1, 0, 1);
+				print "</td>\n";
+
 				// Lastname
 				if ($objp->company != '') {
 					print '<td><a href="card.php?rowid='.$objp->rowid.'">'.img_object($langs->trans("ShowMember"), "user", 'class="paddingright"').$adh->getFullName($langs, 0, -1, 20).' / '.dol_trunc($objp->company, 12).'</a></td>'."\n";
@@ -785,15 +882,16 @@ if ($rowid > 0) {
 				}
 
 				// Actions
-				print '<td class="center">';
-				if ($user->hasRight('adherent', 'creer')) {
-					print '<a class="editfielda marginleftonly" href="card.php?rowid='.$objp->rowid.'&action=edit&token='.newToken().'&backtopage='.urlencode($_SERVER["PHP_SELF"].'?rowid='.$object->id).'">'.img_edit().'</a>';
-				}
-				if ($user->hasRight('adherent', 'supprimer')) {
-					print '<a class="marginleftonly" href="card.php?rowid='.$objp->rowid.'&action=resiliate&token='.newToken().'">'.img_picto($langs->trans("Resiliate"), 'disable.png').'</a>';
+				if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+					print '<td class="center">';
+					if ($user->hasRight('adherent', 'creer')) {
+						print '<a class="editfielda marginleftonly" href="card.php?rowid='.$objp->rowid.'&action=edit&token='.newToken().'&backtopage='.urlencode($_SERVER["PHP_SELF"].'?rowid='.$object->id).'">'.img_edit().'</a>';
+					}
+					if ($user->hasRight('adherent', 'supprimer')) {
+						print '<a class="marginleftonly" href="card.php?rowid='.$objp->rowid.'&action=resiliate&token='.newToken().'">'.img_picto($langs->trans("Resiliate"), 'disable.png').'</a>';
+					}
+					print "</td>";
 				}
-				print "</td>";
-
 				print "</tr>\n";
 				$i++;
 			}
@@ -805,10 +903,6 @@ if ($rowid > 0) {
 			print "</table>\n";
 			print '</div>';
 			print '</form>';
-
-			if ($num > $limit) {
-				print_barre_liste('', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num, $nbtotalofrecords, '');
-			}
 		} else {
 			dol_print_error($db);
 		}
@@ -877,12 +971,12 @@ if ($rowid > 0) {
 
 		print '<tr><td class="tdtop">'.$langs->trans("Description").'</td><td>';
 		require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
-		$doleditor = new DolEditor('comment', $object->note_public, '', 220, 'dolibarr_notes', '', false, true, empty($conf->fckeditor->enabled) ? false : $conf->fckeditor->enabled, 15, '90%');
+		$doleditor = new DolEditor('comment', $object->note_public, '', 220, 'dolibarr_notes', '', false, true, isModEnabled('fckeditor'), 15, '90%');
 		$doleditor->Create();
 		print "</td></tr>";
 
 		print '<tr><td class="tdtop">'.$langs->trans("WelcomeEMail").'</td><td>';
-		$doleditor = new DolEditor('mail_valid', $object->mail_valid, '', 280, 'dolibarr_notes', '', false, true, empty($conf->fckeditor->enabled) ? false : $conf->fckeditor->enabled, 15, '90%');
+		$doleditor = new DolEditor('mail_valid', $object->mail_valid, '', 280, 'dolibarr_notes', '', false, true, isModEnabled('fckeditor'), 15, '90%');
 		$doleditor->Create();
 		print "</td></tr>";
 

+ 1 - 1
htdocs/adherents/type_translation.php

@@ -289,7 +289,7 @@ if ($action == 'create' && $user->hasRight('adherent', 'configurer')) {
 	print '</td></tr>';
 	print '<tr><td class="tdtop fieldrequired">'.$langs->trans('Label').'</td><td><input name="libelle" class="minwidth300" value="'.dol_escape_htmltag(GETPOST("libelle", 'alphanohtml')).'"></td></tr>';
 	print '<tr><td class="tdtop">'.$langs->trans('Description').'</td><td>';
-	$doleditor = new DolEditor('desc', '', '', 160, 'dolibarr_notes', '', false, true, empty($conf->fckeditor->enabled) ? false : $conf->fckeditor->enabled, ROWS_3, '90%');
+	$doleditor = new DolEditor('desc', '', '', 160, 'dolibarr_notes', '', false, true, isModEnabled('fckeditor'), ROWS_3, '90%');
 	$doleditor->Create();
 	print '</td></tr>';
 

+ 1 - 0
htdocs/admin/accounting.php

@@ -55,6 +55,7 @@ $error = 0;
 
 $title = $langs->trans("ConfigAccountingExpert");
 $help_url = '';
+
 llxHeader('', $title, $help_url);
 
 $linkback = '<a href="'.DOL_URL_ROOT.'/admin/modules.php?restore_lastsearch_values=1">'.$langs->trans("BackToModuleList").'</a>';

+ 7 - 3
htdocs/admin/agenda.php

@@ -115,7 +115,7 @@ if ($action == "save" && empty($cancel)) {
 
 // $wikihelp = 'EN:Module_Agenda_En|FR:Module_Agenda|ES:Módulo_Agenda';
 
-$help_url = 'EN:Module_Agenda_En|FR:Module_Agenda|ES:Módulo_Agenda';
+$help_url = 'EN:Module_Agenda_En|FR:Module_Agenda|ES:Módulo_Agenda|DE:Modul_Terminplanung';
 
 llxHeader('', $langs->trans("AgendaSetup"), $help_url);
 
@@ -161,7 +161,7 @@ if (!empty($triggers)) {
 			$module = 'fournisseur';
 		}
 		if ($module == 'shipping') {
-			$module = 'expedition_bon';
+			$module = 'expedition';
 		}
 		if ($module == 'member') {
 			$module = 'adherent';
@@ -194,6 +194,10 @@ if (!empty($triggers)) {
 			if ($trigger['code'] == 'FICHINTER_CLASSIFY_UNBILLED' && empty($conf->global->FICHINTER_CLASSIFY_BILLED)) {
 				continue;
 			}
+			if ($trigger['code'] == 'ACTION_CREATE') {
+				// This is the trigger to add an event, enabling it will create infinite loop
+				continue;
+			}
 
 			if ($search_event === '' || preg_match('/'.preg_quote($search_event, '/').'/i', $trigger['code'])) {
 				print '<!-- '.$trigger['position'].' -->';
@@ -202,7 +206,7 @@ if (!empty($triggers)) {
 				print '<td>'.$trigger['label'].'</td>';
 				print '<td class="right" width="40">';
 				$key = 'MAIN_AGENDA_ACTIONAUTO_'.$trigger['code'];
-				$value = $conf->global->$key;
+				$value = getDolGlobalInt($key);
 				print '<input class="oddeven" type="checkbox" name="'.$key.'" value="1"'.((($action == 'selectall' || $value) && $action != "selectnone") ? ' checked' : '').'>';
 				print '</td></tr>'."\n";
 			}

+ 1 - 1
htdocs/admin/agenda_extrafields.php

@@ -72,7 +72,7 @@ require DOL_DOCUMENT_ROOT.'/core/actions_extrafields.inc.php';
 
 $textobject = $langs->transnoentitiesnoconv("Agenda");
 
-$wikihelp = 'EN:Module_Agenda_En|FR:Module_Agenda|ES:Módulo_Agenda';
+$wikihelp = 'EN:Module_Agenda_En|FR:Module_Agenda|ES:Módulo_Agenda|DE:Modul_Terminplanung';
 llxHeader('', $langs->trans("AgendaSetup"), $wikihelp);
 
 $linkback = '<a href="'.DOL_URL_ROOT.'/admin/modules.php?restore_lastsearch_values=1">'.$langs->trans("BackToModuleList").'</a>';

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません