فهرست منبع

Merge branch '18.0' into 18.0-mmi

Mathieu Moulin 1 سال پیش
والد
کامیت
953eee10d3
100فایلهای تغییر یافته به همراه5524 افزوده شده و 6354 حذف شده
  1. 28 0
      .github/workflows/code_quality_qodana.yml
  2. 38 0
      .github/workflows/doxygen-gh-pages.yml.disabled
  3. 5 0
      .github/workflows/exakat.yml.disabled
  4. 21 12
      .github/workflows/github_ci_php71_pgsql.yml.disabled
  5. 9 4
      .github/workflows/github_ci_php81_mysql.yml.disabled
  6. 30 0
      .github/workflows/phpcs.yml
  7. 44 0
      .github/workflows/phpcsfixer.yml.disabled
  8. 2 1
      .github/workflows/stale-issues-safe.yml
  9. 11 1
      .gitignore
  10. 6 0
      .mailmap
  11. 86 81
      .scrutinizer.yml
  12. 15 21
      .travis.yml
  13. 10 8
      COPYRIGHT
  14. 484 10
      ChangeLog
  15. 1 2
      README.md
  16. 4 3
      SECURITY.md
  17. 2 2
      build/debian/patches/use-etc-dolibarr-conf.patch
  18. 3 3
      build/docker/Dockerfile
  19. 3 3
      build/docker/README.md
  20. 5 3
      build/docker/docker-compose.yml
  21. 5 5
      build/docker/docker-run.sh
  22. 3 1
      build/doxygen/dolibarr-doxygen.doxyfile
  23. 22 22
      build/exe/doliwamp/Languages/MyEnglish.isl
  24. 16 29
      build/exe/doliwamp/index.php.install
  25. 1 0
      build/phpstan/bootstrap.php
  26. 2 2
      build/rpm/dolibarr-forrpm.patch
  27. 10 10
      build/travis-ci/apache.conf
  28. 10 12
      composer.json.disabled
  29. 1 1
      dev/dolibarr_changes.txt
  30. 2 2
      dev/initdata/purge-data.php
  31. 0 28
      dev/initdemo/README
  32. 32 0
      dev/initdemo/README.md
  33. 2 2
      dev/initdemo/initdemopassword.sh
  34. 0 4497
      dev/initdemo/mysqldump_dolibarr_17.0.0.sql
  35. 57 0
      dev/initdemo/mysqldump_dolibarr_18.0.0.sql
  36. 0 0
      dev/initdemo/mysqldump_dolibarr_3.5.0.sql
  37. 1 2
      dev/resources/iso-normes/qr-bar-codes/QR code for invoices.txt
  38. 134 101
      dev/resources/iso-normes/qr-bar-codes/barcode_EAN13.txt
  39. 40 28
      dev/setup/apache/virtualhost
  40. 1 1
      dev/setup/fail2ban/filter.d/web-dolibarr-rulesbruteforce.conf
  41. 24 7
      dev/setup/fail2ban/jail.local
  42. 5 4
      dev/setup/git/hooks/pre-commit
  43. 14 0
      dev/setup/qodana/README.md
  44. 150 0
      dev/tools/codespell/codespell-lines-ignore.txt
  45. 13 13
      dev/tools/dolibarr-postgres2mysql.php
  46. 20 0
      dev/tools/fixhasRights.sh
  47. 0 0
      dev/tools/fixnotabfiles.sh
  48. 0 0
      dev/tools/fixsnakeCaseToCamelCase.sh
  49. 2 2
      dev/tools/github_commits_perversion.sh
  50. 7 3
      dev/tools/github_lines_perusers.sh
  51. 22 21
      dev/tools/spider.php
  52. 1 0
      dev/tools/test/namespacemig/aaa.class.php
  53. 2 0
      dev/tools/test/namespacemig/bbb.class.php
  54. 1 1
      dev/tools/test/namespacemig/bbb.php
  55. 4 3
      dev/tools/test/testtcpdf.php
  56. 7 6
      dev/tools/test/testutf.php
  57. 1 1
      dev/translation/txpull.sh
  58. BIN
      doc/images/dolibarr_screenshot12_1280x800.jpg
  59. BIN
      doc/images/dolibarr_screenshot1_1280x800.jpg
  60. BIN
      doc/images/dolibarr_screenshot2_1280x800.jpg
  61. BIN
      doc/images/dolibarr_screenshot4_1280x800.jpg
  62. BIN
      doc/images/dolibarr_screenshot5_1280x800.jpg
  63. 2 1
      doc/user/README
  64. 82 37
      htdocs/accountancy/admin/account.php
  65. 4 3
      htdocs/accountancy/admin/accountmodel.php
  66. 6 6
      htdocs/accountancy/admin/card.php
  67. 35 14
      htdocs/accountancy/admin/categories.php
  68. 180 107
      htdocs/accountancy/admin/categories_list.php
  69. 19 2
      htdocs/accountancy/admin/defaultaccounts.php
  70. 16 20
      htdocs/accountancy/admin/fiscalyear.php
  71. 44 69
      htdocs/accountancy/admin/index.php
  72. 5 4
      htdocs/accountancy/admin/journals_list.php
  73. 59 29
      htdocs/accountancy/admin/productaccount.php
  74. 110 44
      htdocs/accountancy/admin/subaccount.php
  75. 275 111
      htdocs/accountancy/bookkeeping/balance.php
  76. 2 2
      htdocs/accountancy/bookkeeping/card.php
  77. 1404 0
      htdocs/accountancy/bookkeeping/export.php
  78. 79 288
      htdocs/accountancy/bookkeeping/list.php
  79. 75 31
      htdocs/accountancy/bookkeeping/listbyaccount.php
  80. 74 61
      htdocs/accountancy/class/accountancycategory.class.php
  81. 543 251
      htdocs/accountancy/class/accountancyexport.class.php
  82. 5 0
      htdocs/accountancy/class/accountancysystem.class.php
  83. 22 15
      htdocs/accountancy/class/accountingaccount.class.php
  84. 6 42
      htdocs/accountancy/class/accountingjournal.class.php
  85. 276 0
      htdocs/accountancy/class/api_accountancy.class.php
  86. 130 95
      htdocs/accountancy/class/bookkeeping.class.php
  87. 1 1
      htdocs/accountancy/closure/index.php
  88. 45 38
      htdocs/accountancy/customer/index.php
  89. 12 4
      htdocs/accountancy/customer/lines.php
  90. 12 5
      htdocs/accountancy/customer/list.php
  91. 2 2
      htdocs/accountancy/expensereport/card.php
  92. 42 7
      htdocs/accountancy/expensereport/index.php
  93. 12 4
      htdocs/accountancy/expensereport/lines.php
  94. 15 9
      htdocs/accountancy/expensereport/list.php
  95. 5 1
      htdocs/accountancy/index.php
  96. 36 25
      htdocs/accountancy/journal/bankjournal.php
  97. 5 3
      htdocs/accountancy/journal/expensereportsjournal.php
  98. 155 28
      htdocs/accountancy/journal/purchasesjournal.php
  99. 295 33
      htdocs/accountancy/journal/sellsjournal.php
  100. 15 4
      htdocs/accountancy/journal/variousjournal.php

+ 28 - 0
.github/workflows/code_quality_qodana.yml

@@ -0,0 +1,28 @@
+name: Qodana
+on:
+  schedule:
+  - cron: "0 20 * * 1,3,5"
+  workflow_dispatch:
+    branches:
+      - develop
+#  push:
+#    branches:
+#      - develop
+
+permissions:
+  contents: read
+
+jobs:
+  qodana:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          fetch-depth: 1
+          #php-version: '7.1'
+      - name: 'Qodana Scan'
+        uses: JetBrains/qodana-action@v2023.1.5
+        #with:
+        #  php-version: '7.1'
+        env:
+          QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}

+ 38 - 0
.github/workflows/doxygen-gh-pages.yml.disabled

@@ -0,0 +1,38 @@
+# See syntax file on https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions
+name: Doxygen
+on:
+  schedule:
+  - cron: "0 15 * * *"
+  workflow_dispatch:
+    branches:
+      - develop
+permissions:
+  contents: write
+  
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Build
+        uses: DenverCoder1/doxygen-github-pages-action@v1.2.0
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          branch: gh-pages
+          folder: build/html
+          config_file: build/doxygen/dolibarr-doxygen.doxyfile
+
+#      - name: Deploy
+#        uses: JamesIves/github-pages-deploy-action@v4
+#        with:
+#          branch: gh-pages
+#          folder: build/html # The folder the action should deploy.
+#          target-folder: docs/html2
+          
+#jobs:
+#  doxygen:
+#    runs-on: ubuntu-latest
+#    steps:
+#      - name: 'Doxygen'
+#        uses: mattnotmitt/doxygen-action@1.9.5
+#        with:
+#          doxyfile-path: build/doxygen

+ 5 - 0
.github/workflows/exakat.yml → .github/workflows/exakat.yml.disabled

@@ -4,6 +4,9 @@ name: "Exakat analysis"
 on:
   schedule:
   - cron: "0 20 * * *"
+  workflow_dispatch:
+    branches:
+      - develop
 
 permissions:
   contents: read
@@ -13,6 +16,8 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@v3
+      with:
+        fetch-depth: 1
     - name: Exakat
       uses: docker://exakat/exakat-ga
       with:

+ 21 - 12
.github/workflows/github_ci_php71_pgsql.yml.disabled

@@ -116,18 +116,19 @@ jobs:
         mysql --version | head -
         mysql --host 127.0.0.1 --port 32574 -uroot -ppassword -e "SELECT VERSION();"  | head -
         mysql --host 127.0.0.1 --port 32574 -uroot -ppassword -e "SHOW DATABASES"
+        
         echo "Drop and create database"
         mysql --host 127.0.0.1 --port 32574 -uroot -ppassword -e 'DROP DATABASE IF EXISTS travis;'
         mysql --host 127.0.0.1 --port 32574 -uroot -ppassword -e "CREATE DATABASE IF NOT EXISTS travis CHARACTER SET = 'utf8';"
         mysql --host 127.0.0.1 --port 32574 -uroot -ppassword -e "CREATE USER 'travis'@'127.0.0.1' IDENTIFIED BY 'password';"
         mysql --host 127.0.0.1 --port 32574 -uroot -ppassword -e 'GRANT ALL PRIVILEGES ON travis.* TO travis@127.0.0.1;'
         mysql --host 127.0.0.1 --port 32574 -uroot -ppassword -e 'FLUSH PRIVILEGES;'
+        
         mysql --host 127.0.0.1 --port 32574 -uroot -ppassword -D travis < dev/initdemo/mysqldump_dolibarr_3.5.0.sql  
-        mysql --host 127.0.0.1 --port 32574 -uroot -ppassword -D travis -e "UPDATE llx_const set value = '0666' WHERE name = 'MAIN_UMASK';" 
         
-        #psql -c 'create database travis;' -U postgres
-        #psql travis < dev/initdemo/mysqldump_dolibarr_3.5.0.sql
-        #pgloader mysql://root:pass@127.0.0.1:32574/dolibarr_src postgresql://dolibarrowner:dolibarrownerpass@127.0.0.1/dolibarr_dest
+        mysql --host 127.0.0.1 --port 32574 -uroot -ppassword -D travis -e "UPDATE llx_const set value = '0666' WHERE name = 'MAIN_UMASK';" 
+
+        echo "Init postgresql database"        
         ps fauxww | grep postgres
         ls /etc/postgresql/14/main/
         sudo chmod -R a+rwx /etc/postgresql/14/main/pg_hba.conf
@@ -143,10 +144,12 @@ jobs:
 
         sudo mkdir -p /tmp/pgloader
         sudo chmod -R a+rwx /tmp/pgloader/
+        
         echo sudo pgloader mysql://root:password@127.0.0.1:32574/travis postgresql://postgres:postgres@127.0.0.1:5432/travis
         sudo pgloader mysql://root:password@127.0.0.1:32574/travis postgresql://postgres:postgres@127.0.0.1:5432/travis
         echo 'ALTER SEQUENCE llx_accountingaccount_rowid_seq RENAME TO llx_accounting_account_rowid_seq' | psql postgresql://postgres:postgres@127.0.0.1:5432/travis
         echo 'ALTER SEQUENCE llx_accounting_account_rowid_seq RESTART WITH 1000001;' | psql postgresql://postgres:postgres@127.0.0.1:5432/travis
+
         # Create pgsql compatibility functions
         psql postgresql://postgres:postgres@127.0.0.1:5432/travis < htdocs/install/pgsql/functions/functions.sql
 
@@ -160,11 +163,11 @@ jobs:
         echo '$'dolibarr_main_data_root=\'/var/www/html/documents\'';' >> $CONF_FILE
         echo '$'dolibarr_main_db_host=\'127.0.0.1\'';' >> $CONF_FILE
         echo '$'dolibarr_main_db_name=\'travis\'';' >> $CONF_FILE
-        echo '$'dolibarr_main_db_user=\'root\'';' >> $CONF_FILE
-        echo '$'dolibarr_main_db_pass=\'password\'';' >> $CONF_FILE
+        echo '$'dolibarr_main_db_user=\'postgres\'';' >> $CONF_FILE
+        echo '$'dolibarr_main_db_pass=\'postgres\'';' >> $CONF_FILE
         echo '$'dolibarr_main_instance_unique_id=\'travis1234567890\'';' >> $CONF_FILE
-        echo '$'dolibarr_main_db_type=\'mysqli\'';' >> $CONF_FILE
-        echo '$'dolibarr_main_db_port=\'32574\'';' >> $CONF_FILE
+        echo '$'dolibarr_main_db_type=\'pgsql\'';' >> $CONF_FILE
+        echo '$'dolibarr_main_db_port=\'5432\'';' >> $CONF_FILE
         echo '$'dolibarr_main_authentication=\'dolibarr\'';' >> $CONF_FILE
         cat $CONF_FILE
     - name: Generate install.forced.php file to test installation
@@ -175,13 +178,11 @@ jobs:
         set +e
         echo '<?php' > $INSTALL_FORCED_FILE
         echo '$'force_install_noedit=2';' >> $INSTALL_FORCED_FILE
-        #echo '$'force_install_type=\'mysqli\'';' >> $INSTALL_FORCED_FILE
         echo '$'force_install_type=\'pgsql\'';' >> $INSTALL_FORCED_FILE
         echo '$'force_install_dbserver=\'127.0.0.1\'';' >> $INSTALL_FORCED_FILE
         echo '$'force_install_database=\'travis\'';' >> $INSTALL_FORCED_FILE
-        echo '$'force_install_databaselogin=\'root\'';' >> $INSTALL_FORCED_FILE
-        echo '$'force_install_databasepass=\'password\'';' >> $INSTALL_FORCED_FILE
-        #echo '$'force_install_port=\'32574\'';' >> $INSTALL_FORCED_FILE
+        echo '$'force_install_databaselogin=\'postgres\'';' >> $INSTALL_FORCED_FILE
+        echo '$'force_install_databasepass=\'postgres\'';' >> $INSTALL_FORCED_FILE
         echo '$'force_install_port=\'5432\'';' >> $INSTALL_FORCED_FILE
         echo '$'force_install_prefix=\'llx_\'';' >> $INSTALL_FORCED_FILE
         echo '$'force_install_createdatabase=false';' >> $INSTALL_FORCED_FILE
@@ -260,6 +261,7 @@ jobs:
         sudo chmod -R a+rwx /var/www
         ls -l /var/www
         cd /var/www/html/install
+
         echo Execute upgrade, upgrade2 and step5 for each major version
         php upgrade.php 3.5.0 3.6.0 ignoredbversion > $GITHUB_WORKSPACE/upgrade350360.log
         php upgrade2.php 3.5.0 3.6.0 > $GITHUB_WORKSPACE/upgrade350360-2.log
@@ -285,6 +287,10 @@ jobs:
         php upgrade.php 6.0.0 7.0.0 ignoredbversion > $GITHUB_WORKSPACE/upgrade600700.log
         php upgrade2.php 6.0.0 7.0.0 > $GITHUB_WORKSPACE/upgrade600700-2.log
         php step5.php 6.0.0 7.0.0 > $GITHUB_WORKSPACE/upgrade600700-3.log
+        
+        echo "\dt llx_c_paiement" | psql 'postgresql://postgres:postgres@127.0.0.1:5432/travis'
+        echo "\dt llx_c_payment_term" | psql 'postgresql://postgres:postgres@127.0.0.1:5432/travis'
+
         php upgrade.php 7.0.0 8.0.0 ignoredbversion > $GITHUB_WORKSPACE/upgrade700800.log
         php upgrade2.php 7.0.0 8.0.0 > $GITHUB_WORKSPACE/upgrade700800-2.log
         php step5.php 7.0.0 8.0.0 > $GITHUB_WORKSPACE/upgrade700800-3.log
@@ -315,6 +321,9 @@ jobs:
         php upgrade.php 16.0.0 17.0.0 ignoredbversion > $GITHUB_WORKSPACE/upgrade16001700.log
         php upgrade2.php 16.0.0 17.0.0 > $GITHUB_WORKSPACE/upgrade16001700-2.log
         php step5.php 16.0.0 17.0.0 > $GITHUB_WORKSPACE/upgrade16001700-3.log
+        php upgrade.php 17.0.0 18.0.0 ignoredbversion > $GITHUB_WORKSPACE/upgrade17001800.log
+        php upgrade2.php 17.0.0 18.0.0 > $GITHUB_WORKSPACE/upgrade17001800-2.log
+        php step5.php 17.0.0 18.0.0 > $GITHUB_WORKSPACE/upgrade17001800-3.log
         
     - name: Result of migration scripts
       if: always()

+ 9 - 4
.github/workflows/github_ci_php81_mysql.yml.disabled

@@ -46,6 +46,8 @@ jobs:
         sudo update-alternatives --set php /usr/bin/php8.1
         php -i | head -
         
+        cd $GITHUB_WORKSPACE
+        ls $GITHUB_WORKSPACE
         composer -n require --ignore-platform-reqs phpunit/phpunit ^8 \
           php-parallel-lint/php-parallel-lint ^1.2 \
           php-parallel-lint/php-console-highlighter ^0 \
@@ -94,14 +96,18 @@ jobs:
         mysql --version | head -
         mysql --host 127.0.0.1 --port 32574 -uroot -ppassword -e "SELECT VERSION();"  | head -
         mysql --host 127.0.0.1 --port 32574 -uroot -ppassword -e "SHOW DATABASES"
+        
         echo "Drop and create database"
         mysql --host 127.0.0.1 --port 32574 -uroot -ppassword -e 'DROP DATABASE IF EXISTS travis;'
         mysql --host 127.0.0.1 --port 32574 -uroot -ppassword -e "CREATE DATABASE IF NOT EXISTS travis CHARACTER SET = 'utf8';"
         mysql --host 127.0.0.1 --port 32574 -uroot -ppassword -e "CREATE USER 'travis'@'127.0.0.1' IDENTIFIED BY 'password';"
         mysql --host 127.0.0.1 --port 32574 -uroot -ppassword -e 'GRANT ALL PRIVILEGES ON travis.* TO travis@127.0.0.1;'
         mysql --host 127.0.0.1 --port 32574 -uroot -ppassword -e 'FLUSH PRIVILEGES;'
+        
         mysql --host 127.0.0.1 --port 32574 -uroot -ppassword -D travis < dev/initdemo/mysqldump_dolibarr_3.5.0.sql  
+
         mysql --host 127.0.0.1 --port 32574 -uroot -ppassword -D travis -e "UPDATE llx_const set value = '0666' WHERE name = 'MAIN_UMASK';" 
+
     - name: Generate Dolibarr conf file
       run: |
         export CONF_FILE=$GITHUB_WORKSPACE/htdocs/conf/conf.php
@@ -127,16 +133,12 @@ jobs:
         set +e
         echo '<?php' > $INSTALL_FORCED_FILE
         echo '$'force_install_noedit=2';' >> $INSTALL_FORCED_FILE
-        # For mysql
         echo '$'force_install_type=\'mysqli\'';' >> $INSTALL_FORCED_FILE
-        #echo '$'force_install_type=\'pgsql\'';' >> $INSTALL_FORCED_FILE
         echo '$'force_install_dbserver=\'127.0.0.1\'';' >> $INSTALL_FORCED_FILE
         echo '$'force_install_database=\'travis\'';' >> $INSTALL_FORCED_FILE
         echo '$'force_install_databaselogin=\'root\'';' >> $INSTALL_FORCED_FILE
         echo '$'force_install_databasepass=\'password\'';' >> $INSTALL_FORCED_FILE
-        # For mysql
         echo '$'force_install_port=\'32574\'';' >> $INSTALL_FORCED_FILE
-        #echo '$'force_install_port=\'5432\'';' >> $INSTALL_FORCED_FILE
         echo '$'force_install_prefix=\'llx_\'';' >> $INSTALL_FORCED_FILE
         echo '$'force_install_createdatabase=false';' >> $INSTALL_FORCED_FILE
         echo '$'force_install_createuser=false';' >> $INSTALL_FORCED_FILE
@@ -269,6 +271,9 @@ jobs:
         php upgrade.php 16.0.0 17.0.0 ignoredbversion > $GITHUB_WORKSPACE/upgrade16001700.log
         php upgrade2.php 16.0.0 17.0.0 > $GITHUB_WORKSPACE/upgrade16001700-2.log
         php step5.php 16.0.0 17.0.0 > $GITHUB_WORKSPACE/upgrade16001700-3.log
+        php upgrade.php 17.0.0 18.0.0 ignoredbversion > $GITHUB_WORKSPACE/upgrade17001800.log
+        php upgrade2.php 17.0.0 18.0.0 > $GITHUB_WORKSPACE/upgrade17001800-2.log
+        php step5.php 17.0.0 18.0.0 > $GITHUB_WORKSPACE/upgrade17001800-3.log
         
     - name: Result of migration scripts
       if: always()

+ 30 - 0
.github/workflows/phpcs.yml

@@ -0,0 +1,30 @@
+name: "PHPCS"
+
+on:
+  pull_request:
+    paths:
+      - "**.php"
+      - "phpcs.xml"
+      - ".github/workflows/phpcs.yml"
+
+jobs:
+  phpcs:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          fetch-depth: 50 # important!
+
+      # we may use whatever way to install phpcs, just specify the path on the next step
+      # however, curl seems to be the fastest
+      - name: Install PHP_CodeSniffer
+        run: |
+          curl -OL https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar
+          php phpcs.phar --version
+
+      - uses: thenabeel/action-phpcs@v8
+        with:
+          files: "**.php" # you may customize glob as needed
+          phpcs_path: php phpcs.phar
+          standard: dev/setup/codesniffer/ruleset.xml
+          fail_on_warnings: false

+ 44 - 0
.github/workflows/phpcsfixer.yml.disabled

@@ -0,0 +1,44 @@
+name: GitHub CI PHPCS and PHPCBF
+
+on: push
+#on: 
+#  push:
+#    paths:
+#    - '**.php'
+
+jobs:
+  #filesChanged:
+  #  uses: ./.github/workflows/files_changed.yaml
+  #  with:
+  #    folder_path: .*
+      
+  linter_name:
+    name: Run & fix PHP Code Sniffer
+    runs-on: ubuntu-latest
+    #needs: filesChanged
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          fetch-depth: 10
+
+      - name: echo Get list of all changed files
+        run: |
+          #echo ${{ needs.filesChanged.outputs.all_changed_files }} 
+          #echo boolean_output=${{ needs.filesChanged.outputs.boolean_output }}
+          echo github.head_ref=${{github.head_ref}}
+          echo github.base_ref=${{github.base_ref}}
+          echo github.ref_name=${{github.ref_name}}
+
+      - uses: eldy/phpcsfixer-action@master
+        with:
+          github_token: ${{ secrets.github_token }}
+          use_default_configuration_file: false
+          phpcs_standard: 'dev/setup/codesniffer/ruleset.xml'
+          phpcs_head_ref: ${{github.head_ref}}
+          phpcs_base_ref: ${{github.base_ref}}
+          phpcs_ref_name: ${{github.ref_name}}
+          phpcs_github_event_name: ${{github.event_name}}
+          phpcs_files: ${{ needs.filesChanged.outputs.all_changed_files }}
+      - uses: stefanzweifel/git-auto-commit-action@v4 # auto commit the fixes action for GitHub
+        with:
+          commit_message: Fix PHPCS errors by GitHub PHPCSfixer action

+ 2 - 1
.github/workflows/stale-issues-safe.yml

@@ -6,7 +6,8 @@ on:
   - cron: "0 21 * * *"
   issue_comment:
     types: [created]
-    
+  workflow_dispatch:
+  
 permissions: {} # none
 
 jobs:

+ 11 - 1
.gitignore

@@ -14,11 +14,11 @@ default.properties
 /.pydevproject
 /.vscode
 .DS_Store
-.idea
 *.iml
 *.orig
 Thumbs.db
 /dolibarr_genesis.mp4
+.phpunit.result.cache
 # Log files
 dolibarr_install.log
 upgrade.log
@@ -60,3 +60,13 @@ yarn.lock
 package-lock.json
 
 doc/install.lock
+/.asciidoctorconfig.adoc
+
+# Qodana
+.idea/vcs.xml
+.idea/modules.xml
+.idea/workspace.xml
+.idea/inspectionProfiles/Project_Default.xml
+.idea/jsLinters/jshint.xml
+/composer.json
+/composer.lock

+ 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>

+ 86 - 81
.scrutinizer.yml

@@ -8,21 +8,24 @@ build:
                 override:
                     - command: php-scrutinizer-run
                       idle_timeout: 8000
+                    #- php-scrutinizer-run --sub-project-dir=htdocs/admin
 
 imports:
-    - javascript
     - php
 
 filter:
     excluded_paths:
-        - build/*
-        - dev/*
-        - doc/*
-        - documents/*
-        - node_modules/*
-        - test/*
-    dependency_paths:
-        - htdocs/includes/*
+        - build/
+        - dev/
+        - doc/
+        - documents/
+        - node_modules/
+        - test/
+        - htdocs/custom/
+        - htdocs/includes/
+        - htdocs/install/doctemplates/
+    #dependency_paths:
+    #    - htdocs/includes/
     paths: 
         - htdocs/*
         - scripts/*
@@ -33,21 +36,23 @@ tools:
         enabled: true
         extensions:
             - php
-        dependency_paths: 
-            - htdocs/includes/
+        #dependency_paths: 
+        #    - htdocs/includes/
         filter:
             excluded_paths:
-                - build/*
-                - dev/*
-                - doc/*
-                - documents/*
-                - htdocs/includes/*
+                - build/
+                - dev/
+                - doc/
+                - documents/
+                - htdocs/custom/
+                - htdocs/includes/
+                - htdocs/install/doctemplates/
                 - htdocs/core/class/lessc.class.php
-                - node_modules/*
-                - test/*
+                - node_modules/
+                - test/
             paths:
-                - htdocs/
-                - scripts/
+                - htdocs/*
+                - scripts/*
         config:
             parameter_reference_check:
                 enabled:              true
@@ -156,73 +161,73 @@ tools:
          # To use specific config for a specific path, use path_configs: (see example on page https://scrutinizer-ci.com/docs/configuration/tool_config_structure)
                         
     # php_depend
-    php_pdepend:
-        enabled: false
-        configuration_file: null
-        suffixes:
-            - php
-        excluded_dirs: {  }
-        filter:
-            excluded_paths:
-                - 'build/*'
-                - 'dev/*'
-                - 'doc/*'
-                - 'test/*'
-                - 'htdocs/includes/*'
-            paths: {  }
+    #php_pdepend:
+    #    enabled: false
+    #    configuration_file: null
+    #    suffixes:
+    #        - php
+    #    excluded_dirs: {  }
+    #    filter:
+    #        excluded_paths:
+    #            - 'build/*'
+    #            - 'dev/*'
+    #            - 'doc/*'
+    #            - 'test/*'
+    #            - 'htdocs/includes/*'
+    #        paths: {  }
 
     # change tracking
-    php_changetracking:
-        enabled: false
-        bug_patterns:
-            - '\bfix(?:es|ed)?\b'
-        feature_patterns:
-            - '\badd(?:s|ed)?\b'
-            - '\bimplement(?:s|ed)?\b'
-        filter:
-            excluded_paths:
-                - 'build/*'
-                - 'dev/*'
-                - 'doc/*'
-                - 'documents/*'
-                - 'htdocs/includes/*'
-                - 'node_modules/*'
-                - 'test/*'
-            paths: {  }
+    #php_changetracking:
+    #    enabled: false
+    #    bug_patterns:
+    #        - '\bfix(?:es|ed)?\b'
+    #    feature_patterns:
+    #        - '\badd(?:s|ed)?\b'
+    #        - '\bimplement(?:s|ed)?\b'
+    #    filter:
+    #        excluded_paths:
+    #            - 'build/*'
+    #            - 'dev/*'
+    #            - 'doc/*'
+    #            - 'documents/*'
+    #            - 'htdocs/includes/*'
+    #            - 'node_modules/*'
+    #            - 'test/*'
+    #        paths: {  }
 
     # Similar code detection
-    php_sim:
-        enabled: false
-        min_mass: 30
-        filter:
-            excluded_paths:
-                - 'build/*'
-                - 'dev/*'
-                - 'doc/*'
-                - 'documents/*'
-                - 'htdocs/includes/*'
-                - 'node_modules/*'
-                - 'test/*'
-            paths: {  }
+    #php_sim:
+    #    enabled: false
+    #    min_mass: 30
+    #    filter:
+    #        excluded_paths:
+    #            - 'build/*'
+    #            - 'dev/*'
+    #            - 'doc/*'
+    #            - 'documents/*'
+    #            - 'htdocs/includes/*'
+    #            - 'node_modules/*'
+    #            - 'test/*'
+    #        paths: {  }
             
     # Coding-Style / Bug Detection
-    js_hint:
-        enabled: false
-        use_native_config: true
-        extensions:
-            - js
-        filter:
-            excluded_paths:
-                - 'build/*'
-                - 'dev/*'
-                - 'doc/*'
-                - 'documents/*'
-                - 'htdocs/includes/*'
-                - 'node_modules/*'
-                - 'test/*'
-            paths: {  }
-        config: {  }
-        path_configs: {  }
+    #js_hint:
+    #    enabled: false
+    #    use_native_config: true
+    #    extensions:
+    #        - js
+    #    filter:
+    #        excluded_paths:
+    #            - 'build/*'
+    #            - 'dev/*'
+    #            - 'doc/*'
+    #            - 'documents/*'
+    #            - 'htdocs/includes/*'
+    #            - 'node_modules/*'
+    #            - 'test/*'
+    #        paths: {  }
+    #    config: {  }
+    #    path_configs: {  }
 
 
 before_commands: {  }

+ 15 - 21
.travis.yml

@@ -36,10 +36,10 @@ jobs:
   include:
     - stage: PHP 7.0-8.1
       if: type = push
-      php: '7.0'
+      php: '7.1'
       env: 
       - DB=postgresql
-      - TRAVIS_PHP_VERSION=7.0
+      - TRAVIS_PHP_VERSION=7.1
     - stage: PHP 7.0-8.1
       if: type = pull_request OR type = push
       php: '8.1'
@@ -75,8 +75,8 @@ before_install:
 - |
   echo Install packages for PHP
   sudo apt-get install -y pgloader memcached
-  if [ "$TRAVIS_PHP_VERSION" = '7.0' ]; then
-  	sudo apt install unzip apache2 php7.0 php7.0-cli php7.0-curl php7.0-mysql php7.0-pgsql php7.0-gd php7.0-imap php7.0-intl php7.0-ldap php7.0-xml php7.0-mbstring php7.0-xml php7.0-zip libapache2-mod-php7.0
+  if [ "$TRAVIS_PHP_VERSION" = '7.1' ]; then
+  	sudo apt install unzip apache2 php7.1 php7.1-cli php7.1-curl php7.1-mysql php7.1-pgsql php7.1-gd php7.1-imap php7.1-intl php7.1-ldap php7.1-xml php7.1-mbstring php7.1-xml php7.1-zip libapache2-mod-php7.1
   fi
   if [ "$TRAVIS_PHP_VERSION" = '8.1' ]; then
   	sudo apt install unzip apache2 php8.1 php8.1-cli php8.1-curl php8.1-mysql php8.1-pgsql php8.1-gd php8.1-imap php8.1-intl php8.1-ldap php8.1-xml php8.1-mbstring php8.1-xml php8.1-zip libapache2-mod-php8.1
@@ -106,8 +106,8 @@ before_install:
 
 install:
 - |
-  if [ "$TRAVIS_PHP_VERSION" = '7.0' ]; then
-    sudo update-alternatives --set php /usr/bin/php7.0
+  if [ "$TRAVIS_PHP_VERSION" = '7.1' ]; then
+    sudo update-alternatives --set php /usr/bin/php7.1
   fi 
   if [ "$TRAVIS_PHP_VERSION" = '8.1' ]; then
     sudo update-alternatives --set php /usr/bin/php8.1
@@ -129,14 +129,6 @@ install:
 - |
   echo "Update Composer version and Install tools - PHP Unit, Parallel Lint, PHP CodeSniffer, PHP Vardump check - for $TRAVIS_PHP_VERSION"
   echo "(composer version 2.5 is bugged and generate phpunit error Exception: Serialization of 'Closure' is not allowed)"
-  if [ "$TRAVIS_PHP_VERSION" = '7.0' ]; then
-    sudo composer self-update 2.2.18
-    composer -n require phpunit/phpunit ^6.0 \
-                        php-parallel-lint/php-parallel-lint ^1 \
-                        php-parallel-lint/php-console-highlighter ^0 \
-                        php-parallel-lint/php-var-dump-check ~0.4 \
-                        squizlabs/php_codesniffer ^3
-  fi
   if [ "$TRAVIS_PHP_VERSION" = '7.1' ] || [ "$TRAVIS_PHP_VERSION" = '7.2' ]; then
     sudo composer self-update 2.2.18
     composer -n require phpunit/phpunit ^7.5 \
@@ -253,8 +245,8 @@ before_script:
       echo 'ALTER SEQUENCE llx_accountingaccount_rowid_seq RENAME TO llx_accounting_account_rowid_seq' | psql 'postgresql://postgres:postgres@127.0.0.1:5432/travis'
       echo 'ALTER SEQUENCE llx_accounting_account_rowid_seq RESTART WITH 1000001;' | psql 'postgresql://postgres:postgres@127.0.0.1:5432/travis'
 
-      echo '\d llx_adherent' | psql 'postgresql://postgres:postgres@127.0.0.1:5432/travis'
-      echo '\d llx_c_country' | psql 'postgresql://postgres:postgres@127.0.0.1:5432/travis'
+      #echo '\d llx_adherent' | psql 'postgresql://postgres:postgres@127.0.0.1:5432/travis'
+      #echo '\d llx_c_country' | psql 'postgresql://postgres:postgres@127.0.0.1:5432/travis'
 
       # Create pgsql compatibility functions
       psql 'postgresql://postgres:postgres@127.0.0.1:5432/travis' < htdocs/install/pgsql/functions/functions.sql
@@ -441,6 +433,9 @@ script:
     php upgrade.php 16.0.0 17.0.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade16001700.log
     php upgrade2.php 16.0.0 17.0.0 > $TRAVIS_BUILD_DIR/upgrade16001700-2.log
     php step5.php 16.0.0 17.0.0 > $TRAVIS_BUILD_DIR/upgrade16001700-3.log
+    php upgrade.php 17.0.0 18.0.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade17001800.log
+    php upgrade2.php 17.0.0 18.0.0 > $TRAVIS_BUILD_DIR/upgrade17001800-2.log
+    php step5.php 17.0.0 18.0.0 > $TRAVIS_BUILD_DIR/upgrade17001800-3.log
 
     #show table content and log
     #echo '\d llx_adherent' | psql 'postgresql://postgres:postgres@127.0.0.1:5432/travis'
@@ -461,6 +456,7 @@ script:
     php upgrade2.php 0.0.0 0.0.0 MAIN_MODULE_WEBSITE,MAIN_MODULE_TICKET,MAIN_MODULE_ACCOUNTING,MAIN_MODULE_MRP >> $TRAVIS_BUILD_DIR/enablemodule.log
     php upgrade2.php 0.0.0 0.0.0 MAIN_MODULE_RECEPTION,MAIN_MODULE_RECRUITMENT >> $TRAVIS_BUILD_DIR/enablemodule.log
     php upgrade2.php 0.0.0 0.0.0 MAIN_MODULE_KnowledgeManagement,MAIN_MODULE_EventOrganization,MAIN_MODULE_PARTNERSHIP >> $TRAVIS_BUILD_DIR/enablemodule.log
+    php upgrade2.php 0.0.0 0.0.0 MAIN_MODULE_EmailCollector >> $TRAVIS_BUILD_DIR/enablemodule.log
     echo $?
     cd -
     set +e
@@ -472,11 +468,9 @@ script:
     echo "Unit testing"
     # Ensure we catch errors with -e. Set this to +e instead of -e if you want to go to the end to see dolibarr.log file.
     set -e
-    if [ "$TRAVIS_PHP_VERSION" != '7.0' ]; then
-      phpunit -d memory_limit=-1 -c test/phpunit/phpunittest.xml test/phpunit/AllTests.php
-      phpunitresult=$?
-      echo "Phpunit return code = $phpunitresult"
-    fi
+    phpunit -d memory_limit=-1 -c test/phpunit/phpunittest.xml test/phpunit/AllTests.php
+    phpunitresult=$?
+    echo "Phpunit return code = $phpunitresult"
     set +e
 
 after_script:

+ 10 - 8
COPYRIGHT

@@ -24,34 +24,36 @@ 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
 PSR/Logs               1.0           MIT License                 Yes             Library for logs (used by DebugBar)
 PSR/simple-cache       ?             MIT License                 Yes             Library for cache (used by PHPSpreadSheet)
 Restler                3.1.1         LGPL-3+                     Yes             Library to develop REST Web services (+ swagger-ui js lib into dir explorer)
-Sabre                  3.2.2         BSD                         Yes             DAV support
+Sabre                  4.0.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
 
+bacon, dasprid, swiss-qr-bill, kmukku, symfony/validator
+
 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)

+ 484 - 10
ChangeLog

@@ -2,6 +2,480 @@
 English Dolibarr ChangeLog
 --------------------------------------------------------------
 
+***** ChangeLog for 18.0.5 compared to 18.0.4 *****
+FIX: 17.0: deprecated field should only be a fallback
+FIX: 17.0 - php8 warnings: test for $field existence before checking if it is null or empty
+FIX: #24185: v18: display of the merged pdf lists
+FIX: #26416 BOM_SUB_BOM blank page
+FIX: #27166
+FIX: #27262 Recurrent invoice - user to string conversion
+FIX: #27970 #26283 #27970
+FIX: Accountancy - Level 3 of binding not working on supplier side (#27462)
+FIX: Accounting files export - Use th instead of td on all title columns (#28003)
+FIX: add action update_extras to don card
+FIX: Adding hooks init
+FIX: Adding the $encode parameter to recursive _replaceHtmlWithOdtTag() utilisation
+FIX: add new hook context for mo production card (#28037)
+FIX: avoid from re-initializing result on nested hook getEntity (#27799)
+FIX: avoid sql error (issue #26342)
+FIX: bad accountancy code autoselection for supplier ventilation
+FIX: Bad visible status of proposal after reopen
+FIX: Barcode header cell not well displayed
+FIX: BarCode Header not well displayed
+FIX: Bar code verification should be done by entity because generation does (#28087)
+FIX: can edit reminders on past events
+FIX: check parameter socid before cloning a customer proposal (#28085)
+FIX: crabe PDF is generating in conf->entity instead of object->entity
+FIX: CVE-2024-23817 (#28089)
+FIX: disable pointer events on jQuery-UI tooltips to prevent a glitch (fast-blinking tooltip)
+FIX: Error on emailreminder not reported
+FIX: Fatal error converting object of class User to string (php8)
+FIX: filter by entity on contact is missing
+FIX: Fix supplier invoice security check
+FIX: format of color in manifest is wrong when using a custom color
+FIX: #GHSA-7947-48q7-cp5m
+FIX: HTML injection vulnerability in Dolibarr Application Home Page
+FIX: invoice add line save devise
+FIX: Keep a link to enable a 'always_enabled' module to solve pb.
+FIX: label
+FIX: line special_code never saved (#28051)
+FIX: link to print when there is a search on multiselect fields
+FIX: Menu Create of project no working on smartphone with no top menu.
+FIX: missing $search_sale var (backport from v19)
+FIX: Missing begin transaction when updating supplier recurring invoice
+FIX: missing entity filter for check if period exists
+FIX: more correctly parse the select part to be replaced in sql queries
+FIX: MouvementStock::origin is not an object
+FIX: notification information on intervention validated confirmation message (v17+)
+FIX: not load all contacts by default when creating an event
+FIX: port in Docker MailDev
+FIX: propal use devise changes
+FIX: public user photo not visible if $dolibarr_main_instance_unique_id
+FIX: remove DISTINCT (backport from v19)
+FIX: remove specific name from v19
+FIX: Retours PR
+FIX: Return a better error message when token is not valid
+FIX: search by ref & rowid in don list
+FIX: search by thirdparty in don list
+FIX: several names for one const THIRDPARTY_CAN_HAVE_CUSTOMER_CATEGORY_EVEN_IF_NOT_CUSTOMER_PROSPECT
+FIX: SQL concatenation error
+FIX: [TAKEPOS] display prices with or without taxes depending on setup (TAKEPOS_CHANGE_PRICE_HT)
+FIX: Ternary operator condition is always true/false
+FIX: too long output
+FIX: Undefined property: Task::$fk_parent
+FIX: uniformization to use "intervention"
+FIX: Update loan.class.php (#27971)
+FIX: update price extrafield on propal card
+FIX: user filter in per user view of event list (#28049)
+FIX: use the currency for propal signature page
+
+***** ChangeLog for 18.0.4 compared to 18.0.3 *****
+FIX: $this->newref already exists and could have been modified by trigger but we still use a local variable for the filesystem-based renaming
+FIX: 16.0 only, backport fix for SQL error on global search product
+FIX: #25399 (#26694)
+FIX: #25458 intervention localizations (backport v17) (#26757)
+FIX: #26518
+FIX: #26536 Accountancy - Balance - Not divided lines by label & account, only by account (#26547)
+FIX: #26553 Supplier invoice - Do not display the delete button for reconciled payment (#26554)
+FIX: #26735
+FIX: #26994
+FIX: Accountancy - Possibility to write in bookkeeping expense report operation with some line not bound (#26545)
+FIX: add display of an error when attempting to delete a committed transaction (#26573)
+FIX: avoid warning : Cannot use a scalar value as an array (#26437)
+FIX: backport SQL error on global search product
+FIX: # Bug Estimated Stock at date value in V14 (#26479)
+FIX: commande context (#26497)
+FIX: delivery note disappear after generation
+FIX: double hook and paging search param in product list (#26767)
+FIX: Email reminder template must not be visible when option is off
+FIX: escape HTML tags in return value of getFullName() (#26735)
+FIX: Fix set private note (#26610)
+FIX: Fix when options FAC_FORCE_DATE_VALIDATION and INVOICE_CHECK_POSTERIOR_DATE enabled. The date is forced after the test and not before
+FIX: menu auguria
+FIX: pagination parameters on save and cancel buttons (#26605)
+FIX: pdf cornas page head multicell width (backport v17)
+FIX: php8 fatal on edit supplier order when multicurrency is activated (#26758)
+FIX: possible inconsistency between llx_ecm_files and file system when BILL_SUPPLIER_VALIDATES changes ref
+FIX: regression on planned bank entries (#26556)
+FIX: Social contribution - Payment list - Wrong information in type column (#26561)
+FIX: special_code update line keep old value. (#26819)
+FIX: substitute project variables in invoice documents (#26445)
+FIX: Test on permission for holiday tooltips
+FIX: v17: Param $notrigger in $societe->create() causes method to return true regardless of actual result of database functions (#26499)
+FIX: v18 SQL error in llx_c_forme_juridique.sql when installing
+FIX: Warehouse Global Amounts not displayed (#26478)
+FIX: warning param $lineID getSpecialCode is negatif (#26826)
+FIX: warning php8.2 undefined_array_key (#26830)
+
+***** ChangeLog for 18.0.3 compared to 18.0.2 *****
+FIX: #25793 Cannot add time spent (#26405)
+FIX: #26100 Ticket - On edit, list of closed project must be excluded (#26223)
+FIX: #26170
+FIX: #26195 Various payment - List of project excluded those assigned to third parties (#26222)
+FIX: #26349 add_customer_ref_on_linked_shipment
+FIX: avoid warning + CSRF
+FIX: avoid warning if module not activated
+FIX: Bad name for trigger in HRM module
+FIX: check tva_tx before comparing price_min_ttc (#25220)
+FIX: Compare the result of the send mail file function
+FIX: dol_eval with function starting with !
+FIX: Error handling for computed values on import (#24897)
+FIX: Error not returned on sales with takepos and batch module enabled
+FIX: error of webhook not returned
+FIX: firstname and lastname were not saved in attendee subscription
+FIX: HTML in ODT templates (#26181)
+FIX: Link to list of movement from the inventory code
+FIX: Mass import of stock from a file must accept empty source
+FIX: Max version of PHP (#26327)
+FIX: missing load group members for ldap synchro (#26167)
+FIX: missing project entity filter (Issue #26243) (#26247)
+FIX: modification of complementary attributes in invoices (#26180)
+FIX: On object validation, ecm index are not updated for uploaded files
+FIX: Propal's negative quantities
+FIX: public subscription page should not display acceptation for public register when it is not enabled (#26354)
+FIX: regression on rounding stocks fields on product list
+FIX: removed a non expected hidden input field in stockatdate page
+FIX: src_object_id and ttype not filed when uploading a file with API.
+FIX: suggested end for membership can be before subscription start (#26351)
+FIX: Supplier card - VAT Reserve Charge - Undefined function isInEEC() (#26379)
+FIX: syntax error
+FIX: template invoice list extrafield filters (backport v17) (#26227)
+FIX: Tooltip for search syntax must not appear on date fields
+FIX: upload of files src_object_type
+FIX: use event.key instead event.wich to avoid keyboard difference
+FIX: Use of line->insert instead of line->create
+FIX: user creation when LDAP is configured (#26332)
+FIX: Wrong backtopage given for the stocktransfer button from the stocktransfer list (#26271)
+
+***** ChangeLog for 18.0.2 compared to 18.0.1 *****
+FIX: 17.0 PHP8: supplier invoice class
+FIX: #24908 #25824
+FIX: #25780 Various payment - List - Fatal error on PHP8.x
+FIX: #25884
+FIX: #25919
+FIX: #25934 #25929
+FIX: Accountancy - Update Quadra export format
+FIX: add field "entity" only in $tabfieldinsert
+FIX: add warning in the changelog
+FIX: Avoid duplicate popup when popup setup to work as ajax
+FIX: avoid excess line breaks
+FIX: removed some php8 warning
+FIX: avoid wrong backtopage url
+FIX: bad check return for sendfile
+FIX: Bad choice of filter on product/service
+FIX: bad from and to
+FIX: Bad length for value
+FIX: Bad value of accounting account shown in list. Edit fails.
+FIX: batch to update non valid backlink check
+FIX: better sql request for all cases
+FIX: Can't access to rec supplier invoice card
+FIX: Can't delete a fourn commande row if a commande ligne is linked
+FIX: could not delete a fourn commande row if a commande ligne is linked
+FIX: create intervention from time spend
+FIX: customer code search filter on invoice list
+FIX: #CVE-2023-4197
+FIX: date comparison for user expiration on user list
+FIX: Debug the dispatch page to work on corrupted data
+FIX: delete useless condition in massaction_pre
+FIX: Edition of line on the list of timespent for all projects
+FIX: Edition of shipment detail with lot not updating correctly tables
+FIX: emailcollector to search existing thirdparty when extract not found
+FIX: Error message
+FIX: Expense report is created on the wrong entity
+FIX: export FEC
+FIX: Filter on partnership status
+FIX: fix the wrong position of the hook 'printFieldListTitle' and 'printFieldListValue' in the stock at date page
+FIX: If PHPIMAP is active, emailcollector "recordjoinpiece" operation will not work
+FIX: include
+FIX: Kanban view
+FIX: link to create purchase order from sale order
+FIX: Look and feel search v18
+FIX: message order in ticket public view is not coherent with tickets events tab
+FIX: missing contact_id for the trigger
+FIX: missing entity filter to customize ticket dictionaries by entity
+FIX: Missing error message on CommandeFourn creation
+FIX: missing fk_account situation invoice
+FIX: missing group "members" + multiple broken features for Multicompany
+FIX: missing 'overwrite_trans' for $addzero (Multicompany)
+FIX: modification of complementary attributes in commercial proposals
+FIX: multicompany compatibility
+FIX: multiple broken features for Multicompany !!!!
+FIX: Navigation to/from a project from page of projects of a thirdparty
+FIX: payment : language is not propagated to following pages
+FIX: Prices visible on TakePOS KO with multiprices support
+FIX: product list accounting length
+FIX: Quick search Intervention redirect to wrong page
+FIX: Return right content type
+FIX: right access on salary card and tabs
+FIX: rights paymentsc paiementcharge
+FIX: same broken feature as v18 (Multicompany)
+FIX: Save user modif id when changing a contact status
+FIX: Social Contrib - List - Fatal error on kanban view PHP8.x & Missing colspan
+FIX: thirdparty object in proposal card is not loaded
+FIX: Total of holidays is doubled
+FIX: translation button
+FIX: trigger for email sent from partnership
+FIX: uniformize getEntity sql request
+FIX: use urlencode for origin and originid
+FIX: Various payment - List - Fatal error on php8.1
+FIX: warning when Workboard Responses display non numeric strings
+FIX: wrong place of trigger delete
+FIX: wrong test + is_int is better for negative integer
+
+
+***** ChangeLog for 18.0.1 compared to 18.0.0 *****
+FIX: Adding a product in recurring invoice does not use the correct VAT
+FIX: API /product/getAttributes
+FIX: avoid php8 warnings
+FIX: bad balance of TR tag in multicurrency price view
+FIX: Bad calculation of localtax when price_base_type not defined.
+FIX: Bad link into message
+FIX: Bad message on menu to go to setup of accounting custom groups
+FIX: Bank receipt was empty
+FIX: Billing massaction should be possible on Processed Reception.
+FIX: clone when cloning object with ->lines containing not object
+FIX: Condition to show column POSModule and POSTerminal in invoice list
+FIX: CSS
+FIX: date survey : button to add dates would not work. Session would store cells numbers which would stay to 10 for next surveys created.
+FIX: dir output path for ODT models on reception card
+FIX: Duplicate tooltip on ref of an agenda event
+FIX: encrypt sensitive data must not be done for const MAIN_AGENDA_ACTIONAUTO
+FIX: Export when old file export_csv.modules.php is still present
+FIX: fatal error with bad definition of dictionaries
+FIX: fatal error with some parameters
+FIX: hook formBuilddocOptions was broken when used by 2 modules
+FIX: Line for revenuestamp in accountancy transfer must appear only if it exists
+FIX: Navigation between bank receipts
+FIX: payment card: misleading message when delete button disabled
+FIX: reception odt dir output path
+FIX: SQL request parenthesis
+FIX: Suppliers addlines never have VAT if buyprice for this supplier not set
+FIX: TakePOS receipt preview in admin #25648
+FIX: technical error on conciliation of lines
+FIX: the account in chart of account to use for revenue stamp is on dict
+FIX: url to check keyword not saved on partnership from public form
+FIX: when adding new times on a survey, all hours would be erased.
+
+
+***** ChangeLog for 18.0.0 compared to 17.0.0 *****
+
+For uses:
+---------
+
+NEW: PHP 8.2 compatibility (not yet complete).
+NEW: Module Workstations Management upgraded to stable status.
+NEW: Module Webhook upgraded to stable status
+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: Accountancy - iSuiteExpert export model
+NEW: Accountancy - Quadratus export with attachments in accountancy export
+NEW: Accountancy - Can filter on a custom group of accounts. Perf or ledger list.
+NEW: Can upload a file with drag and drop on purchase invoice, vats, salaries and social contributions
+NEW: Authentication: #22740 add OpenID Connect impl
+NEW: Authentication: add experimental support for Google OAuth2 connexion
+NEW: Authentication: can now edit service name for OAuth token
+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 bookmarks in selectable landing pages for users
+NEW: Add column ext_payment_site into societe_rib to allow multiple payment mode
+NEW: add convertion of images to webp for a single image in website media editor
+NEW: Add CRC for currency symbol before amount
+NEW: add customer code to invoices listing
+NEW: Add filter on nb of generation done in list of recurring invoices
+NEW: Add filters and sort on product unit column
+NEW: Add link to edit VAT list from error message of missing VAT
+NEW: add margins in paiement/card.php
+NEW: Add mass action delete on VAT
+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 STRIPE_DEBUG, a way to log Stripe IPN
+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: Bank: Add fields zip, town, country for owner of a bank account
+NEW: batch referential objets
+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 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: Customers: add date due and labels into customer comm card
+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 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: comment in api_mymodule for seperate methods
+NEW: constant PROPALE_ADDON_NOTE_PUBLIC_DEFAULT
+NEW: create email substitution variable for intervention signature URL
+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: element time integration code + SQL
+NEW: Email: don't have closed contact proposed as receiver for the mails
+NEW: Email-Collector: add field reply-to in email collector as possible filter
+NEW: Email-Collector: substitute date now in email collector
+NEW: Email-Collector: operation type in email collector to load or create contact
+NEW: Email-Collector: easier setup - can also use ! for negative search
+NEW: Events: can add any contact on events if global MAIN_ACTIONCOM_CAN_ADD_ANY_CONTACT is set at 1
+NEW: Events: list with color
+NEW: expend/collapse list of social networks
+NEW: Filter on amount and qty on list of service's contracts
+NEW: formconfirm can support field with format datetime
+NEW: getCommonSubstitutionArray to have more substitute keys
+NEW: GeoIP: Can test a geoip conversion from the geoip setup page
+NEW: GUI: add a CSS editor into the admin GUI
+NEW: GUI: add dropdown button actions (example on Create button on project)
+NEW: GUI: color for start date and owner
+NEW: GUI: new tab for reception and shipment
+NEW: GUI: better design for the page of discounts of a thirdparty
+NEW: GUI: can set background style with MAIN_LOGIN_BACKGROUND_STYLE
+NEW: Help: Tooltip to explain how to add a photo on a product
+NEW: Help: Possibility to link to German pages in help
+NEW: helper functions for dates + small demo case
+NEW: hook printFieldListFrom in contact list
+NEW: HR - Salary: can modify the date of payment of a salary (if not reconciled)
+NEW: HR - Salary: date for salary payment includes the hour/min
+NEW: HR - Salary: adding button Send Email on the salary file
+NEW: Import: filter on entity in import
+NEW: Import: map table to element for get entity in import
+NEW: inc.php: handle parameters from argv
+NEW: Invoice - show category of operations
+NEW: Keep a link between user created from recruitment and application
+NEW: List product in orders
+NEW: Mass Actions: Better responsive for mass actions
+NEW: Members: add numbering modules for members
+NEW: Members: add widget box_members_by_tags.php
+NEW: Members: Captcha for public member's subscription form
+NEW: migration script + delete old table + rename fields and indexes
+NEW: MRP MO: Dynamic choice of warehouse and batch in MO production.
+NEW: Multicurrency REST API to create, update, delete, update rate...
+NEW: Multiselect for filter on prospection status
+NEW: [Bulk delete Project tasks]
+NEW: No overwrite of optionals during put() contact
+NEW: Notifications: add Customer Order delivered (ORDER_NEW) in module Notification
+NEW: Notifications: for Sign or Refused Propal from Online Page
+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: 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: Orders: add sub total in order list det
+NEW: Orders export: allow to export field 'shipment method'
+NEW: payment default values when supplier order created from reception
+NEW: Payment : manage contracts
+NEW: presend mass action in contact list
+NEW: Print PDF: category of operation for crabe PDF model
+NEW: Print PDF: Name and date to print on PDF Sign
+NEW: Print PDF: Use the more recent PDF templates for documents by default on a fresh install
+NEW: product images on popup are cached
+NEW: Products: Add statistics by amount on statistics of products.
+NEW: Proposals: filter for Signed+Billed in proposals
+NEW: Proposals: can modify margin rates in offers like VAT rates
+NEW: Proposals: option filter for NoSalesRepresentativeAffected in proposals list
+NEW: Provide the oldcopy value when calling setValueFrom() function with a trigger key
+NEW: Reception: can receive more than qty ordered on reception
+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: Security: Save date to invalidate other session into user table
+NEW: Security: Invalidate all sessions of a user when password is modified.
+NEW: search on time spent duration range
+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 today start time at beginning
+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: Supplier Invoices: add ability of ODT support to supplier invoices
+NEW: Stock limit for alert and desired optimal stock by product and warehouse import
+NEW: Stock: Add warehouse create and modify triggers.
+NEW: Stock: Can select several warehouses into the view stock at date in past
+NEW: Support different bank account for several direct debit payments
+NEW: Support multiselect in the warehouse selection combo box
+NEW: Option: MAIN_SECURITY_MAXFILESIZE_DOWNLOADED #yogosha10660
+NEW: Survey: Comment on survey is possible only after vote.
+NEW: tables: llx_element_time to store time spent on several elements (mo, ticket...)
+NEW: TakePOS: adapt category and product pictures sizes on TakePOS
+NEW: TakePOS: limit load products in TakePOS
+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: Tickets: --Send an email when ticket assigned--
+NEW: Tickets: Send a notification email when ticket assigned
+NEW: Tickets: set ticket status to answered if the client has answered from the public interface
+NEW: Tickets: added an option to display the progress of tickets on the public interface
+NEW: Tickets: add link to thirdparty tickets history
+NEW: Tickets: 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: 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: Website Module: Increment website counter on each page access in website module
+NEW: write all fields and their properties in asciidoc format
+NEW: Can add an array of several links in date selector
+NEW: Option PDF_SHOW_PHONE_AFTER_USER_CONTACT to show phone after specific assigned contact on PDF
+NEW: Option PDF_SHOW_EMAIL_AFTER_USER_CONTACT to show email after specific assigned contact on PDF
+NEW: Widgets: Implement MAIN_ACTIVATE_FILECACHE on birthday widget
+NEW: Widgets: Add widget "The next upcoming events"
+NEW: Widgets: Add widget of open opportunities
+NEW: use an ajax component to switch prospection status on thirdparty list
+NEW: Add partial payment reason "withholding tax"
+
+
+
+For developers or integrators:
+------------------------------
+
+NEW: Make it possible to select hours and minutes in form_confirm
+NEW: add triggers on mailing
+NEW: Add a trigger when create a shipping line batch and fix propagate missing errors
+NEW: add function for listiong objects from directory
+NEW: add helplist property to describe fields of objects
+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, jquery, Stripe.
+NEW: Support contact in post() document API
+NEW: More APIs (update currency rate, upload of supplier documents, ...)
+NEW: ModuleBuilder: updating in modulbuilder on tab Menu when adding object
+NEW: ModuleBuilder: add/edit permissions
+NEW: ModuleBuilder: better generated documentation
+NEW: add sent info in the parameters provided to the hook sendMailAfter
+NEW: add setAsSelectUser into factory for generic setup page
+NEW: add option keepspace into dol_string_nospecialchar()
+
+
+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
+* Sensitive datas like keys in setup pages, that need encyption (for example the API keys of users, the CRON security key, the keys into the Stripe module, or
+external modules setup pages that store sensitive keys or password), are using the $dolibarr_main_instance_unique_id as part of the key for encryption. So,
+if you restore or duplicate the data from another instance dump, you must also update this parameter in ther conf.php file to allow decryption in the new instance, or
+better, you must reenter the sensitive data into the setup pages of the new instance to resave them correctly.
+Note that to find all the parameters that are encrypted into the setup database, you can do a "SELECT * FROM llx_const WHERE value LIKE '%dolcrypt%';"
+* 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 does 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
+* The method getCheckOption() and deleteCPUser() of class Holiday has been removed (it was not used)
+
 
 ***** ChangeLog for 17.0.4 compared to 17.0.3 *****
 FIX: $this->newref already exists and could have been modified by trigger but we still use a local variable for the filesystem-based renaming
@@ -506,18 +980,20 @@ 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
+* 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
-* You must use "$objectoffield" to manipulate the current object inside the formulare of computed custom extrafields instead of $obj/$object.
+* 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;
-* Filters in class field definitions must be a Dolibarr filter syntax string.
+
 
 ***** ChangeLog for 16.0.5 compared to 16.0.4 *****
 
@@ -573,23 +1049,24 @@ FIX: wrong url param name action
 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: #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: Amount of localtax1 and 2 not correctly save on purchase order (the
 FIX: API access for deactivated users
 FIX: bad selection of barcode numbering module
 FIX: Can't see all time spent by all user
@@ -600,8 +1077,6 @@ 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: #23075
-FIX: #23117
 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
@@ -609,7 +1084,6 @@ FIX: methods declaration (backport fix 67b9a7dc07d708231d12b5e58800334d4a01ef98)
 FIX: multicurrency_tx and not currency_tx
 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: PGSQL Int 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

+ 1 - 2
README.md

@@ -1,8 +1,7 @@
 # DOLIBARR ERP & CRM
 
 ![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%207.0-8892BF.svg?style=flat-square)](https://php.net/)
+[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.1-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)
 

+ 4 - 3
SECURITY.md

@@ -4,7 +4,7 @@ This file contains some policies about the security reports on Dolibarr ERP CRM
 
 ## Supported Versions for security reports
 
-Security report are valid only on current stable version (see dolibarr.org web site to get current stable version) or on development version (branch "develop" on https://github.com/Dolibarr/dolibarr).
+Security report are valid only on current stable version (see https://dolibarr.org web site to get current stable version) or on development version (branch "develop" on https://github.com/Dolibarr/dolibarr).
 
 ## Reporting a Vulnerability
 
@@ -48,14 +48,15 @@ Reports are processed around once a month.
 
 ONLY vulnerabilities discovered, when the following setup on test platform is used, are "valid":
 
+* The version to analyze must be the last version available into "develop" branch or into last stable "vX.Y" released version.  
 * $dolibarr_main_prod must be set to 1 into conf.php
 * $dolibarr_nocsrfcheck must be kept to the value 0 into conf.php (this is the default value)
 * $dolibarr_main_force_https must be set to something else than 0.
-* The constant MAIN_SECURITY_CSRF_WITH_TOKEN must be set to 3 into backoffice menu Home - Setup - Other (this protection should be set to 3 soon by default)
+* The constant MAIN_SECURITY_CSRF_WITH_TOKEN must be set to 3 into backoffice menu Home - Setup - Other (this protection should be set to 3 soon by default). CSRF attacks are accepted but
+ double check that you have set MAIN_SECURITY_CSRF_WITH_TOKEN to value 3.
 * ONLY security reports on modules provided by default and with the "stable" status are valid (troubles into "experimental", "developement" or external modules are not valid vulnerabilities).
 * The root of web server must link to htdocs and the documents directory must be outside of the web server root (this is the default when using the default installer but may differs with external installer).
 * The web server setup must be done so that only the documents directory is in write mode. The root directory called htdocs must be read-only.
-* CSRF attacks are accepted but double check that you have set MAIN_SECURITY_CSRF_WITH_TOKEN to value 3.
 * The modules DebugBar and ModuleBuilder must NOT be enabled. (by default, these modules are not enabled. They are developer tools)
 * Ability for a high level user to edit web site pages into the CMS by including HTML or Javascript is an expected feature. Vulnerabilities into the website module are validated only if HTML or Javascript injection can be done by a non allowed user.
 * Fail2ban rules for rate limit on the login page,password forgotten page, api calls and all public pages (/public/*) must be installed as recommendend into the section "About - Admin tools - Section Access limits and mitigation".

+ 2 - 2
build/debian/patches/use-etc-dolibarr-conf.patch

@@ -33,5 +33,5 @@ This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
 +$conffile = "/etc/dolibarr/conf.php";
 +$conffiletoshow = "/etc/dolibarr/conf.php";
  
- 
- // Load conf file if it is already defined
+ $short_options = "c:h";
+ $long_options = array(

+ 3 - 3
build/docker/Dockerfile

@@ -1,4 +1,4 @@
-FROM php:7.3-apache
+FROM php:8.1-apache-bullseye
 
 ENV PHP_INI_DATE_TIMEZONE 'UTC'
 ENV PHP_INI_MEMORY_LIMIT 256M
@@ -25,7 +25,7 @@ RUN apt-get update -y \
         mailutils \
     && apt-get autoremove -y \
     && rm -rf /var/lib/apt/lists/* \
-    && docker-php-ext-configure gd --with-png-dir=/usr --with-jpeg-dir=/usr \
+    && docker-php-ext-configure gd --with-freetype --with-jpeg \
     && docker-php-ext-install -j$(nproc) calendar intl mysqli pdo_mysql gd soap zip \
     && docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu/ \
     && docker-php-ext-install -j$(nproc) ldap && \
@@ -50,7 +50,7 @@ RUN echo 'xdebug.idekey="netbeans-xdebug"' >> ${PHP_INI_DIR}/php.ini
 # set up sendmail config, to use maildev
 RUN echo "account default" > /etc/msmtprc
 RUN echo "auth off" >> /etc/msmtprc
-RUN echo "port 25" >> /etc/msmtprc
+RUN echo "port 1025" >> /etc/msmtprc
 RUN echo "host mail" >> /etc/msmtprc
 RUN echo "from local@localdomain.com" >> /etc/msmtprc
 RUN echo "domain localhost.localdomain" >> /etc/msmtprc

+ 3 - 3
build/docker/README.md

@@ -1,7 +1,7 @@
 # How to use it ?
 
-The docker-compose.yml file is used to build and run Dolibarr in the current workspace.
-This docker image intended for developpement usage.
+The docker-compose.yml file is a sample of a config file to use to build and run Dolibarr in the current workspace with Docker.
+This docker image is intended for developpement usage.
 For production usage you should consider other contributor reference like https://hub.docker.com/r/tuxgasy/dolibarr 
 
 Before build/run, define the variable HOST_USER_ID as following:
@@ -25,7 +25,7 @@ The URL to go to the Dolibarr is :
 The URL to go to PhpMyAdmin is (login/password is root/root) :
 
         http://0.0.0.0:8080
-        
+
 In Dolibarr configuration Email let PHP mail function, To see all mail send by Dolibarr go to maildev
 
         http://0.0.0.0:8081

+ 5 - 3
build/docker/docker-compose.yml

@@ -10,7 +10,7 @@ services:
     mariadb:
         image: mariadb:latest
         environment:
-            MYSQL_ROOT_PASSWORD: root
+            MYSQL_ROOT_PASSWORD: rootpassfordev
             MYSQL_DATABASE: dolibarr
         ports:
             - "3306:3306"
@@ -34,6 +34,8 @@ services:
         build: .
         environment:
             HOST_USER_ID: $HOST_USER_ID
+            PHP_INI_DATE_TIMEZONE: $PHP_INI_DATE_TIMEZONE
+            PHP_INI_MEMORY_LIMIT: $PHP_INI_MEMORY_LIMIT
         volumes:
             - ../../htdocs:/var/www/html/
             - ../../documents:/var/documents
@@ -53,8 +55,8 @@ services:
     mail:
         image: maildev/maildev
         ports:
-            - "8081:80"
-            - "25:25"
+            - "8081:1080"
+            - "25:1025"
         networks:
             - internal-pod
             - external-pod

+ 5 - 5
build/docker/docker-run.sh

@@ -10,15 +10,15 @@ chmod g+rwx /var/www/html/conf
 
 if [ ! -d /var/documents ]; then
 	echo "[docker-run] => create volume directory /var/documents ..."
-  	mkdir -p /var/documents
+	mkdir -p /var/documents
 fi
 echo "[docker-run] => Set Permission to www-data for /var/documents"
 chown -R www-data:www-data /var/documents
 
-if [ ! -f /usr/local/etc/php/php.ini ]; then
-  cat <<EOF > /usr/local/etc/php/php.ini
-date.timezone = $PHP_INI_DATE_TIMEZONE
+echo "[docker-run] => update ${PHP_INI_DIR}/conf.d/dolibarr-php.ini"
+cat <<EOF > ${PHP_INI_DIR}/conf.d/dolibarr-php.ini
+date.timezone = ${PHP_INI_DATE_TIMEZONE:-UTC}
+memory_limit = ${PHP_INI_MEMORY_LIMIT:-256M}
 EOF
-fi
 
 exec apache2-foreground

+ 3 - 1
build/doxygen/dolibarr-doxygen.doxyfile

@@ -220,7 +220,8 @@ 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
+EXTENSION_MAPPING =
 
 # 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,6 +603,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 *.sql *.example
 FILE_PATTERNS = *.php *.pl
 
 # The RECURSIVE tag can be used to turn specify whether or not subdirectories

+ 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 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 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)

+ 16 - 29
build/exe/doliwamp/index.php.install

@@ -304,22 +304,15 @@ if (isset($_GET['img']))
 
 
 
-// Definition de la langue et des textes 
+// Definition of language and texts
 
-if (isset ($_GET['lang']))
-{
-	$langue = $_GET['lang'];
-}
-elseif (preg_match("/^fr/", $_SERVER['HTTP_ACCEPT_LANGUAGE']))
-{
+if (isset ($_GET['lang'])) {
+	$langue = preg_replace('/[^a-z_]/i', '', $_GET['lang']);
+} elseif (preg_match("/^fr/", $_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
 	$langue = 'fr';
-}
-elseif (preg_match("/^es/", $_SERVER['HTTP_ACCEPT_LANGUAGE']))
-{
+} elseif (preg_match("/^es/", $_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
 	$langue = 'es';
-}
-else
-{
+} else {
 	$langue = 'en';
 }
 
@@ -327,29 +320,25 @@ else
 // Read PHP extensions
 $loaded_extensions = get_loaded_extensions();
 $phpExtContents='';
-foreach ($loaded_extensions as $extension)
+foreach ($loaded_extensions as $extension) {
 	$phpExtContents .= "<li>${extension}</li>";
-
+}
 
 	
 // Read alias directory
 $listoffile=array();
 $aliasarray=array();
 $aliasContents='';
-if (is_dir($aliasDir))
-{
+if (is_dir($aliasDir)) {
     $handle=opendir($aliasDir);
-    if (is_resource($handle))
-    {
-        while ($file = readdir($handle)) 
-        {
+    if (is_resource($handle)) {
+        while ($file = readdir($handle)) {
     		$listoffiles[]=$file;
     	}
     }
 	sort($listoffiles);
 	
-	foreach($listoffiles as $file) 
-	{
+	foreach($listoffiles as $file) {
 	    if (is_file($aliasDir.$file) && preg_match('/\.conf/',$file))
 	    {		
 		    $msg = '';
@@ -374,8 +363,7 @@ if (!isset($aliasContents))
 // Read projects in www dir
 $listoffiles=array();
 $handle=opendir(".");
-if (is_resource($handle))
-{
+if (is_resource($handle)) {
     while ($file = readdir($handle)) 
     {
     	$listoffiles[]=$file;
@@ -383,8 +371,7 @@ if (is_resource($handle))
     closedir($handle);
 }
 
-foreach($listoffiles as $file)
-{
+foreach($listoffiles as $file) {
 	if (is_dir($file) && !in_array($file,$projectsListIgnore) && !in_array($file,$aliasarray)) 
 	{		
 		$projectContents .= '<tr><td><ul class="projects">';
@@ -397,9 +384,9 @@ foreach($listoffiles as $file)
 	}
 }
 
-if (!isset($projectContents))
+if (!isset($projectContents)) {
 	$projectContents = '<tr><td colspan="3">'.$langues[$langue]['txtNoProjet'].'</td></tr>';
-
+}
 
 
 $nameServer=getenv("COMPUTERNAME");

+ 1 - 0
build/phpstan/bootstrap.php

@@ -6,5 +6,6 @@
 
 // Load the main.inc.php file to have functions env defined
 if (! defined("NOLOGIN")) define("NOLOGIN", '1');
+if (! defined("NOHTTPSREDIRECT")) define("NOHTTPSREDIRECT", '1');
 global $conf, $langs, $user, $db;
 include_once __DIR__ . '/../../htdocs/main.inc.php';

+ 2 - 2
build/rpm/dolibarr-forrpm.patch

@@ -24,5 +24,5 @@ diff -up htdocs/install/inc.php.patch htdocs/install/inc.php
 +$conffile = "/etc/dolibarr/conf.php";
 +$conffiletoshow = "/etc/dolibarr/conf.php";
  
- 
- // Load conf file if it is already defined
+ $short_options = "c:h";
+ $long_options = array(

+ 10 - 10
build/travis-ci/apache.conf

@@ -9,14 +9,14 @@
   </Directory>
 
   # Wire up Apache to use Travis CI's php-fpm.
-  <IfModule mod_fastcgi.c>
-    AddHandler php5-fcgi .php
-    Action php5-fcgi /php5-fcgi
-    Alias /php5-fcgi /usr/lib/cgi-bin/php5-fcgi
-    FastCgiExternalServer /usr/lib/cgi-bin/php5-fcgi -host 127.0.0.1:9000 -pass-header Authorization
-    
-    <Directory /usr/lib/cgi-bin>
-        Require all granted
-    </Directory>
-  </IfModule>
+  #<IfModule mod_fastcgi.c>
+  #  AddHandler php5-fcgi .php
+  #  Action php5-fcgi /php5-fcgi
+  #  Alias /php5-fcgi /usr/lib/cgi-bin/php5-fcgi
+  #  FastCgiExternalServer /usr/lib/cgi-bin/php5-fcgi -host 127.0.0.1:9000 -pass-header Authorization
+  #  
+  #<Directory /usr/lib/cgi-bin>
+  #      Require all granted
+  #</Directory>
+  #</IfModule>
 </VirtualHost>

+ 10 - 12
composer.json.disabled

@@ -24,32 +24,30 @@
 		"vendor-dir" : "htdocs/includes"
 	},
 	"require" : {
-		"php" : ">=5.6.0",
-		"ext-curl" : "*",
+		"php" : ">=7.1.0",
 		"ckeditor/ckeditor" : "4.12.1",
-		"mike42/escpos-php" : "2.2",
-		"mobiledetect/mobiledetectlib" : "2.8.39",
-		"phpoffice/phpexcel" : "1.8.2",
+		"mike42/escpos-php" : "3.0",
+		"mobiledetect/mobiledetectlib" : "2.8.41",
+		"phpoffice/phpspreadsheet" : ">=1.12",
 		"restler/framework" : "3.0.0-RC6",
 		"tecnickcom/tcpdf" : "6.3.2",
-		"nnnick/chartjs" : "^2.9",
-		"stripe/stripe-php" : "6.43.1",
-		"maximebf/debugbar" : "1.15.1",
-		"symfony/var-dumper" : "3.2"
+		"nnnick/chartjs" : "^3.7.1",
+		"stripe/stripe-php" : "10.7.0",
+		"maximebf/debugbar" : "1.18.2",
+		"symfony/var-dumper" : ">=3.2"
 	},
 	"require-dev" : {
 		"php-parallel-lint/php-parallel-lint" : "^0",
 		"php-parallel-lint/php-console-highlighter" : "^0",
 		"phpunit/phpunit" : "^4",
 		"squizlabs/php_codesniffer" : "^2",
-		"phpunit/phpunit-selenium" : "^2"
+		"phpunit/phpunit-selenium" : "^2",
+		"rector/rector" : "^0.16.0"
 	},
 	"suggest" : {
 		"ext-mysqlnd" : "To use with MySQL or MariaDB",
 		"ext-mysqli" : "To use with MySQL or MariaDB",
 		"ext-pgsql" : "To use with PostgreSQL",
-		"ext-mssql" : "To use with MSSQL (experimental)",
-		"ext-pdo_sqlite" : "To use with SQLite (experimental)",
 		"ext-gd" : "Image manipulation (Required but maybe built-in PHP)",
 		"ext-imagick" : "Generation of thumbs from PDF",
 		"ext-mcrypt" : "(Required but maybe built-in PHP)",

+ 1 - 1
dev/dolibarr_changes.txt

@@ -211,7 +211,7 @@ with
 * Fix by replacing 
 	if ($res[0] == PDF_TYPE_OBJECT)
 with
-	if ($res && $res[0] == PDF_TYPE_OBJECT)
+	if (isset($res[0]) && $res[0] == PDF_TYPE_OBJECT)
 
 
 

+ 2 - 2
dev/initdata/purge-data.php

@@ -148,8 +148,8 @@ $sqls=array(
 		"DELETE FROM ".MAIN_DB_PREFIX."product where datec < '__DATE__'",
 	),
 	'project'=>array(
-		// TODO set fk_project to null on object that refer to project
-		"DELETE FROM ".MAIN_DB_PREFIX."projet_task_time WHERE fk_task IN (select rowid FROM ".MAIN_DB_PREFIX."projet_task WHERE fk_projet IN (select rowid FROM ".MAIN_DB_PREFIX."projet where datec < '__DATE__'))",
+		// TODO set fk_project to null on all objects/tables that refer to project
+		"DELETE FROM ".MAIN_DB_PREFIX."element_time WHERE elementtype = 'task' AND fk_element IN (select rowid FROM ".MAIN_DB_PREFIX."projet_task WHERE fk_projet IN (select rowid FROM ".MAIN_DB_PREFIX."projet where datec < '__DATE__'))",
 		"DELETE FROM ".MAIN_DB_PREFIX."projet_task WHERE fk_projet IN (select rowid FROM ".MAIN_DB_PREFIX."projet where datec < '__DATE__')",
 		"DELETE FROM ".MAIN_DB_PREFIX."projet where datec < '__DATE__'",
 	),

+ 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/initdemopassword.sh

@@ -154,12 +154,11 @@ if [ "x${demopasshash}" != "xpassword_hash" ]
 then
 	echo '<?php echo MD5("'$demopass'"); ?>' > /tmp/tmp.php 
 	newpass=`php -f /tmp/tmp.php`
-	rm /tmp/tmp.php
 else
 	echo '<?php echo password_hash("'$demopass'", PASSWORD_DEFAULT); ?>' > /tmp/tmp.php
 	newpass=`php -f /tmp/tmp.php`
-	rm /tmp/tmp.php
 fi
+#rm /tmp/tmp.php
 
 echo "echo \"UPDATE llx_user SET pass_crypted = '$newpass' WHERE login = '$demologin';\" | mysql -P$port $base"
 echo "UPDATE llx_user SET pass_crypted = '$newpass' WHERE login = '$demologin';" | mysql -P$port $base
@@ -172,6 +171,7 @@ fi
 
 if [ -s "$mydir/initdemopostsql.sql" ]; then
 	echo A file initdemopostsql.sql was found, we execute it.
+	echo "mysql -P$port $base < \"$mydir/initdemopostsql.sql\""
 	mysql -P$port $base < "$mydir/initdemopostsql.sql"
 else
 	echo No file initdemopostsql.sql found, so no extra sql action done.

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 4497
dev/initdemo/mysqldump_dolibarr_17.0.0.sql


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 57 - 0
dev/initdemo/mysqldump_dolibarr_18.0.0.sql


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
dev/initdemo/mysqldump_dolibarr_3.5.0.sql


+ 1 - 2
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
-----------------------------
+* 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/

+ 134 - 101
dev/resources/iso-normes/qr-bar-codes/barcode_EAN13.txt

@@ -10,8 +10,7 @@ Signification des chiffres.
 - 1 chiffre pour la somme de controle
 
 Cette regle subit de nombreuses entorses pour ameliorer l'usage des chiffres disponibles.
-Voici la liste des codes pays ou systeme :
-
+Voici la liste des codes pays ou systeme, les préfixes qui ne sont pas explicitement mentionnés sont réservés par GS1 :
 
 
 EN
@@ -25,105 +24,139 @@ Meaning of the numbers:
 
 This rule has been twisted many times to improve the use of the available numbers.
 
-Here is the list of country codes or system:
+Here is the list of country codes or system, prefixes not explicitly listed are reserved by GS1:
 
 
-List
-====
+List (https://www.gs1.org/prefixes)
+===================================
 
-00 - 13  UCC  (U.S.A / États-Unis & Canada)
-20 - 29  Flag for internal numbering / Codification interne en magasin
-30 - 37  GENCOD-EAN  France
-380      BCCI  (Bulgaria)
-383      SANA  (Slovenia)
-385      CRO-EAN  (Croatia)
-387      EAN-BIH  (Bosnia-Herzegovina)
-400-440  CCG  (DE/Germany/Allemagne)
-45 + 49  Distribution Code Center - DCC  (Japan)
-460-469  UNISCAN - EAN Russia  (Federation de Russie)
-471      CAN  Taiwan
-474      EAN  Estonia
-475      EAN  Latvia
-476      EAN  Azerbaijan
-477      EAN  Lithuania
-478      EAN  Uzbekistan
-479      EAN  Sri Lanka
-480      PANC Philippines
-481      EAN  Belarus
-482      EAN  Ukraine
-484      EAN  Moldova 
-485      EAN  Armenia
-486      EAN  Georgia
-487      EAN  Kazakhstan
-489      HKANA Hong Kong
-50       E Centre UK - United Kingdom 
-520      HELLCAN-EAN HELLAS - Greece
-528      EAN  Lebanon
-529      EAN  Cyprus
-531      EAN-MAC (FYR Macedonia)
-535      EAN  Malta
-539      EAN  Ireland
-54       ICODIF/EAN  Belgium & Luxembourg
-560      CODIPOR (Portugal)
-569      EAN  Iceland/Islande
-57       EAN  Denmark
-590      EAN  Poland
-594      EAN  Romania
-599      H.A.P.M.H. (Hungary)
-600-601  EAN  South Africa
-609      EAN  Mauritius Island
-611      EAN  Morocco
-613      EAN  Algeria
-619      Tunicode (Tunisia)
-621      EAN  Syria
-622      EAN  Egypt
-625      EAN  Jordan/Jordanie
-626      EAN  Iran
-628      EAN  Saudi Arabia
-64       EAN  Finland
-690-693  ANCC - Article Numbering Centre of China
-70  EAN Norge (Norvege)
-729 Israeli Bar Code Association - EAN Israel
-73  EAN Suede
-740 EAN Guatemala
-741 EAN El Salvador
-742 ICCC (Honduras)
-743 EAN Nicaragua
-744 EAN Costa Rica Panama
-746 746 EAN Republique Dominicaine
-750 AMECE (Mexique)
-759 EAN Venezuela
-76  EAN (Schweiz, Suisse, Svizzera)
-770 IAC (Colombie)
-773 EAN Uruguay
-775 APC - EAN Peru (Perou)
-777 EAN Bolivie
-779 CODIGO - EAN Argentine
-780 EAN Chili
-784 EAN Paraguay
-786 ECOP (Equateur)
-789 EAN Bresil
-80 - 83 INDICOD (Italy)
-84  AECOC (Espagne)
-850 Camera de Comercio de la Republica de Cuba (Cuba)
-858 EAN Slovaquie
-859 EAN Republique Tcheque
-860 EAN YU (Yougoslavie)
-867 EAN DPR Korea (Coree du Nord)
-869 Union of Chambers of Commerce of Turkey (Turquie)
-87  EAN Nederland (Hollande)
-880 EAN Korea (Coree du Sud)
-885 EAN Thailande
-888 SANC (Singapour)
-890 EAN Inde
-893 EAN Vietnam
-899 EAN Indonesie
-90 - 91 EAN Autriche
-93  EAN Australie
-94  EAN Nouvelle Zelande
-955 Malaysian Article Numbering Council (MANC) - Malaisie
-977 Publications sirielles (ISSN)
-978 - 979 Livres (ISBN)
-980 Refus de remboursement
-981 - 982 Coupons (monnaie courante)
-99  Coupons
+0000000      Flag for internal numbering / Codification interne en magasin
+00001–01999  GS1 US (U.S.A / États-Unis & Canada)
+020-029      Restricted / Restreint
+030-039      GS1 US (U.S.A / États-Unis & Canada)
+040-049      Flag for internal numbering / Codification interne en magasin
+050-059      GS1 US (U.S.A / États-Unis & Canada)
+060-139      GS1 US (U.S.A / États-Unis & Canada)
+300-379      GS1 France
+380          GS1 Bulgaria
+383          GS1 Slovenija
+385          GS1 Croatia
+387          GS1 BIH (Bosnia-Herzegovina)
+389          GS1 Montenegro
+400-440      GS1 Germany
+450-459      GS1 Japan
+460-469      GS1 Russia
+470          GS1 Kyrgyzstan
+471          GS1 Chinese Taipei
+474          GS1 Estonia
+475          GS1 Latvia
+476          GS1 Azerbaijan
+477          GS1 Lithuania
+478          GS1 Uzbekistan
+479          GS1 Sri Lanka
+480          GS1 Philippines
+481          GS1 Belarus
+482          GS1 Ukraine
+483          GS1 Turkmenistan
+484          GS1 Moldova
+485          GS1 Armenia
+486          GS1 Georgia
+487          GS1 Kazakstan
+488          GS1 Tajikistan
+489          GS1 Hong Kong, China
+490-499      GS1 Japan
+500-509      GS1 UK
+520-521      GS1 Association Greece
+528          GS1 Lebanon
+529          GS1 Cyprus
+530          GS1 Albania
+531          GS1 Macedonia
+535          GS1 Malta
+539          GS1 Ireland
+540-549      GS1 Belgium & Luxembourg
+560          GS1 Portugal
+569          GS1 Iceland
+570-579      GS1 Denmark
+590          GS1 Poland
+594          GS1 Romania
+599          GS1 Hungary
+600-601      GS1 South Africa
+603          GS1 Ghana
+604          GS1 Senegal
+607          GS1 Oman
+608          GS1 Bahrain
+609          GS1 Mauritius
+611          GS1 Morocco
+613          GS1 Algeria
+615          GS1 Nigeria
+616          GS1 Kenya
+617          GS1 Cameroon
+618          GS1 Côte d'Ivoire
+619          GS1 Tunisia
+620          GS1 Tanzania
+621          GS1 Syria
+622          GS1 Egypt
+624          GS1 Libya
+625          GS1 Jordan
+626          GS1 Iran
+627          GS1 Kuwait
+628          GS1 Saudi Arabia
+629          GS1 Emirates
+630          GS1 Qatar
+631          GS1 Namibia
+640-649      GS1 Finland
+690-699      GS1 China
+700-709      GS1 Norway
+729          GS1 Israel
+730-739      GS1 Sweden
+740          GS1 Guatemala
+741          GS1 El Salvador
+742          GS1 Honduras
+743          GS1 Nicaragua
+744          GS1 Costa Rica
+745          GS1 Panama
+746          GS1 Republica Dominicana
+750          GS1 Mexico
+754-755      GS1 Canada
+759          GS1 Venezuela
+760-769      GS1 Schweiz, Suisse, Svizzera
+770-771      GS1 Colombia
+773          GS1 Uruguay
+775          GS1 Peru
+777          GS1 Bolivia
+778-779      GS1 Argentina
+780          GS1 Chile
+784          GS1 Paraguay
+786          GS1 Ecuador
+789-790      GS1 Brasil
+800-839      GS1 Italy
+840-849      GS1 Spain
+850          GS1 Cuba
+858          GS1 Slovakia
+859          GS1 Czech
+860          GS1 Serbia
+865          GS1 Mongolia
+867          GS1 North Korea
+868-869      GS1 Türkiye
+870-879      GS1 Netherlands
+880          GS1 South Korea
+883          GS1 Myanmar
+884          GS1 Cambodia
+885          GS1 Thailand
+888          GS1 Singapore
+890          GS1 India
+893          GS1 Vietnam
+896          GS1 Pakistan
+899          GS1 Indonesia
+900-919      GS1 Austria
+930-939      GS1 Australia
+940-949      GS1 New Zealand
+950          GS1 Global Office
+955          GS1 Malaysia
+958          GS1 Macao, China
+960-969      Global Office - GTIN-8
+977          Serial publications / Publications en série (ISSN)
+978-979      Bookland / Livres (ISBN)
+980          Refund receipts / Remboursements
+981-983      GS1 Coupons
+99           GS1 Coupons

+ 40 - 28
dev/setup/apache/virtualhost

@@ -1,16 +1,18 @@
 <VirtualHost *:80>
 	#php_admin_value sendmail_path "/usr/sbin/sendmail -t -i"
 	#php_admin_value mail.force_extra_parameters "-f postmaster@mydomain.com"
-	php_admin_value sendmail_path "/usr/sbin/sendmail -t -i -f postmaster@mydomain.com"
+	#php_admin_value sendmail_path "/usr/sbin/sendmail -t -i -f postmaster@mydomain.com"
+	
 	php_admin_value open_basedir /tmp/:/home/.../htdocs:/home/.../dolibarr_documents:
 
 	
 	# Add this to use a custom apparmor profile when using apache php handler
-    <IfModule mod_apparmor.c>
-    AADefaultHatName sellyoursaas-instances
-    </IfModule>
+	<IfModule mod_apparmor.c>
+	AADefaultHatName sellyoursaas-instances
+	</IfModule>
 
     
+    # The URLs of the web site
 	ServerName myvirtualalias
 	ServerAlias myvirtualalias
 	
@@ -22,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
@@ -35,43 +42,47 @@
        	#AuthUserFile /etc/apache2/.htpasswd
        	#require valid-user
 	</Directory>
-	
-	# Leaving /public and /api, /dav, .well_known but also wrappers for document and viewimage accessible to everyone
+
+	# 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
@@ -82,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>

+ 1 - 1
dev/setup/fail2ban/filter.d/web-dolibarr-rulesbruteforce.conf

@@ -15,5 +15,5 @@
 # To test rule file on a existing log file
 # fail2ban-regex /mypath/documents/dolibarr.log /etc/fail2ban/filter.d/web-dolibarr-rulesbruteforce.conf
 
-failregex = ^ [A-Z\s]+ <HOST>\s+functions_dolibarr::check_user_password_.* Authentication KO
+failregex = ^ [A-Z\s]+ <HOST>\s+functions_.*::check_user_.* Authentication KO
 ignoreregex =

+ 24 - 7
dev/setup/fail2ban/jail.local

@@ -2,17 +2,20 @@
 # Examle of rule you can add to fail2ban to restrict bruteforce attacks.
 #
 
-[web-dol-passforgotten]
 
-; rule against call of passwordforgottenpage
+[web-accesslog-limit403]
+
+; rule against call of 403 forbidden access
+; note: you must change the path of log file to the one of the web server for the virtual host of the Dolibarr
 enabled = true
 port    = http,https
-filter  = web-dolibarr-rulespassforgotten
-logpath = /mypath/documents/documents/dolibarr.log
+filter  = web-accesslog-limit403
+logpath = /var/log/apache2/access.log
+;logpath = /var/log/apache2/other_vhosts_access.log
 action  = %(action_mw)s
 bantime  = 4320000   ; 50 days
 findtime = 86400     ; 1 day
-maxretry = 10
+maxretry = 100
 
 
 [web-dol-bruteforce]
@@ -24,13 +27,27 @@ filter  = web-dolibarr-rulesbruteforce
 logpath = /mypath/documents/documents/dolibarr.log
 action  = %(action_mw)s
 bantime  = 86400     ; 1 day
-findtime = 3600      ; 1 hour
+findtime = 14400     ; 4 hours
+maxretry = 20
+
+
+[web-dol-passforgotten]
+
+; rule against call of passwordforgottenpage
+enabled = true
+port    = http,https
+filter  = web-dolibarr-rulespassforgotten
+logpath = /mypath/documents/documents/dolibarr.log
+action  = %(action_mw)s
+bantime  = 4320000   ; 50 days
+findtime = 86400     ; 1 day
 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 +55,5 @@ logpath = /mypath/documents/documents/dolibarr.log
 action  = %(action_mw)s
 bantime  = 86400     ; 1 day
 findtime = 86400     ; 1 day
-maxretry = 500
+maxretry = 1000
 

+ 5 - 4
dev/setup/git/hooks/pre-commit

@@ -45,12 +45,11 @@ if [ "$FILES" != "" ]
 then
     echo "Running PHPCS Code Sniffer..."
 
-    #~/vendor/bin/phpcs --version
-    #phpcs --standard=PSR2 --encoding=utf-8 -n -p $FILES
     # Check Dolibarr standard
     ${DIRPHPCS}phpcs -s -p -d memory_limit=-1 --parallel=2 --extensions=php --colors --tab-width=4 --standard=dev/setup/codesniffer/ruleset.xml --encoding=utf-8 --runtime-set ignore_warnings_on_exit true $FILES
-    # Check your own standard
-    #${DIRPHPCS}phpcs -s -p -d memory_limit=-1 --parallel=2 --extensions=php --colors --tab-width=4 --standard=htdocs/custom/codesniffer/ruleset.xml --encoding=utf-8 --runtime-set ignore_warnings_on_exit true $FILES
+    
+    # Check a common standard
+    #${DIRPHPCS}phpcs -s -p -d memory_limit=-1 --parallel=2 --extensions=php --colors --tab-width=4 --standard=PSR2 --encoding=utf-8 --runtime-set ignore_warnings_on_exit true $FILES
     
     result2=$?    
 
@@ -60,7 +59,9 @@ then
         if [ "x$AUTOFIX" != "x0" ]
         then
             ${DIRPHPCS}phpcbf -s -p -d memory_limit=-1 --extensions=php --colors --tab-width=4 --standard=dev/setup/codesniffer/ruleset.xml --encoding=utf-8 --runtime-set ignore_warnings_on_exit true $FILES
+            
             #${DIRPHPCS}phpcbf -s -p -d memory_limit=-1 --extensions=php --colors --tab-width=4 --standard=htdocs/custom/codesniffer/ruleset.xml --encoding=utf-8 --runtime-set ignore_warnings_on_exit true $FILES
+            
             echo "Found some errors in syntax rules. An automatic fix has been applied. Check it before commit." 1>&2;
             exit 1
         else 

+ 14 - 0
dev/setup/qodana/README.md

@@ -0,0 +1,14 @@
+QODANA TUTO
+-----------
+This README explains how to use qodana to generate static analytics reports on the code
+ 
+Install docker
+ 
+ 
+Install qodana
+ 
+ 
+To run inspection on CLI
+ cd ~/git/dirtoscan
+ sudo qodana scan --show-report
+ 

+ 150 - 0
dev/tools/codespell/codespell-lines-ignore.txt

@@ -1,13 +1,18 @@
 													'capture' => true,							// Charge immediatly
+												// Save a stripe payment was done in realy life so later we will be able to force a commit on recorded payments
 											$pdf->SetFont('', '', $default_font_size - 1); // On repositionne la police par defaut
 											// Conversion du PDF en image png si fichier png non existant
 											// Save a stripe payment was done in realy life so later we will be able to force a commit on recorded payments
+											// To make a Stripe SEPA payment request, we must have the payment mode source already saved into societe_rib and retreived with ->sepaStripe
 										//break;	// No break for sortfield and sortorder so we can cumulate fields (is it realy usefull ?)
 									$errmsg = 'Failed to retreive paymentintent or charge from id';
 									$minifile = getImageFileNameForSize($fileinfo['basename'], '_mini'); // For new thumbs using same ext (in lower case howerver) than original
+									$more .= '<div clas="tagtd' . (empty($input['tdclass']) ? '' : (' "' . $input['tdclass'])) . '">&nbsp;</div>';
 									$more .= '<div clas="tagtd'.(empty($input['tdclass']) ? '' : (' "'.$input['tdclass'])).'">&nbsp;</div>';
+									$this->errors[] = "Error on updateing fk_prelevement_bons to ".$bon->id;
 									// Defaut
 									// Table of entities for export / Tableau des entites a exporter (cle=champ, valeur=entite)
+									// Table of entities requiring DISTINCT abandonment / Tableau des entites qui requiert abandon du DISTINCT (cle=entite, valeur=champ id child records)
 									// Table of fields to be filtered / Tableau des champs a filtrer (cle=champ, valeur1=type de donnees) on verifie que le module a des filtres
 									// Tableau des entites qui requiert abandon du DISTINCT (cle=entite, valeur=champ id child records)
 									console.log("We hide childs tickets of '.$groupcodefather.' group ticket")
@@ -29,6 +34,7 @@
 								// On retire les espaces autour des = et parenthèses
 								// remove invalid value, as it didnt match anything
 								// we dont use the rank from orderline because we may have lines from several orders
+								dol_syslog("makeStripeSepaRequest get stripe connet account", LOG_DEBUG);
 								print '<input type="hidden" class="amount" name="'.$namef.'" value="'.dol_escape_htmltag(GETPOST($namef)).'">'; // class is requied to be used by javascript callForResult();
 								print 'jQuery("select[name=\''.$paramkey.'\']").focus();'."\n"; // Not really usefull, but we keep it in case of.
 							$filles[$obj->fk_categorie_fille] = 1; // Set record for this child
@@ -49,6 +55,7 @@
 							'type'=>$fille->type,
 							/* Force to recompute the width of a select2 field when it was hidden and then shown programatically */
 							// Batch number managment
+							// Detailed virtual stock, looks bugged, uncomplete and need heavy load.
 							// Example for remplacement
 							// If error is more than 10 times the accurancy of rounding. This should not happen.
 							// If margin is calculated on Cost price, we set it by defaut (but only if value is not 0)
@@ -98,6 +105,7 @@
 						// Information if theres a rule restriction
 						// Jump on next occurence
 						// Label mouvement
+						// Login is successfull with this method
 						// Lot/serie
 						// Message-ID=A, In-Reply-To=B, References=B and message can BE an answer or NOT (a transfer rewriten)
 						// Not a recongized record
@@ -122,6 +130,7 @@
 						console.log("objectline_create.tpl Load desciption into text area : "+proddesc);
 						continue; // The field was not submited to be saved
 						dol_syslog("Entity was not set on http header with HTTP_DOLAPIENTITY (recommanded for performance purpose), so we switch now on entity of user (".$conf->entity.") and we have to reload configuration.", LOG_WARNING);
+						dol_syslog("functions_dolibarr::check_user_password_dolibarr Authentification ok - found old pass in database", LOG_WARNING);
 						dol_syslog("functions_dolibarr::check_user_password_dolibarr Authentification ok - found pass in database");
 						dol_syslog("functions_dolibarr::check_user_password_dolibarr Authentification ok - hash ".$cryptType." of pass is ok");
 						dol_syslog('We found unconsistent data into detailed line (diff_on_current_total = '.$diff_on_current_total.') for line rowid = '.$obj->rowid." (ht=".$obj->total_ht." vat=".$obj->total_tva." tax1=".$obj->total_localtax1." tax2=".$obj->total_localtax2." ttc=".$obj->total_ttc."). We fix the total_vat and total_ttc of line by running sqlfix = ".$sqlfix, LOG_WARNING);
@@ -134,6 +143,7 @@
 						setEventMessage("actions.lib::show_actions_messaging Error fetch ressource", 'errors');
 						setEventMessage("company.lib::show_actions_done Error fetch ressource", 'errors');
 						} else {				// We decrease agressiveness of reference color for color 3, 5, 7, ..
+					$_SESSION["dol_loginmesg"] = "Failed to login using Google. OAuth callback URL retreives a token with non valid data";
 					$alreadygrabbed[$urltograbbis] = 1; // Track that file was alreay grabbed.
 					$childrens = $this->getChildrenOfLine($row[0]);
 					$cluser = new User($this->db);
@@ -143,6 +153,7 @@
 					$heigth = $tmp[3];
 					$info[$conf->global->LDAP_FIELD_PASSWORD] = $this->pass_indatabase; // $this->pass_indatabase = mot de passe non crypte
 					$info[$conf->global->LDAP_MEMBER_FIELD_PASSWORD] = $this->pass_indatabase; // $this->pass_indatabase = mot de passe non crypte
+					$optstart .= ' data-tvatx-formated="' . dol_escape_htmltag(price($objp->tva_tx, 0, $langs, 1, -1, 2)) . '"';
 					$optstart .= ' data-tvatx-formated="'.dol_escape_htmltag(price($objp->tva_tx, 0, $langs, 1, -1, 2)).'"';
 					$outprice_ht = price($objp->price);			// formated for langage user because is inserted into input field
 					$outprice_ttc = price($objp->price_ttc);	// formated for langage user because is inserted into input field
@@ -187,6 +198,7 @@
 					// Strip off the beggining '<'
 					// TODO If not defined, use $objectobj->model_pdf (or defaut invoice config) to know what is template to use to regenerate doc.
 					// TODO Replace this with a checkbox for each payment mode: "Send request to PaymentModeManager immediatly..."
+					// TODO Replace this with a checkbox for each payment mode: "Send request to XXX immediatly..."
 					// TODO Show vat amout per tax level
 					// The image must have the class 'boxhandle' beause it's value used in DOM draggable objects to define the area used to catch the full object
 					// This member is linked with a thirdparty, so we also update thirdparty informations
@@ -209,6 +221,8 @@
 					//No diff => mean everythings is shipped
 					<p class="content">Nam elementum nisl et mi a commodo porttitor. Morbi sit amet nisl eu arcu faucibus hendrerit vel a risus. Nam a orci mi, elementum ac arcu sit amet, fermentum pellentesque et purus. Integer maximus varius lorem, sed convallis diam accumsan sed. Etiam porttitor placerat sapien, sed eleifend a enim pulvinar faucibus semper quis ut arcu. Ut non nisl a mollis est efficitur vestibulum. Integer eget purus nec nulla mattis et accumsan ut magna libero. Morbi auctor iaculis porttitor. Sed ut magna ac risus et hendrerit scelerisque. Praesent eleifend lacus in lectus aliquam porta. Cras eu ornare dui curabitur lacinia.</p>
 					GETPOST("mouvement", 'int'),
+					console.log("Clik on #topmenulogincompanyinfo-btn");
+					console.log("Clik on #topmenuloginmoreinfo-btn");
 					console.log("Clik on topmenulogincompanyinfo-btn");
 					console.log("Clik on topmenuloginmoreinfo-btn");
 					console.log("chartofaccounts seleted = "+$("#chartofaccounts").val());
@@ -223,6 +237,7 @@
 					if (((isModEnabled("fournisseur") && empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)) || isModEnabled("supplier_order")) && !empty($conf->global->WORKFLOW_BILL_ON_RECEPTION)) {  // Quand l'option est on, il faut avoir le bouton en plus et non en remplacement du Close ?
 					if (count($diff_array) == 0 && count($keysinwishednotindelivered) == 0 && count($keysindeliverednotinwished) == 0) { //No diff => mean everythings is received
 					if (empty($conf->global->PROJECT_DISABLE_UNLINK_FROM_OVERVIEW) || $user->admin) {		// PROJECT_DISABLE_UNLINK_FROM_OVERVIEW is empty by defaut, so this test true
+					if (isModEnabled("supplier_order") && !empty($conf->global->WORKFLOW_BILL_ON_RECEPTION)) {  // Quand l'option est on, il faut avoir le bouton en plus et non en remplacement du Close ?
 					if (isModEnabled('facture') && !empty($conf->global->WORKFLOW_BILL_ON_SHIPMENT)) {  // Quand l'option est on, il faut avoir le bouton en plus et non en remplacement du Close ?
 					if (isNaN(pbq)) { console.log("We use experimental option PRODUIT_CUSTOMER_PRICES_BY_QTY or PRODUIT_CUSTOMER_PRICES_BY_QTY but we could not get the id of pbq from product combo list, so load of price may be 0 if product has differet prices"); }
 					jQuery("#mouvement option").removeAttr("selected").change();
@@ -248,7 +263,9 @@
 				$newmenu->add("/projet/list.php?leftmenu=projets".($search_project_user ? '&search_project_user='.$search_project_user : ''), $langs->trans("List"), 1, $showmode, '', 'project', 'list');
 				$newmenu->add("/projet/list.php?leftmenu=projets".($search_project_user ? '&search_project_user='.$search_project_user : '').'&search_status=99', $langs->trans("List"), 1, $showmode, '', 'project', 'list');
 				$object->actionmsg = dol_concatdesc($object->actionmsg, "\n".$langs->transnoentities("AttachedFiles").': '.$attachs);
+				$paramfortooltipimg .= ' title="' . ($noencodehtmltext ? $htmltext : dol_escape_htmltag($htmltext, 1)) . '"'; // Attribut to put on img tag to store tooltip
 				$paramfortooltipimg .= ' title="'.($noencodehtmltext ? $htmltext : dol_escape_htmltag($htmltext, 1)).'"'; // Attribut to put on img tag to store tooltip
+				$paramfortooltiptd .= ' title="' . ($noencodehtmltext ? $htmltext : dol_escape_htmltag($htmltext, 1)) . '"'; // Attribut to put on td tag to store tooltip
 				$paramfortooltiptd .= ' title="'.($noencodehtmltext ? $htmltext : dol_escape_htmltag($htmltext, 1)).'"'; // Attribut to put on td tag to store tooltip
 				$showfield = 1; // Par defaut
 				$tagdatabase = true; // We don't know what it was before, so now we consider we are version choosed.
@@ -274,6 +291,7 @@
 				// Add field of attribut
 				// Ajout de l'utilisateur dans le groupe
 				// Batch number managment
+				// By default, electronic transfert from bank to bank
 				// Calculcate number of days consumed
 				// Complete object by loading several other informations
 				// Default and recommended: New method using ajax without submiting a page making a javascript history.go(-1) back
@@ -288,6 +306,7 @@
 				// Fix Get multicurrency param for transmited
 				// Fonctions de conversion non presente dans ce PHP
 				// Get lines of sources alread delivered
+				// Get next free nuber for the ref of bon
 				// If create form is coming from same page, it means that post was sent but an error occured
 				// If not abandonned
 				// If option "one bill per third" is set, and an invoice for this thirdparty was already created, we re-use it.
@@ -348,6 +367,8 @@
 				GETPOST("mouvement", 'int'),
 				console.log("Capture paymentIntent successfull "+paymentIntentId);
 				continue; // The field was not submited to be saved
+				dol_syslog("The user login has a validity between [".$user->datestartvalidity." and ".$user->dateendvalidity."], curren date is ".dol_now());
+				dol_syslog("functions_isallowed::check_user_api_key Authentication KO for '".$login."': The user login has a validity between [".$fuser->datestartvalidity." and ".$fuser->dateendvalidity."], curren date is ".dol_now());
 				dol_syslog('Bad password, connexion refused', LOG_DEBUG);
 				dol_syslog('Bad value for code, connexion refused');
 				dol_syslog('Call fetch_barcode with barcode_type not defined and cant be guessed', LOG_WARNING);
@@ -355,6 +376,7 @@
 				foreach ($legends as $val) {	// Loop on each serie
 				fwrite($handle, "\n-- WARNING: Show create table ".$table." return empy string when it should not.\n");
 				if ($ex) { // are we expecting an operator but have a number/variable/function/opening parethesis?
+				if ($forcedroundingmode == '1') {	// Check if we need adjustement onto line for vat. TODO This works on the company currency but not on foreign currency
 				if ($forcedroundingmode == '1') {	// Check if we need adjustement onto line for vat. TODO This works on the company currency but not on multicurrency
 				if (isModEnabled('facture') && !empty($conf->global->WORKFLOW_BILL_ON_RECEPTION)) {  // Quand l'option est on, il faut avoir le bouton en plus et non en remplacement du Close ?
 				if (isModEnabled('facture') && !empty($conf->global->WORKFLOW_BILL_ON_SHIPMENT)) {  // Quand l'option est on, il faut avoir le bouton en plus et non en remplacement du Close ?
@@ -394,9 +416,12 @@
 			$jsListType .= (!empty($jsListType) ? ',' : '').'"'.$type.'":"'.$curent.'"';
 			$level = 0; // if $level = -1, we dont' use sublevel recursion, we show all lines
 			$line->pa_ht = $line->pa_ht; // we choosed to have buy/cost price always positive, so no revert of sign here
+			$msgishtml = -1; // Unknow = autodetect by default
 			$msgishtml = -1; // Unknow by default
 			$paht_ret = $paht;
+			$paramfortooltipimg = ($extracss ? ' class="' . $extracss . '"' : '') . ($extrastyle ? ' style="' . $extrastyle . '"' : ''); // Attribut to put on td text tag
 			$paramfortooltipimg = ($extracss ? ' class="'.$extracss.'"' : '').($extrastyle ? ' style="'.$extrastyle.'"' : ''); // Attribut to put on td text tag
+			$paramfortooltiptd = ($extracss ? ' class="' . $extracss . '"' : '') . ($extrastyle ? ' style="' . $extrastyle . '"' : ''); // Attribut to put on td text tag
 			$paramfortooltiptd = ($extracss ? ' class="'.$extracss.'"' : '').($extrastyle ? ' style="'.$extrastyle.'"' : ''); // Attribut to put on td text tag
 			$pdf->MultiCell($this->posxdiscount - $this->posxunit, 2, $outputlangs->transnoentities("Label Mouvement"), '', 'C');
 			$pdf->SetXY($this->getColumnContentXStart($colKey), $curY); // Set curent position
@@ -444,8 +469,10 @@
 			// 2 - Suppression des utilisateurs du groupe Dolibarr qui ne sont plus dans le groupe LDAP
 			// A redirect is added if API call successfull
 			// Action according to choosed sending method
+			// Add entry into bank accoun
 			// Add personnal information
 			// Adding <b> may convert the original string into a HTML string. Sowe have to first
+			// Adding a RSS feed into a sitemap should nto be required. The RSS contains pages that are already included into
 			// Amount payed
 			// Atom support many links per containging element.
 			// Aucun model par defaut.
@@ -473,6 +500,7 @@
 			// Gestion des utilisateurs associés au groupe
 			// Groupes
 			// If a bank account is prodived and we ask to use it as creditor, we use the bank address
+			// If googleoauth_login has been set (by google_oauthcallback after a successfull OAUTH2 request on openid scope
 			// If not abandonned
 			// If stock decrease is on invoice validation, the theorical stock continue to
 			// If there is a nown BOM, we force the type of MO to the type of BOM
@@ -487,6 +515,7 @@
 			// Note: We accept disabled account as parent account so we can build a hierarchy and use only childs
 			// Note: We are here only if $conf->global->MAIN_AGENDA_ACTIONAUTO_action is on (tested at begining of this function).
 			// Nouveau système du comon object renvoi des rowid et non un id linéaire de 1 à n
+			// On create mode, force separator group to not be collapsable
 			// On nettoie le header pour qu'il ne se termine pas par un retour chariot.
 			// On parcourt donc une liste d'objets en tant qu'objet unique
 			// On selectionne les users qui ne sont pas deja dans le groupe
@@ -512,6 +541,8 @@
 			// Succes
 			// TODO : revoir la gestion des groupes (ou script de sync groupes)
 			// TODO A virer quand sera gere par l'appelant
+			// TODO Add a link "Show more..." for all ohter informations.
+			// TODO Use a cahe on user
 			// TODO We can't, we dont' have full path of file, only last_main_doc and ->element, so we must first rebuild full path $destfull
 			// TODO We show localtax from $object, but this properties may not be correct. Only value $object->default_vat_code is guaranted.
 			// TODO mettre dans une classe propre au pays
@@ -526,6 +557,7 @@
 			// We must filter on assignement table
 			// We need to keep the 10 lastest number of invoice doc_ref not the beginning part that is the unusefull almost same part
 			// We use invoice date $data->doc_date not $date_ecriture which is the transfert date
+			// We use invoice date $line->doc_date not $date_ecriture which is the transfert date
 			// add substition variable for ticket
 			// add variables subtitutions ticket
 			// count the orders to ship in theorical stock when some are already removed by invoice validation.
@@ -549,7 +581,9 @@
 			//Origin project strat date
 			//Stock mouvement
 			//We use invoice date $data->doc_date not $date_ecriture which is the transfert date
+			//We use invoice date $line->doc_date not $date_ecriture which is the transfert date
 			//XXX: Should be done just befor commit no ?
+			//but the note is saved, so just add a notification will be enought
 			//if ($user->socid > 0) $socid = $user->socid;    // For external user, no check is done on company because readability is managed by public status of project and assignement.
 			//print "connexion de type=".$conf->db->type." sur host=".$conf->db->host." port=".$conf->db->port." user=".$conf->db->user." name=".$conf->db->name;
 			//si le sujet n'est pas celui qui a été effacé alors on concatene
@@ -561,6 +595,7 @@
 			dol_syslog("Failed to read image using Imagick (Try to install package 'apt-get install php-imagick ghostscript' and check there is no policy to disable ".$ext." convertion in /etc/ImageMagick*/policy.xml): ".$e->getMessage(), LOG_WARNING);
 			dol_syslog("Fichier invalide",LOG_WARNING);
 			dol_syslog("RejetPrelevement::_send_email Userid invalide");
+			dol_syslog('User not found or not valid, connexion refused');
 			dol_syslog('User not found, connexion refused');
 			dol_syslog(get_class($this) . "::validate action abandonned: already validated", LOG_WARNING);
 			dol_syslog(get_class($this). '::setFrequencyAndUnit was called on objet with params frequency defined but unit not defined', LOG_ERR);
@@ -585,6 +620,7 @@
 			dol_syslog(get_class($this)."::setNextDate was called on objet with property table_element not defined", LOG_ERR);
 			dol_syslog(get_class($this)."::setProject was called on objet with property table_element not defined", LOG_ERR);
 			dol_syslog(get_class($this)."::setShippingMethod was called on objet with property table_element not defined", LOG_ERR);
+			dol_syslog(get_class($this)."::setVATReverseCharge was called on objet with property table_element not defined", LOG_ERR);
 			dol_syslog(get_class($this)."::setWarehouse was called on objet with property table_element not defined", LOG_ERR);
 			dol_syslog(get_class($this)."::updateAttribute successfull", LOG_DEBUG);
 			dol_syslog(get_class($this)."::update_note was called on objet with property table_element not defined", LOG_ERR);
@@ -619,6 +655,8 @@
 			return -1; // Alternate souce not found
 			return false; // Sould be 6
 			return false; // Sould be 6 but can be 123-456
+			setEventMessage('The element '.$element.' is not supported for uploading file. dir_output is unknow.', 'errors');
+			throw new Exception('The element '.$element.' is not supported for uploading file. dir_output is unknow.');
 			throw new RestException(403, 'Forbidden. This parameter cant be read with APIs');
 			while ($i < $nblot) {	// Loop on each serie
 			|| empty($fk_price_level) // if fetch an unique level dont erase all already fetched
@@ -680,6 +718,7 @@
 		$rouge = hexdec(substr($color, 0, 2)); //conversion du canal rouge
 		$serie = array();
 		$showfield = 1; // By defaut
+		$sql .= " AND (p.last_check_backlink IS NULL OR p.last_check_backlink <= '".$this->db->idate($now - 24 * 3600)."')"; // Never more than 1 check every day to check that website contains a referal link.
 		$sql .= " AND ff.fk_statut IS NULL"; // Renvoi vrai si pas facture de remplacement
 		$sql .= " AND ff.type IS NULL"; // Renvoi vrai si pas facture de remplacement
 		$sql .= " AND mc.statut NOT IN (-1,0)"; // -1 erreur, 0 non envoye, 1 envoye avec succes
@@ -705,6 +744,8 @@
 		$this->assertEquals('a : b " c \' d \' e é', $decodedstring, 'Function did not sanitize correclty');
 		$this->assertEquals('afile', $result);
 		$this->assertEquals('e&eacute;e', $decodedstring, 'Function did not sanitize correclty with test 1');
+		$this->assertEquals('text  text', $decodedstring, 'Function did not sanitize correclty with test 4a');
+		$this->assertEquals('text <link href="aaa"> text', $decodedstring, 'Function did not sanitize correclty with test 4b');
 		$this->assertTrue($result, 'move of directory with directory whitout rename needed in directory');
 		$this->assertTrue($result, 'move of directory with file whitout rename needed in directory');
 		$this->const[$r][2] = "DOL_DATA_ROOT/doctemplates/stocks/mouvements";
@@ -727,6 +768,7 @@
 		$this->rights[4][3] = 0; // La permission est-elle une permission par defaut
 		$this->signature_line = dol_hash($keyforsignature, '5'); // Not really usefull
 		$this->tva_intra = empty($conf->global->MAIN_INFO_TVAINTRA) ? '' : $conf->global->MAIN_INFO_TVAINTRA; // VAT number, not necessarly INTRA.
+		$this->tva_intra = getDolGlobalString('MAIN_INFO_TVAINTRA'); // VAT number, not necessarly INTRA.
 		$valuetoshow = ucfirst($fieldlist[$field]); // Par defaut
 		'filles' => array('name'=>'filles', 'type'=>'tns:FillesArray')
 		'fk_statut' =>array('type'=>'smallint(6)', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'position'=>1000, 'arrayofkeyval'=>array(0=>'Draft', 1=>'Validated', 2=>'Paid', 3=>'Abandonned')),
@@ -744,6 +786,7 @@
 		// 'member'           to add a tab in fundation member view
 		// - If not set, we accept ot have amount defined as parameter (for backward compatibility).
 		// Action according to choosed sending method
+		// Add a where here keeping only the citeria on $tabletouse
 		// Add code to open url using the popup. Add also hidden field to retreive the returned variables
 		// Add infor from $object->xxx where xxx has been loaded by fetch_origin() of shipment
 		// Add the count of record only for the main/first level object. Parents are necessarly unique for each record.
@@ -757,8 +800,10 @@
 		// Chargement librairie pour acces fonction controle RIB
 		// Check if field was submited to be edited
 		// Check paramaters
+		// Check that the redirect_uri that wil be used is same than url of current domain
 		// Classif "paid partialy"
 		// Clean paramater $typeofdata
+		// Clear all fields out of interrest
 		// Concatenation des differents codes.
 		// Confirm cancelation
 		// Confirm deleteion
@@ -767,6 +812,7 @@
 		// Create with status validated immediatly
 		// Creation de la classe d'import du model Import_XXX
 		// Creation objet
+		// Date delivery planed
 		// Delivery date planed
 		// Dependancies
 		// Don't log Luracast Restler Explorer recources calls
@@ -796,6 +842,7 @@
 		// Keep the orginal
 		// Limite acces si droits non corrects
 		// Links beetween objects are stored in this table
+		// Load extrafields if not allready done
 		// Load extrafiels if not allready does
 		// Log the init of hook but only for hooks thare are declared to be managed
 		// Loop on each line keword was found into file.
@@ -843,6 +890,7 @@
 		// Syntaxe ko
 		// Syntaxe ok
 		// TODO Check the lineid $lineid is a line of ojbect
+		// TODO Remove hooks with type 'output' (exemple createFrom). All hooks must be converted into 'addreplace' hooks.
 		// TODO Remove hooks with type 'output' (exemple getNomUrl). All hooks must be converted into 'addreplace' hooks.
 		// Tableau des parametres complementaires du post
 		// Test with restricthtml + MAIN_RESTRICTHTML_ONLY_VALID_HTML to test disabling of bad atrributes
@@ -861,9 +909,11 @@
 		// We start scan from the not before so if two tabs were opend at differents seconds and we close one (so the js timer),
 		// Wrapping pour les projets
 		// accomodate both SMTP AND ESMTP capable servers
+		// add menu manualy
 		// additionnal list with adherents of company
 		// admin login no exectued.
 		// attemp to create without mandatory fields :
+		// delete menu manuelly
 		// for js desabled compatibility set $url as call to confirm action and $params['confirm']['url'] to confirmed action
 		// groupe
 		// if "frequency" is empty or = 0, the reccurence is disabled
@@ -872,6 +922,7 @@
 		// l'adherent n'est pas public par defaut
 		// need to be ignored from scrutinizer setTypeFromTypeString was created as deprecated to incite developper to use object oriented usage
 		// on verifie si l'objet est en numerotation provisoire
+		// parcourir les objets
 		// personnal stocks are not tagged into table llx_entrepot
 		// reload page to retrieve customer informations
 		// reload page to retrieve supplier informations
@@ -884,6 +935,7 @@
 		//Add hook to filter on user (for exemple on usergroup define in custom modules)
 		//If dispach process running we add the number of item to dispatch into the head
 		//If invoice has been converted and the conversion has been used, we dont have remain to pay on invoice
+		//If no task avaiblable, redirec to to add confirm
 		//In some case $object is not instanciate (for paiement on custom object) We need to deal with payment
 		//Iterate over each expression splitted by $separator_chr
 		//Label mouvement
@@ -891,14 +943,18 @@
 		//We should use dol_now function not time however this is wrong date to transfert in accounting
 		//check if tag type submited exists into Tag Map categorie class
 		//decoding the respose
+		//fetch informations needs on this mode
+		//http_response_code(500);		// If we use 500, message is not ouput with some command line tools
 		//postion of Key
 		//prevents agains infinite loop when we can't create root folder
 		//print "L'EAN se compose de 8 caracteres, 7 chiffres plus une cle de controle.<br>";
 		//print $rouge.$vert.$bleu;
 		//sinon on remplace les choix de l'utilisateur par une ligne de checkbox pour recuperer de nouvelles valeurs
+		//sinon on remplace les choix de l'utilisateur par une ligne de checkbox pour saisie
 		//var_dump($serie);
 		console.log("Cancel check_events() with dolnotif_nb_test_for_page="+dolnotif_nb_test_for_page+". Check is useless because javascript Notification.permission is "+Notification.permission+" (blocked manualy or web site is not https).");
 		console.log("Change montly amount echeance="+echeance+" idcap="+idcap+" capital="+capital);
+		dol_syslog("The user login has a validity between [".$user->datestartvalidity." and ".$user->dateendvalidity."], curren date is ".dol_now());
 		dol_syslog("Warning: Function form_constantes is calle with parameter strictw3c = 0, this is deprecated. Value must be 2 now.", LOG_DEBUG);
 		dol_syslog(get_class($this)."::getCustomerAccount Try to find the first system customer id for ".$site." of thirdparty id=".$id." (exemple: cus_.... for stripe)", LOG_DEBUG);
 		dol_syslog(get_class($this)."::setCategoriesCommon Oject Id:".$this->id.' type_categ:'.$type_categ.' nb tag add:'.count($categories), LOG_DEBUG);
@@ -906,10 +962,12 @@
 		foreach ($arrayofcriterias as $criterias) {
 		foreach ($parent as $key => $value) {        // key=label, value is array of childs
 		header("Location: ".$_SERVER["PHP_SELF"].'?id='.$id); // To avoid pb whith back
+		http_response_code(202);		// If we use 202, this is not really an error message, but this allow to ouput message on command line tools
 		if (!$login || (in_array('ldap', $authmode) && empty($passwordtotest))) {	// With LDAP we refused empty password because some LDAP are "opened" for anonymous access so connexion is a success.
 		if (!empty($this->phone)) {	// If a phone of thirdparty is defined, we add it ot mobile of contacts
 		if (!is_array($this->userassigned) && !empty($this->userassigned)) {	// For backward compatibility when userassigned was an int instead fo array
 		if ($lines[$i]->fk_parent == $parent || $level < 0) {       // if $level = -1, we dont' use sublevel recursion, we show all lines
+		if ($lines[$i]->fk_task_parent == $parent || $level < 0) {       // if $level = -1, we dont' use sublevel recursion, we show all lines
 		if ($user->hasRight('stock', 'mouvement', 'creer')) {
 		if (GETPOST('import_name')) {	// If we have submited a form, we take value used fot the update try
 		if (dol_strlen($phone) == 10) {// fixe 6 chiffres +352_AA_BB_CC
@@ -940,6 +998,7 @@
 		} else // We decrease agressiveness
 		} else {	// If thirdparty unkown, output the waiting account
 		} else { // old method. deprecated because ot can't retrieve type
+		} elseif (!empty($this->childtables)) {	// If object has childs linked with a foreign key field, we check all child tables.
 		} elseif (!empty($this->fk_element) && !empty($this->childtables)) {	// If object has childs linked with a foreign key field, we check all child tables.
 		} elseif (dol_strlen($phone) == 11) {// fixe 7 chiffres +352_AA_BB_CC_D
 		} elseif (dol_strlen($phone) == 12) {// fixe 8 chiffres +352_AA_BB_CC_DD
@@ -985,6 +1044,7 @@
 	 *	@param	int		$id      Id du paiement dont il faut afficher les infos
 	 *	@param	mixed				$gm			'gmt'=Input informations are GMT values, 'tzserver'=Local to server TZ
 	 *	@param	string		$method		method of transmision to bank
+	 *	@param	string		$method		method of transmision to bank (0=Internet, 1=Api...)
 	 *	@param	string	$dolibarr_main_db_pass 		Mot de passe user a creer
 	 *	@param	string	$field_desc 		Tableau associatif de description du champ a inserer[nom du parametre][valeur du parametre]
 	 *	@param	string	$resko          resultat si test non egal
@@ -1048,6 +1108,7 @@
 	 * 	@return	resource|int         		1 if cancelation is ok or transaction not open, 0 if error
 	 * 	Charge les informations d'ordre info dans l'objet entrepot
 	 * 	Class line Contructor
+	 * 	Load the array of extrafields defintion $this->attributes
 	 * 	Renvoi la description par defaut du modele de numerotation
 	 * 	Return list of all child users id in herarchy (all sublevels).
 	 * 	Total of the VAT payed
@@ -1059,7 +1120,12 @@
 	 *                  					      Sinon la TVA proposee par defaut=0. Fin de regle.
 	 *                  		                  Si vendeur non assujeti a TVA, TVA par defaut=0. Fin de regle.
 	 *                                                          - string (categories ids seprated by comma)
+	 *                                      - string (categories ids seprated by comma)
 	 *                                      All types can also return some values into an array ->results that will be finaly merged into this->resArray for caller.
+	 *                         Si (vendeur et acheteur dans Communaute europeenne) et bien vendu = moyen de transports neuf (auto, bateau, avion), TVA par defaut=0 (La TVA doit etre paye par l'acheteur au centre d'impots de son pays et non au vendeur). Fin de regle.
+	 *                         Si le (pays vendeur = pays acheteur) alors la TVA par defaut=TVA du produit vendu. Fin de regle.
+	 *                         Si vendeur non assujeti a TVA, TVA par defaut=0. Fin de regle.
+	 *                         Sinon la TVA proposee par defaut=0. Fin de regle.
 	 *                fulllabel = nom avec chemin complet de la categorie
 	 *                fullpath = chemin complet compose des id
 	 *      @param	User	$user   		Objet user
@@ -1068,6 +1134,7 @@
 	 *      @param      User	$user       	Objet user
 	 *      @param      User	$user        	Objet user making change
 	 *      @param      User	$user       Objet user
+	 *      @param  User	$user       Objet user
 	 *      @param  string	$vatrate     		VAT rate (may contain the vat code too). Exemple: '1.23', '1.23 (ABC)', ...
 	 *      Build the conditionnal string from filter the query
 	 *      Charge indicateurs this->nb de tableau de bord
@@ -1090,6 +1157,7 @@
 	 *    Renvoi si un compte peut etre supprimer ou non (sans mouvements)
 	 *    Retourne la liste deroulante des differents etats d'une note de frais.
 	 *    Retourne la liste deroulante des formes juridiques tous pays confondus ou pour un pays donne.
+	 *    Return HTML to show the search and clear seach button
 	 *    Return combo list of differents status of a proposal
 	 *    Return incoterms informations
 	 *    Return incoterms informations for pdf display
@@ -1109,6 +1177,7 @@
 	 *  @param		string		$criteria	Use %% as magic caracters. For exemple to find all item like <b>jean, joe, jim</b>, you can input <b>j%%</b>, you can also use ; as separator for value,
 	 *  @param		string	$filter				SQL filter on users. This parameter must not come from user intput.
 	 *  @param	    string	$pass		Mot de passe
+	 *  @param	 int			$disablecrop		Disable crop feature on images (-1 = auto, prefer to set it explicitely to 0 or 1)
 	 *  @param	 int	$disablecrop		Disable crop feature on images (-1 = auto, prefer to set it explicitely to 0 or 1)
 	 *  @param	DoliDB		$db			Handler acces base
 	 *  @param	Product		$product		Objet product
@@ -1124,6 +1193,7 @@
 	 *  @param	array			$restrictlinksto	Restrict links to some elements, for exemple array('order') or array('supplier_order'). null or array() if no restriction.
 	 *  @param	array		$arrayofrecords  	Array of record informations (array('textleft'=>,'textheader'=>, ..., 'id'=>,'photo'=>)
 	 *  @param	bool		$multiple		add [] in the name of element and add 'multiple' attribut
+	 *  @param	float		$curY    		curent Y position
 	 *  @param	float		$curY    	curent Y position
 	 *  @param	int			$fk_product_stock	id product_stock for objet
 	 *  @param	int			$fk_product_stock   id product_stock for objet
@@ -1148,6 +1218,7 @@
 	 *  @param 		User	$user   Objet utilisateur qui met a jour le don
 	 *  @param 	User	$user       Objet du user qui cree
 	 *  @param 	User	$user       Objet user
+	 *  @param 	array 		$params    		array of additionals parameters
 	 *  @param    	int				$info_bits        	Miscellaneous informations on line
 	 *  @param    	int		$socid			Id third pary
 	 *  @param      Translate   $outputlangs    objet lang a utiliser pour traduction
@@ -1189,12 +1260,14 @@
 	 *  Fonction qui dit si cet utilisateur est un redacteur existant dans spip
 	 *  Function to build PDF on disk, then output on HTTP strem.
 	 *  Les parametres sont deja cense etre juste et avec valeurs finales a l'appel
+	 *  Mise a jour de l'objet ligne de commande en base
 	 *  Mise a jour en base de la date de derniere connexion d'un utilisateur
 	 *  On compare juste manuellement si la database choisie est bien celle activee par la connexion
 	 *  Renvoi la description par defaut du modele de numerotation
 	 *  Renvoi si un code est pris ou non (par autre tiers)
 	 *  Renvoi si un code respecte la syntaxe
 	 *  Retourne la version traduite du texte passe en parametre complete du code pays
+	 *  Retrieve informations about internal contacts
 	 *  Return a HTML link to the user card (with optionaly the picto)
 	 *  Return a link (with optionaly the picto)
 	 *  Return a link to the a lot card (with optionaly the picto)
@@ -1248,13 +1321,19 @@
 	 * @param 	Object			$objecttmp			Object to knwo the table to scan for combo.
 	 * @param 	User		$user		User wich display
 	 * @param 	array	$type		Array with type for each serie. Example: array('type1', 'type2', ...) where type can be:
+	 * @param 	array 			$excludelinksto 	Do not show links of this type, for exemple array('order') or array('supplier_order'). null or array() if no exclusion.
+	 * @param 	array 			$restrictlinksto 	Restrict links to some elements, for exemple array('order') or array('supplier_order'). null or array() if no restriction.
+	 * @param 	array 	$arrayofcriterias 					Array of available search criterias. Example: array($object->element => $object->fields, 'otherfamily' => otherarrayoffields, ...)
+	 * @param 	array 	$search_component_params 			Array of selected search criterias
 	 * @param 	int		$no_email	    1=Do not send mailing, 0=Ok to recieve mailling
 	 * @param 	object		$line_ext			Objet with full information of line. $line_ext->detail_batch must be an array of ExpeditionLineBatch
 	 * @param 	string		$uploaded_file		Uploade file
 	 * @param 	string	$label				Label (Example: 'Leave', 'Manual update', 'Leave request cancelation'...)
 	 * @param 	string	$uploaded_file		Uploade file
 	 * @param 	string 			$head				Optionnal head lines
+	 * @param 	string 		$elemtype 		Type of element we show ('category', ...). Will execute a formating function on it. To use in readonly mode if js component support HTML formatting.
 	 * @param 	string 		$type_categ 		Category type ('customer', 'supplier', 'website_page', ...) definied into const class Categorie type
+	 * @param 	string 	$search_component_params_hidden 	String with $search_component_params criterias
 	 * @param    User 	$user 			Objet User who close contract
 	 * @param    int	$rowid      Id of third party to load (Use 0 to get a specimen record, use null to use other search criterias)
 	 * @param    string $dolibarr_main_db_pass 	Mot de passe user a creer
@@ -1266,7 +1345,9 @@
 	 * @param   double		$alreadypaid	0=No payment already done, >0=Some payments were already done (we recommand to put here amount payed if you have it, 1 otherwise)
 	 * @param   int 	$fk_socpeople       	Id of thirdparty contact (if source = 'external') or id of user (if souce = 'internal') to link
 	 * @param   int         	$default_font_size  default siez of font
+	 * @param   int     $id      id du paiement dont il faut afficher les infos
 	 * @param   string   $alias   	String of alias of table for fields. For example 't'. It is recommended to use '' and set alias into fields defintion.
+	 * @param   string  $alias   		String of alias of table for fields. For example 't'. It is recommended to use '' and set alias into fields defintion.
 	 * @param   string  $ref                	Reference of object (This will define subdir automatically and store submited file into it)
 	 * @param   string $resko resultat si test non egal
 	 * @param   string $resok resultat si test egal
@@ -1277,19 +1358,30 @@
 	 * @param  string        $extrafieldsobjectkey	The key to use to store retreived data (for example $object->table_element)
 	 * @param  string  $moreparam      			To add more parametes on html input tag
 	 * @param  string  $moreparam      To add more parametes on html input tag
+	 * @param Object 		$objecttmp 			Object to knwo the table to scan for combo.
 	 * @param Translate $outputlangs objet lang a utiliser pour traduction
 	 * @param array    $dict      Array of dictionnary for translation
 	 * @param array  $array_receiver   	  Array of receiver. exemple array('name' => 'John Doe', 'email' => 'john@doe.com', etc...)
+	 * @param bool 			$multiple 			add [] in the name of element and add 'multiple' attribut
+	 * @param bool 			$multiple 		add [] in the name of element and add 'multiple' attribut
+	 * @param bool 		$multiple 			add [] in the name of element and add 'multiple' attribut (not working with ajax_autocompleter)
+	 * @param bool 		$multiple 		add [] in the name of element and add 'multiple' attribut
+	 * @param bool $multiple add [] in the name of element and add 'multiple' attribut (not working with ajax_autocompleter)
 	 * @param int			$mode 				O for create, R for regenerate (Look always 0 ment toujours 0 within the framework of XML exchanges according to documentation)
+	 * @param int 			$socid 				Id ot third party or 0 for all or -1 for empty list
+	 * @param int 		$socid 			Id ot third party or 0 for all
 	 * @param int 	$i 		Rank from which we want to create skilldets (level $i to HRM_MAXRANK wil be created)
 	 * @param int       $month 				Specifig month - Can be empty
 	 * @param int       $year 				Specifig year - Can be empty
 	 * @param int $_type  Interger value representing Mail Transport Type
 	 * @param string	$str 		Original string to encode and optionaly truncate
+	 * @param string 		$page 				Url of page to call if confirmation is OK. Can contains parameters (param 'action' and 'confirm' will be reformated)
+	 * @param string 	$editvalue 		When in edit mode, use this value as $value instead of value (for example, you can provide here a formated price instead of numeric value, or a select combo). Use '' to use same than $value
 	 * @param string 	$output_format 	(html/opton (for option html only)/array (to return options arrays
 	 * @param string    $page           Page name (website id must also be filled if this parameter is used). Exemple 'myaliaspage' or 'fr/myaliaspage'
 	 * @param string $_path Path to the sendmail execuable
 	 * @param string $key Authentification key
+	 * @param string $selected Id remise fixe pre-selectionnee
 	 * @return	        string      Id connexion
 	 * @return	int         			1 if transaction successfuly opened or already opened, 0 if error
 	 * @return	string							Formated value
@@ -1313,6 +1405,7 @@
 	 * @var array Contents informations. Usually created at runtime by loadBox().
 	 * @var array Custom family informations
 	 * @var array Header informations. Usually created at runtime by loadBox().
+	 * @var array box dependancies
 	 * @var int 	Date for cancelation
 	 * @var int 	ID for cancelation
 	 * @var int -1=Unkown duration
@@ -1351,6 +1444,7 @@
 	 * Method was used to test  module builder convertion to this form usage.
 	 * Mot de passe de l'administrateur
 	 * Multi-diminsional array containg addresses the message will
+	 * Note: To complete search with a particular filter on select, you can set $object->next_prev_filter set to define SQL criterias.
 	 * Optionaly with $selected_warehouse_id parameter user can get stock of specific warehouse
 	 * Parse criteria to return a SQL qury formated
 	 * Path to the sendmail execuable
@@ -1363,6 +1457,7 @@
 	 * Retrieve number of equipments for a product lot/serial
 	 * Return Unix time from ical date time fomrat (YYYYMMDD[T]HHMMSS[Z] or YYYYMMDD[T]HHMMSS)
 	 * Return an array with Agenda Events informations
+	 * Return an array with Currency informations
 	 * Return an array with Expense Report informations
 	 * Return an array with MO informations
 	 * Return an array with bom informations
@@ -1402,8 +1497,10 @@
 	 * Return the addtional SQL SELECT query for filtering a list by a category
 	 * Return verion of data file
 	 * Returns the partial diff for the specificed sequences, in reverse order.
+	 * Serivce expiration unit
 	 * The string return is not formated (translated with transnoentitiesnoconv).
 	 * This can be changed for 2byte characers sets
+	 * This method takes a list of given addresses, via an array or a COMMA delimted string, and inserts them into a highly
 	 * Udpate the percent value of a event with the given id
 	 * Unsuscribe all : 1 = contact has globaly unsubscribe of all mass emailings
 	 * and restore it into another database with different id wihtout comprimising checksums
@@ -1418,6 +1515,9 @@
 	 * to define the UNIX file system path to the sendmail execuable
 	 If an error occured, show the resulting errors
 	# ---------------------------- mot de passe admin mysql
+	# Add cach performance directives
+	# Log directoves
+	# Log directoves        
 	$IBS_RETOUR = "montant:M;ref:R;auto:A;trans:T"; // Format des parametres du get de validation en reponse (url a definir sous paybox)
 	$alwaysuncheckedmodules = array('dav', 'dynamicprices', 'incoterm', 'loan', 'multicurrency', 'paybox', 'paypal', 'stripe', 'google', 'printing', 'scanner', 'skype', 'website'); // Module we dont want by default
 	$amount = (is_numeric($amount) ? $amount : 0); // Check if amount is numeric, for example, an error occured when amount value = o (letter) instead 0 (number)
@@ -1431,6 +1531,8 @@
 	$ldaprecords = $ldap->getRecords('*', $conf->global->LDAP_MEMBER_DN, $conf->global->LDAP_KEY_MEMBERS, $required_fields, 'member'); // Fiter on 'member' filter param
 	$ldaprecords = $ldap->getRecords('*', $conf->global->LDAP_USER_DN, $conf->global->LDAP_KEY_USERS, $required_fields, 'user'); // Fiter on 'user' filter param
 	$mege = imap_fetchbody($mbox, $jk, $fpos);
+	$mege = imap_fetchbody($mbox, $jk, $fpos, FT_UID);
+	$object->status = $object->fk_statut; // for backwad compatibility
 	$opensurveysondage->mail_admin = $_SESSION['adresse'];
 	$pdf->SetXY($savx, $savy);
 	$savy = $pdf->getY();
@@ -1474,6 +1576,7 @@
 	// Definition des parametres vente produit pour paybox
 	// Definition, nettoyage parametres
 	// Delivery date planed
+	// Donwload file
 	// Edition des varibales globales
 	// FIX for compatibity habitual tabs
 	// Fixe les dimensions de la vignette
@@ -1488,12 +1591,15 @@
 	// Initialize array of search criterias
 	// Link for delivery fields ref and date. Does not duplicate the line because we should always have ony 1 link or 0 per shipment
 	// List of fiels for action=list
+	// None. Beeing connected is enough.
 	// Nunber of files
 	// On remet cette lecture de permission ici car nécessaire d'avoir le nouveau statut de l'objet après toute action exécutée dessus (après incrémentation par exemple, le bouton supprimer doit disparaître)
 	// Parameteres execution
 	// Payment informations
 	// Payments not linked to an invoice. Should not happend. For debug only.
 	// Peut valoir un nombre ou liste de nombre separes par virgules
+	// Properties to store project informations
+	// Replace HTML coments
 	// Replace protected special codes with matching number of _ as wild card caracter
 	// Search parent to set task_parent_alternate_id (requird by ganttchart)
 	// Set also dependencies between use taks and bill time
@@ -1514,20 +1620,25 @@
 	// We keep it with value ForceBuyingPriceIfNull = 2 for retroactive effect but results are unpredicable.
 	// We open a list of transaction of a dedicated account and no page was set by defaut
 	// When a dictionnary is commented
+	// add properties and declare them in consturctor
+	// but in some situations that is required (update legal informations for example)
 	// for gravatar use get_avatar_from_service('gravatar', md5 hash email@adress, size-in-px )
 	// on transfert les données de l'un vers l'autre
 	// si le filtrage est parametre pour l'export ou pas
 	// start and end date that change with time andd that may be different that the period of reference for price.
+	// verify informations entred
 	//' If an error occured, show the resulting errors
 	//' If the API call succeded, then redirect the buyer to PayPal to begin to authorize payment.
 	//' of the authorization, incuding any shipping information of the
 	//'__PERSONALIZED__' => 'TESTPersonalized'	// Hiden because not used yet
 	//'options_attr2'=>'Attr2 balbal' //Extra field exemple where field code is attr2
 	//,'options_attr1'=>'Attr1 balbal', //Extra field exemple where field code is attr1
+	//If invoice has been converted and the conversion has been used, we dont have remain to pay on invoice
 	//If no task avaiblable, redirec to to add confirm
 	//TODO : Note and docuement
 	//console.log("amount before="+amount+" rouding="+rounding)
 	//if ($val['notnull'] > 0) $rightpart .= ' fieldrequired';		// No fieldrequired inthe view output
+	//search and get all permssion in stirng
 	<dt>pRes</dt><dd>(optional) resource name</dd>
 	<strong>TaskItem(<em>pID, pName, pStart, pEnd, pColor, pLink, pMile, pRes, pComp, pGroup, pParent, pOpen, pDepend, pCaption, pNotes, pGantt</em>)</strong></p>
 	<td colspan="3"><textarea name="adress" cols="40" rows="3"><?php echo $this->control->tpl['address']; ?></textarea></td>
@@ -1599,6 +1710,7 @@
        SOCIETE_USEPREFIX can restore old feature.
        miscelaneous contries.
       * the tagret is useful with hooks : that allow externals modules to add setup items on good place
+      - htdocs/modulebuilder/template/test/phpunit/functionnal
       <!-- Looking for our sevices -->
       description: Screenshots, screencasts, dolibarr.log, debugging informations
       | dolibar  |          |
@@ -1738,7 +1850,13 @@
  *   	Return a string with full address formated for output on documents
  *    	@param     string	$extName        	Extension to differenciate thumb file name ('_small', '_mini')
  *    	@return     string		 		 	Formated text of duration
+ *       			 This Ajax service is oftenly called when option MAIN_DIRECT_STATUS_UPDATE is set.
  *                          				'contract'		   to add a tabl in contract view
+ *                              'action-btn-label' => '', // Overide label of action button,  if empty default label use "Confirm" lang key
+ *                              'cancel-btn-label' => '', // Overide label of cancel button,  if empty default label use "CloseDialog" lang key
+ *                              'content' => '', // Overide text of content,  if empty default content use "ConfirmBtnCommonContent" lang key
+ *                              'title' => '', // Overide title of modal,  if empty default title use "ConfirmBtnCommonTitle" lang key
+ *                              'url' => 'http://', // Overide Url to go when user click on action btn, if empty default url is $url.?confirm=yes, for no js compatibility use $url for fallback confirm.
  *                          'action-btn-label' => '', // Overide label of action button,  if empty default label use "Confirm" lang key
  *                          'cancel-btn-label' => '', // Overide label of cancel button,  if empty default label use "CloseDialog" lang key
  *                          'content' => '', // Overide text of content,  if empty default content use "ConfirmBtnCommonContent" lang key
@@ -1773,6 +1891,7 @@
  *      \brief      Fichier de la classe des fonctions predefinie de composants html cron
  *      \brief      Page des informations dolistore
  *     Classe du modele de numerotation de reference de projet Universal
+ *     \brief      Page list of invoice paied by direct debit or credit transfer
  *    Also modified to handle attachements.
  *    \brief      Fichier contenant la classe du modele de numerotation de reference de projet Universal
  *   - corrected the defualt value for 'setPriority()'
@@ -1789,6 +1908,7 @@
  *  - modifed 'getFrom()' to handle "striping" the email address
  *  - modified getHeader() to ustilize new Message Sensitivity and Priorty properties
  *  - removed leading dashes from message boundry
+ *  @param		int			$onlysqltoimportwebsite		Only sql resquests used to import a website template are allowed
  *  @param		int		$onlysqltoimportwebsite		Only sql resquests used to import a website template are allowed
  *  @param		string		$context		'add'=Output field for the "add form", 'edit'=Output field for the "edit form", 'hide'=Output field for the "add form" but we dont want it to be rendered
  *  @param		string	$context		'add'=Output field for the "add form", 'edit'=Output field for the "edit form", 'hide'=Output field for the "add form" but we dont want it to be rendered
@@ -1847,9 +1967,12 @@
  * @param		array		$replaceambiguouschars	Discard ambigous characters. For example array('I').
  * @param	array		$arrayofmesures	Array of mesures already filled
  * @param	mixed	$position		key of postion to insert to
+ * @param	string		$phpfullcodestring			PHP new string. For exemple "<?php echo 'a' ?><php echo 'c' ?>"
+ * @param	string		$phpfullcodestringold		PHP old string. For exemple "<?php echo 'a' ?><php echo 'b' ?>"
  * @param	string	$modulepart			Module of document ('module', 'module_user_temp', 'module_user' or 'module_temp'). Exemple: 'medias', 'invoice', 'logs', 'tax-vat', ...
  * @param	string	intput		Array of complementary actions to do if success
  * @param	string    $param			    Parameters of URL (x=value1&y=value2) or may be a formated content with $postorget='PUTALREADYFORMATED'
+ * @param	{string}	intput		Array of complementary actions to do if success
  * @param 	float	$paht				Buying price without tax
  * @param 	int		$fk_pa				Id of buying price (prefer set this to 0 and provide $paht instead. With id, buying price may have change)
  * @param 	string		$urltograb		URL to grab (exemple: http://www.nltechno.com/ or http://www.nltechno.com/dir1/ or http://www.nltechno.com/dir1/mapage1)
@@ -1868,7 +1991,10 @@
  * @param   string  $extName        Extension to differenciate thumb file name ('', '_small', '_mini')
  * @param   string $resourceType    ressource type
  * @param boolean	$extraRightColumn		(optional)	Add a addtional column after the summary word and total number
+ * @param int         $action          0 for delete, 1 for add, 2 for update, -1 when delete object completly, -2 for generate rights after add
  * @param string	$noneWord				(optional)	The word that is shown when the table has no entires ($num === 0)
+ * @param string   $objectname   name of object whant to remove
+ * @retun   boolean
  * @return	array					returns an associtive array containing the response from the server.
  * @return	string				A HTML table that conatins a list with open (unpaid) supplier invoices
  * @return	string			Formated value
@@ -1877,6 +2003,7 @@
  * @return  array								Array with time spent for $fuser for each day of week on tasks in $lines and substasks
  * @return  string          Formated value
  * @return string		Array of id of orders wit all dispathing already done or not required
+ * @return string      Formated size
  * Abort invoice creationg with a given error message
  * Check whether given extension is in html etensions list
  * Classe permettant la gestion des stats des deplacements et notes de frais
@@ -1885,6 +2012,7 @@
  * Copyright (C) 2004		Sebastien Di Cintio			<sdicintio@ressource-toi.org>
  * Copyright (C) 2005-2019	Laurent Destailleur		<eldy@uers.sourceforge.net>
  * Copyright (C) 2005-2021 Laurent Destailleur  <eldy@uers.sourceforge.net>
+ * Copyright (C) 2005-2023 Laurent Destailleur  <eldy@uers.sourceforge.net>
  * Copyright (C) 2016 Laurent Destailleur  <eldy@uers.sourceforge.net>
  * Correspondance des expeditions et des commandes clients dans la table llx_co_exp
  * Correspondance des livraisons et des commandes clients dans la table llx_co_liv
@@ -1962,10 +2090,13 @@ $usercanread = (($user->rights->stock->mouvement->lire));
 *  TODO: use color definition vars above for define badges color status X -> exemple $badgeStatusValidate, $badgeStatusClosed, $badgeStatusActive ....
 * ALL EXTERNAL MODULES THAT WERE NOT CORRECTLY DEVELOPPED WILL NOT WORK ON V15 (All modules that forgot to manage the security token field
 * All functions fetch_all() have been set to deprecated for naming consitency, use fetchAll() instead.
+* 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
 * ONLY security reports on modules provided by default and with the "stable" status are valid (troubles into "experimental", "developement" or external modules are not valid vulnerabilities).
 * Optionnaly, made freemono the default monotype font if we removed courier
 * Optionnaly, removed all fonts except
 * Removed the method 4 of GETPOST (to get $_COOKIE). It was not used and not recommanded to use in Dolibarr.
+* Sensitive datas like keys in setup pages, that need encyption (for example the API keys of users, the CRON security key, the keys into the Stripe module, or
+* Sensitive datas like keys in setup pages, that need encyption (for example the API keys of users, the CRON security key, the keys into the Stripe module, or 
 * The deprecated subsitution key __SIGNATURE__ has been removed. Replace it with __USER_SIGNATURE__ if you used the old syntax in your email templates.
 * The substition key __SIGNATURE__ was renamed into __USER_SIGNATURE__ to follow naming conventions.
 * You can test patching of serie with "quilt push" (autant de fois que de patch). Avec "quilt pop -a", on revient a l'état du upstream sans les patch.
@@ -2012,6 +2143,7 @@ $usercanread = (($user->rights->stock->mouvement->lire));
 /* Warning: setting this may make screen not beeing refreshed after a combo selection */
 /* default color for status : After a quick check, somme status can have oposite function according to objects
 /** @var bool Hide PHP informations */
+/** @var boolean	$force_install_nophpinfo 		Hide PHP informations */
 //				  and printing in millimiter by setting unit to 'mm' in constructor.
 //		font-size	: defaut char size (can be changed by calling Set_Char_Size(xx);
 // "commitment engagment" method and "cash accounting" method
@@ -2028,6 +2160,7 @@ $usercanread = (($user->rights->stock->mouvement->lire));
 // Creation de la classe d'export du model ExportXXX
 // Customer Default Langauge
 // DN pour les groupes
+// Date appoval
 // Defaut sortorder
 // Defini objet langs
 // Defini si peux lire/modifier permisssions
@@ -2049,11 +2182,15 @@ $usercanread = (($user->rights->stock->mouvement->lire));
 // No cahce on PHP
 // No check is done on company permission because readability is managed by public status of project and assignement.
 // Nomber of try
+// None. Beeing connected is enough.
 // Now database connexion is known, so we can forget password
 // Personal informations
 // Personalized search criterias. Example: $conf->global->PRODUCT_QUICKSEARCH_ON_FIELDS = 'p.ref=ProductRef;p.label=ProductLabel;p.description=Description;p.note=Note;'
 // Personalized search criterias. Example: $conf->global->THIRDPARTY_QUICKSEARCH_ON_FIELDS = 's.nom=ThirdPartyName;s.name_alias=AliasNameShort;s.code_client=CustomerCode'
 // Place customer adress to the ISO location
+// Repair llx_commande_fournisseur to eleminate duplicate reference
+// SQL Aliase adherent
+// SQL Aliase adherent_type
 // Sauvegardes parametres
 // Search Criterias
 // Securite acces client
@@ -2068,6 +2205,7 @@ $usercanread = (($user->rights->stock->mouvement->lire));
 // TODO Better solution to be able to sort on already payed or remain to pay is to store amount_payed in a denormalized field.
 // TODO ajouter regle pour restreindre acces paiement
 // Table to store complete informations (will replace all other table). Key is table name.
+// Test to check image can be publically viewed is done inside the viewimage.php wrapper.
 // This 2 lines are usefull only if we want to exclude some Urls from the explorer
 // This refresh list of dirs, not list of files (for preformance reason). List of files is refresh only if dir was not synchronized.
 // To disable a constant whithout javascript
@@ -2202,6 +2340,7 @@ FIX: supplier invoice payment total dont care about deposit or credit
 FIX: tag object_total_vat_x need x to be a string with unknown decimal lenght. Now use for x the real vat real with no more decimal (x = 20 or x = 8.5 or x = 5.99, ...)
 FIX: the time spent on project was not visible in its overwiew
 FIX: typo on ckeck method
+FIX: use event.key instead event.wich to avoid keyboard difference
 FIX: when fetch_optionnal_by_label in Extrafields with $this->db cannot work because this->db is never instanciated
 FIX: wrong occurence number of contract on contact card, we must only count externals
 FIX: wrong path sociales/index.php doesnt exist anymore
@@ -2220,6 +2359,7 @@ NEW: #18401 Add __NEWREF__ subtitute to get new object reference.
 NEW: A new function getImageFileNameForSize was also introduced to choose image best size according to usage to save bandwith.
 NEW: Accounting - Add default accounting account for member subcriptions.
 NEW: Add "depends on" and "required by" into module informations
+NEW: Add SQL contraint on product_stock table to allow only exsting product and warehouse #23543
 NEW: Add email in event history, for reminder email of expired subsription
 NEW: Add exemple of setup for multitail to render dolibarr log files
 NEW: Add hidden option MAIN_EMAIL_SUPPORT_ACK to restore Email ack checkbox (feature abandonned by mailers)
@@ -2233,7 +2373,9 @@ NEW: Add tooltip in payment term edition in dictionnary.
 NEW: Add workflow to classifed propal bill on invoice validation.
 NEW: All language tranlsations (except source en_US) is now managed on https://www.transifex.com/projects/p/dolibarr/.
 NEW: Architecture to manage search criteria persistance (using save_lastsearch_values=1 on exit links and restore_lastsearch_values=1 in entry links)
+NEW: Authentication: add experimental support for Google OAuth2 connexion
 NEW: Better reponsive design
+NEW: Can edit account on miscellaneous payment (if not transfered)
 NEW: Can edit list of prospect status for customers/prospects. Add a new entry into dictionary table to manage list fo status.
 NEW: Can edit list of prospect status for customers/prospects. Add a new entry into dictionary table to manage list fo status. Removed deprecated files.
 NEW: Can filter on code in dictionnaries
@@ -2266,14 +2408,18 @@ NEW: add API shipment mode dictionnary
 NEW: add a prospect status for the contact with managment of custom icon
 NEW: add constant  MAIN_COMPANY_PERENTITY_SHARED                    to manage some informations (Accounting account) when company is shared on several entities
 NEW: add constant  MAIN_PRODUCT_PERENTITY_SHARED                    to manage some informations (Accounting account) when product is shared on several entities
+NEW: add convertion of images to webp for a single image in website media editor
 NEW: add price in burger menu on mouvement list
 NEW: add show preview for mail attachement on form mail
+NEW: batch referential objets
 NEW: can substitue project title in mail template
+NEW: comment in api_mymodule for seperate methods
 NEW: conditionnal add member button by statut
 NEW: contacts type dictionnary in api_setup.class.php
 NEW: get state dictionnary by REST API
 NEW: get user connected informations in REST API
 NEW: hook getnomurltooltip is replaced with hook getNomUrl more powerfull
+NEW: only get openned contact from liste_contact function, to not have acces to closed contact as mail receiver
 NEW: option to copy into attachement files of events, files send by mail (with auto event creation)
 NEW: possibilty to group payments by mode and show their subtotal
 NEW: show place from events on import calender
@@ -2287,11 +2433,13 @@ The output patch file can then be submited on Dolibarr
 This directory contains ruleset files to use to develop Dolibarr EPR & CRM.
 This directory contains several subdirectories with entries for informations on Dolibarr.<br>
 This docker image intended for developpement usage.
+This docker image is intended for developpement usage.
 This module provides a sheduled job that scan regularly one or several IMAP email boxes, with filtering rules, to automatically record data in your application, like
 Upgrading to any other version or database system is abolutely required BEFORE trying to
 We recommand to install Dolibarr ERP CRM on your own server (as most Open Source software, download and use is free: [https://www.dolibarr.org/download](https://www.dolibarr.org/download)) to get access on every side of application.
 You must avoid tests that could cause degradation or interruption of our service (refrain from using automated tools, and limit yourself about requests per second), that's why we recommand to install software on your own platform.
 class ModeleBoxes // Can't be abtract as it is instantiated to build "empty" boxes
+class ModeleExports extends CommonDocGenerator    // This class can't be abstract as there is instance propreties loaded by listOfAvailableExportFormat
 class ModeleExports extends CommonDocGenerator    // This class can't be abstract as there is instance propreties loaded by liste_modeles
 define('DOL_CLASS_PATH', 'class/'); // Filsystem path to class dir
 echo price($line->qty, 0, '', 0, 0); // Yes, it is a quantity, not a price, but we just want the formating role of function price
@@ -2303,6 +2451,8 @@ if (!empty($conf->variants->eabled) && empty($conf->global->VARIANT_ALLOW_STOCK_
 if (!empty($contactname)) { // acces a partir du module de recherche
 if ($action == "transfert") {
 if (preg_match('/^dopayment/', $action)) {			// If we choosed/click on the payment mode
+if you restore or duplicate the data from another instance dump, you must also update this parameter in ther conf.php file to allow decryption in the new instance, or
+if you restore or duplicate the data from another instance dump, you must also update this parameter in ther conf.php file to allow decryption in the new instance, or 
 print $form->multiselectarray('BLOCKEDLOG_DISABLE_NOT_ALLOWED_FOR_COUNTRY', $countryArray, $seledted);
 print $langs->trans("Size").': '.ini_get('xcache.size').' &nbsp; &nbsp; &nbsp; '.$langs->trans("Recommanded").': 16*Split<br>'."\n";
 print $langs->trans("Split").': '.ini_get('xcache.count').' &nbsp; &nbsp; &nbsp; '.$langs->trans("Recommanded").': (cat /proc/cpuinfo | grep -c processor) + 1<br>'."\n";

+ 13 - 13
dev/tools/dolibarr-postgres2mysql.php

@@ -22,8 +22,8 @@
  */
 
 /**
- * \file 	dev/tools/dolibarr-postgres2mysql.php
- * \brief 	Script to migrate a postgresql dump into a mysql dump
+ * \file    dev/tools/dolibarr-postgres2mysql.php
+ * \brief   Script to migrate a postgresql dump into a mysql dump
  */
 
 $sapi_type = php_sapi_name();
@@ -67,8 +67,8 @@ XHTML;
 /**
  * getfieldname
  *
- * @param	string		$l		String
- * @return	string|null			Field name
+ * @param  string $l String
+ * @return string|null         Field name
  */
 function getfieldname($l)
 {
@@ -94,8 +94,8 @@ function getfieldname($l)
 /**
  * formatsize
  *
- * @param 	string $s	Size to format
- * @return 	string		Formated size
+ * @param  string $s Size to format
+ * @return string      Formated size
  */
 function formatsize($s)
 {
@@ -113,9 +113,9 @@ function formatsize($s)
 /**
  * pg2mysql_large
  *
- * @param string	$infilename			Input filename
- * @param string	$outfilename		Output filename
- * @return int							<0 if KO, >=0 if OK
+ * @param  string $infilename  Input filename
+ * @param  string $outfilename Output filename
+ * @return int                          <0 if KO, >=0 if OK
  */
 function pg2mysql_large($infilename, $outfilename)
 {
@@ -234,10 +234,10 @@ function pg2mysql_large($infilename, $outfilename)
 /**
  * pg2mysql
  *
- * @param array		$input								Array of input
- * @param array		$arrayofprimaryalreadyintabledef	Array of table already output with a primary key set into definition
- * @param boolean 	$header								Boolean
- * @return string[]										Array of output
+ * @param  array   $input                           Array of input
+ * @param  array   $arrayofprimaryalreadyintabledef Array of table already output with a primary key set into definition
+ * @param  boolean $header                          Boolean
+ * @return string[]                                     Array of output
  */
 function pg2mysql(&$input, &$arrayofprimaryalreadyintabledef, $header = true)
 {

+ 20 - 0
dev/tools/fixhasRights.sh

@@ -0,0 +1,20 @@
+#/bin/bash
+#
+# Example of script to fix code writing of permissions
+#
+
+for f in $(grep -l -e 'user->rights' -R); do
+  sed -i -r 's/!empty\(\$user->rights->([_a-z0-9]+)->([_a-z0-9]+)->([_a-z0-9]+)\) *\? *\$user->rights->\1->\2->\3 *: *0;/$user->hasRight("\1", "\2", "\3");/' $f
+  sed -i -r 's/ empty\(\$user->rights->([_a-z0-9]+)->([_a-z0-9]+)->([_a-z0-9]+)\) *\? *0 *: *\$user->rights->\1->\2->\3;/ !$user->hasRight("\1", "\2", "\3");/' $f
+  sed -i -r 's/!empty\((DolibarrApiAccess::)\$user->rights->([_a-z0-9]+)->([_a-z0-9]+)->([_a-z0-9]+)\)/\1$user->hasRight("\2", "\3", "\4")/g' $f
+  sed -i -r 's/!empty\((DolibarrApiAccess::)\$user->rights->([_a-z0-9]+)->([_a-z0-9]+)\)/\1$user->hasRight("\2", "\3")/g' $f
+  sed -i -r 's/empty\((DolibarrApiAccess::)\$user->rights->([_a-z0-9]+)->([_a-z0-9]+)->([_a-z0-9]+)\)/!\1$user->hasRight("\2", "\3", "\4")/g' $f
+  sed -i -r 's/empty\((DolibarrApiAccess::)\$user->rights->([_a-z0-9]+)->([_a-z0-9]+)\)/!\1$user->hasRight("\2", "\3")/g' $f
+  sed -i -r 's/!empty\(\$user->rights->([_a-z0-9]+)->([_a-z0-9]+)->([_a-z0-9]+)\)/$user->hasRight("\1", "\2", "\3")/g' $f
+  sed -i -r 's/!empty\(\$user->rights->([_a-z0-9]+)->([_a-z0-9]+)\)/$user->hasRight("\1", "\2")/g' $f
+  sed -i -r 's/empty\(\$user->rights->([_a-z0-9]+)->([_a-z0-9]+)->([_a-z0-9]+)\)/!$user->hasRight("\1", "\2", "\3")/g' $f
+  sed -i -r 's/empty\(\$user->rights->([_a-z0-9]+)->([_a-z0-9]+)\)/!$user->hasRight("\1", "\2")/g' $f
+  sed -i -r 's/\$user->rights\??->([_a-z0-9]+)\??->([_a-z0-9]+)\??->([_a-z0-9]+)/$user->hasRight("\1", "\2", "\3")/g' $f
+  sed -i -r 's/\$user->rights\??->([_a-z0-9]+)\??->([_a-z0-9]+)/$user->hasRight("\1", "\2")/g' $f
+done
+

+ 0 - 0
dev/tools/detectnotabfiles.sh → dev/tools/fixnotabfiles.sh


+ 0 - 0
dev/tools/snakeCaseToCamelCase.sh → dev/tools/fixsnakeCaseToCamelCase.sh


+ 2 - 2
dev/tools/github_commits_perversion.sh

@@ -3,8 +3,8 @@
 # Count number of commits per user and per versions (using date for version detection)
 #
 
-Releases=("16.0" "develop")
-Dates=("2022-01-01" "2022-08-31" "2050-01-01")
+Releases=("17.0" "18.0" "develop")
+Dates=("2023-02-01" "2023-08-31" "2050-01-01")
 let "counter = 1"
 
 for i in "${Releases[@]}"

+ 7 - 3
dev/tools/github_lines_perusers.sh

@@ -4,13 +4,17 @@
 #
 
 if [ "x$2" = "x" ]; then
-	echo "Usage: $0  tagnamestart|START  tagnameend|HEAD"
+	echo "Usage: $0  origin/branchstart|tagnamestart|START  origin/branchend|tagnameend|HEAD"
 	exit
 fi
 
+START=$1
+if [ "x$START" = "xSTART" ]; then
+	START=""
+fi
 
-echo "git log $1..$2 --shortstat | grep ... | perl ... > /tmp/github_lines_perusers.tmp"
-git log $1..$2 --shortstat | grep -e 'Author:' -e 'Date:' -e ' changed' -e ' insertion' -e ' deletion' | perl -n -e '/^(.*)$/; $line = $1; if ($line =~ /(changed|insertion|deletion)/) { $line =~ s/[^0-9\s]//g; my @arr=split /\s+/, $line; $tot=0; for (1..@arr) { $tot += $arr[$_]; }; print $tot."\n"; } else { print $line."\n"; };' > /tmp/github_lines_perusers.tmp
+echo "git log $START..$2 --shortstat | grep ... | perl ... > /tmp/github_lines_perusers.tmp"
+git log $START..$2 --shortstat | grep -e 'Author:' -e 'Date:' -e ' changed' -e ' insertion' -e ' deletion' | perl -n -e '/^(.*)$/; $line = $1; if ($line =~ /(changed|insertion|deletion)/) { $line =~ s/[^0-9\s]//g; my @arr=split /\s+/, $line; $tot=0; for (1..@arr) { $tot += $arr[$_]; }; print $tot."\n"; } else { print $line."\n"; };' > /tmp/github_lines_perusers.tmp
 
 cat /tmp/github_lines_perusers.tmp | awk 'BEGIN { FS="\n"; print "user and nb of lines"; lastuser=""; } { if ($1 ~ /Author:/) { lastuser=$1 }; if ($1 ~ /^[0-9]+$/) { aaa[lastuser]+=$1; } } END { for (var in aaa) print var," ",aaa[var]; } '
 

+ 22 - 21
dev/tools/spider.php

@@ -16,8 +16,8 @@
  */
 
 /**
- * \file 	dev/tools/spider.php
- * \brief 	Script to spider Dolibarr app.
+ * \file    dev/tools/spider.php
+ * \brief   Script to spider Dolibarr app.
  *
  * To use it:
  * - Disable module "bookmark"
@@ -29,9 +29,9 @@ const MAX_DEPTH=2;
 
 
 /**
- * @param string $url	URL
- * @param string $depth	Depth
- * @return string		String
+ * @param  string $url   URL
+ * @param  string $depth Depth
+ * @return string       String
  */
 function followLink($url, $depth = 0)
 {
@@ -68,23 +68,24 @@ function followLink($url, $depth = 0)
 }
 
 /**
- * @param string $site	Site
- * @param string $path	Path
- * @return string		String
+ * @param  string $site Site
+ * @param  string $path Path
+ * @return string       String
  */
 function convertLink($site, $path)
 {
 	if (substr_compare($path, "//", 0, 2)==0)
 		return parse_url($site)['scheme'].$path;
-	elseif (substr_compare($path, "http://", 0, 7)==0 or
-		substr_compare($path, "https://", 0, 8)==0 or
-		substr_compare($path, "www.", 0, 4)==0)
+	elseif (substr_compare($path, "http://", 0, 7)==0
+		or substr_compare($path, "https://", 0, 8)==0
+		or substr_compare($path, "www.", 0, 4)==0
+	)
 		return $path;
 	else return $site.'/'.$path;
 }
 
 /**
- * @param string $url	URL
+ * @param  string $url URL
  * @return boolean
  */
 function ignoreLink($url)
@@ -93,10 +94,10 @@ function ignoreLink($url)
 }
 
 /**
- * @param string 	$link		URL
- * @param string	$title		Title
- * @param string 	$metaData	Array
- * @param int 		$depth		Depth
+ * @param  string $link     URL
+ * @param  string $title    Title
+ * @param  string $metaData Array
+ * @param  int    $depth    Depth
  * @return void
  */
 function insertIntoDatabase($link, $title, &$metaData, $depth)
@@ -109,9 +110,9 @@ function insertIntoDatabase($link, $title, &$metaData, $depth)
 }
 
 /**
- * @param string 	$doc		Doc
- * @param string	$url		URL
- * @return string				URL/Title
+ * @param  string $doc Doc
+ * @param  string $url URL
+ * @return string               URL/Title
  */
 function getDocTitle(&$doc, $url)
 {
@@ -123,8 +124,8 @@ function getDocTitle(&$doc, $url)
 }
 
 /**
- * @param string 	$doc		Doc
- * @return array				Array
+ * @param  string $doc Doc
+ * @return array                Array
  */
 function getDocMetaData(&$doc)
 {

+ 1 - 0
dev/tools/test/namespacemig/aaa.class.php

@@ -24,6 +24,7 @@ class Aaa
 
 	/**
 	 * do
+	 *
 	 * @return void
 	 */
 	public function do()

+ 2 - 0
dev/tools/test/namespacemig/bbb.class.php

@@ -5,6 +5,7 @@ $globalbbb = 'globalbbb';
 
 /**
  * fbbb
+ *
  * @return string
  */
 function fbbb()
@@ -21,6 +22,7 @@ class Bbb
 
 	/**
 	 * do
+	 *
 	 * @return void
 	 */
 	public function do()

+ 1 - 1
dev/tools/test/namespacemig/bbb.php

@@ -3,7 +3,7 @@
 //use \Aaa as Aaa;
 
 use Dolibarr\Aaa as Aaa;
-use function Dolibarr\faaa as faaa;	// Need php 5.6+
+use function Dolibarr\faaa as faaa; // Need php 5.6+
 
 //use const Dolibarr\AAA;
 

+ 4 - 3
dev/tools/test/testtcpdf.php

@@ -21,10 +21,11 @@
 
 /**
  * Creates an example PDF TEST document using TCPDF
- * @package com.tecnick.tcpdf
+ *
+ * @package  com.tecnick.tcpdf
  * @abstract TCPDF - Example: Document Encryption / Security
- * @author Nicola Asuni
- * @since 2008-03-04
+ * @author   Nicola Asuni
+ * @since    2008-03-04
  */
 
 require_once '../../htdocs/includes/tecnickcom/tcpdf/config/tcpdf_config.php';

+ 7 - 6
dev/tools/test/testutf.php

@@ -38,13 +38,14 @@ print 'Files has been created. Check its name from your explorer'."\n";
 
 /**
  * Creates an example PDF TEST document using TCPDF
- * @package com.tecnick.tcpdf
- * @abstract TCPDF - Example: CID-0 CJK unembedded font
- * @author Nicola Asuni
+ *
+ * @package   com.tecnick.tcpdf
+ * @abstract  TCPDF - Example: CID-0 CJK unembedded font
+ * @author    Nicola Asuni
  * @copyright 2004-2009 Nicola Asuni - Tecnick.com S.r.l (www.tecnick.com) Via Della Pace, 11 - 09044 - Quartucciu (CA) - ITALY - www.tecnick.com - info@tecnick.com
- * @link http://tcpdf.org
- * @license https://www.gnu.org/copyleft/lesser.html LGPL
- * @since 2008-09-15
+ * @link      http://tcpdf.org
+ * @license   https://www.gnu.org/copyleft/lesser.html LGPL
+ * @since     2008-09-15
  */
 
 require_once '../../htdocs/includes/tecnickcom/tcpdf/config/tcpdf_config.php';

+ 1 - 1
dev/translation/txpull.sh

@@ -62,5 +62,5 @@ fi
 
 echo Think to launch also: 
 echo "> dev/tools/fixaltlanguages.sh fix all"
-echo "For v11: Replace also regex \(.*(sponge|cornas|eratosthene|cyan).*\) with '' on *.lang files"
+#echo "For v11: Replace also regex \(.*(sponge|cornas|eratosthene|cyan).*\) with '' on *.lang files"
 

BIN
doc/images/dolibarr_screenshot12_1280x800.jpg


BIN
doc/images/dolibarr_screenshot1_1280x800.jpg


BIN
doc/images/dolibarr_screenshot2_1280x800.jpg


BIN
doc/images/dolibarr_screenshot4_1280x800.jpg


BIN
doc/images/dolibarr_screenshot5_1280x800.jpg


+ 2 - 1
doc/user/README

@@ -3,6 +3,7 @@ README (english)
 User guide
 --------------------------------
 
-* All Dolibarr guides are available, on line, on the Dolibarr Web site:
+* All Dolibarr guides are available, online, on the Dolibarr Websites:
 
 https://www.dolibarr.org
+https://wiki.dolibarr.org

+ 82 - 37
htdocs/accountancy/admin/account.php

@@ -33,7 +33,6 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
 // Load translation files required by the page
 $langs->loadLangs(array('accountancy', 'admin', 'bills', 'compta', 'salaries'));
 
-$mesg = '';
 $action = GETPOST('action', 'aZ09');
 $cancel = GETPOST('cancel', 'alpha');
 $id = GETPOST('id', 'int');
@@ -41,6 +40,7 @@ $rowid = GETPOST('rowid', 'int');
 $massaction = GETPOST('massaction', 'aZ09');
 $optioncss = GETPOST('optioncss', 'alpha');
 $contextpage = GETPOST('contextpage', 'aZ') ?GETPOST('contextpage', 'aZ') : 'accountingaccountlist'; // To manage different context of search
+$mode = GETPOST('mode', 'aZ'); // The output mode ('list', 'kanban', 'hierarchy', 'calendar', ...)
 
 $search_account = GETPOST('search_account', 'alpha');
 $search_label = GETPOST('search_label', 'alpha');
@@ -91,11 +91,11 @@ $arrayfields = array(
 	'aa.pcg_type'=>array('label'=>"Pcgtype", 'checked'=>1, 'help'=>'PcgtypeDesc'),
 	'categories'=>array('label'=>"AccountingCategories", 'checked'=>-1, 'help'=>'AccountingCategoriesDesc'),
 	'aa.reconcilable'=>array('label'=>"Reconcilable", 'checked'=>1),
-	'aa.active'=>array('label'=>"Activated", 'checked'=>1),
-	'aa.import_key'=>array('label'=>"ImportId", 'checked'=>-1)
+	'aa.import_key'=>array('label'=>"ImportId", 'checked'=>-1, 'help'=>''),
+	'aa.active'=>array('label'=>"Activated", '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);
@@ -414,7 +414,7 @@ if ($resql) {
 	print '<br>';
 
 	$varpage = empty($contextpage) ? $_SERVER["PHP_SELF"] : $contextpage;
-	$selectedfields = $form->multiSelectArrayWithCheckbox('selectedfields', $arrayfields, $varpage); // 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
 	$selectedfields .= (count($arrayofmassactions) ? $form->showCheckAddButtons('checkforselect', 1) : '');
 
 	$moreforfilter = '';
@@ -429,6 +429,13 @@ if ($resql) {
 
 	// Line for search fields
 	print '<tr class="liste_titre_filter">';
+	// Action column
+	if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print '<td class="liste_titre maxwidthsearch">';
+		$searchpicto = $form->showFilterButtons();
+		print $searchpicto;
+		print '</td>';
+	}
 	if (!empty($arrayfields['aa.account_number']['checked'])) {
 		print '<td class="liste_titre"><input type="text" class="flat width100" name="search_account" value="'.$search_account.'"></td>';
 	}
@@ -463,12 +470,19 @@ if ($resql) {
 	if (!empty($arrayfields['aa.active']['checked'])) {
 		print '<td class="liste_titre">&nbsp;</td>';
 	}
-	print '<td class="liste_titre maxwidthsearch">';
-	$searchpicto = $form->showFilterButtons();
-	print $searchpicto;
-	print '</td>';
+	// Action column
+	if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print '<td class="liste_titre maxwidthsearch">';
+		$searchpicto = $form->showFilterButtons();
+		print $searchpicto;
+		print '</td>';
+	}
 	print '</tr>';
 	print '<tr class="liste_titre">';
+	// Action column
+	if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', '', $sortfield, $sortorder, 'center maxwidthsearch ');
+	}
 	if (!empty($arrayfields['aa.account_number']['checked'])) {
 		print_liste_field_titre($arrayfields['aa.account_number']['label'], $_SERVER["PHP_SELF"], "aa.account_number", "", $param, '', $sortfield, $sortorder);
 	}
@@ -498,7 +512,10 @@ if ($resql) {
 	if (!empty($arrayfields['aa.active']['checked'])) {
 		print_liste_field_titre($arrayfields['aa.active']['label'], $_SERVER["PHP_SELF"], 'aa.active', '', $param, '', $sortfield, $sortorder);
 	}
-	print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', '', $sortfield, $sortorder, 'center maxwidthsearch ');
+	// Action column
+	if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', '', $sortfield, $sortorder, 'center maxwidthsearch ');
+	}
 	print "</tr>\n";
 
 	$i = 0;
@@ -511,6 +528,32 @@ if ($resql) {
 
 		print '<tr class="oddeven">';
 
+		// Action column
+		if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+			print '<td class="center nowraponall">';
+			if ($user->hasRight('accounting', 'chartofaccount')) {
+				print '<a class="editfielda" href="./card.php?action=update&token='.newToken().'&id='.$obj->rowid.'&backtopage='.urlencode($_SERVER["PHP_SELF"].'?'.$param).'">';
+				print img_edit();
+				print '</a>';
+				print '&nbsp;';
+				print '<a class="marginleftonly" href="./card.php?action=delete&token='.newToken().'&id='.$obj->rowid.'&backtopage='.urlencode($_SERVER["PHP_SELF"].'?'.$param).'">';
+				print img_delete();
+				print '</a>';
+				print '&nbsp;';
+				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->rowid, $arrayofselected)) {
+						$selected = 1;
+					}
+					print '<input id="cb'.$obj->rowid.'" class="flat checkforselect marginleftonly" type="checkbox" name="toselect[]" value="'.$obj->rowid.'"'.($selected ? ' checked="checked"' : '').'>';
+				}
+			}
+			print '</td>'."\n";
+			if (!$i) {
+				$totalarray['nbfield']++;
+			}
+		}
+
 		// Account number
 		if (!empty($arrayfields['aa.account_number']['checked'])) {
 			print "<td>";
@@ -523,7 +566,7 @@ if ($resql) {
 
 		// Account label
 		if (!empty($arrayfields['aa.label']['checked'])) {
-			print "<td>";
+			print '<td class="tdoverflowmax150" title="'.dol_escape_htmltag($obj->label).'">';
 			print dol_escape_htmltag($obj->label);
 			print "</td>\n";
 			if (!$i) {
@@ -636,28 +679,30 @@ if ($resql) {
 			}
 		}
 
-		// Action
-		print '<td class="center nowraponall">';
-		if ($user->hasRight('accounting', 'chartofaccount')) {
-			print '<a class="editfielda" href="./card.php?action=update&token='.newToken().'&id='.$obj->rowid.'&backtopage='.urlencode($_SERVER["PHP_SELF"].'?'.$param).'">';
-			print img_edit();
-			print '</a>';
-			print '&nbsp;';
-			print '<a class="marginleftonly" href="./card.php?action=delete&token='.newToken().'&id='.$obj->rowid.'&backtopage='.urlencode($_SERVER["PHP_SELF"].'?'.$param).'">';
-			print img_delete();
-			print '</a>';
-			print '&nbsp;';
-			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->rowid, $arrayofselected)) {
-					$selected = 1;
+		// Action column
+		if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+			print '<td class="center nowraponall">';
+			if ($user->hasRight('accounting', 'chartofaccount')) {
+				print '<a class="editfielda" href="./card.php?action=update&token='.newToken().'&id='.$obj->rowid.'&backtopage='.urlencode($_SERVER["PHP_SELF"].'?'.$param).'">';
+				print img_edit();
+				print '</a>';
+				print '&nbsp;';
+				print '<a class="marginleftonly" href="./card.php?action=delete&token='.newToken().'&id='.$obj->rowid.'&backtopage='.urlencode($_SERVER["PHP_SELF"].'?'.$param).'">';
+				print img_delete();
+				print '</a>';
+				print '&nbsp;';
+				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->rowid, $arrayofselected)) {
+						$selected = 1;
+					}
+					print '<input id="cb'.$obj->rowid.'" class="flat checkforselect marginleftonly" type="checkbox" name="toselect[]" value="'.$obj->rowid.'"'.($selected ? ' checked="checked"' : '').'>';
 				}
-				print '<input id="cb'.$obj->rowid.'" class="flat checkforselect marginleftonly" type="checkbox" name="toselect[]" value="'.$obj->rowid.'"'.($selected ? ' checked="checked"' : '').'>';
 			}
-		}
-		print '</td>'."\n";
-		if (!$i) {
-			$totalarray['nbfield']++;
+			print '</td>'."\n";
+			if (!$i) {
+				$totalarray['nbfield']++;
+			}
 		}
 
 		print "</tr>\n";

+ 4 - 3
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
@@ -640,7 +640,8 @@ if ($id) {
 						foreach ($fieldlist as $field => $value) {
 							$showfield = 1;
 							$class = "left";
-							$valuetoshow = $obj->{$fieldlist[$field]};
+							$tmpvar = $fieldlist[$field];
+							$valuetoshow = $obj->$tmpvar;
 							if ($value == 'type_template') {
 								$valuetoshow = isset($elementList[$valuetoshow]) ? $elementList[$valuetoshow] : $valuetoshow;
 							}

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

@@ -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');
 
@@ -282,7 +282,7 @@ if ($action == 'create') {
 	print $form->textwithpicto($langs->trans("AccountingCategory"), $langs->transnoentitiesnoconv("AccountingAccountGroupsDesc"));
 	print '</td>';
 	print '<td>';
-	$formaccounting->select_accounting_category($object->account_category, 'account_category', 1, 0, 1);
+	print $formaccounting->select_accounting_category($object->account_category, 'account_category', 1, 0, 1);
 	print '</td></tr>';
 
 	print '</table>';
@@ -359,7 +359,7 @@ if ($action == 'create') {
 			print $form->textwithpicto($langs->trans("AccountingCategory"), $langs->transnoentitiesnoconv("AccountingAccountGroupsDesc"));
 			print '</td>';
 			print '<td>';
-			$formaccounting->select_accounting_category($object->account_category, 'account_category', 1);
+			print $formaccounting->select_accounting_category($object->account_category, 'account_category', 1);
 			print '</td></tr>';
 
 			print '</table>';
@@ -381,7 +381,7 @@ if ($action == 'create') {
 			print '<div class="fichecenter">';
 			print '<div class="underbanner clearboth"></div>';
 
-			print '<table class="border centpercent">';
+			print '<table class="border centpercent tableforfield">';
 
 			// Label
 			print '<tr><td class="titlefield">'.$langs->trans("Label").'</td>';

+ 35 - 14
htdocs/accountancy/admin/categories.php

@@ -45,6 +45,26 @@ if ($cat_id == 0) {
 	$cat_id = null;
 }
 
+// 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 < 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;
+}
+$offset = $limit * $page;
+$pageprev = $page - 1;
+$pagenext = $page + 1;
+
+if (empty($sortfield)) {
+	$sortfield = 'account_number';
+}
+if (empty($sortorder)) {
+	$sortorder = 'ASC';
+}
+
 // Security check
 if (!$user->hasRight('accounting', 'chartofaccount')) {
 	accessforbidden();
@@ -111,8 +131,12 @@ print '<table class="border centpercent">';
 // Select the category
 print '<tr><td class="titlefield">'.$langs->trans("AccountingCategory").'</td>';
 print '<td>';
-$formaccounting->select_accounting_category($cat_id, 'account_category', 1, 0, 0, 0);
-print '<input type="submit" class="button small" value="'.$langs->trans("Select").'">';
+$s = $formaccounting->select_accounting_category($cat_id, 'account_category', 1, 0, 0, 0);
+if ($formaccounting->nbaccounts_category <= 0) {
+	print '<span class="opacitymedium">'.$s.'</span>';
+} else {
+	print '<input type="submit" class="button small" value="'.$langs->trans("Select").'">';
+}
 print '</td></tr>';
 
 print '</table>';
@@ -137,14 +161,6 @@ if (!empty($cat_id)) {
 	if (is_array($accountingcategory->lines_cptbk) && count($accountingcategory->lines_cptbk) > 0) {
 		print img_picto($langs->trans("AccountingAccount"), 'accounting_account', 'class="pictofixedwith"');
 		print $form->multiselectarray('cpt_bk', $arraykeyvalue, GETPOST('cpt_bk', 'array'), null, null, '', 0, "80%", '', '', $langs->transnoentitiesnoconv("AddAccountFromBookKeepingWithNoCategories"));
-		//print '<br>';
-		/*print '<select class="flat minwidth200" size="8" name="cpt_bk[]" multiple>';
-		foreach ( $accountingcategory->lines_cptbk as $cpt ) {
-			print '<option value="' . length_accountg($cpt->numero_compte) . '">' . length_accountg($cpt->numero_compte) . ' (' . $cpt->label_compte . ' ' . $cpt->doc_ref . ')</option>';
-		}
-		print '</select><br>';
-		print ajax_combobox('cpt_bk');
-		*/
 		print '<input type="submit" class="button button-add small" id="" class="action-delete" value="'.$langs->trans("Add").'"> ';
 	}
 }
@@ -152,13 +168,16 @@ if (!empty($cat_id)) {
 print '</form>';
 
 
-if ($action == 'display' || $action == 'delete') {
+if ((empty($action) || $action == 'display' || $action == 'delete') && $cat_id > 0) {
+	$param = 'account_category='.((int) $cat_id);
+
 	print '<br>';
 	print '<table class="noborder centpercent">'."\n";
 	print '<tr class="liste_titre">';
-	print '<td class="liste_titre">'.$langs->trans("AccountAccounting")."</td>";
-	print '<td class="liste_titre" colspan="2">'.$langs->trans("Label")."</td>";
-	print "</tr>\n";
+	print getTitleFieldOfList('AccountAccounting', 0, $_SERVER['PHP_SELF'], 'account_number', '', $param, '', $sortfield, $sortorder, '')."\n";
+	print getTitleFieldOfList('Label', 0, $_SERVER['PHP_SELF'], 'label', '', $param, '', $sortfield, $sortorder, '')."\n";
+	print getTitleFieldOfList('', 0, $_SERVER['PHP_SELF'], '', '', $param, '', $sortfield, $sortorder, '')."\n";
+	print '</tr>'."\n";
 
 	if (!empty($cat_id)) {
 		$return = $accountingcategory->display($cat_id); // This load ->lines_display
@@ -167,6 +186,8 @@ if ($action == 'display' || $action == 'delete') {
 		}
 
 		if (is_array($accountingcategory->lines_display) && count($accountingcategory->lines_display) > 0) {
+			$accountingcategory->lines_display = dol_sort_array($accountingcategory->lines_display, $sortfield, $sortorder, -1, 0, 1);
+
 			foreach ($accountingcategory->lines_display as $cpt) {
 				print '<tr class="oddeven">';
 				print '<td>'.length_accountg($cpt->account_number).'</td>';

+ 180 - 107
htdocs/accountancy/admin/categories_list.php

@@ -1,5 +1,5 @@
 <?php
-/* Copyright (C) 2004-2017  Laurent Destailleur     <eldy@users.sourceforge.net>
+/* Copyright (C) 2004-2023  Laurent Destailleur     <eldy@users.sourceforge.net>
  * Copyright (C) 2011-2021  Alexandre Spangaro      <aspangaro@open-dsi.fr>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -58,9 +58,10 @@ $listlimit = GETPOST('listlimit', 'int') > 0 ?GETPOST('listlimit', 'int') : 1000
 $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 = $listlimit * $page;
 $pageprev = $page - 1;
 $pagenext = $page + 1;
@@ -444,17 +445,41 @@ if ($search_country_id > 0) {
 if ($sortfield == 'country') {
 	$sortfield = 'country_code';
 }
+if (empty($sortfield)) {
+	$sortfield = 'position';
+}
+
 $sql .= $db->order($sortfield, $sortorder);
 $sql .= $db->plimit($listlimit + 1, $offset);
-//print $sql;
+
 
 $fieldlist = explode(',', $tabfield[$id]);
 
+$param = '&id='.$id;
+if ($search_country_id > 0) {
+	$param .= '&search_country_id='.urlencode($search_country_id);
+}
+$paramwithsearch = $param;
+if ($sortorder) {
+	$paramwithsearch .= '&sortorder='.urlencode($sortorder);
+}
+if ($sortfield) {
+	$paramwithsearch .= '&sortfield='.urlencode($sortfield);
+}
+if (GETPOST('from', 'alpha')) {
+	$paramwithsearch .= '&from='.urlencode(GETPOST('from', 'alpha'));
+}
+if ($listlimit) {
+	$paramwithsearch .= '&listlimit='.urlencode(GETPOST('listlimit', 'int'));
+}
 print '<form action="'.$_SERVER['PHP_SELF'].'?id='.$id.'" method="POST">';
 print '<input type="hidden" name="token" value="'.newToken().'">';
 print '<input type="hidden" name="from" value="'.dol_escape_htmltag(GETPOST('from', 'alpha')).'">';
+print '<input type="hidden" name="sortfield" value="'.dol_escape_htmltag($sortfield).'">';
+print '<input type="hidden" name="sortorder" value="'.dol_escape_htmltag($sortorder).'">';
 
-print '<div class="div-table-responsive">';
+
+print '<div class="div-table-responsive-no-min">';
 print '<table class="noborder centpercent">';
 
 // Form to add a new line
@@ -463,6 +488,10 @@ if ($tabname[$id]) {
 
 	// Line for title
 	print '<tr class="liste_titre">';
+	// Action column
+	if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print '<td></td>';
+	}
 	foreach ($fieldlist as $field => $value) {
 		// Determine le nom du champ par rapport aux noms possibles
 		// dans les dictionnaires de donnees
@@ -502,6 +531,7 @@ if ($tabname[$id]) {
 		}
 		if ($fieldlist[$field] == 'range_account') {
 			$valuetoshow = $langs->trans("Comment");
+			$class = 'width75';
 		}
 		if ($fieldlist[$field] == 'category_type') {
 			$valuetoshow = $langs->trans("Calculated");
@@ -523,14 +553,21 @@ 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>';
+	// Action column
+	if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print '<td></td>';
+	}
 	print '</tr>';
 
 	// Line to enter new values
 	print '<tr class="oddeven nodrag nodrop nohover">';
 
+	// Action column
+	if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print '<td></td>';
+	}
+
 	$obj = new stdClass();
 	// If data was already input, we define them in obj to populate input fields.
 	if (GETPOST('actionadd', 'alpha')) {
@@ -550,19 +587,29 @@ if ($tabname[$id]) {
 		fieldListAccountingCategories($fieldlist, $obj, $tabname[$id], 'add');
 	}
 
-	print '<td colspan="4" class="right">';
+	print '<td colspan="2" class="right">';
 	print '<input type="submit" class="button button-add" name="actionadd" value="'.$langs->trans("Add").'">';
 	print '</td>';
+
+	// Action column
+	if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print '<td></td>';
+	}
+
 	print "</tr>";
 
 	$colspan = count($fieldlist) + 3;
 	if ($id == 32) {
 		$colspan++;
 	}
-
-	print '<tr><td colspan="'.$colspan.'">&nbsp;</td></tr>'; // Keep &nbsp; to have a line with enough height
 }
 
+print '</table>';
+print '</div>';
+
+print '<div class="div-table-responsive">';
+print '<table class="noborder centpercent">';
+
 // List of available record in database
 dol_syslog("htdocs/accountancy/admin/categories_list.php", LOG_DEBUG);
 
@@ -571,29 +618,39 @@ if ($resql) {
 	$num = $db->num_rows($resql);
 	$i = 0;
 
-	$param = '&id='.$id;
-	if ($search_country_id > 0) {
-		$param .= '&search_country_id='.urlencode($search_country_id);
-	}
-	$paramwithsearch = $param;
-	if ($sortorder) {
-		$paramwithsearch .= '&sortorder='.$sortorder;
-	}
-	if ($sortfield) {
-		$paramwithsearch .= '&sortfield='.$sortfield;
-	}
-	if (GETPOST('from', 'alpha')) {
-		$paramwithsearch .= '&from='.GETPOST('from', 'alpha');
-	}
 	// There is several pages
 	if ($num > $listlimit) {
-		print '<tr class="none"><td class="right" colspan="'.(3 + count($fieldlist)).'">';
+		print '<tr class="none"><td class="right" colspan="'.(2 + count($fieldlist)).'">';
 		print_fleche_navigation($page, $_SERVER["PHP_SELF"], $paramwithsearch, ($num > $listlimit), '<li class="pagination"><span>'.$langs->trans("Page").' '.($page + 1).'</span></li>');
 		print '</td></tr>';
 	}
 
+	$filterfound = 0;
+	foreach ($fieldlist as $field => $value) {
+		$showfield = 1; // By defaut
+		if ($fieldlist[$field] == 'region_id' || $fieldlist[$field] == 'country_id') {
+			$showfield = 0;
+		}
+		if ($showfield) {
+			if ($value == 'country') {
+				$filterfound++;
+			}
+		}
+	}
+
 	// Title line with search boxes
 	print '<tr class="liste_titre liste_titre_add liste_titre_filter">';
+
+	// Action column
+	if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print '<td class="liste_titre center">';
+		if ($filterfound) {
+			$searchpicto = $form->showFilterAndCheckAddButtons(0);
+			print $searchpicto;
+		}
+		print '</td>';
+	}
+
 	$filterfound = 0;
 	foreach ($fieldlist as $field => $value) {
 		$showfield = 1; // By defaut
@@ -615,17 +672,23 @@ if ($resql) {
 	}
 	print '<td class="liste_titre"></td>';
 	print '<td class="liste_titre"></td>';
-	print '<td class="liste_titre"></td>';
-	print '<td class="liste_titre center">';
-	if ($filterfound) {
-		$searchpicto = $form->showFilterAndCheckAddButtons(0);
-		print $searchpicto;
+	// Action column
+	if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print '<td class="liste_titre center">';
+		if ($filterfound) {
+			$searchpicto = $form->showFilterAndCheckAddButtons(0);
+			print $searchpicto;
+		}
+		print '</td>';
 	}
-	print '</td>';
 	print '</tr>';
 
 	// Title of lines
 	print '<tr class="liste_titre">';
+	// Action column
+	if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print getTitleFieldOfList('');
+	}
 	foreach ($fieldlist as $field => $value) {
 		// Determines the name of the field in relation to the possible names
 		// in data dictionaries
@@ -699,16 +762,22 @@ if ($resql) {
 			print getTitleFieldOfList($valuetoshow, 0, $_SERVER["PHP_SELF"], ($sortable ? $fieldlist[$field] : ''), ($page ? 'page='.$page.'&' : ''), $param, "", $sortfield, $sortorder, $class.' ');
 		}
 	}
+	print getTitleFieldOfList($langs->trans("ListOfAccounts"), 0, $_SERVER["PHP_SELF"], "", ($page ? 'page='.$page.'&' : ''), $param, '', $sortfield, $sortorder, '');
 	print getTitleFieldOfList($langs->trans("Status"), 0, $_SERVER["PHP_SELF"], "active", ($page ? 'page='.$page.'&' : ''), $param, '', $sortfield, $sortorder, 'center ');
-	print getTitleFieldOfList('');
-	print getTitleFieldOfList('');
-	print getTitleFieldOfList('');
+	// Action column
+	if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print getTitleFieldOfList('');
+	}
 	print '</tr>';
 
+
 	if ($num) {
+		$imaxinloop = ($listlimit ? min($num, $listlimit) : $num);
+
 		// Lines with values
-		while ($i < $num) {
+		while ($i < $imaxinloop) {
 			$obj = $db->fetch_object($resql);
+
 			//print_r($obj);
 			print '<tr class="oddeven" id="rowid-'.$obj->rowid.'">';
 			if ($action == 'edit' && ($rowid == (!empty($obj->rowid) ? $obj->rowid : $obj->code))) {
@@ -717,12 +786,16 @@ if ($resql) {
 				$reshook = $hookmanager->executeHooks('editDictionaryFieldlist', $parameters, $obj, $tmpaction); // Note that $action and $object may have been modified by some hooks
 				$error = $hookmanager->error; $errors = $hookmanager->errors;
 
+				// Actions
+				if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+					print '<td></td>';
+				}
+
 				// Show fields
 				if (empty($reshook)) {
 					fieldListAccountingCategories($fieldlist, $obj, $tabname[$id], 'edit');
 				}
 
-				print '<td></td>';
 				print '<td></td>';
 				print '<td class="center">';
 				print '<div name="'.(!empty($obj->rowid) ? $obj->rowid : $obj->code).'"></div>';
@@ -731,19 +804,54 @@ if ($resql) {
 				print '<input type="submit" class="button button-edit smallpaddingimp" name="actionmodify" value="'.$langs->trans("Modify").'">';
 				print '<input type="submit" class="button button-cancel smallpaddingimp" name="actioncancel" value="'.$langs->trans("Cancel").'">';
 				print '</td>';
-				print '<td></td>';
+				// Actions
+				if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+					print '<td></td>';
+				}
 			} else {
+				// Can an entry be erased or disabled ?
+				$iserasable = 1; $canbedisabled = 1; $canbemodified = 1; // true by default
+				if (isset($obj->code)) {
+					if (($obj->code == '0' || $obj->code == '' || preg_match('/unknown/i', $obj->code))) {
+						$iserasable = 0; $canbedisabled = 0;
+					}
+				}
+				$url = $_SERVER["PHP_SELF"].'?'.($page ? 'page='.$page.'&' : '').'sortfield='.$sortfield.'&sortorder='.$sortorder.'&rowid='.(!empty($obj->rowid) ? $obj->rowid : (!empty($obj->code) ? $obj->code : '')).'&code='.(!empty($obj->code) ?urlencode($obj->code) : '');
+				if ($param) {
+					$url .= '&'.$param;
+				}
+				$url .= '&';
+
+				$canbemodified = $iserasable;
+
 				$tmpaction = 'view';
 				$parameters = array('fieldlist'=>$fieldlist, 'tabname'=>$tabname[$id]);
 				$reshook = $hookmanager->executeHooks('viewDictionaryFieldlist', $parameters, $obj, $tmpaction); // Note that $action and $object may have been modified by some hooks
 
 				$error = $hookmanager->error; $errors = $hookmanager->errors;
 
+				// Actions
+				if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+					print '<td class="center">';
+					if ($canbemodified) {
+						print '<a class="reposition editfielda marginleftonly marginrightonly" href="'.$url.'action=edit&token='.newToken().'">'.img_edit().'</a>';
+					}
+					if ($iserasable) {
+						if ($user->admin) {
+							print '<a class="marginleftonly marginrightonly" href="'.$url.'action=delete&token='.newToken().'">'.img_delete().'</a>';
+						}
+					}
+					print '</td>';
+				}
+
 				if (empty($reshook)) {
 					foreach ($fieldlist as $field => $value) {
 						$showfield = 1;
-						$class = "left";
-						$valuetoshow = $obj->{$fieldlist[$field]};
+						$title = '';
+						$class = 'tddict';
+
+						$tmpvar = $fieldlist[$field];
+						$valuetoshow = $obj->$tmpvar;
 						if ($value == 'category_type') {
 							$valuetoshow = yn($valuetoshow);
 						} elseif ($valuetoshow == 'all') {
@@ -755,43 +863,35 @@ if ($resql) {
 								$key = $langs->trans("Country".strtoupper($obj->country_code));
 								$valuetoshow = ($key != "Country".strtoupper($obj->country_code) ? $obj->country_code." - ".$key : $obj->country);
 							}
-						} elseif ($fieldlist[$field] == 'label' && $tabname[$id] == MAIN_DB_PREFIX.'c_country') {
-							$key = $langs->trans("Country".strtoupper($obj->code));
-							$valuetoshow = ($obj->code && $key != "Country".strtoupper($obj->code) ? $key : $obj->{$fieldlist[$field]});
-						} elseif ($fieldlist[$field] == 'label' && $tabname[$id] == MAIN_DB_PREFIX.'c_availability') {
-							$langs->loadLangs(array("propal"));
-							$key = $langs->trans("AvailabilityType".strtoupper($obj->code));
-							$valuetoshow = ($obj->code && $key != "AvailabilityType".strtoupper($obj->code) ? $key : $obj->{$fieldlist[$field]});
-						} elseif ($fieldlist[$field] == 'libelle' && $tabname[$id] == MAIN_DB_PREFIX.'c_actioncomm') {
-							$key = $langs->trans("Action".strtoupper($obj->code));
-							$valuetoshow = ($obj->code && $key != "Action".strtoupper($obj->code) ? $key : $obj->{$fieldlist[$field]});
+						} elseif (in_array($fieldlist[$field], array('label', 'range_account', 'formula'))) {
+							$class = "tdoverflowmax250";
+							$title = $valuetoshow;
 						} elseif ($fieldlist[$field] == 'region_id' || $fieldlist[$field] == 'country_id') {
 							$showfield = 0;
 						}
 
-						$class = 'tddict';
 						// Show value for field
 						if ($showfield) {
-							print '<!-- '.$fieldlist[$field].' --><td class="'.$class.'">'.dol_escape_htmltag($valuetoshow).'</td>';
+							print '<!-- '.$fieldlist[$field].' --><td class="'.$class.'"'.($title ? ' title="'.dol_escape_htmltag($title).'"': '').'>'.dol_escape_htmltag($valuetoshow).'</td>';
 						}
 					}
 				}
 
-				// Can an entry be erased or disabled ?
-				$iserasable = 1; $canbedisabled = 1; $canbemodified = 1; // true by default
-				if (isset($obj->code)) {
-					if (($obj->code == '0' || $obj->code == '' || preg_match('/unknown/i', $obj->code))) {
-						$iserasable = 0; $canbedisabled = 0;
-					}
-				}
-
-				$canbemodified = $iserasable;
+				// Link to setup the group
+				print '<td>';
+				if (empty($obj->formula)) {
+					// Count number of accounts into group
+					$nbofaccountintogroup = 0;
+					$listofaccountintogroup = $accountingcategory->getCptsCat($obj->rowid);
+					$nbofaccountintogroup = count($listofaccountintogroup);
 
-				$url = $_SERVER["PHP_SELF"].'?'.($page ? 'page='.$page.'&' : '').'sortfield='.$sortfield.'&sortorder='.$sortorder.'&rowid='.(!empty($obj->rowid) ? $obj->rowid : (!empty($obj->code) ? $obj->code : '')).'&code='.(!empty($obj->code) ?urlencode($obj->code) : '');
-				if ($param) {
-					$url .= '&'.$param;
+					print '<a href="'.DOL_URL_ROOT.'/accountancy/admin/categories.php?action=display&save_lastsearch_values=1&account_category='.$obj->rowid.'">';
+					print $langs->trans("NAccounts", $nbofaccountintogroup);
+					print '</a>';
+				} else {
+					print '<span class="opacitymedium">'.$langs->trans("Formula").'</span>';
 				}
-				$url .= '&';
+				print '</td>';
 
 				// Active
 				print '<td class="center" class="nowrap">';
@@ -802,47 +902,26 @@ if ($resql) {
 				}
 				print "</td>";
 
-				// Modify link
-				if ($canbemodified) {
-					print '<td class="center"><a class="reposition editfielda" href="'.$url.'action=edit&token='.newToken().'">'.img_edit().'</a></td>';
-				} else {
-					print '<td>&nbsp;</td>';
-				}
-
-				// Delete link
-				if ($iserasable) {
+				// Actions
+				if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
 					print '<td class="center">';
-					if ($user->admin) {
-						print '<a href="'.$url.'action=delete&token='.newToken().'">'.img_delete().'</a>';
+					if ($canbemodified) {
+						print '<a class="reposition editfielda paddingleft marginleftonly marginrightonly paddingright" href="'.$url.'action=edit&token='.newToken().'">'.img_edit().'</a>';
+					}
+					if ($iserasable) {
+						if ($user->admin) {
+							print '<a class="paddingleft marginleftonly marginrightonly paddingright" href="'.$url.'action=delete&token='.newToken().'">'.img_delete().'</a>';
+						}
 					}
-					//else print '<a href="#">'.img_delete().'</a>';    // Some dictionary can be edited by other profile than admin
 					print '</td>';
-				} else {
-					print '<td>&nbsp;</td>';
 				}
-
-				// Link to setup the group
-				print '<td class="center">';
-				if (empty($obj->formula)) {
-					print '<a href="'.DOL_URL_ROOT.'/accountancy/admin/categories.php?action=display&save_lastsearch_values=1&account_category='.$obj->rowid.'">';
-					print $langs->trans("ListOfAccounts");
-					print '</a>';
-
-					// Count number of accounts into group
-					$nbofaccountintogroup = 0;
-					$listofaccountintogroup = $accountingcategory->getCptsCat($obj->rowid);
-					$nbofaccountintogroup = count($listofaccountintogroup);
-					//if ($nbofaccountintogroup > 0) {
-					print ' <span class="opacitymedium">('.$langs->trans("NAccounts", $nbofaccountintogroup).')</span>';
-					//} else {
-					//	print ' <span class="opacitymedium">(0)</span>';
-					//}
-				}
-				print '</td>';
 			}
 			print "</tr>\n";
 			$i++;
 		}
+	} else {
+		$colspan = 10;
+		print '<tr><td colspan="'.$colspan.'"><span class="opacitymedium">'.$langs->trans("None").'</td></tr>';
 	}
 } else {
 	dol_print_error($db);
@@ -908,20 +987,14 @@ function fieldListAccountingCategories($fieldlist, $obj = '', $tabname = '', $co
 			print '<td><input type="text" class="flat minwidth100" value="'.(!empty($obj->{$fieldlist[$field]}) ? $obj->{$fieldlist[$field]}:'').'" name="'.$fieldlist[$field].'"></td>';
 		} else {
 			print '<td>';
-			$size = ''; $class = '';
-			if ($fieldlist[$field] == 'code') {
+			$class = '';
+			if (in_array($fieldlist[$field], array('code', 'range_account', 'label', 'formula'))) {
 				$class = 'maxwidth100';
 			}
 			if ($fieldlist[$field] == 'position') {
 				$class = 'maxwidth50';
 			}
-			if ($fieldlist[$field] == 'libelle') {
-				$class = 'quatrevingtpercent';
-			}
-			if ($fieldlist[$field] == 'sortorder' || $fieldlist[$field] == 'category_type') {
-				$size = 'size="2" ';
-			}
-			print '<input type="text" '.$size.'class="flat'.($class ? ' '.$class : '').'" value="'.(isset($obj->{$fieldlist[$field]}) ? $obj->{$fieldlist[$field]}:'').'" name="'.$fieldlist[$field].'">';
+			print '<input type="text" class="flat'.($class ? ' '.$class : '').'" value="'.(isset($obj->{$fieldlist[$field]}) ? $obj->{$fieldlist[$field]}:'').'" name="'.$fieldlist[$field].'">';
 			print '</td>';
 		}
 	}

+ 19 - 2
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>
@@ -78,12 +78,26 @@ if ($mysoc->isInEEC()) {
 $list_account[] = 'ACCOUNTING_SERVICE_BUY_EXPORT_ACCOUNT';
 
 $list_account[] = '---Others---';
-$list_account[] = 'ACCOUNTING_VAT_BUY_ACCOUNT';
 $list_account[] = 'ACCOUNTING_VAT_SOLD_ACCOUNT';
+$list_account[] = 'ACCOUNTING_VAT_BUY_ACCOUNT';
+
+/*if ($mysoc->useRevenueStamp()) {
+	$list_account[] = 'ACCOUNTING_REVENUESTAMP_SOLD_ACCOUNT';
+	$list_account[] = 'ACCOUNTING_REVENUESTAMP_BUY_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';
 }
+if (!empty($conf->global->INVOICE_USE_RETAINED_WARRANTY)) {
+	$list_account[] = 'ACCOUNTING_ACCOUNT_CUSTOMER_RETAINED_WARRANTY';
+}
 if (isModEnabled('don')) {
 	$list_account[] = 'DONATION_ACCOUNTINGACCOUNT';
 }
@@ -103,6 +117,7 @@ if (isModEnabled('societe')) {
 /*
  * Actions
  */
+
 if ($action == 'update') {
 	$error = 0;
 	// Process $list_account_main
@@ -250,6 +265,8 @@ foreach ($list_account as $key) {
 			print img_picto('', 'payment_vat', 'class="pictofixedwidth"');
 		} elseif (preg_match('/^ACCOUNTING_VAT/', $key)) {
 			print img_picto('', 'vat', 'class="pictofixedwidth"');
+			/*} elseif (preg_match('/^ACCOUNTING_REVENUESTAMP/', $key)) {
+			print img_picto('', 'vat', 'class="pictofixedwidth"');*/
 		} elseif (preg_match('/^ACCOUNTING_ACCOUNT_CUSTOMER/', $key)) {
 			print img_picto('', 'bill', 'class="pictofixedwidth"');
 		} elseif (preg_match('/^LOAN_ACCOUNTING_ACCOUNT/', $key)) {

+ 16 - 20
htdocs/accountancy/admin/fiscalyear.php

@@ -49,33 +49,27 @@ if (!$sortorder) {
 // Load translation files required by the page
 $langs->loadLangs(array("admin", "compta"));
 
-// Security check
-if ($user->socid > 0) {
-	accessforbidden();
-}
-if (!$user->hasRight('accounting', 'fiscalyear', 'write')) {              // If we can read accounting records, we should be able to see fiscal year.
-	accessforbidden();
-}
-
 $error = 0;
+$errors = array();
 
 // List of status
 static $tmpstatut2label = array(
 		'0' => 'OpenFiscalYear',
 		'1' => 'CloseFiscalYear'
 );
-$statut2label = array(
-		''
-);
-foreach ($tmpstatut2label as $key => $val) {
-	$statut2label[$key] = $langs->trans($val);
-}
-
-$errors = array();
 
 $object = new Fiscalyear($db);
 
 
+// Security check
+if ($user->socid > 0) {
+	accessforbidden();
+}
+if (!$user->hasRight('accounting', 'fiscalyear', 'write')) {              // If we can read accounting records, we should be able to see fiscal year.
+	accessforbidden();
+}
+
+
 /*
  * Actions
  */
@@ -104,7 +98,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
@@ -137,7 +131,7 @@ 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) {
@@ -146,6 +140,8 @@ if ($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;
 
@@ -158,12 +154,12 @@ 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++;
 		}
 	} else {
-		print '<tr class="oddeven"><td colspan="7" class="opacitymedium">'.$langs->trans("None").'</td></tr>';
+		print '<tr class="oddeven"><td colspan="7"><span class="opacitymedium">'.$langs->trans("None").'</span></td></tr>';
 	}
 	print '</table>';
 	print '</div>';

+ 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>';

+ 5 - 4
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
@@ -587,7 +587,8 @@ if ($id) {
 						foreach ($fieldlist as $field => $value) {
 							$showfield = 1;
 							$class = "left";
-							$valuetoshow = $obj->{$fieldlist[$field]};
+							$tmpvar = $fieldlist[$field];
+							$valuetoshow = $obj->$tmpvar;
 							if ($valuetoshow == 'all') {
 								$valuetoshow = $langs->trans('All');
 							} elseif ($fieldlist[$field] == 'nature' && $tabname[$id] == MAIN_DB_PREFIX.'accounting_journal') {

+ 59 - 29
htdocs/accountancy/admin/productaccount.php

@@ -52,11 +52,13 @@ if (!$user->hasRight('accounting', 'bind', 'write')) {
 // search & action GETPOST
 $action = GETPOST('action', 'aZ09');
 $massaction = GETPOST('massaction', 'alpha');
+$confirm = GETPOST('confirm', 'alpha');
+$optioncss = GETPOST('optioncss', 'alpha');
+
 $codeventil_buy = GETPOST('codeventil_buy', 'array');
 $codeventil_sell = GETPOST('codeventil_sell', 'array');
 $chk_prod = GETPOST('chk_prod', 'array');
 $default_account = GETPOST('default_account', 'int');
-$confirm = GETPOST('confirm', 'alpha');
 $account_number_buy = GETPOST('account_number_buy');
 $account_number_sell = GETPOST('account_number_sell');
 $changeaccount = GETPOST('changeaccount', 'array');
@@ -78,7 +80,6 @@ $search_onpurchase = GETPOST('search_onpurchase', 'alpha');
 
 $accounting_product_mode = GETPOST('accounting_product_mode', 'alpha');
 $btn_changetype = GETPOST('changetype', 'alpha');
-$optioncss = GETPOST('optioncss', 'alpha');
 
 if (empty($accounting_product_mode)) {
 	$accounting_product_mode = 'ACCOUNTANCY_SELL';
@@ -194,7 +195,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 +204,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 +395,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 +417,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);
@@ -467,24 +468,24 @@ if ($resql) {
 	print '<tr class="liste_titre">';
 	print '<td>'.$langs->trans('Options').'</td><td>'.$langs->trans('Description').'</td>';
 	print "</tr>\n";
-	print '<tr class="oddeven"><td><input type="radio" name="accounting_product_mode" value="ACCOUNTANCY_SELL"'.($accounting_product_mode == 'ACCOUNTANCY_SELL' ? ' checked' : '').'> '.$langs->trans('OptionModeProductSell').'</td>';
+	print '<tr class="oddeven"><td><input type="radio" id="accounting_product_mode1" name="accounting_product_mode" value="ACCOUNTANCY_SELL"'.($accounting_product_mode == 'ACCOUNTANCY_SELL' ? ' checked' : '').'> <label for="accounting_product_mode1">'.$langs->trans('OptionModeProductSell').'</label></td>';
 	print '<td>'.$langs->trans('OptionModeProductSellDesc');
 	print "</td></tr>\n";
 	if ($mysoc->isInEEC()) {
-		print '<tr class="oddeven"><td><input type="radio" name="accounting_product_mode" value="ACCOUNTANCY_SELL_INTRA"'.($accounting_product_mode == 'ACCOUNTANCY_SELL_INTRA' ? ' checked' : '').'> '.$langs->trans('OptionModeProductSellIntra').'</td>';
+		print '<tr class="oddeven"><td><input type="radio" id="accounting_product_mode2" name="accounting_product_mode" value="ACCOUNTANCY_SELL_INTRA"'.($accounting_product_mode == 'ACCOUNTANCY_SELL_INTRA' ? ' checked' : '').'> <label for="accounting_product_mode2">'.$langs->trans('OptionModeProductSellIntra').'</label></td>';
 		print '<td>'.$langs->trans('OptionModeProductSellIntraDesc');
 		print "</td></tr>\n";
 	}
-	print '<tr class="oddeven"><td><input type="radio" name="accounting_product_mode" value="ACCOUNTANCY_SELL_EXPORT"'.($accounting_product_mode == 'ACCOUNTANCY_SELL_EXPORT' ? ' checked' : '').'> '.$langs->trans('OptionModeProductSellExport').'</td>';
+	print '<tr class="oddeven"><td><input type="radio" id="accounting_product_mode3" name="accounting_product_mode" value="ACCOUNTANCY_SELL_EXPORT"'.($accounting_product_mode == 'ACCOUNTANCY_SELL_EXPORT' ? ' checked' : '').'> <label for="accounting_product_mode3">'.$langs->trans('OptionModeProductSellExport').'</label></td>';
 	print '<td>'.$langs->trans('OptionModeProductSellExportDesc');
 	print "</td></tr>\n";
-	print '<tr class="oddeven"><td><input type="radio" name="accounting_product_mode" value="ACCOUNTANCY_BUY"'.($accounting_product_mode == 'ACCOUNTANCY_BUY' ? ' checked' : '').'> '.$langs->trans('OptionModeProductBuy').'</td>';
+	print '<tr class="oddeven"><td><input type="radio" id="accounting_product_mode4" name="accounting_product_mode" value="ACCOUNTANCY_BUY"'.($accounting_product_mode == 'ACCOUNTANCY_BUY' ? ' checked' : '').'> <label for="accounting_product_mode4">'.$langs->trans('OptionModeProductBuy').'</label></td>';
 	print '<td>'.$langs->trans('OptionModeProductBuyDesc')."</td></tr>\n";
 	if ($mysoc->isInEEC()) {
-		print '<tr class="oddeven"><td><input type="radio" name="accounting_product_mode" value="ACCOUNTANCY_BUY_INTRA"'.($accounting_product_mode == 'ACCOUNTANCY_BUY_INTRA' ? ' checked' : '').'> '.$langs->trans('OptionModeProductBuyIntra').'</td>';
+		print '<tr class="oddeven"><td><input type="radio" id="accounting_product_mode5" name="accounting_product_mode" value="ACCOUNTANCY_BUY_INTRA"'.($accounting_product_mode == 'ACCOUNTANCY_BUY_INTRA' ? ' checked' : '').'> <label for="accounting_product_mode5">'.$langs->trans('OptionModeProductBuyIntra').'</label></td>';
 		print '<td>'.$langs->trans('OptionModeProductBuyDesc')."</td></tr>\n";
 	}
-	print '<tr class="oddeven"><td><input type="radio" name="accounting_product_mode" value="ACCOUNTANCY_BUY_EXPORT"'.($accounting_product_mode == 'ACCOUNTANCY_BUY_EXPORT' ? ' checked' : '').'> '.$langs->trans('OptionModeProductBuyExport').'</td>';
+	print '<tr class="oddeven"><td><input type="radio" id="accounting_product_mode6" name="accounting_product_mode" value="ACCOUNTANCY_BUY_EXPORT"'.($accounting_product_mode == 'ACCOUNTANCY_BUY_EXPORT' ? ' checked' : '').'> <label for="accounting_product_mode6">'.$langs->trans('OptionModeProductBuyExport').'</label></td>';
 	print '<td>'.$langs->trans('OptionModeProductBuyDesc')."</td></tr>\n";
 	print "</table>\n";
 
@@ -528,11 +529,11 @@ if ($resql) {
 		$categoriesProductArr = $form->select_all_categories(Categorie::TYPE_PRODUCT, '', '', 64, 0, 1);
 		$categoriesProductArr[-2] = '- '.$langs->trans('NotCategorized').' -';
 		$moreforfilter .= Form::multiselectarray('search_category_product_list', $categoriesProductArr, $searchCategoryProductList, 0, 0, 'minwidth300');
-		$moreforfilter .= ' <input type="checkbox" class="valignmiddle" name="search_category_product_operator" value="1"'.($searchCategoryProductOperator == 1 ? ' checked="checked"' : '').'/> <span class="none">'.$langs->trans('UseOrOperatorForCategories').'</span>';
+		$moreforfilter .= ' <input type="checkbox" class="valignmiddle" id="search_category_product_operator" name="search_category_product_operator" value="1"'.($searchCategoryProductOperator == 1 ? ' checked="checked"' : '').'/> <label for="search_category_product_operator"><span class="none">'.$langs->trans('UseOrOperatorForCategories').'</span></label>';
 		$moreforfilter .= '</div>';
 	}
 
-	//Show/hide child products. Hidden by default
+	// Show/hide child products. Hidden by default
 	if (isModEnabled('variants') && !empty($conf->global->PRODUIT_ATTRIBUTES_HIDECHILD)) {
 		$moreforfilter .= '<div class="divsearchfield">';
 		$moreforfilter .= '<input type="checkbox" id="search_show_childproducts" name="search_show_childproducts"'.($show_childproducts ? 'checked="checked"' : '').'>';
@@ -558,9 +559,16 @@ if ($resql) {
 	print '<table class="liste '.($moreforfilter ? "listwithfilterbefore" : "").'">';
 
 	print '<tr class="liste_titre_filter">';
+	// Action column
+	if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print '<td class="center liste_titre">';
+		$searchpicto = $form->showFilterButtons();
+		print $searchpicto;
+		print '</td>';
+	}
 	print '<td class="liste_titre"><input type="text" class="flat" size="8" name="search_ref" value="'.dol_escape_htmltag($search_ref).'"></td>';
 	print '<td class="liste_titre"><input type="text" class="flat" size="10" name="search_label" value="'.dol_escape_htmltag($search_label).'"></td>';
-	print '<td class="liste_titre right"><input type="text" class="flat maxwidth50 right" size="5" name="search_vat" placeholder="%" value="'.dol_escape_htmltag($search_vat).'"></td>';
+	print '<td class="liste_titre right"><input type="text" class="flat maxwidth50 right" name="search_vat" placeholder="%" value="'.dol_escape_htmltag($search_vat).'"></td>';
 
 	if (!empty($conf->global->ACCOUNTANCY_SHOW_PROD_DESC)) {
 		print '<td class="liste_titre"><input type="text" class="flat" size="20" name="search_desc" value="'.dol_escape_htmltag($search_desc).'"></td>';
@@ -579,13 +587,21 @@ if ($resql) {
 	print ' '.$langs->trans("or").' '.$form->selectarray('search_current_account_valid', $listofvals, $search_current_account_valid, 1);
 	print '</td>';
 	print '<td class="liste_titre">&nbsp;</td>';
-	print '<td class="center liste_titre">';
-	$searchpicto = $form->showFilterButtons();
-	print $searchpicto;
-	print '</td>';
+	// Action column
+	if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print '<td class="center liste_titre">';
+		$searchpicto = $form->showFilterButtons();
+		print $searchpicto;
+		print '</td>';
+	}
 	print '</tr>';
 
 	print '<tr class="liste_titre">';
+	// Action column
+	if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		$clickpitco = $form->showCheckAddButtons('checkforselect', 1);
+		print_liste_field_titre($clickpitco, '', '', '', '', '', '', '', 'center ');
+	}
 	print_liste_field_titre("Ref", $_SERVER["PHP_SELF"], "p.ref", "", $param, '', $sortfield, $sortorder);
 	print_liste_field_titre("Label", $_SERVER["PHP_SELF"], "p.label", "", $param, '', $sortfield, $sortorder);
 	if (!empty($conf->global->ACCOUNTANCY_SHOW_PROD_DESC)) {
@@ -600,8 +616,11 @@ if ($resql) {
 	}
 	print_liste_field_titre("CurrentDedicatedAccountingAccount", $_SERVER["PHP_SELF"], (empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED) ? "p." : "ppe.") . $accountancy_field_name, "", $param, '', $sortfield, $sortorder);
 	print_liste_field_titre("AssignDedicatedAccountingAccount");
-	$clickpitco = $form->showCheckAddButtons('checkforselect', 1);
-	print_liste_field_titre($clickpitco, '', '', '', '', '', '', '', 'center ');
+	// Action column
+	if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		$clickpitco = $form->showCheckAddButtons('checkforselect', 1);
+		print_liste_field_titre($clickpitco, '', '', '', '', '', '', '', 'center ');
+	}
 	print '</tr>';
 
 	$product_static = new Product($db);
@@ -681,8 +700,22 @@ if ($resql) {
 			}
 		}
 
+		$selected = 0;
+		if (!empty($chk_prod)) {
+			if (in_array($product_static->id, $chk_prod)) {
+				$selected = 1;
+			}
+		}
+
 		print '<tr class="oddeven">';
 
+		// Action column
+		if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+			print '<td class="center">';
+			print '<input type="checkbox" class="checkforselect productforselectcodeventil_'.$product_static->id.'" name="chk_prod[]" '.($selected ? "checked" : "").' value="'.$obj->rowid.'"/>';
+			print '</td>';
+		}
+
 		print '<td>';
 		print $product_static->getNomUrl(1);
 		print '</td>';
@@ -830,16 +863,13 @@ if ($resql) {
 			print '</td>';
 		}
 
-		if (!empty($chk_prod)) {
-			$ischecked = 0;
-			if (in_array($product_static->id, $chk_prod)) {
-				$ischecked=true;
-			}
+		// Action column
+		if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+			print '<td class="center">';
+			print '<input type="checkbox" class="checkforselect productforselectcodeventil_'.$product_static->id.'" name="chk_prod[]" '.($selected ? "checked" : "").' value="'.$obj->rowid.'"/>';
+			print '</td>';
 		}
 
-		// Checkbox select
-		print '<td class="center">';
-		print '<input type="checkbox" class="checkforselect productforselectcodeventil_'.$product_static->id.'" name="chk_prod[]" '.($ischecked ? "checked" : "").' value="'.$obj->rowid.'"/></td>';
 		print "</tr>";
 		$i++;
 	}

+ 110 - 44
htdocs/accountancy/admin/subaccount.php

@@ -19,17 +19,18 @@
 
 /**
  * \file 		htdocs/accountancy/admin/subaccount.php
- * \ingroup     Accountancy (Double entries)
- * \brief		List of accounting sub-account (auxiliary accounts)
+ * \ingroup 	Accountancy (Double entries)
+ * \brief 		List of accounting sub-account (auxiliary accounts)
  */
 
 // Load Dolibarr environment
 require '../../main.inc.php';
-require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
+
 
 // Load translation files required by the page
-$langs->loadLangs(array("compta", "bills", "admin", "accountancy", "salaries", "hrm", "errors"));
+$langs->loadLangs(array("accountancy", "admin", "bills", "compta", "errors", "hrm", "salaries"));
 
 $mesg = '';
 $action = GETPOST('action', 'aZ09');
@@ -38,6 +39,7 @@ $id = GETPOST('id', 'int');
 $rowid = GETPOST('rowid', 'int');
 $massaction = GETPOST('massaction', 'aZ09');
 $optioncss = GETPOST('optioncss', 'alpha');
+$mode = GETPOST('mode', 'aZ'); // The output mode ('list', 'kanban', 'hierarchy', 'calendar', ...)
 $contextpage = GETPOST('contextpage', 'aZ') ?GETPOST('contextpage', 'aZ') : 'accountingsubaccountlist'; // To manage different context of search
 
 $search_subaccount = GETPOST('search_subaccount', 'alpha');
@@ -77,10 +79,11 @@ $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']);
 }
 
+
 /*
  * Actions
  */
@@ -120,21 +123,23 @@ if (empty($reshook)) {
 
 $form = new Form($db);
 
-$help_url = '';
-$title = $langs->trans('ChartOfIndividualAccountsOfSubsidiaryLedger');
 
+// Page Header
+$help_url = 'EN:Module_Double_Entry_Accounting#Setup';
+$title = $langs->trans('ChartOfIndividualAccountsOfSubsidiaryLedger');
 llxHeader('', $title, $help_url);
 
+
 // Customer
-$sql = "SELECT sa.rowid, sa.nom as label, sa.code_compta as subaccount, '1' as type, sa.entity";
+$sql = "SELECT sa.rowid, sa.nom as label, sa.code_compta as subaccount, '1' as type, sa.entity, sa.client as nature";
 $sql .= " FROM ".MAIN_DB_PREFIX."societe sa";
 $sql .= " WHERE sa.entity IN (".getEntity('societe').")";
 $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;
@@ -174,14 +179,14 @@ if (!empty($search_type) && $search_type >= 0) {
 
 // Supplier
 $sql .= " UNION ";
-$sql .= " SELECT sa.rowid, sa.nom as label, sa.code_compta_fournisseur as subaccount, '2' as type, sa.entity FROM ".MAIN_DB_PREFIX."societe sa";
+$sql .= " SELECT sa.rowid, sa.nom as label, sa.code_compta_fournisseur as subaccount, '2' as type, sa.entity, '0' as nature FROM ".MAIN_DB_PREFIX."societe sa";
 $sql .= " WHERE sa.entity IN (".getEntity('societe').")";
 $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;
@@ -219,16 +224,16 @@ if (!empty($search_type) && $search_type >= 0) {
 	$sql .= " HAVING type LIKE '".$db->escape($search_type)."'";
 }
 
-// User
+// User - Employee
 $sql .= " UNION ";
-$sql .= " SELECT u.rowid, u.lastname as label, u.accountancy_code as subaccount, '3' as type, u.entity FROM ".MAIN_DB_PREFIX."user u";
+$sql .= " SELECT u.rowid, u.lastname as label, u.accountancy_code as subaccount, '3' as type, u.entity, '0' as nature FROM ".MAIN_DB_PREFIX."user u";
 $sql .= " WHERE u.entity IN (".getEntity('user').")";
 $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;
@@ -270,7 +275,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
@@ -292,7 +297,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);
@@ -304,6 +309,9 @@ if ($resql) {
 		$param .= '&optioncss='.urlencode($optioncss);
 	}
 
+	// List of mass actions available
+	$arrayofmassactions = array();
+
 	print '<form method="POST" id="searchFormList" action="'.$_SERVER["PHP_SELF"].'">';
 	if ($optioncss != '') {
 		print '<input type="hidden" name="optioncss" value="'.$optioncss.'">';
@@ -320,7 +328,8 @@ if ($resql) {
 	print '<div class="info">'.$langs->trans("WarningCreateSubAccounts").'</div>';
 
 	$varpage = empty($contextpage) ? $_SERVER["PHP_SELF"] : $contextpage;
-	$selectedfields = $form->multiSelectArrayWithCheckbox('selectedfields', $arrayfields, $varpage); // 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
+	$selectedfields .= (count($arrayofmassactions) ? $form->showCheckAddButtons('checkforselect', 1) : '');
 
 	$moreforfilter = '';
 	$massactionbutton = '';
@@ -330,6 +339,13 @@ if ($resql) {
 
 	// Line for search fields
 	print '<tr class="liste_titre_filter">';
+	// Action column
+	if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print '<td class="liste_titre center maxwidthsearch">';
+		$searchpicto = $form->showFilterAndCheckAddButtons($massactionbutton ? 1 : 0, 'checkforselect', 1);
+		print $searchpicto;
+		print '</td>';
+	}
 	if (!empty($arrayfields['subaccount']['checked'])) {
 		print '<td class="liste_titre"><input type="text" class="flat" size="10" name="search_subaccount" value="'.$search_subaccount.'"></td>';
 	}
@@ -344,13 +360,20 @@ if ($resql) {
 			print '<td class="liste_titre">&nbsp;</td>';
 		}
 	}
-	print '<td class="liste_titre maxwidthsearch">';
-	$searchpicto = $form->showFilterAndCheckAddButtons($massactionbutton ? 1 : 0, 'checkforselect', 1);
-	print $searchpicto;
-	print '</td>';
+	// Action column
+	if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print '<td class="liste_titre maxwidthsearch">';
+		$searchpicto = $form->showFilterAndCheckAddButtons($massactionbutton ? 1 : 0, 'checkforselect', 1);
+		print $searchpicto;
+		print '</td>';
+	}
 	print '</tr>';
 
 	print '<tr class="liste_titre">';
+	// Action column
+	if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', '', $sortfield, $sortorder, 'center maxwidthsearch ');
+	}
 	if (!empty($arrayfields['subaccount']['checked'])) {
 		print_liste_field_titre($arrayfields['subaccount']['label'], $_SERVER["PHP_SELF"], "subaccount", "", $param, '', $sortfield, $sortorder);
 	}
@@ -365,7 +388,10 @@ if ($resql) {
 			print_liste_field_titre($arrayfields['reconcilable']['label'], $_SERVER["PHP_SELF"], 'reconcilable', '', $param, '', $sortfield, $sortorder, 'center ');
 		}
 	}
-	print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', '', $sortfield, $sortorder, 'center maxwidthsearch ');
+	// Action column
+	if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', '', $sortfield, $sortorder, 'center maxwidthsearch ');
+	}
 	print "</tr>\n";
 
 	$totalarray = array();
@@ -376,6 +402,28 @@ if ($resql) {
 
 		print '<tr class="oddeven">';
 
+		// Action column
+		if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+			print '<td class="center">';
+			$e = '';
+
+			// Customer
+			if ($obj->type == 1) {
+				$e .= '<a class="editfielda" title="'.$langs->trans("Customer").'" href="'.DOL_URL_ROOT.'/societe/card.php?action=edit&token='.newToken().'&socid='.$obj->rowid.'&backtopage='.urlencode($_SERVER["PHP_SELF"]).'">'.img_edit().'</a>';
+			} elseif ($obj->type == 2) {
+				// Supplier
+				$e .= '<a class="editfielda" title="'.$langs->trans("Supplier").'" href="'.DOL_URL_ROOT.'/societe/card.php?action=edit&token='.newToken().'&socid='.$obj->rowid.'&backtopage='.urlencode($_SERVER["PHP_SELF"]).'">'.img_edit().'</a>';
+			} elseif ($obj->type == 3) {
+				// User - Employee
+				$e .= '<a class="editfielda" title="'.$langs->trans("Employee").'" href="'.DOL_URL_ROOT.'/user/card.php?action=edit&token='.newToken().'&id='.$obj->rowid.'&backtopage='.urlencode($_SERVER["PHP_SELF"]).'">'.img_edit().'</a>';
+			}
+			print $e;
+			print '</td>'."\n";
+			if (!$i) {
+				$totalarray['nbfield']++;
+			}
+		}
+
 		// Account number
 		if (!empty($arrayfields['subaccount']['checked'])) {
 			print "<td>";
@@ -389,7 +437,7 @@ if ($resql) {
 		// Subaccount label
 		if (!empty($arrayfields['label']['checked'])) {
 			print "<td>";
-			print $obj->label;
+			print dol_escape_htmltag($obj->label);
 			print "</td>\n";
 			if (!$i) {
 				$totalarray['nbfield']++;
@@ -400,6 +448,7 @@ if ($resql) {
 		if (!empty($arrayfields['type']['checked'])) {
 			print '<td class="center">';
 			$s = '';
+
 			// Customer
 			if ($obj->type == 1) {
 				$s .= '<a class="customer-back" style="padding-left: 6px; padding-right: 6px" title="'.$langs->trans("Customer").'" href="'.DOL_URL_ROOT.'/comm/card.php?socid='.$obj->rowid.'">'.$langs->trans("Customer").'</a>';
@@ -407,10 +456,13 @@ if ($resql) {
 				// Supplier
 				$s .= '<a class="vendor-back" style="padding-left: 6px; padding-right: 6px" title="'.$langs->trans("Supplier").'" href="'.DOL_URL_ROOT.'/fourn/card.php?socid='.$obj->rowid.'">'.$langs->trans("Supplier").'</a>';
 			} elseif ($obj->type == 3) {
-				// User
+				// User - Employee
 				$s .= '<a class="user-back" style="padding-left: 6px; padding-right: 6px" title="'.$langs->trans("Employee").'" href="'.DOL_URL_ROOT.'/user/card.php?id='.$obj->rowid.'">'.$langs->trans("Employee").'</a>';
 			}
 			print $s;
+			if ($obj->nature == 2) {
+				print ' <span class="warning bold">('.$langs->trans("Prospect").')</span>';
+			}
 			print '</td>';
 			if (!$i) {
 				$totalarray['nbfield']++;
@@ -437,29 +489,43 @@ if ($resql) {
 			}
 		}
 
-		// Action
-		print '<td class="center">';
-		$e = '';
-		// Customer
-		if ($obj->type == 1) {
-			$e .= '<a class="editfielda" title="'.$langs->trans("Customer").'" href="'.DOL_URL_ROOT.'/societe/card.php?action=edit&token='.newToken().'&socid='.$obj->rowid.'&backtopage='.urlencode($_SERVER["PHP_SELF"]).'">'.img_edit().'</a>';
-		} elseif ($obj->type == 2) {
-			// Supplier
-			$e .= '<a class="editfielda" title="'.$langs->trans("Supplier").'" href="'.DOL_URL_ROOT.'/societe/card.php?action=edit&token='.newToken().'&socid='.$obj->rowid.'&backtopage='.urlencode($_SERVER["PHP_SELF"]).'">'.img_edit().'</a>';
-		} elseif ($obj->type == 3) {
-			// User
-			$e .= '<a class="editfielda" title="'.$langs->trans("Employee").'" href="'.DOL_URL_ROOT.'/user/card.php?action=edit&token='.newToken().'&id='.$obj->rowid.'&backtopage='.urlencode($_SERVER["PHP_SELF"]).'">'.img_edit().'</a>';
-		}
-		print $e;
-		print '</td>'."\n";
-		if (!$i) {
-			$totalarray['nbfield']++;
+		// Action column
+		if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+			print '<td class="center">';
+			$e = '';
+
+			// Customer
+			if ($obj->type == 1) {
+				$e .= '<a class="editfielda" title="'.$langs->trans("Customer").'" href="'.DOL_URL_ROOT.'/societe/card.php?action=edit&token='.newToken().'&socid='.$obj->rowid.'&backtopage='.urlencode($_SERVER["PHP_SELF"]).'">'.img_edit().'</a>';
+			} elseif ($obj->type == 2) {
+				// Supplier
+				$e .= '<a class="editfielda" title="'.$langs->trans("Supplier").'" href="'.DOL_URL_ROOT.'/societe/card.php?action=edit&token='.newToken().'&socid='.$obj->rowid.'&backtopage='.urlencode($_SERVER["PHP_SELF"]).'">'.img_edit().'</a>';
+			} elseif ($obj->type == 3) {
+				// User - Employee
+				$e .= '<a class="editfielda" title="'.$langs->trans("Employee").'" href="'.DOL_URL_ROOT.'/user/card.php?action=edit&token='.newToken().'&id='.$obj->rowid.'&backtopage='.urlencode($_SERVER["PHP_SELF"]).'">'.img_edit().'</a>';
+			}
+			print $e;
+			print '</td>'."\n";
+			if (!$i) {
+				$totalarray['nbfield']++;
+			}
 		}
 
 		print '</tr>'."\n";
 		$i++;
 	}
 
+	// 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>';
+	}
+
 	$db->free($resql);
 
 	$parameters = array('arrayfields'=>$arrayfields, 'sql'=>$sql);

+ 275 - 111
htdocs/accountancy/bookkeeping/balance.php

@@ -1,7 +1,7 @@
 <?php
 /* Copyright (C) 2016       Olivier Geffroy         <jeff@jeffinfo.com>
  * Copyright (C) 2016       Florian Henry           <florian.henry@open-concept.pro>
- * Copyright (C) 2016-2022  Alexandre Spangaro      <aspangaro@open-dsi.fr>
+ * Copyright (C) 2016-2023  Alexandre Spangaro      <aspangaro@open-dsi.fr>
  * Copyright (C) 2018       Frédéric France         <frederic.france@netlogic.fr>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -42,7 +42,26 @@ $langs->loadLangs(array("accountancy", "compta"));
 
 $action = GETPOST('action', 'aZ09');
 $optioncss = GETPOST('optioncss', 'alpha');
-$contextpage = GETPOST('contextpage', 'aZ09');
+$type = GETPOST('type', 'alpha');
+if ($type == 'sub') {
+	$context_default = 'balancesubaccountlist';
+} else {
+	$context_default = 'balancelist';
+}
+$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : $context_default;
+$show_subgroup = GETPOST('show_subgroup', 'alpha');
+$search_date_start = dol_mktime(0, 0, 0, GETPOST('date_startmonth', 'int'), GETPOST('date_startday', 'int'), GETPOST('date_startyear', 'int'));
+$search_date_end = dol_mktime(23, 59, 59, GETPOST('date_endmonth', 'int'), GETPOST('date_endday', 'int'), GETPOST('date_endyear', 'int'));
+$search_ledger_code = GETPOST('search_ledger_code', 'array');
+$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_not_reconciled = GETPOST('search_not_reconciled', 'alpha');
 
 // Load variable for pagination
 $limit = GETPOST('limit', 'int') ?GETPOST('limit', 'int') : $conf->liste_limit;
@@ -55,25 +74,16 @@ if (empty($page) || $page == -1 || GETPOST('button_search', 'alpha') || GETPOST(
 $offset = $limit * $page;
 $pageprev = $page - 1;
 $pagenext = $page + 1;
-//if (! $sortfield) $sortfield="p.date_fin";
-//if (! $sortorder) $sortorder="DESC";
-
-$show_subgroup = GETPOST('show_subgroup', 'alpha');
-$search_date_start = dol_mktime(0, 0, 0, GETPOST('date_startmonth', 'int'), GETPOST('date_startday', 'int'), GETPOST('date_startyear', 'int'));
-$search_date_end = dol_mktime(23, 59, 59, GETPOST('date_endmonth', 'int'), GETPOST('date_endday', 'int'), GETPOST('date_endyear', 'int'));
-$search_ledger_code = GETPOST('search_ledger_code', 'array');
-$search_accountancy_code_start = GETPOST('search_accountancy_code_start', 'alpha');
-if ($search_accountancy_code_start == - 1) {
-	$search_accountancy_code_start = '';
+if ($sortorder == "") {
+	$sortorder = "ASC";
 }
-$search_accountancy_code_end = GETPOST('search_accountancy_code_end', 'alpha');
-if ($search_accountancy_code_end == - 1) {
-	$search_accountancy_code_end = '';
+if ($sortfield == "") {
+	$sortfield = "t.numero_compte";
 }
 
 // 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('balancelist'));  // Note that conf->hooks_modules contains array
+$hookmanager->initHooks(array($contextpage));  // Note that conf->hooks_modules contains array
 
 $formaccounting = new FormAccounting($db);
 $formother = new FormOther($db);
@@ -84,6 +94,7 @@ if (empty($search_date_start) && !GETPOSTISSET('formfilteraction')) {
 	$sql .= " WHERE date_start < '".$db->idate(dol_now())."' AND date_end > '".$db->idate(dol_now())."'";
 	$sql .= $db->plimit(1);
 	$res = $db->query($sql);
+
 	if ($res->num_rows > 0) {
 		$fiscalYear = $db->fetch_object($res);
 		$search_date_start = strtotime($fiscalYear->date_start);
@@ -104,45 +115,6 @@ if (empty($search_date_start) && !GETPOSTISSET('formfilteraction')) {
 		$search_date_end = dol_get_last_day($year_end, $month_end);
 	}
 }
-if ($sortorder == "") {
-	$sortorder = "ASC";
-}
-if ($sortfield == "") {
-	$sortfield = "t.numero_compte";
-}
-
-
-$param = '';
-if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) {
-	$param .= '&contextpage='.urlencode($contextpage);
-}
-if ($limit > 0 && $limit != $conf->liste_limit) {
-	$param .= '&limit='.urlencode($limit);
-}
-
-$filter = array();
-if (!empty($search_date_start)) {
-	$filter['t.doc_date>='] = $search_date_start;
-	$param .= '&date_startmonth='.GETPOST('date_startmonth', 'int').'&date_startday='.GETPOST('date_startday', 'int').'&date_startyear='.GETPOST('date_startyear', 'int');
-}
-if (!empty($search_date_end)) {
-	$filter['t.doc_date<='] = $search_date_end;
-	$param .= '&date_endmonth='.GETPOST('date_endmonth', 'int').'&date_endday='.GETPOST('date_endday', 'int').'&date_endyear='.GETPOST('date_endyear', 'int');
-}
-if (!empty($search_accountancy_code_start)) {
-	$filter['t.numero_compte>='] = $search_accountancy_code_start;
-	$param .= '&search_accountancy_code_start='.urlencode($search_accountancy_code_start);
-}
-if (!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_ledger_code)) {
-	$filter['t.code_journal'] = $search_ledger_code;
-	foreach ($search_ledger_code as $code) {
-		$param .= '&search_ledger_code[]='.urlencode($code);
-	}
-}
 
 if (!isModEnabled('accounting')) {
 	accessforbidden();
@@ -154,14 +126,13 @@ if (!$user->hasRight('accounting', 'mouvements', 'lire')) {
 	accessforbidden();
 }
 
-
-
 /*
  * Action
  */
 
-$parameters = array();
-$arrayfields = array();
+$param = '';
+
+$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');
@@ -172,16 +143,67 @@ if (empty($reshook)) {
 		$show_subgroup = '';
 		$search_date_start = '';
 		$search_date_end = '';
+		$search_date_startyear = '';
+		$search_date_startmonth = '';
+		$search_date_startday = '';
+		$search_date_endyear = '';
+		$search_date_endmonth = '';
+		$search_date_endday = '';
 		$search_accountancy_code_start = '';
 		$search_accountancy_code_end = '';
+		$search_not_reconciled = '';
 		$search_ledger_code = array();
 		$filter = array();
 	}
-}
 
-/*
- * View
- */
+	// Must be after the remove filter action, before the export.
+	$filter = array();
+
+	if (!empty($search_date_start)) {
+		$filter['t.doc_date>='] = $search_date_start;
+		$param .= '&date_startmonth=' . GETPOST('date_startmonth', 'int') . '&date_startday=' . GETPOST('date_startday', 'int') . '&date_startyear=' . GETPOST('date_startyear', 'int');
+	}
+	if (!empty($search_date_end)) {
+		$filter['t.doc_date<='] = $search_date_end;
+		$param .= '&date_endmonth=' . GETPOST('date_endmonth', 'int') . '&date_endday=' . GETPOST('date_endday', 'int') . '&date_endyear=' . GETPOST('date_endyear', 'int');
+	}
+	if (!empty($search_doc_date)) {
+		$filter['t.doc_date'] = $search_doc_date;
+		$param .= '&doc_datemonth=' . GETPOST('doc_datemonth', 'int') . '&doc_dateday=' . GETPOST('doc_dateday', 'int') . '&doc_dateyear=' . GETPOST('doc_dateyear', 'int');
+	}
+	if (!empty($search_accountancy_code_start)) {
+		if ($type == 'sub') {
+			$filter['t.subledger_account>='] = $search_accountancy_code_start;
+		} else {
+			$filter['t.numero_compte>='] = $search_accountancy_code_start;
+		}
+		$param .= '&search_accountancy_code_start=' . urlencode($search_accountancy_code_start);
+	}
+	if (!empty($search_accountancy_code_end)) {
+		if ($type == 'sub') {
+			$filter['t.subledger_account<='] = $search_accountancy_code_end;
+		} else {
+			$filter['t.numero_compte<='] = $search_accountancy_code_end;
+		}
+		$param .= '&search_accountancy_code_end=' . urlencode($search_accountancy_code_end);
+	}
+	if (!empty($search_ledger_code)) {
+		$filter['t.code_journal'] = $search_ledger_code;
+		foreach ($search_ledger_code as $code) {
+			$param .= '&search_ledger_code[]=' . urlencode($code);
+		}
+	}
+	if (!empty($search_not_reconciled)) {
+		$filter['t.reconciled_option'] = $search_not_reconciled;
+		$param .= '&search_not_reconciled='.urlencode($search_not_reconciled);
+	}
+
+	// param with type of list
+	$url_param = substr($param, 1); // remove first "&"
+	if (!empty($type)) {
+		$param = '&type=' . $type . $param;
+	}
+}
 
 if ($action == 'export_csv') {
 	$sep = $conf->global->ACCOUNTING_EXPORT_SEPARATORCSV;
@@ -190,14 +212,23 @@ if ($action == 'export_csv') {
 	$type_export = 'balance';
 	include DOL_DOCUMENT_ROOT.'/accountancy/tpl/export_journal.tpl.php';
 
-	$result = $object->fetchAllBalance($sortorder, $sortfield, $limit, 0, $filter);
+	if ($type == 'sub') {
+		$result = $object->fetchAllBalance($sortorder, $sortfield, $limit, 0, $filter, 'AND', 1);
+	} else {
+		$result = $object->fetchAllBalance($sortorder, $sortfield, $limit, 0, $filter);
+	}
 	if ($result < 0) {
 		setEventMessages($object->error, $object->errors, 'errors');
 	}
 
 	foreach ($object->lines as $line) {
-		print '"'.length_accountg($line->numero_compte).'"'.$sep;
-		print '"'.$object->get_compte_desc($line->numero_compte).'"'.$sep;
+		if ($type == 'sub') {
+			print '"' . length_accounta($line->subledger_account) . '"' . $sep;
+			print '"' . $line->subledger_label . '"' . $sep;
+		} else {
+			print '"' . length_accountg($line->numero_compte) . '"' . $sep;
+			print '"' . $object->get_compte_desc($line->numero_compte) . '"' . $sep;
+		}
 		print '"'.price($line->debit).'"'.$sep;
 		print '"'.price($line->credit).'"'.$sep;
 		print '"'.price($line->debit - $line->credit).'"'.$sep;
@@ -207,8 +238,15 @@ if ($action == 'export_csv') {
 	exit;
 }
 
+/*
+ * View
+ */
 
-$title_page = $langs->trans("AccountBalance");
+if ($type == 'sub') {
+	$title_page = $langs->trans("AccountBalanceSubAccount");
+} else {
+	$title_page = $langs->trans("AccountBalance");
+}
 
 llxHeader('', $title_page);
 
@@ -216,44 +254,57 @@ llxHeader('', $title_page);
 if ($action != 'export_csv') {
 	// List
 	$nbtotalofrecords = '';
-	if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
-		$nbtotalofrecords = $object->fetchAllBalance($sortorder, $sortfield, 0, 0, $filter);
+	if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
+		if ($type == 'sub') {
+			$nbtotalofrecords = $object->fetchAllBalance($sortorder, $sortfield, 0, 0, $filter, 'AND', 1);
+		} else {
+			$nbtotalofrecords = $object->fetchAllBalance($sortorder, $sortfield, 0, 0, $filter);
+		}
+
 		if ($nbtotalofrecords < 0) {
 			setEventMessages($object->error, $object->errors, 'errors');
 		}
 	}
 
-	$result = $object->fetchAllBalance($sortorder, $sortfield, $limit, $offset, $filter);
+	if ($type == 'sub') {
+		$result = $object->fetchAllBalance($sortorder, $sortfield, $limit, $offset, $filter, 'AND', 1);
+	} else {
+		$result = $object->fetchAllBalance($sortorder, $sortfield, $limit, $offset, $filter);
+	}
+
 	if ($result < 0) {
 		setEventMessages($object->error, $object->errors, 'errors');
 	}
 
 	print '<form method="POST" id="searchFormList" action="'.$_SERVER["PHP_SELF"].'">';
+	print '<input type="hidden" name="token" value="'.newToken().'">';
+	print '<input type="hidden" name="action" id="action" value="list">';
 	if ($optioncss != '') {
 		print '<input type="hidden" name="optioncss" value="'.$optioncss.'">';
 	}
-	print '<input type="hidden" name="token" value="'.newToken().'">';
 	print '<input type="hidden" name="formfilteraction" id="formfilteraction" value="list">';
-	print '<input type="hidden" name="action" id="action" value="list">';
+	print '<input type="hidden" name="type" value="'.$type.'">';
 	print '<input type="hidden" name="sortfield" value="'.$sortfield.'">';
 	print '<input type="hidden" name="sortorder" value="'.$sortorder.'">';
+	print '<input type="hidden" name="contextpage" value="'.$contextpage.'">';
 	print '<input type="hidden" name="page" value="'.$page.'">';
 
 
 	$parameters = array();
-	$reshook = $hookmanager->executeHooks('addMoreActionsButtonsList', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+	$reshook = $hookmanager->executeHooks('addMoreActionsButtons', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+
 	if ($reshook < 0) {
 		setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
 	}
 
-	$button = empty($hookmanager->resPrint) ? '' : $hookmanager->resPrint;
+	$newcardbutton = empty($hookmanager->resPrint) ? '' : $hookmanager->resPrint;
 
 	if (empty($reshook)) {
-		$button .= '<input type="button" id="exportcsvbutton" name="exportcsvbutton" class="butAction" value="'.$langs->trans("Export").' ('.$conf->global->ACCOUNTING_EXPORT_FORMAT.')" />';
+		$newcardbutton = '<input type="button" id="exportcsvbutton" name="exportcsvbutton" class="butAction" value="'.$langs->trans("Export").' ('.$conf->global->ACCOUNTING_EXPORT_FORMAT.')" />';
 
 		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");
@@ -262,12 +313,32 @@ if ($action != 'export_csv') {
 			});
 		});
 		</script>';
+
+		if ($type == 'sub') {
+			$newcardbutton .= dolGetButtonTitle($langs->trans('AccountBalance')." - ".$langs->trans('GroupByAccountAccounting'), '', 'fa fa-stream paddingleft imgforviewmode', DOL_URL_ROOT . '/accountancy/bookkeeping/balance.php?' . $url_param, '', 1, array('morecss' => 'marginleftonly'));
+			$newcardbutton .= dolGetButtonTitle($langs->trans('AccountBalance')." - ".$langs->trans('GroupBySubAccountAccounting'), '', 'fa fa-align-left vmirror paddingleft imgforviewmode', DOL_URL_ROOT . '/accountancy/bookkeeping/balance.php?type=sub&' . $url_param, '', 1, array('morecss' => 'marginleftonly btnTitleSelected'));
+		} else {
+			$newcardbutton .= dolGetButtonTitle($langs->trans('AccountBalance')." - ".$langs->trans('GroupByAccountAccounting'), '', 'fa fa-stream paddingleft imgforviewmode', DOL_URL_ROOT . '/accountancy/bookkeeping/balance.php?' . $url_param, '', 1, array('morecss' => 'marginleftonly btnTitleSelected'));
+			$newcardbutton .= dolGetButtonTitle($langs->trans('AccountBalance')." - ".$langs->trans('GroupBySubAccountAccounting'), '', 'fa fa-align-left vmirror paddingleft imgforviewmode', DOL_URL_ROOT . '/accountancy/bookkeeping/balance.php?type=sub&' . $url_param, '', 1, array('morecss' => 'marginleftonly'));
+		}
+		$newcardbutton .= dolGetButtonTitle($langs->trans('NewAccountingMvt'), '', 'fa fa-plus-circle paddingleft', DOL_URL_ROOT.'/accountancy/bookkeeping/card.php?action=create');
+	}
+	if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) {
+		$param .= '&contextpage='.urlencode($contextpage);
+	}
+	if ($limit > 0 && $limit != $conf->liste_limit) {
+		$param .= '&limit='.((int) $limit);
 	}
 
-	print_barre_liste($title_page, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $button, $result, $nbtotalofrecords, 'title_accountancy', 0, '', '', $limit);
+	print_barre_liste($title_page, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $result, $nbtotalofrecords, 'title_accountancy', 0, $newcardbutton, '', $limit, 0, 0, 1);
 
 	$selectedfields = '';
 
+	// Warning to explain why list of record is not consistent with the other list view (missing a lot of lines)
+	if ($type == 'sub') {
+		print info_admin($langs->trans("WarningRecordWithoutSubledgerAreExcluded"));
+	}
+
 	$moreforfilter = '';
 
 	$moreforfilter .= '<div class="divsearchfield">';
@@ -275,20 +346,42 @@ if ($action != 'export_csv') {
 	$moreforfilter .= $form->selectDate($search_date_start ? $search_date_start : -1, 'date_start', 0, 0, 1, '', 1, 0);
 	$moreforfilter .= $langs->trans('DateEnd').': ';
 	$moreforfilter .= $form->selectDate($search_date_end ? $search_date_end : -1, 'date_end', 0, 0, 1, '', 1, 0);
+	$moreforfilter .= '</div>';
 
-	$moreforfilter .= ' - ';
+	$moreforfilter .= '<div class="divsearchfield">';
 	$moreforfilter .= '<label for="show_subgroup">'.$langs->trans('ShowSubtotalByGroup').'</label>: ';
 	$moreforfilter .= '<input type="checkbox" name="show_subgroup" id="show_subgroup" value="show_subgroup"'.($show_subgroup == 'show_subgroup' ? ' checked' : '').'>';
-
 	$moreforfilter .= '</div>';
 
 	$moreforfilter .= '<div class="divsearchfield">';
-
-	$moreforfilter .= $langs->trans("Journal");
+	$moreforfilter .= $langs->trans("Journals").': ';
 	$moreforfilter .= $formaccounting->multi_select_journal($search_ledger_code, 'search_ledger_code', 0, 1, 1, 1);
+	$moreforfilter .= '</div>';
 
+	$moreforfilter .= '</br>';
+	$moreforfilter .= '<div class="divsearchfield">';
+	// Accountancy account
+	$moreforfilter .= $langs->trans('AccountAccounting').': ';
+	if ($type == 'sub') {
+		$moreforfilter .= $formaccounting->select_auxaccount($search_accountancy_code_start, 'search_accountancy_code_start', $langs->trans('From'), 'maxwidth200');
+	} else {
+		$moreforfilter .= $formaccounting->select_account($search_accountancy_code_start, 'search_accountancy_code_start', $langs->trans('From'), array(), 1, 1, 'maxwidth200', 'accounts');
+	}
+	$moreforfilter .= ' ';
+	if ($type == 'sub') {
+		$moreforfilter .= $formaccounting->select_auxaccount($search_accountancy_code_end, 'search_accountancy_code_end', $langs->trans('to'), 'maxwidth200');
+	} else {
+		$moreforfilter .= $formaccounting->select_account($search_accountancy_code_end, 'search_accountancy_code_end', $langs->trans('to'), array(), 1, 1, 'maxwidth200', 'accounts');
+	}
 	$moreforfilter .= '</div>';
 
+	if (!empty($conf->global->ACCOUNTING_ENABLE_LETTERING)) {
+		$moreforfilter .= '<div class="divsearchfield">';
+		$moreforfilter .= '<label for="notreconciled">'.$langs->trans('NotReconciled').'</label>: ';
+		$moreforfilter .= '<input type="checkbox" name="search_not_reconciled" id="notreconciled" value="notreconciled"'.($search_not_reconciled == 'notreconciled' ? ' checked' : '').'>';
+		$moreforfilter .= '</div>';
+	}
+
 	if (!empty($moreforfilter)) {
 		print '<div class="liste_titre liste_titre_bydiv centpercent">';
 		print $moreforfilter;
@@ -304,10 +397,15 @@ if ($action != 'export_csv') {
 	print '<table class="liste '.($moreforfilter ? "listwithfilterbefore" : "").'">';
 
 	print '<tr class="liste_titre_filter">';
+
+	if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print '<td class="liste_titre maxwidthsearch">';
+		$searchpicto = $form->showFilterButtons();
+		print $searchpicto;
+		print '</td>';
+	}
+
 	print '<td class="liste_titre" colspan="'.$colspan.'">';
-	print $formaccounting->select_account($search_accountancy_code_start, 'search_accountancy_code_start', $langs->trans('From'), array(), 1, 1, '', 'accounts');
-	print ' ';
-	print $formaccounting->select_account($search_accountancy_code_end, 'search_accountancy_code_end', $langs->trans('to'), array(), 1, 1, '', 'accounts');
 	print '</td>';
 
 	// Fields from hook
@@ -316,19 +414,28 @@ if ($action != 'export_csv') {
 	print $hookmanager->resPrint;
 
 	// Action column
-	print '<td class="liste_titre maxwidthsearch">';
-	$searchpicto = $form->showFilterButtons();
-	print $searchpicto;
-	print '</td>';
+	if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print '<td class="liste_titre maxwidthsearch">';
+		$searchpicto = $form->showFilterButtons();
+		print $searchpicto;
+		print '</td>';
+	}
 	print '</tr>'."\n";
 
 	print '<tr class="liste_titre">';
+	if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print getTitleFieldOfList($selectedfields, 0, $_SERVER["PHP_SELF"], '', '', '', '', $sortfield, $sortorder, 'center maxwidthsearch ')."\n";
+	}
 	print_liste_field_titre("AccountAccounting", $_SERVER['PHP_SELF'], "t.numero_compte", "", $param, "", $sortfield, $sortorder);
+	// TODO : Retrieve the type of third party: Customer / Supplier / Employee
+	//if ($type == 'sub') {
+	//	print_liste_field_titre("Type", $_SERVER['PHP_SELF'], "t.type", "", $param, "", $sortfield, $sortorder);
+	//}
 	if (!empty($conf->global->ACCOUNTANCY_SHOW_OPENING_BALANCE)) {
 		print_liste_field_titre("OpeningBalance", $_SERVER['PHP_SELF'], "", $param, "", 'class="right"', $sortfield, $sortorder);
 	}
-	print_liste_field_titre("Debit", $_SERVER['PHP_SELF'], "t.debit", "", $param, 'class="right"', $sortfield, $sortorder);
-	print_liste_field_titre("Credit", $_SERVER['PHP_SELF'], "t.credit", "", $param, 'class="right"', $sortfield, $sortorder);
+	print_liste_field_titre("AccountingDebit", $_SERVER['PHP_SELF'], "t.debit", "", $param, 'class="right"', $sortfield, $sortorder);
+	print_liste_field_titre("AccountingCredit", $_SERVER['PHP_SELF'], "t.credit", "", $param, 'class="right"', $sortfield, $sortorder);
 	print_liste_field_titre("Balance", $_SERVER["PHP_SELF"], "", $param, "", 'class="right"', $sortfield, $sortorder);
 
 	// Hook fields
@@ -336,7 +443,9 @@ if ($action != 'export_csv') {
 	$reshook = $hookmanager->executeHooks('printFieldListTitle', $parameters, $object); // Note that $action and $object may have been modified by hook
 	print $hookmanager->resPrint;
 	// Action column
-	print getTitleFieldOfList($selectedfields, 0, $_SERVER["PHP_SELF"], '', '', '', '', $sortfield, $sortorder, 'center maxwidthsearch ')."\n";
+	if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print getTitleFieldOfList($selectedfields, 0, $_SERVER["PHP_SELF"], '', '', '', '', $sortfield, $sortorder, 'center maxwidthsearch ')."\n";
+	}
 	print '</tr>'."\n";
 
 	$total_debit = 0;
@@ -359,7 +468,7 @@ if ($action != 'export_csv') {
 		$sql .= " GROUP BY t.numero_compte";
 
 		$resql = $db->query($sql);
-		$nrows = $db->num_rows($resql);
+		$nrows = $resql->num_rows;
 		$opening_balances = array();
 		for ($i = 0; $i < $nrows; $i++) {
 			$arr = $resql->fetch_array();
@@ -372,11 +481,13 @@ if ($action != 'export_csv') {
 		$accountingaccountstatic->id = 0;
 		$accountingaccountstatic->account_number = '';
 
-		$accountingaccountstatic->fetch(null, $line->numero_compte, true);
-		if (!empty($accountingaccountstatic->account_number)) {
-			$accounting_account = $accountingaccountstatic->getNomUrl(0, 1, 0, '', 0, -1, 0, 'accountcard');
-		} else {
-			$accounting_account = length_accountg($line->numero_compte);
+		if ($type != 'sub') {
+			$accountingaccountstatic->fetch(null, $line->numero_compte, true);
+			if (!empty($accountingaccountstatic->account_number)) {
+				$accounting_account = $accountingaccountstatic->getNomUrl(0, 1, 1);
+			} else {
+				$accounting_account = length_accountg($line->numero_compte);
+			}
 		}
 
 		$link = '';
@@ -438,19 +549,51 @@ if ($action != 'export_csv') {
 		}
 
 		print '<tr class="oddeven">';
-		print '<td>'.$accounting_account.'</td>';
+
+		// Action column
+		if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+			print '<td class="center">';
+			print $link;
+			print '</td>';
+		}
+
+		// Accounting account
+		if ($type == 'sub') {
+			print '<td>'.$line->subledger_account.' <span class="opacitymedium">('.$line->subledger_label.')</span></td>';
+		} else {
+			print '<td>'.$accounting_account.'</td>';
+		}
+
+		// Type
+		// TODO Retrieve the type of third party: Customer / Supplier / Employee
+		//if ($type == 'sub') {
+		//	print '<td></td>';
+		//}
+
 		if (!empty($conf->global->ACCOUNTANCY_SHOW_OPENING_BALANCE)) {
 			print '<td class="right nowraponall amount">'.price(price2num($opening_balance, 'MT')).'</td>';
 		}
 
 		$urlzoom = '';
-		if ($line->numero_compte) {
-			$urlzoom = DOL_URL_ROOT.'/accountancy/bookkeeping/listbyaccount.php?search_accountancy_code_start='.urlencode($line->numero_compte).'&search_accountancy_code_end='.urlencode($line->numero_compte);
-			if (GETPOSTISSET('date_startmonth')) {
-				$urlzoom .= '&search_date_startmonth='.GETPOST('date_startmonth', 'int').'&search_date_startday='.GETPOST('date_startday', 'int').'&search_date_startyear='.GETPOST('date_startyear', 'int');
+		if ($type == 'sub') {
+			if ($line->subledger_account) {
+				$urlzoom = DOL_URL_ROOT . '/accountancy/bookkeeping/listbyaccount.php?type=sub&search_accountancy_code_start=' . urlencode($line->subledger_account) . '&search_accountancy_code_end=' . urlencode($line->subledger_account);
+				if (GETPOSTISSET('date_startmonth')) {
+					$urlzoom .= '&search_date_startmonth=' . GETPOST('date_startmonth', 'int') . '&search_date_startday=' . GETPOST('date_startday', 'int') . '&search_date_startyear=' . GETPOST('date_startyear', 'int');
+				}
+				if (GETPOSTISSET('date_endmonth')) {
+					$urlzoom .= '&search_date_endmonth=' . GETPOST('date_endmonth', 'int') . '&search_date_endday=' . GETPOST('date_endday', 'int') . '&search_date_endyear=' . GETPOST('date_endyear', 'int');
+				}
 			}
-			if (GETPOSTISSET('date_endmonth')) {
-				$urlzoom .= '&search_date_endmonth='.GETPOST('date_endmonth', 'int').'&search_date_endday='.GETPOST('date_endday', 'int').'&search_date_endyear='.GETPOST('date_endyear', 'int');
+		} else {
+			if ($line->numero_compte) {
+				$urlzoom = DOL_URL_ROOT . '/accountancy/bookkeeping/listbyaccount.php?search_accountancy_code_start=' . urlencode($line->numero_compte) . '&search_accountancy_code_end=' . urlencode($line->numero_compte);
+				if (GETPOSTISSET('date_startmonth')) {
+					$urlzoom .= '&search_date_startmonth=' . GETPOST('date_startmonth', 'int') . '&search_date_startday=' . GETPOST('date_startday', 'int') . '&search_date_startyear=' . GETPOST('date_startyear', 'int');
+				}
+				if (GETPOSTISSET('date_endmonth')) {
+					$urlzoom .= '&search_date_endmonth=' . GETPOST('date_endmonth', 'int') . '&search_date_endday=' . GETPOST('date_endday', 'int') . '&search_date_endyear=' . GETPOST('date_endyear', 'int');
+				}
 			}
 		}
 		// Debit
@@ -463,9 +606,14 @@ if ($action != 'export_csv') {
 		} else {
 			print '<td class="right nowraponall amount">'.price(price2num($line->debit - $line->credit, 'MT')).'</td>';
 		}
-		print '<td class="center">';
-		print $link;
-		print '</td>';
+
+		// Action column
+		if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+			print '<td class="center">';
+			print $link;
+			print '</td>';
+		}
+
 		print "</tr>\n";
 
 		// Records the sub-total
@@ -475,7 +623,12 @@ if ($action != 'export_csv') {
 	}
 
 	if (!empty($show_subgroup)) {
-		print '<tr class="liste_total"><td class="right">'.$langs->trans("SubTotal").':</td>';
+		print '<tr class="liste_total">';
+		// Action column
+		if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+			print "<td></td>\n";
+		}
+		print '<td class="right">'.$langs->trans("SubTotal").':</td>';
 		if (!empty($conf->global->ACCOUNTANCY_SHOW_OPENING_BALANCE)) {
 			print '<td class="right nowraponall amount">'.price(price2num($sous_total_opening_balance, 'MT')).'</td>';
 		}
@@ -486,11 +639,19 @@ if ($action != 'export_csv') {
 		} else {
 			print '<td class="right nowraponall amount">' . price(price2num($sous_total_debit - $sous_total_credit, 'MT')) . '</td>';
 		}
-		print "<td></td>\n";
+		// Action column
+		if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+			print "<td></td>\n";
+		}
 		print '</tr>';
 	}
 
-	print '<tr class="liste_total"><td class="right">'.$langs->trans("AccountBalance").':</td>';
+	print '<tr class="liste_total">';
+	// Action column
+	if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print "<td></td>\n";
+	}
+	print '<td class="right">'.$langs->trans("AccountBalance").':</td>';
 	if (!empty($conf->global->ACCOUNTANCY_SHOW_OPENING_BALANCE)) {
 		print '<td class="nowrap right">'.price(price2num($total_opening_balance, 'MT')).'</td>';
 	}
@@ -501,7 +662,10 @@ if ($action != 'export_csv') {
 	} else {
 		print '<td class="right nowraponall amount">' . price(price2num($total_debit - $total_credit, 'MT')) . '</td>';
 	}
-	print "<td></td>\n";
+	// Action column
+	if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
+		print "<td></td>\n";
+	}
 	print '</tr>';
 
 	$parameters = array('arrayfields'=>$arrayfields, 'sql'=>$sql);

+ 2 - 2
htdocs/accountancy/bookkeeping/card.php

@@ -497,7 +497,7 @@ if ($action == 'create') {
 
 		// Ref document
 		print '<tr><td>';
-		print '<table class="nobordernopadding" width="100%"><tr><td>';
+		print '<table class="nobordernopadding centpercent"><tr><td>';
 		print $langs->trans('Piece');
 		print '</td>';
 		if ($action != 'editdocref') {
@@ -612,7 +612,7 @@ if ($action == 'create') {
 
 		print dol_get_fiche_end();
 
-		print '<div style="clear:both"></div>';
+		print '<div class="clearboth"></div>';
 
 		print '<br>';
 

+ 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();

+ 79 - 288
htdocs/accountancy/bookkeeping/list.php

@@ -1,9 +1,11 @@
 <?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>
+ * 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
@@ -27,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';
@@ -94,15 +95,9 @@ $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");
+$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 = '';
@@ -155,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";
@@ -197,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),
 );
 
@@ -206,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')) {
@@ -253,6 +241,7 @@ if (empty($reshook)) {
 		$search_doc_type = '';
 		$search_doc_ref = '';
 		$search_doc_date = '';
+		$search_account_category = '';
 		$search_accountancy_code = '';
 		$search_accountancy_code_start = '';
 		$search_accountancy_code_end = '';
@@ -335,6 +324,20 @@ if (empty($reshook)) {
 		$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);
@@ -438,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';
 
@@ -646,6 +606,9 @@ $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();
@@ -676,105 +639,25 @@ if (count($filter) > 0) {
 		} elseif ($key == 't.reconciled_option') {
 			$sqlwhere[] = 't.lettering_code IS NULL';
 		} elseif ($key == 't.code_journal' && !empty($value)) {
-			$sqlwhere[] = natural_search("t.code_journal", join(',', $value), 3, 1);
+			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)) {
-	$sql .= " AND t.date_export IS NULL";
-}
+
 if (count($sqlwhere) > 0) {
 	$sql .= ' AND '.implode(' AND ', $sqlwhere);
 }
-if (!empty($sortfield)) {
-	$sql .= $db->order($sortfield, $sortorder);
-}
 //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 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);
-
-		$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);
-				}
-			}
-		}
-
-		$mimetype = $accountancyexport->getMimeType($formatexportset);
-
-		top_httphead($mimetype, 1);
-
-		// Output data on screen
-		$accountancyexport->export($object->lines, $formatexportset);
-
-		$notifiedexportdate = GETPOST('notifiedexportdate', 'alpha');
-		$notifiedvalidationdate = GETPOST('notifiedvalidationdate', 'alpha');
-
-		if (!empty($accountancyexport->errors)) {
-			dol_print_error('', '', $accountancyexport->errors);
-		} elseif (!empty($notifiedexportdate) || !empty($notifiedvalidationdate)) {
-			// Specify as export : update field date_export or date_validated
-			$error = 0;
-			$db->begin();
-
-			if (is_array($object->lines)) {
-				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++;
-				$db->rollback();
-				dol_print_error('', $langs->trans("NotAllExportedMovementsCouldBeRecordedAsExportedOrValidated"));
-			}
-		}
-		exit;
-	}
-}
-
-
 /*
  * View
  */
@@ -786,29 +669,39 @@ $title_page = $langs->trans("Operations").' - '.$langs->trans("Journals");
 
 // Count total nb of records
 $nbtotalofrecords = '';
-if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
-	$resql = $db->query($sql);
-	$nbtotalofrecords = $db->num_rows($resql);
-	if (($page * $limit) > $nbtotalofrecords) {	// if total of record found is smaller than page * limit, goto and load page 0
+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);
 }
-// if total of record found is smaller than limit, no need to do paging and to restart another select with limits set.
-if (is_numeric($nbtotalofrecords) && $limit > $nbtotalofrecords) {
-	$num = $nbtotalofrecords;
-} else {
-	$sql .= $db->plimit($limit + 1, $offset);
 
-	$resql = $db->query($sql);
-	if (!$resql) {
-		dol_print_error($db);
-		exit;
-	}
+// Complete request and execute it with limit
+$sql .= $db->order($sortfield, $sortorder);
+if ($limit) {
+	$sql .= $db->plimit($limit + 1, $offset);
+}
 
-	$num = $db->num_rows($resql);
+$resql = $db->query($sql);
+if (!$resql) {
+	dol_print_error($db);
+	exit;
 }
 
+$num = $db->num_rows($resql);
+
 $arrayofselected = is_array($toselect) ? $toselect : array();
 
 // Output page
@@ -818,99 +711,6 @@ llxHeader('', $title_page);
 
 $formconfirm = '';
 
-if ($action == 'export_file') {
-	$form_question = array();
-
-	$form_question['notexportlettering'] = array(
-		'name' => 'notexportlettering',
-		'type' => 'other',
-		'label' => '',		// TODO  Use Selectmodelcsv and show a select combo
-		'value' => $langs->trans('Modelcsv').' : <b>'.$listofformat[$formatexportset].'</b>'
-	);
-
-	$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');
-	}
-
-	$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?'.$param, $langs->trans("ExportFilteredList").'...', $langs->trans('ConfirmExportFile'), 'export_fileconfirm', $form_question, '', 1, 350, 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;
 
@@ -919,12 +719,12 @@ 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
 $arrayofmassactions = array();
-if (getDolGlobalInt('ACCOUNTING_ENABLE_LETTERING') && $user->rights->accounting->mouvements->creer) {
+if (getDolGlobalInt('ACCOUNTING_ENABLE_LETTERING') && $user->hasRight('accounting', 'mouvements', 'creer')) {
 	$arrayofmassactions['letteringauto'] = img_picto('', 'check', 'class="pictofixedwidth"') . $langs->trans('LetteringAuto');
 	$arrayofmassactions['preunletteringauto'] = img_picto('', 'uncheck', 'class="pictofixedwidth"') . $langs->trans('UnletteringAuto');
 	$arrayofmassactions['letteringmanual'] = img_picto('', 'check', 'class="pictofixedwidth"') . $langs->trans('LetteringManual');
@@ -964,21 +764,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'));
@@ -1013,15 +798,25 @@ if ($massactionbutton && $contextpage != 'poslist') {
 }
 
 $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, getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')); // Note that $action and $object may have been modified by hook
+$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" : "").'">';
 
@@ -1297,6 +1092,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
@@ -1512,11 +1310,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";
 
@@ -1544,13 +1342,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

+ 75 - 31
htdocs/accountancy/bookkeeping/listbyaccount.php

@@ -79,7 +79,8 @@ $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');
 
-$search_accountancy_code = GETPOST("search_accountancy_code");
+$search_account_category = GETPOST('search_account_category', 'int');
+
 $search_accountancy_code_start = GETPOST('search_accountancy_code_start', 'alpha');
 if ($search_accountancy_code_start == - 1) {
 	$search_accountancy_code_start = '';
@@ -226,7 +227,7 @@ if (empty($reshook)) {
 
 	if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All tests are required to be compatible with all browsers
 		$search_doc_date = '';
-		$search_accountancy_code = '';
+		$search_account_category = '';
 		$search_accountancy_code_start = '';
 		$search_accountancy_code_end = '';
 		$search_label_account = '';
@@ -282,6 +283,20 @@ if (empty($reshook)) {
 		$filter['t.doc_date'] = $search_doc_date;
 		$param .= '&doc_datemonth='.GETPOST('doc_datemonth', 'int').'&doc_dateday='.GETPOST('doc_dateday', 'int').'&doc_dateyear='.GETPOST('doc_dateyear', 'int');
 	}
+	if ($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_start)) {
 		if ($type == 'sub') {
 			$filter['t.subledger_account>='] = $search_accountancy_code_start;
@@ -360,14 +375,13 @@ if (empty($reshook)) {
 		$filter['t.import_key'] = $search_import_key;
 		$param .= '&search_import_key='.urlencode($search_import_key);
 	}
-
 	// param with type of list
 	$url_param = substr($param, 1); // remove first "&"
 	if (!empty($type)) {
 		$param = '&type='.$type.$param;
 	}
 
-	//if ($action == 'delbookkeepingyearconfirm' && $user->rights->accounting->mouvements->supprimer_tous) {
+	//if ($action == 'delbookkeepingyearconfirm' && $user->hasRight('accounting', 'mouvements', 'supprimer')_tous) {
 	//	$delmonth = GETPOST('delmonth', 'int');
 	//	$delyear = GETPOST('delyear', 'int');
 	//	if ($delyear == -1) {
@@ -399,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';
 
@@ -549,26 +563,30 @@ 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);
+		$nbtotalofrecords = $object->fetchAllByAccount($sortorder, $sortfield, 0, 0, $filter, 'AND', 1, 1);
 	} else {
-		$nbtotalofrecords = $object->fetchAllByAccount($sortorder, $sortfield, 0, 0, $filter);
+		$nbtotalofrecords = $object->fetchAllByAccount($sortorder, $sortfield, 0, 0, $filter, 'AND', 0, 1);
 	}
 
 	if ($nbtotalofrecords < 0) {
 		setEventMessages($object->error, $object->errors, 'errors');
+		$error++;
 	}
 }
 
-if ($type == 'sub') {
-	$result = $object->fetchAllByAccount($sortorder, $sortfield, $limit, $offset, $filter, 'AND', 1);
-} else {
-	$result = $object->fetchAllByAccount($sortorder, $sortfield, $limit, $offset, $filter);
-}
+if (!$error) {
+	if ($type == 'sub') {
+		$result = $object->fetchAllByAccount($sortorder, $sortfield, $limit, $offset, $filter, 'AND', 1);
+	} else {
+		$result = $object->fetchAllByAccount($sortorder, $sortfield, $limit, $offset, $filter, 'AND', 0);
+	}
 
-if ($result < 0) {
-	setEventMessages($object->error, $object->errors, 'errors');
+	if ($result < 0) {
+		setEventMessages($object->error, $object->errors, 'errors');
+	}
 }
 
 $arrayofselected = is_array($toselect) ? $toselect : array();
@@ -672,7 +690,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);
@@ -717,7 +735,7 @@ if ($type == 'sub') {
 
 $moreforfilter = '';
 
-// Accountancy account
+// Search on accountancy custom groups or account
 $moreforfilter .= '<div class="divsearchfield">';
 $moreforfilter .= $langs->trans('AccountAccounting').': ';
 $moreforfilter .= '<div class="nowrap inline-block">';
@@ -735,6 +753,13 @@ if ($type == 'sub') {
 $moreforfilter .= '</div>';
 $moreforfilter .= '</div>';
 
+$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); // Note that $action and $object may have been modified by hook
 if (empty($reshook)) {
@@ -928,8 +953,12 @@ while ($i < min($num, $limit)) {
 	if (!empty($arrayfields['t.doc_ref']['checked'])) { $colspan++; }
 	if (!empty($arrayfields['t.label_operation']['checked'])) { $colspan++; }
 	if (!empty($arrayfields['t.date_export']['checked'])) { $colspanend++; }
-	if (!empty($arrayfields['t.date_validating']['checked'])) { $colspanend++; }
+	if (!empty($arrayfields['t.date_validated']['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)) {
@@ -941,8 +970,8 @@ while ($i < min($num, $limit)) {
 			} else {
 				print '<td class="right" colspan="' . $colspan . '">' . $langs->trans("TotalForAccount") . ' ' . length_accountg($displayed_account_number) . ':</td>';
 			}
-			print '<td class="nowrap right">'.price($sous_total_debit).'</td>';
-			print '<td class="nowrap right">'.price($sous_total_credit).'</td>';
+			print '<td class="nowrap right">'.price(price2num($sous_total_debit, 'MT')).'</td>';
+			print '<td class="nowrap right">'.price(price2num($sous_total_credit, 'MT')).'</td>';
 			print '<td colspan="'.$colspanend.'"></td>';
 			print '</tr>';
 			// Show balance of last shown account
@@ -951,13 +980,13 @@ while ($i < min($num, $limit)) {
 			print '<td class="right" colspan="'.$colspan.'">'.$langs->trans("Balance").':</td>';
 			if ($balance > 0) {
 				print '<td class="nowraponall right">';
-				print price($sous_total_debit - $sous_total_credit);
+				print price(price2num($sous_total_debit - $sous_total_credit, 'MT'));
 				print '</td>';
 				print '<td></td>';
 			} else {
 				print '<td></td>';
 				print '<td class="nowraponall right">';
-				print price($sous_total_credit - $sous_total_debit);
+				print price(price2num($sous_total_credit - $sous_total_debit, 'MT'));
 				print '</td>';
 			}
 			print '<td colspan="'.$colspanend.'"></td>';
@@ -969,7 +998,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");
@@ -1012,6 +1043,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'])) {
@@ -1195,9 +1229,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
@@ -1212,8 +1246,8 @@ while ($i < min($num, $limit)) {
 if ($num > 0 && $colspan > 0) {
 	print '<tr class="liste_total">';
 	print '<td class="right" colspan="'.$colspan.'">'.$langs->trans("TotalForAccount").' '.$accountg.':</td>';
-	print '<td class="nowrap right">'.price($sous_total_debit).'</td>';
-	print '<td class="nowrap right">'.price($sous_total_credit).'</td>';
+	print '<td class="nowrap right">'.price(price2num($sous_total_debit, 'MT')).'</td>';
+	print '<td class="nowrap right">'.price(price2num($sous_total_credit, 'MT')).'</td>';
 	print '<td colspan="'.$colspanend.'"></td>';
 	print '</tr>';
 	// Show balance of last shown account
@@ -1222,19 +1256,29 @@ if ($num > 0 && $colspan > 0) {
 	print '<td class="right" colspan="'.$colspan.'">'.$langs->trans("Balance").':</td>';
 	if ($balance > 0) {
 		print '<td class="nowraponall right">';
-		print price($sous_total_debit - $sous_total_credit);
+		print price(price2num($sous_total_debit - $sous_total_credit, 'MT'));
 		print '</td>';
 		print '<td></td>';
 	} else {
 		print '<td></td>';
 		print '<td class="nowraponall right">';
-		print price($sous_total_credit - $sous_total_debit);
+		print price(price2num($sous_total_credit - $sous_total_debit, 'MT'));
 		print '</td>';
 	}
 	print '<td colspan="'.$colspanend.'"></td>';
 	print '</tr>';
 }
 
+
+// Clean total values to round them
+if (!empty($totalarray['val']['totaldebit'])) {
+	$totalarray['val']['totaldebit'] = price2num($totalarray['val']['totaldebit'], 'MT');
+}
+if (!empty($totalarray['val']['totalcredit'])) {
+	$totalarray['val']['totalcredit'] = price2num($totalarray['val']['totalcredit'], 'MT');
+}
+
+
 // Show total line
 include DOL_DOCUMENT_ROOT.'/core/tpl/list_print_total.tpl.php';
 
@@ -1257,7 +1301,7 @@ print "</table>";
 print '</div>';
 
 // TODO Replace this with mass delete action
-//if ($user->rights->accounting->mouvements->supprimer_tous) {
+//if ($user->hasRight('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>';

+ 74 - 61
htdocs/accountancy/class/accountancycategory.class.php

@@ -353,7 +353,7 @@ class AccountancyCategory // extends CommonObject
 		$sql .= " formula=".(isset($this->formula) ? "'".$this->db->escape($this->formula)."'" : "null").",";
 		$sql .= " position=".(isset($this->position) ? $this->position : "null").",";
 		$sql .= " fk_country=".(isset($this->fk_country) ? $this->fk_country : "null").",";
-		$sql .= " active=".(isset($this->active) ? $this->active : "null")."";
+		$sql .= " active=".(isset($this->active) ? $this->active : "null");
 		$sql .= " WHERE rowid=".((int) $this->id);
 
 		$this->db->begin();
@@ -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
@@ -605,60 +605,6 @@ class AccountancyCategory // extends CommonObject
 		}
 	}
 
-	/**
-	 * Function to know all custom groupd from an accounting account
-	 *
-	 * @return array|integer       Result in table (array), -1 if KO
-	 */
-	public function getCatsCpts()
-	{
-		global $mysoc, $conf;
-
-		if (empty($mysoc->country_id)) {
-			dol_print_error('', 'Call to select_accounting_account with mysoc country not yet defined');
-			exit();
-		}
-
-		$sql = "SELECT t.rowid, t.account_number, t.label as account_label, cat.code, cat.position, cat.label as name_cat, cat.sens ";
-		$sql .= " FROM ".MAIN_DB_PREFIX."accounting_account as t, ".MAIN_DB_PREFIX."c_accounting_category as cat";
-		$sql .= " WHERE t.fk_accounting_category IN ( SELECT c.rowid ";
-		$sql .= " FROM ".MAIN_DB_PREFIX."c_accounting_category as c";
-		$sql .= " WHERE c.active = 1";
-		$sql .= " AND c.entity = ".$conf->entity;
-		$sql .= " AND (c.fk_country = ".((int) $mysoc->country_id)." OR c.fk_country = 0)";
-		$sql .= " AND cat.rowid = t.fk_accounting_category";
-		$sql .= " AND t.entity = ".$conf->entity;
-		$sql .= " ORDER BY cat.position ASC";
-
-		$resql = $this->db->query($sql);
-		if ($resql) {
-			$i = 0;
-			$obj = '';
-			$num = $this->db->num_rows($resql);
-			$data = array();
-			if ($num) {
-				while ($obj = $this->db->fetch_object($resql)) {
-					$name_cat = $obj->name_cat;
-					$data[$name_cat][$i] = array(
-							'id' => $obj->rowid,
-							'code' => $obj->code,
-							'position' => $obj->position,
-							'account_number' => $obj->account_number,
-							'account_label' => $obj->account_label,
-							'sens' => $obj->sens
-					);
-					$i++;
-				}
-			}
-			return $data;
-		} else {
-			$this->error = "Error ".$this->db->lasterror();
-			dol_syslog(__METHOD__." ".$this->error, LOG_ERR);
-
-			return -1;
-		}
-	}
-
 	/**
 	 * Function to show result of an accounting account from the ledger with a direction and a period
 	 *
@@ -749,12 +695,75 @@ class AccountancyCategory // extends CommonObject
 		}
 	}
 
+	/**
+	 * Function to get an array of all active custom groups (llx_c_accunting_categories) with their accounts from the chart of account (ll_accounting_acount)
+	 *
+	 * @param	int				$catid		Custom group ID
+	 * @return 	array|integer   		    Result in table (array), -1 if KO
+	 * @see getCats(), getCptsCat()
+	 */
+	public function getCatsCpts($catid = 0)
+	{
+		global $mysoc, $conf;
+
+		if (empty($mysoc->country_id)) {
+			$this->error = "Error ".$this->db->lasterror();
+			dol_syslog(__METHOD__." ".$this->error, LOG_ERR);
+			return -1;
+		}
+
+		$sql = "SELECT t.rowid, t.account_number, t.label as account_label,";
+		$sql .= " cat.code, cat.position, cat.label as name_cat, cat.sens, cat.category_type, cat.formula";
+		$sql .= " FROM ".MAIN_DB_PREFIX."accounting_account as t, ".MAIN_DB_PREFIX."c_accounting_category as cat";
+		$sql .= " WHERE t.fk_accounting_category IN (SELECT c.rowid";
+		$sql .= " FROM ".MAIN_DB_PREFIX."c_accounting_category as c";
+		$sql .= " WHERE c.active = 1";
+		$sql .= " AND c.entity = ".$conf->entity;
+		$sql .= " AND (c.fk_country = ".((int) $mysoc->country_id)." OR c.fk_country = 0)";
+		$sql .= " AND cat.rowid = t.fk_accounting_category";
+		$sql .= " AND t.entity = ".$conf->entity;
+		if ($catid > 0) {
+			$sql .= " AND cat.rowid = ".((int) $catid);
+		}
+		$sql .= " ORDER BY cat.position ASC";
+
+		$resql = $this->db->query($sql);
+		if ($resql) {
+			$obj = '';
+			$num = $this->db->num_rows($resql);
+			$data = array();
+			if ($num) {
+				while ($obj = $this->db->fetch_object($resql)) {
+					$name_cat = $obj->name_cat;
+					$data[$name_cat][$obj->rowid] = array(
+						'id' => $obj->rowid,
+						'code' => $obj->code,
+						'label' => $obj->label,
+						'position' => $obj->position,
+						'category_type' => $obj->category_type,
+						'formula' => $obj->formula,
+						'sens' => $obj->sens,
+						'account_number' => $obj->account_number,
+						'account_label' => $obj->account_label
+					);
+				}
+			}
+			return $data;
+		} else {
+			$this->error = "Error ".$this->db->lasterror();
+			dol_syslog(__METHOD__." ".$this->error, LOG_ERR);
+			return -1;
+		}
+	}
+
 	/**
 	 * Return list of custom groups.
+	 * For list + detail of accounting account, see getCatsCpt()
 	 *
 	 * @param	int			$categorytype		-1=All, 0=Only non computed groups, 1=Only computed groups
 	 * @param	int			$active				1= active, 0=not active
 	 * @return	array|int						Array of groups or -1 if error
+	 * @see getCatsCpts(), getCptsCat()
 	 */
 	public function getCats($categorytype = -1, $active = 1)
 	{
@@ -789,9 +798,10 @@ class AccountancyCategory // extends CommonObject
 							'rowid' => $obj->rowid,
 							'code' => $obj->code,
 							'label' => $obj->label,
-							'formula' => $obj->formula,
 							'position' => $obj->position,
 							'category_type' => $obj->category_type,
+							'formula' => $obj->formula,
+							'sens' => $obj->sens,
 							'bc' => $obj->sens
 					);
 					$i++;
@@ -809,12 +819,15 @@ class AccountancyCategory // extends CommonObject
 
 
 	/**
-	 * Get all accounting account of a custom group (or a list of custom groups).
+	 * Get all accounting account of a given custom group (or a list of custom groups).
 	 * You must choose between first parameter (personalized group) or the second (free criteria filter)
 	 *
 	 * @param 	int 		$cat_id 				Id if personalized accounting group/category
-	 * @param 	string 		$predefinedgroupwhere 	Sql criteria filter to select accounting accounts. This value must not come from an input of a user.
+	 * @param 	string 		$predefinedgroupwhere 	Sql criteria filter to select accounting accounts. This value must be sanitized and not come from an input of a user.
+	 * 												Example: "pcg_type = 'EXPENSE' AND fk_pcg_version = 'xx'"
+	 * 												Example: "fk_accounting_category = 99"
 	 * @return 	array|int							Array of accounting accounts or -1 if error
+	 * @see getCats(), getCatsCpts()
 	 */
 	public function getCptsCat($cat_id, $predefinedgroupwhere = '')
 	{
@@ -826,7 +839,7 @@ class AccountancyCategory // extends CommonObject
 			exit();
 		}
 
-		$pcgverid = $conf->global->CHARTOFACCOUNTS;
+		$pcgverid = getDolGlobalInt('CHARTOFACCOUNTS');
 		$pcgvercode = dol_getIdFromCode($this->db, $pcgverid, 'accounting_system', 'rowid', 'pcg_version');
 		if (empty($pcgvercode)) {
 			$pcgvercode = $pcgverid;

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 543 - 251
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
 	 */

+ 22 - 15
htdocs/accountancy/class/accountingaccount.class.php

@@ -1,6 +1,6 @@
 <?php
 /* Copyright (C) 2013-2014  Olivier Geffroy      <jeff@jeffinfo.com>
- * Copyright (C) 2013-2021  Alexandre Spangaro   <aspangaro@open-dsi.fr>
+ * Copyright (C) 2013-2024  Alexandre Spangaro   <aspangaro@easya.solutions>
  * Copyright (C) 2013-2021  Florian Henry        <florian.henry@open-concept.pro>
  * Copyright (C) 2014       Juanjo Menent        <jmenent@2byte.es>
  * Copyright (C) 2015       Ari Elbaz (elarifr)  <github@accedinfo.com>
@@ -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)."'";
@@ -214,8 +214,10 @@ class AccountingAccount extends CommonObject
 					$this->id = $obj->rowid;
 					$this->rowid = $obj->rowid;
 					$this->ref = $obj->account_number;
-					$this->datec = $obj->datec;
-					$this->tms = $obj->tms;
+					$this->datec = $this->db->jdate($obj->datec);
+					$this->date_creation = $this->db->jdate($obj->datec);
+					$this->date_modification = $this->db->jdate($obj->tms);
+					//$this->tms = $this->datem;
 					$this->fk_pcg_version = $obj->fk_pcg_version;
 					$this->pcg_type = $obj->pcg_type;
 					$this->account_number = $obj->account_number;
@@ -399,6 +401,7 @@ class AccountingAccount extends CommonObject
 	{
 		global $langs;
 
+		// TODO Looks a stupid check
 		$sql = "(SELECT fk_code_ventilation FROM ".MAIN_DB_PREFIX."facturedet";
 		$sql .= " WHERE fk_code_ventilation=".((int) $this->id).")";
 		$sql .= "UNION";
@@ -587,7 +590,7 @@ class AccountingAccount extends CommonObject
 	 */
 	public function info($id)
 	{
-		$sql = 'SELECT a.rowid, a.datec, a.fk_user_author, a.fk_user_modif, a.tms';
+		$sql = 'SELECT a.rowid, a.datec, a.fk_user_author, a.fk_user_modif, a.tms as date_modification';
 		$sql .= ' FROM ' . MAIN_DB_PREFIX . 'accounting_account as a';
 		$sql .= ' WHERE a.rowid = ' . ((int) $id);
 
@@ -602,7 +605,7 @@ class AccountingAccount extends CommonObject
 				$this->user_creation_id = $obj->fk_user_author;
 				$this->user_modification_id = $obj->fk_user_modif;
 				$this->date_creation = $this->db->jdate($obj->datec);
-				$this->date_modification = $this->db->jdate($obj->tms);
+				$this->date_modification = $this->db->jdate($obj->date_modification);
 			}
 			$this->db->free($resql);
 		} else {
@@ -684,10 +687,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 +699,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)
 	{
@@ -862,10 +865,14 @@ class AccountingAccount extends CommonObject
 
 			// Level 3 (define $code_t): Search suggested account for this thirdparty (similar code exists in page index.php to make automatic binding)
 			if (!empty($conf->global->ACCOUNTANCY_USE_PRODUCT_ACCOUNT_ON_THIRDPARTY)) {
-				if (!empty($buyer->code_compta_product)) {
+				if ($type == 'customer' && !empty($buyer->code_compta_product)) {
 					$code_t = $buyer->code_compta_product;
 					$suggestedid = $accountingAccount['thirdparty'];
 					$suggestedaccountingaccountfor = 'thirdparty';
+				} elseif ($type == 'supplier' && !empty($seller->code_compta_product)) {
+					$code_t = $seller->code_compta_product;
+					$suggestedid = $accountingAccount['thirdparty'];
+					$suggestedaccountingaccountfor = 'thirdparty';
 				}
 			}
 

+ 6 - 42
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)
@@ -367,6 +367,7 @@ class AccountingJournal extends CommonObject
 				return $langs->trans('AccountingJournalType1');
 			}
 		}
+		return "";
 	}
 
 
@@ -388,12 +389,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'));
@@ -454,27 +449,12 @@ class AccountingJournal extends CommonObject
 		}
 
 		$sql = "";
-
-		// FIXME sql error with Mysql 5.7
-		/*if ($in_bookkeeping == 'already' || $in_bookkeeping == 'notyet') {
-			$sql .= "WITH in_accounting_bookkeeping(fk_docdet) AS (";
-			$sql .= " SELECT DISTINCT fk_docdet";
-			$sql .= " FROM " . MAIN_DB_PREFIX . "accounting_bookkeeping";
-			$sql .= " WHERE doc_type = 'asset'";
-			$sql .= ") ";
-		}*/
-
 		$sql .= "SELECT ad.fk_asset AS rowid, a.ref AS asset_ref, a.label AS asset_label, a.acquisition_value_ht AS asset_acquisition_value_ht";
 		$sql .= ", a.disposal_date AS asset_disposal_date, a.disposal_amount_ht AS asset_disposal_amount_ht, a.disposal_subject_to_vat AS asset_disposal_subject_to_vat";
 		$sql .= ", ad.rowid AS depreciation_id, ad.depreciation_mode, ad.ref AS depreciation_ref, ad.depreciation_date, ad.depreciation_ht, ad.accountancy_code_debit, ad.accountancy_code_credit";
 		$sql .= " FROM " . MAIN_DB_PREFIX . "asset_depreciation as ad";
 		$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "asset as a ON a.rowid = ad.fk_asset";
-		// FIXME sql error with Mysql 5.7
-		/*if ($in_bookkeeping == 'already' || $in_bookkeeping == 'notyet') {
-			$sql .= " LEFT JOIN in_accounting_bookkeeping as iab ON iab.fk_docdet = ad.rowid";
-		}*/
 		$sql .= " WHERE a.entity IN (" . getEntity('asset', 0) . ')'; // We don't share object for accountancy, we use source object sharing
-		// Compatibility with Mysql 5.7
 		if ($in_bookkeeping == 'already') {
 			$sql .= " AND EXISTS (SELECT iab.fk_docdet FROM " . MAIN_DB_PREFIX . "accounting_bookkeeping AS iab WHERE iab.fk_docdet = ad.rowid AND doc_type = 'asset')";
 		} elseif ($in_bookkeeping == 'notyet') {
@@ -488,11 +468,6 @@ class AccountingJournal extends CommonObject
 		if (!empty($conf->global->ACCOUNTING_DATE_START_BINDING)) {
 			$sql .= " AND ad.depreciation_date >= '" . $this->db->idate($conf->global->ACCOUNTING_DATE_START_BINDING) . "'";
 		}
-		// Already in bookkeeping or not
-		// FIXME sql error with Mysql 5.7
-		/*if ($in_bookkeeping == 'already' || $in_bookkeeping == 'notyet') {
-			$sql .= " AND iab.fk_docdet IS" . ($in_bookkeeping == 'already' ? " NOT" : "") . " NULL";
-		}*/
 		$sql .= " ORDER BY ad.depreciation_date";
 
 		dol_syslog(__METHOD__, LOG_DEBUG);
@@ -755,7 +730,7 @@ class AccountingJournal extends CommonObject
 				}
 			}
 
-			$journal_data[$pre_data_id] = $element;
+			$journal_data[(int) $pre_data_id] = $element;
 		}
 		unset($pre_data);
 
@@ -810,12 +785,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'));
@@ -960,11 +929,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();
+			}
+		}
+	}
+}

+ 130 - 95
htdocs/accountancy/class/bookkeeping.class.php

@@ -282,7 +282,7 @@ class BookKeeping extends CommonObject
 				$this->errors[] = $langs->trans('ErrorFieldAccountNotDefinedForBankLine', $this->fk_docdet, $this->doc_type);
 			} else {
 				//$this->errors[]=$langs->trans('ErrorFieldAccountNotDefinedForInvoiceLine', $this->doc_ref,  $this->label_compte);
-				$mesg = $this->doc_ref.', '.$langs->trans("AccountAccounting").': '.$this->numero_compte;
+				$mesg = $this->doc_ref.', '.$langs->trans("AccountAccounting").': '.($this->numero_compte != -1 ? $this->numero_compte : $langs->trans("Unknown"));
 				if ($this->subledger_account && $this->subledger_account != $this->numero_compte) {
 					$mesg .= ', '.$langs->trans("SubledgerAccount").': '.$this->subledger_account;
 				}
@@ -817,56 +817,61 @@ class BookKeeping extends CommonObject
 
 
 	/**
-	 * Load object in memory from the database
-	 *
-	 * @param string $sortorder Sort Order
-	 * @param string $sortfield Sort field
-	 * @param int $limit offset limit
-	 * @param int $offset offset limit
-	 * @param array $filter filter array
-	 * @param string $filtermode filter mode (AND or OR)
-	 * @param int $option option (0: general account or 1: subaccount)
+	 * Load object in memory from the database in ->lines. Or just make a simple count if $countonly=1.
 	 *
-	 * @return int <0 if KO, >=0 if OK
+	 * @param 	string 	$sortorder 		Sort Order
+	 * @param 	string 	$sortfield 		Sort field
+	 * @param 	int 	$limit 			offset limit
+	 * @param 	int 	$offset 		offset limit
+	 * @param 	array 	$filter 		filter array
+	 * @param 	string 	$filtermode 	filter mode (AND or OR)
+	 * @param 	int 	$option 		option (0: general account or 1: subaccount)
+	 * @param	int		$countonly		Do not fill the $object->lines, return only the count.
+	 * @return 	int 					<0 if KO, Number of lines if OK
 	 */
-	public function fetchAllByAccount($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND', $option = 0)
+	public function fetchAllByAccount($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND', $option = 0, $countonly = 0)
 	{
 		global $conf;
 
 		dol_syslog(__METHOD__, LOG_DEBUG);
 
 		$this->lines = array();
+		$num = 0;
 
 		$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.montant as amount,";
-		$sql .= " t.sens,";
-		$sql .= " t.multicurrency_amount,";
-		$sql .= " t.multicurrency_code,";
-		$sql .= " t.lettering_code,";
-		$sql .= " t.date_lettering,";
-		$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.date_export,";
-		$sql .= " t.date_validated as date_validation,";
-		$sql .= " t.import_key";
+		if ($countonly) {
+			$sql .= ' COUNT(t.rowid) as nb';
+		} else {
+			$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.montant as amount,";
+			$sql .= " t.sens,";
+			$sql .= " t.multicurrency_amount,";
+			$sql .= " t.multicurrency_code,";
+			$sql .= " t.lettering_code,";
+			$sql .= " t.date_lettering,";
+			$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.date_export,";
+			$sql .= " t.date_validated as date_validation,";
+			$sql .= " t.import_key";
+		}
 		// Manage filter
 		$sqlwhere = array();
 		if (count($filter) > 0) {
@@ -880,7 +885,7 @@ class BookKeeping extends CommonObject
 				} elseif ($key == 't.fk_doc' || $key == 't.fk_docdet' || $key == 't.piece_num') {
 					$sqlwhere[] = $key.'='.$value;
 				} elseif ($key == 't.subledger_account' || $key == 't.numero_compte') {
-					$sqlwhere[] = $key.' LIKE \''.$this->db->escape($value).'%\'';
+					$sqlwhere[] = $key.' LIKE \''.$this->db->escapeforlike($this->db->escape($value)).'%\'';
 				} elseif ($key == 't.date_creation>=' || $key == 't.date_creation<=') {
 					$sqlwhere[] = $key.'\''.$this->db->idate($value).'\'';
 				} elseif ($key == 't.date_export>=' || $key == 't.date_export<=') {
@@ -897,18 +902,19 @@ class BookKeeping extends CommonObject
 					} else {
 						$sqlwhere[] = natural_search("t.code_journal", $value, 3, 1);
 					}
+				} elseif ($key == 't.search_accounting_code_in' && !empty($value)) {
+					$sqlwhere[] = 't.numero_compte IN ('.$this->db->sanitize($value, 1).')';
 				} else {
 					$sqlwhere[] = natural_search($key, $value, 0, 1);
 				}
 			}
 		}
 		$sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
-		$sql .= ' WHERE 1 = 1';
-		$sql .= " AND entity = " . ((int) $conf->entity); // Do not use getEntity for accounting features
+		$sql .= ' WHERE entity = ' . ((int) $conf->entity); // Do not use getEntity for accounting features
 		if (count($sqlwhere) > 0) {
 			$sql .= " AND ".implode(" ".$filtermode." ", $sqlwhere);
 		}
-		// Affichage par compte comptable
+		// Filter by ledger account or subledger account
 		if (!empty($option)) {
 			$sql .= " AND t.subledger_account IS NOT NULL";
 			$sql .= " AND t.subledger_account <> ''";
@@ -919,54 +925,63 @@ class BookKeeping extends CommonObject
 			$sortorder = 'ASC'.($sortorder ? ','.$sortorder : '');
 		}
 
-		$sql .= $this->db->order($sortfield, $sortorder);
-		if (!empty($limit)) {
-			$sql .= $this->db->plimit($limit + 1, $offset);
+		if (!$countonly) {
+			$sql .= $this->db->order($sortfield, $sortorder);
+			if (!empty($limit)) {
+				$sql .= $this->db->plimit($limit + 1, $offset);
+			}
 		}
 
 		$resql = $this->db->query($sql);
 		if ($resql) {
-			$num = $this->db->num_rows($resql);
-
-			$i = 0;
-			while (($obj = $this->db->fetch_object($resql)) && (empty($limit) || $i < min($limit, $num))) {
-				$line = new BookKeepingLine();
-
-				$line->id = $obj->rowid;
-
-				$line->doc_date = $this->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->multicurrency_amount = $obj->multicurrency_amount;
-				$line->multicurrency_code = $obj->multicurrency_code;
-				$line->lettering_code = $obj->lettering_code;
-				$line->date_lettering = $obj->date_lettering;
-				$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 = $this->db->jdate($obj->date_creation);
-				$line->date_export = $this->db->jdate($obj->date_export);
-				$line->date_validation = $this->db->jdate($obj->date_validation);
-				$line->import_key = $obj->import_key;
-
-				$this->lines[] = $line;
-
-				$i++;
+			if ($countonly) {
+				$obj = $this->db->fetch_object($resql);
+				if ($obj) {
+					$num = $obj->nb;
+				}
+			} else {
+				$num = $this->db->num_rows($resql);
+
+				$i = 0;
+				while (($obj = $this->db->fetch_object($resql)) && (empty($limit) || $i < min($limit, $num))) {
+					$line = new BookKeepingLine();
+
+					$line->id = $obj->rowid;
+
+					$line->doc_date = $this->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->multicurrency_amount = $obj->multicurrency_amount;
+					$line->multicurrency_code = $obj->multicurrency_code;
+					$line->lettering_code = $obj->lettering_code;
+					$line->date_lettering = $obj->date_lettering;
+					$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 = $this->db->jdate($obj->date_creation);
+					$line->date_export = $this->db->jdate($obj->date_export);
+					$line->date_validation = $this->db->jdate($obj->date_validation);
+					$line->import_key = $obj->import_key;
+
+					$this->lines[] = $line;
+
+					$i++;
+				}
 			}
 			$this->db->free($resql);
 
@@ -1139,9 +1154,10 @@ class BookKeeping extends CommonObject
 	 * @param 	int 	$offset 		offset limit
 	 * @param 	array 	$filter 		filter array
 	 * @param 	string 	$filtermode 	filter mode (AND or OR)
+	 * @param 	int 	$option 		option (0: general account or 1: subaccount)
 	 * @return 	int 					<0 if KO, >0 if OK
 	 */
-	public function fetchAllBalance($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND')
+	public function fetchAllBalance($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND', $option = 0)
 	{
 		global $conf;
 
@@ -1151,6 +1167,10 @@ class BookKeeping extends CommonObject
 
 		$sql = 'SELECT';
 		$sql .= " t.numero_compte,";
+		if (!empty($option)) {
+			$sql .= " t.subledger_account,";
+			$sql .= " t.subledger_label,";
+		}
 		$sql .= " SUM(t.debit) as debit,";
 		$sql .= " SUM(t.credit) as credit";
 		$sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
@@ -1176,6 +1196,8 @@ class BookKeeping extends CommonObject
 					} else {
 						$sqlwhere[] = natural_search("t.code_journal", $value, 3, 1);
 					}
+				} elseif ($key == 't.reconciled_option') {
+					$sqlwhere[] = 't.lettering_code IS NULL';
 				} else {
 					$sqlwhere[] = $key." LIKE '%".$this->db->escape($value)."%'";
 				}
@@ -1186,7 +1208,17 @@ class BookKeeping extends CommonObject
 			$sql .= " AND ".implode(" ".$filtermode." ", $sqlwhere);
 		}
 
-		$sql .= ' GROUP BY t.numero_compte';
+		if (!empty($option)) {
+			$sql .= ' AND t.subledger_account IS NOT NULL';
+			$sql .= ' AND t.subledger_account != ""';
+			$sql .= ' GROUP BY t.numero_compte, t.subledger_account, t.subledger_label';
+			$sortfield = 't.subledger_account'.($sortfield ? ','.$sortfield : '');
+			$sortorder = 'ASC'.($sortfield ? ','.$sortfield : '');
+		} else {
+			$sql .= ' GROUP BY t.numero_compte';
+			$sortfield = 't.numero_compte'.($sortfield ? ','.$sortfield : '');
+			$sortorder = 'ASC'.($sortorder ? ','.$sortorder : '');
+		}
 
 		if (!empty($sortfield)) {
 			$sql .= $this->db->order($sortfield, $sortorder);
@@ -1204,6 +1236,9 @@ class BookKeeping extends CommonObject
 				$line = new BookKeepingLine();
 
 				$line->numero_compte = $obj->numero_compte;
+				$line->label_compte = $obj->label_compte;
+				$line->subledger_account = $obj->subledger_account;
+				$line->subledger_label = $obj->subledger_label;
 				$line->debit = $obj->debit;
 				$line->credit = $obj->credit;
 
@@ -1995,7 +2030,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";
@@ -2052,12 +2087,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";
@@ -2099,7 +2134,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";

+ 1 - 1
htdocs/accountancy/closure/index.php

@@ -167,7 +167,7 @@ $y = $year_current;
 
 $buttonvalidate = '<a class="butAction" name="button_validate_movements" href="'.$_SERVER["PHP_SELF"].'?action=validate_movements&year='.$year_start.'">'.$langs->trans("ValidateMovements").'</a>';
 
-print_barre_liste($langs->trans("OverviewOfMovementsNotValidated"), '', '', '', '', '', '', -1, '', '', 0, $buttonvalidate, '', 0, 1, 1);
+print_barre_liste($langs->trans("OverviewOfMovementsNotValidated"), '', '', '', '', '', '', -1, '', '', 0, $buttonvalidate, '', 0, 1, 0);
 
 print '<div class="div-table-responsive-no-min">';
 print '<table class="noborder centpercent">';

+ 45 - 38
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);
@@ -121,26 +121,12 @@ if (($action == 'clean' || $action == 'validatehistory') && $user->hasRight('acc
 if ($action == 'validatehistory') {
 	$error = 0;
 	$nbbinddone = 0;
+	$nbbindfailed = 0;
 	$notpossible = 0;
 
 	$db->begin();
 
 	// Now make the binding. Bind automatically only for product with a dedicated account that exists into chart of account, others need a manual bind
-	/*if ($db->type == 'pgsql') {
-		$sql1 = "UPDATE " . MAIN_DB_PREFIX . "facturedet";
-		$sql1 .= " SET fk_code_ventilation = accnt.rowid";
-		$sql1 .= " FROM " . MAIN_DB_PREFIX . "product as p, " . MAIN_DB_PREFIX . "accounting_account as accnt , " . MAIN_DB_PREFIX . "accounting_system as syst";
-		$sql1 .= " WHERE " . MAIN_DB_PREFIX . "facturedet.fk_product = p.rowid  AND accnt.fk_pcg_version = syst.pcg_version AND syst.rowid=" . ((int) $conf->global->CHARTOFACCOUNTS).' AND accnt.entity = '.((int) $conf->entity);
-		$sql1 .= " AND accnt.active = 1 AND p.accountancy_code_sell=accnt.account_number";
-		$sql1 .= " AND " . MAIN_DB_PREFIX . "facturedet.fk_code_ventilation = 0";
-	} else {
-		$sql1 = "UPDATE " . MAIN_DB_PREFIX . "facturedet as fd, " . MAIN_DB_PREFIX . "product as p, " . MAIN_DB_PREFIX . "accounting_account as accnt , " . MAIN_DB_PREFIX . "accounting_system as syst";
-		$sql1 .= " SET fk_code_ventilation = accnt.rowid";
-		$sql1 .= " WHERE fd.fk_product = p.rowid  AND accnt.fk_pcg_version = syst.pcg_version AND syst.rowid=" . ((int) $conf->global->CHARTOFACCOUNTS).' AND accnt.entity = '.((int) $conf->entity);
-		$sql1 .= " AND accnt.active = 1 AND p.accountancy_code_sell=accnt.account_number";
-		$sql1 .= " AND fd.fk_code_ventilation = 0";
-	}*/
-
 	// 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.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,";
@@ -158,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
@@ -281,12 +267,14 @@ if ($action == 'validatehistory') {
 				if (!$resqlupdate) {
 					$error++;
 					setEventMessages($db->lasterror(), null, 'errors');
+					$nbbindfailed++;
 					break;
 				} else {
 					$nbbinddone++;
 				}
 			} else {
 				$notpossible++;
+				$nbbindfailed++;
 			}
 
 			$i++;
@@ -300,7 +288,10 @@ if ($action == 'validatehistory') {
 		$db->rollback();
 	} else {
 		$db->commit();
-		setEventMessages($langs->trans('AutomaticBindingDone', 	$nbbinddone, $notpossible), null, 'mesgs');
+		setEventMessages($langs->trans('AutomaticBindingDone', 	$nbbinddone, $notpossible), null, ($notpossible ? 'warnings' : 'mesgs'));
+		if ($nbbindfailed) {
+			setEventMessages($langs->trans('DoManualBindingForFailedRecord', $nbbindfailed), null, 'warnings');
+		}
 	}
 }
 
@@ -328,9 +319,9 @@ if (getDolGlobalInt('INVOICE_USE_SITUATION') == 1) {
 
 $y = $year_current;
 
-$buttonbind = '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?action=validatehistory&token='.newToken().'">'.$langs->trans("ValidateHistory").'</a>';
+$buttonbind = '<a class="butAction smallpaddingimp" href="'.$_SERVER['PHP_SELF'].'?action=validatehistory&token='.newToken().'">'.img_picto($langs->trans("ValidateHistory"), 'link', 'class="pictofixedwidth fa-color-unset"').$langs->trans("ValidateHistory").'</a>';
 
-print_barre_liste(img_picto('', 'unlink', 'class="paddingright fa-color-unset"').$langs->trans("OverviewOfAmountOfLinesNotBound"), '', '', '', '', '', '', -1, '', '', 0, $buttonbind, '', 0, 1, 1);
+print_barre_liste(img_picto('', 'unlink', 'class="paddingright fa-color-unset"').$langs->trans("OverviewOfAmountOfLinesNotBound"), '', '', '', '', '', '', -1, '', '', 0, '', '', 0, 1, 1, 0, $buttonbind);
 //print load_fiche_titre($langs->trans("OverviewOfAmountOfLinesNotBound"), $buttonbind, '');
 
 print '<div class="div-table-responsive-no-min">';
@@ -414,7 +405,17 @@ if ($resql) {
 		print '</td>';
 		print '<td>';
 		if ($row[0] == 'tobind') {
-			print $langs->trans("UseMenuToSetBindindManualy", DOL_URL_ROOT.'/accountancy/customer/list.php?search_year='.((int) $y), $langs->transnoentitiesnoconv("ToBind"));
+			$startmonth = ($conf->global->SOCIETE_FISCAL_MONTH_START ? $conf->global->SOCIETE_FISCAL_MONTH_START : 1);
+			if ($startmonth > 12) {
+				$startmonth -= 12;
+			}
+			$startyear = ($startmonth < ($conf->global->SOCIETE_FISCAL_MONTH_START ? $conf->global->SOCIETE_FISCAL_MONTH_START : 1)) ? $y + 1 : $y;
+			$endmonth = ($conf->global->SOCIETE_FISCAL_MONTH_START ? $conf->global->SOCIETE_FISCAL_MONTH_START : 1) + 11;
+			if ($endmonth > 12) {
+				$endmonth -= 12;
+			}
+			$endyear = ($endmonth < ($conf->global->SOCIETE_FISCAL_MONTH_START ? $conf->global->SOCIETE_FISCAL_MONTH_START : 1)) ? $y + 1 : $y;
+			print $langs->trans("UseMenuToSetBindindManualy", DOL_URL_ROOT.'/accountancy/customer/list.php?search_date_startday=1&search_date_startmonth='.((int) $startmonth).'&search_date_startyear='.((int) $startyear).'&search_date_endday=&search_date_endmonth='.((int) $endmonth).'&search_date_endyear='.((int) $endyear), $langs->transnoentitiesnoconv("ToBind"));
 		} else {
 			print $row[1];
 		}
@@ -429,6 +430,12 @@ if ($resql) {
 
 			print '<td class="right nowraponall amount">';
 			print price($row[$i]);
+			// Add link to make binding
+			if (!empty(price2num($row[$i]))) {
+				print '<a href="'.$_SERVER['PHP_SELF'].'?action=validatehistory&year='.$y.'&validatemonth='.((int) $cursormonth).'&validateyear='.((int) $cursoryear).'&token='.newToken().'">';
+				print img_picto($langs->trans("ValidateHistory").' ('.$langs->trans('Month'.str_pad($cursormonth, 2, '0', STR_PAD_LEFT)).' '.$cursoryear.')', 'link', 'class="marginleft2"');
+				print '</a>';
+			}
 			print '</td>';
 		}
 		print '<td class="right nowraponall amount"><b>'.price($row[14]).'</b></td>';
@@ -572,7 +579,7 @@ print "</table>\n";
 print '</div>';
 
 
-if ($conf->global->MAIN_FEATURES_LEVEL > 0) { // This part of code looks strange. Why showing a report that should rely on result of this step ?
+if (getDolGlobalString('SHOW_TOTAL_OF_PREVIOUS_LISTS_IN_LIN_PAGE')) { // This part of code looks strange. Why showing a report that should rely on result of this step ?
 	print '<br>';
 	print '<br>';
 

+ 12 - 4
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
@@ -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);

+ 12 - 5
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')) {
@@ -164,7 +166,7 @@ if (empty($reshook)) {
 }
 
 
-if ($massaction == 'ventil' && $user->rights->accounting->bind->write) {
+if ($massaction == 'ventil' && $user->hasRight('accounting', 'bind', 'write')) {
 	$msg = '';
 
 	//print '<div><span style="color:red">' . $langs->trans("Processing") . '...</span></div>';
@@ -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);
+}
 
 
 /*
@@ -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);
@@ -439,7 +446,7 @@ if ($result) {
 		//'presend'=>img_picto('', 'email', 'class="pictofixedwidth"').$langs->trans("SendByMail"),
 		//'builddoc'=>img_picto('', 'pdf', 'class="pictofixedwidth"').$langs->trans("PDFMerge"),
 	);
-	//if ($user->rights->mymodule->supprimer) $arrayofmassactions['predelete'] = img_picto('', 'delete', 'class="pictofixedwidth"').$langs->trans("Delete");
+	//if ($user->hasRight('mymodule', 'supprimer')) $arrayofmassactions['predelete'] = img_picto('', 'delete', 'class="pictofixedwidth"').$langs->trans("Delete");
 	//if (in_array($massaction, array('presend','predelete'))) $arrayofmassactions=array();
 	if ($massaction !== 'set_default_account') {
 		$massactionbutton = $form->selectMassAction('ventil', $arrayofmassactions, 1);

+ 2 - 2
htdocs/accountancy/expensereport/card.php

@@ -48,7 +48,7 @@ if (!isModEnabled('accounting')) {
 if ($user->socid > 0) {
 	accessforbidden();
 }
-if (empty($user->rights->accounting->mouvements->lire)) {
+if (!$user->hasRight('accounting', 'mouvements', 'lire')) {
 	accessforbidden();
 }
 
@@ -57,7 +57,7 @@ if (empty($user->rights->accounting->mouvements->lire)) {
  * Actions
  */
 
-if ($action == 'ventil' && $user->rights->accounting->bind->write) {
+if ($action == 'ventil' && $user->hasRight('accounting', 'bind', 'write')) {
 	if (!$cancel) {
 		if ($codeventil < 0) {
 			$codeventil = 0;

+ 42 - 7
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);
@@ -103,6 +103,7 @@ if (($action == 'clean' || $action == 'validatehistory') && $user->hasRight('acc
 if ($action == 'validatehistory') {
 	$error = 0;
 	$nbbinddone = 0;
+	$nbbindfailed = 0;
 	$notpossible = 0;
 
 	$db->begin();
@@ -144,12 +145,14 @@ if ($action == 'validatehistory') {
 				if (!$resqlupdate) {
 					$error++;
 					setEventMessages($db->lasterror(), null, 'errors');
+					$nbbindfailed++;
 					break;
 				} else {
 					$nbbinddone++;
 				}
 			} else {
 				$notpossible++;
+				$nbbindfailed++;
 			}
 
 			$i++;
@@ -163,7 +166,10 @@ if ($action == 'validatehistory') {
 		$db->rollback();
 	} else {
 		$db->commit();
-		setEventMessages($langs->trans('AutomaticBindingDone', $nbbinddone, $notpossible), null, 'mesgs');
+		setEventMessages($langs->trans('AutomaticBindingDone', $nbbinddone, $notpossible), null,  ($notpossible ? 'warnings' : 'mesgs'));
+		if ($nbbindfailed) {
+			setEventMessages($langs->trans('DoManualBindingForFailedRecord', $nbbindfailed), null, 'warnings');
+		}
 	}
 }
 
@@ -187,10 +193,10 @@ print '</span><br>';
 
 $y = $year_current;
 
-$buttonbind = '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?action=validatehistory&token='.newToken().'&year='.$year_current.'">'.$langs->trans("ValidateHistory").'</a>';
+$buttonbind = '<a class="butAction smallpaddingimp" href="'.$_SERVER['PHP_SELF'].'?action=validatehistory&token='.newToken().'&year='.$year_current.'">'.img_picto('', 'link', 'class="paddingright fa-color-unset"').$langs->trans("ValidateHistory").'</a>';
 
 
-print_barre_liste(img_picto('', 'unlink', 'class="paddingright fa-color-unset"').$langs->trans("OverviewOfAmountOfLinesNotBound"), '', '', '', '', '', '', -1, '', '', 0, $buttonbind, '', 0, 1, 1);
+print_barre_liste(img_picto('', 'unlink', 'class="paddingright fa-color-unset"').$langs->trans("OverviewOfAmountOfLinesNotBound"), '', '', '', '', '', '', -1, '', '', 0, '', '', 0, 1, 1, 0, $buttonbind);
 //print load_fiche_titre($langs->trans("OverviewOfAmountOfLinesNotBound"), $buttonbind, '');
 
 print '<div class="div-table-responsive-no-min">';
@@ -265,14 +271,37 @@ if ($resql) {
 		print '</td>';
 		print '<td>';
 		if ($row[0] == 'tobind') {
-			print $langs->trans("UseMenuToSetBindindManualy", DOL_URL_ROOT.'/accountancy/expensereport/list.php?search_year='.((int) $y), $langs->transnoentitiesnoconv("ToBind"));
+			$startmonth = ($conf->global->SOCIETE_FISCAL_MONTH_START ? $conf->global->SOCIETE_FISCAL_MONTH_START : 1);
+			if ($startmonth > 12) {
+				$startmonth -= 12;
+			}
+			$startyear = ($startmonth < ($conf->global->SOCIETE_FISCAL_MONTH_START ? $conf->global->SOCIETE_FISCAL_MONTH_START : 1)) ? $y + 1 : $y;
+			$endmonth = ($conf->global->SOCIETE_FISCAL_MONTH_START ? $conf->global->SOCIETE_FISCAL_MONTH_START : 1) + 11;
+			if ($endmonth > 12) {
+				$endmonth -= 12;
+			}
+			$endyear = ($endmonth < ($conf->global->SOCIETE_FISCAL_MONTH_START ? $conf->global->SOCIETE_FISCAL_MONTH_START : 1)) ? $y + 1 : $y;
+			print $langs->trans("UseMenuToSetBindindManualy", DOL_URL_ROOT.'/accountancy/expensereport/list.php?search_date_startday=1&search_date_startmonth='.((int) $startmonth).'&search_date_startyear='.((int) $startyear).'&search_date_endday=&search_date_endmonth='.((int) $endmonth).'&search_date_endyear='.((int) $endyear), $langs->transnoentitiesnoconv("ToBind"));
 		} else {
 			print $row[1];
 		}
 		print '</td>';
 		for ($i = 2; $i <= 13; $i++) {
+			$cursormonth = (($conf->global->SOCIETE_FISCAL_MONTH_START ? $conf->global->SOCIETE_FISCAL_MONTH_START : 1) + $i - 2);
+			if ($cursormonth > 12) {
+				$cursormonth -= 12;
+			}
+			$cursoryear = ($cursormonth < ($conf->global->SOCIETE_FISCAL_MONTH_START ? $conf->global->SOCIETE_FISCAL_MONTH_START : 1)) ? $y + 1 : $y;
+			$tmp = dol_getdate(dol_get_last_day($cursoryear, $cursormonth, 'gmt'), false, 'gmt');
+
 			print '<td class="right nowraponall amount">';
 			print price($row[$i]);
+			// Add link to make binding
+			if (!empty(price2num($row[$i]))) {
+				print '<a href="'.$_SERVER['PHP_SELF'].'?action=validatehistory&year='.$y.'&validatemonth='.((int) $cursormonth).'&validateyear='.((int) $cursoryear).'&token='.newToken().'">';
+				print img_picto($langs->trans("ValidateHistory").' ('.$langs->trans('Month'.str_pad($cursormonth, 2, '0', STR_PAD_LEFT)).' '.$cursoryear.')', 'link', 'class="marginleft2"');
+				print '</a>';
+			}
 			print '</td>';
 		}
 		print '<td class="right nowraponall amount"><b>'.price($row[14]).'</b></td>';
@@ -367,6 +396,12 @@ if ($resql) {
 		print '</tr>';
 	}
 	$db->free($resql);
+
+	if ($num == 0) {
+		print '<tr class="oddeven"><td colspan="16">';
+		print '<span class="opacitymedium">'.$langs->trans("NoRecordFound").'</span>';
+		print '</td></tr>';
+	}
 } else {
 	print $db->lasterror(); // Show last sql error
 }
@@ -375,7 +410,7 @@ print '</div>';
 
 
 
-if ($conf->global->MAIN_FEATURES_LEVEL > 0) { // This part of code looks strange. Why showing a report where results depends on next step (so not yet available) ?
+if (getDolGlobalString('SHOW_TOTAL_OF_PREVIOUS_LISTS_IN_LIN_PAGE')) { // This part of code looks strange. Why showing a report that should rely on result of this step ?
 	print '<br>';
 	print '<br>';
 

+ 12 - 4
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";
 	}
 }
 
@@ -85,7 +87,7 @@ if (!isModEnabled('accounting')) {
 if ($user->socid > 0) {
 	accessforbidden();
 }
-if (empty($user->rights->accounting->mouvements->lire)) {
+if (!$user->hasRight('accounting', 'mouvements', 'lire')) {
 	accessforbidden();
 }
 
@@ -116,7 +118,7 @@ if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x'
 	$search_date_end = '';
 }
 
-if (is_array($changeaccount) && count($changeaccount) > 0 && $user->rights->accounting->bind->write) {
+if (is_array($changeaccount) && count($changeaccount) > 0 && $user->hasRight('accounting', 'bind', 'write')) {
 	$error = 0;
 
 	if (!(GETPOST('account_parent', 'int') >= 0)) {
@@ -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);

+ 15 - 9
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')) {
@@ -102,7 +104,7 @@ if (!isModEnabled('accounting')) {
 if ($user->socid > 0) {
 	accessforbidden();
 }
-if (empty($user->rights->accounting->mouvements->lire)) {
+if (!$user->hasRight('accounting', 'mouvements', 'lire')) {
 	accessforbidden();
 }
 
@@ -156,10 +158,9 @@ if (empty($reshook)) {
 }
 
 
-if ($massaction == 'ventil' && $user->rights->accounting->bind->write) {
+if ($massaction == 'ventil' && $user->hasRight('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);

+ 5 - 1
htdocs/accountancy/index.php

@@ -119,6 +119,7 @@ if (isModEnabled('accounting')) {
 	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";
@@ -261,11 +262,14 @@ if (isModEnabled('accounting')) {
 	print $boxlist;
 
 	print '</div>';
-} else {
+} elseif (isModEnabled('compta')) {
 	print load_fiche_titre($langs->trans("AccountancyArea"), '', 'accountancy');
 
 	print '<span class="opacitymedium">'.$langs->trans("Module10Desc")."</span>\n";
 	print "<br>";
+} else {
+	// This case can happen mode no accounting module is on but module "intracommreport" is on
+	print load_fiche_titre($langs->trans("AccountancyArea"), '', 'accountancy');
 }
 
 // End of page

+ 36 - 25
htdocs/accountancy/journal/bankjournal.php

@@ -8,7 +8,7 @@
  * Copyright (C) 2013-2023  Alexandre Spangaro      <aspangaro@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>
@@ -90,7 +90,7 @@ if (!isModEnabled('accounting')) {
 if ($user->socid > 0) {
 	accessforbidden();
 }
-if (empty($user->rights->accounting->mouvements->lire)) {
+if (!$user->hasRight('accounting', 'mouvements', 'lire')) {
 	accessforbidden();
 }
 
@@ -148,8 +148,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') {
@@ -196,13 +196,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();
@@ -861,7 +861,7 @@ if (!$error && $action == 'writebookkeeping') {
 		if (price2num($totaldebit, 'MT') != price2num($totalcredit, 'MT')) {
 			$error++;
 			$errorforline++;
-			setEventMessages('Try to insert a non balanced transaction in book for '.$ref.'. Canceled. Surely a bug.', null, 'errors');
+			setEventMessages('We tried to insert a non balanced transaction in book for '.$ref.'. Canceled. Surely a bug.', null, 'errors');
 		}
 
 		if (!$errorforline) {
@@ -907,7 +907,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';
@@ -978,13 +978,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;
 					}
@@ -1009,9 +1009,9 @@ 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 "".$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;
 					print '"'.($mt >= 0 ? price($mt) : '').'"'.$sep;
@@ -1083,9 +1083,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);
@@ -1100,8 +1100,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') {
@@ -1206,6 +1206,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++;
 			}
 		}
 
@@ -1331,6 +1333,8 @@ if (empty($action) || $action == 'view') {
 					print '<td class="right nowraponall amount">'.($mt >= 0 ? price($mt) : '')."</td>";
 
 					print "</tr>";
+
+					$i++;
 				}
 			}
 		} else {	// Waiting account
@@ -1368,11 +1372,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>';
 

+ 5 - 3
htdocs/accountancy/journal/expensereportsjournal.php

@@ -64,7 +64,7 @@ if (!isModEnabled('accounting')) {
 if ($user->socid > 0) {
 	accessforbidden();
 }
-if (empty($user->rights->accounting->mouvements->lire)) {
+if (!$user->hasRight('accounting', 'mouvements', 'lire')) {
 	accessforbidden();
 }
 
@@ -414,7 +414,7 @@ if ($action == 'writebookkeeping' && !$error) {
 			$error++;
 			$errorforline++;
 			$errorforinvoice[$key] = 'amountsnotbalanced';
-			setEventMessages('Try to insert a non balanced transaction in book for '.$val["ref"].'. Canceled. Surely a bug.', null, 'errors');
+			setEventMessages('We tried to insert a non balanced transaction in book for '.$val["ref"].'. Canceled. Surely a bug.', null, 'errors');
 		}
 
 		if (!$errorforline) {
@@ -503,6 +503,7 @@ if ($action == 'exportcsv' && !$error) {		// ISO and not UTF8 !
 				print "\n";
 			}
 		}
+
 		// VAT
 		foreach ($tabtva[$key] as $k => $mt) {
 			if ($mt) {
@@ -752,7 +753,8 @@ if (empty($action) || $action == 'view') {
 	}
 
 	if (!$i) {
-		print '<tr class="oddeven"><td colspan="7"><span class="opacitymedium">'.$langs->trans("NoRecordFound").'</span></td></tr>';
+		$colspan = 7;
+		print '<tr class="oddeven"><td colspan="'.$colspan.'"><span class="opacitymedium">'.$langs->trans("NoRecordFound").'</span></td></tr>';
 	}
 
 	print "</table>";

+ 155 - 28
htdocs/accountancy/journal/purchasesjournal.php

@@ -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';
@@ -67,7 +68,7 @@ if (!isModEnabled('accounting')) {
 if ($user->socid > 0) {
 	accessforbidden();
 }
-if (empty($user->rights->accounting->mouvements->lire)) {
+if (!$user->hasRight('accounting', 'mouvements', 'lire')) {
 	accessforbidden();
 }
 
@@ -103,17 +104,19 @@ 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 = "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 .= " s.rowid as socid, s.nom as name, s.fournisseur, s.code_client, s.code_fournisseur,";
-if (!empty($conf->global->MAIN_COMPANY_PERENTITY_SHARED)) {
+$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 (getDolGlobalString('MAIN_COMPANY_PERENTITY_SHARED')) {
 	$sql .= " spe.accountancy_code_customer as code_compta,";
 	$sql .= " spe.accountancy_code_supplier as code_compta_fournisseur,";
 } else {
 	$sql .= " s.code_compta as code_compta,";
 	$sql .= " s.code_compta_fournisseur,";
 }
-if (!empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
+if (getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
 	$sql .= " ppe.accountancy_code_buy,";
 } else {
 	$sql .= " p.accountancy_code_buy,";
@@ -121,19 +124,20 @@ if (!empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
 $sql .= " aa.rowid as fk_compte, aa.account_number as compte, aa.label as label_compte";
 $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn_det as fd";
 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = fd.fk_product";
-if (!empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
+if (getDolGlobalString('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 ".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";
-if (!empty($conf->global->MAIN_COMPANY_PERENTITY_SHARED)) {
+$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_country as co ON co.rowid = s.fk_pays ";
+if (getDolGlobalString('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 .= " WHERE f.fk_statut > 0";
 $sql .= " AND fd.fk_code_ventilation > 0";
 $sql .= " AND f.entity IN (".getEntity('facture_fourn', 0).")"; // We don't share object for accountancy
-if (!empty($conf->global->FACTURE_SUPPLIER_DEPOSITS_ARE_JUST_PAYMENTS)) {
+if (getDolGlobalString('FACTURE_SUPPLIER_DEPOSITS_ARE_JUST_PAYMENTS')) {
 	$sql .= " AND f.type IN (".FactureFournisseur::TYPE_STANDARD.",".FactureFournisseur::TYPE_REPLACEMENT.",".FactureFournisseur::TYPE_CREDIT_NOTE.",".FactureFournisseur::TYPE_SITUATION.")";
 } else {
 	$sql .= " AND f.type IN (".FactureFournisseur::TYPE_STANDARD.",".FactureFournisseur::TYPE_REPLACEMENT.",".FactureFournisseur::TYPE_CREDIT_NOTE.",".FactureFournisseur::TYPE_DEPOSIT.",".FactureFournisseur::TYPE_SITUATION.")";
@@ -142,8 +146,8 @@ if ($date_start && $date_end) {
 	$sql .= " AND f.datef >= '".$db->idate($date_start)."' AND f.datef <= '".$db->idate($date_end)."'";
 }
 // Define begin binding date
-if (!empty($conf->global->ACCOUNTING_DATE_START_BINDING)) {
-	$sql .= " AND f.datef >= '".$db->idate($conf->global->ACCOUNTING_DATE_START_BINDING)."'";
+if (getDolGlobalString('ACCOUNTING_DATE_START_BINDING')) {
+	$sql .= " AND f.datef >= '".$db->idate(getDolGlobalString('ACCOUNTING_DATE_START_BINDING'))."'";
 }
 // Already in bookkeeping or not
 if ($in_bookkeeping == 'already') {
@@ -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';
+	$cptfour = getDolGlobalString('ACCOUNTING_ACCOUNT_SUPPLIER', 'NotDefined');
+	$cpttva = getDolGlobalString('ACCOUNTING_VAT_BUY_ACCOUNT', 'NotDefined');
+	$rcctva = getDolGlobalString('ACCOUNTING_VAT_BUY_REVERSE_CHARGES_CREDIT', 'NotDefined');
+	$rcdtva = getDolGlobalString('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) {
@@ -183,9 +193,9 @@ if ($result) {
 		$compta_prod = $obj->compte;
 		if (empty($compta_prod)) {
 			if ($obj->product_type == 0) {
-				$compta_prod = (!empty($conf->global->ACCOUNTING_PRODUCT_BUY_ACCOUNT)) ? $conf->global->ACCOUNTING_PRODUCT_BUY_ACCOUNT : 'NotDefined';
+				$compta_prod = getDolGlobalString('ACCOUNTING_PRODUCT_BUY_ACCOUNT', 'NotDefined');
 			} else {
-				$compta_prod = (!empty($conf->global->ACCOUNTING_SERVICE_BUY_ACCOUNT)) ? $conf->global->ACCOUNTING_SERVICE_BUY_ACCOUNT : 'NotDefined';
+				$compta_prod = getDolGlobalString('ACCOUNTING_SERVICE_BUY_ACCOUNT', 'NotDefined');
 			}
 		}
 
@@ -193,7 +203,7 @@ if ($result) {
 		$compta_tva = (!empty($vatdata['accountancy_code_buy']) ? $vatdata['accountancy_code_buy'] : $cpttva);
 		$compta_localtax1 = (!empty($vatdata['accountancy_code_buy']) ? $vatdata['accountancy_code_buy'] : $cpttva);
 		$compta_localtax2 = (!empty($vatdata['accountancy_code_buy']) ? $vatdata['accountancy_code_buy'] : $cpttva);
-		$compta_counterpart_tva_npr = (!empty($conf->global->ACCOUNTING_COUNTERPART_VAT_NPR)) ? $conf->global->ACCOUNTING_COUNTERPART_VAT_NPR : 'NotDefined';
+		$compta_counterpart_tva_npr = getDolGlobalString('ACCOUNTING_COUNTERPART_VAT_NPR', 'NotDefined');
 
 		// Define array to display all VAT rates that use this accounting account $compta_tva
 		if (price2num($obj->tva_tx) || !empty($obj->vat_src_code)) {
@@ -230,6 +240,53 @@ if ($result) {
 			$tablocaltax2[$obj->rowid][$compta_localtax2] = 0;
 		}
 
+		// VAT Reverse charge
+		if (($mysoc->country_code == 'FR' || getDolGlobalString('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;
@@ -283,7 +340,7 @@ if ($action == 'writebookkeeping') {
 	$invoicestatic = new FactureFournisseur($db);
 	$accountingaccountsupplier = new AccountingAccount($db);
 
-	$accountingaccountsupplier->fetch(null, $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER, true);
+	$accountingaccountsupplier->fetch(null, getDolGlobalString('ACCOUNTING_ACCOUNT_SUPPLIER'), true);
 
 	foreach ($tabfac as $key => $val) {		// Loop on each invoice
 		$errorforline = 0;
@@ -347,7 +404,7 @@ if ($action == 'writebookkeeping') {
 				$bookkeeping->subledger_account = $tabcompany[$key]['code_compta_fournisseur'];
 				$bookkeeping->subledger_label = $tabcompany[$key]['name'];
 
-				$bookkeeping->numero_compte = $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER;
+				$bookkeeping->numero_compte = getDolGlobalString('ACCOUNTING_ACCOUNT_SUPPLIER');
 				$bookkeeping->label_compte = $accountingaccountsupplier->label;
 
 				$bookkeeping->label_operation = dol_trunc($companystatic->name, 16).' - '.$invoicestatic->ref_supplier.' - '.$langs->trans("SubledgerAccount");
@@ -405,7 +462,7 @@ if ($action == 'writebookkeeping') {
 					$bookkeeping->fk_docdet = 0; // Useless, can be several lines that are source of this record to add
 					$bookkeeping->thirdparty_code = $companystatic->code_fournisseur;
 
-					if (!empty($conf->global->ACCOUNTING_ACCOUNT_SUPPLIER_USE_AUXILIARY_ON_DEPOSIT)) {
+					if (getDolGlobalString('ACCOUNTING_ACCOUNT_SUPPLIER_USE_AUXILIARY_ON_DEPOSIT')) {
 						if ($k == getDolGlobalString('ACCOUNTING_ACCOUNT_SUPPLIER_DEPOSIT')) {
 							$bookkeeping->subledger_account = $tabcompany[$key]['code_compta'];
 							$bookkeeping->subledger_label = $tabcompany[$key]['name'];
@@ -465,6 +522,29 @@ if ($action == 'writebookkeeping') {
 					$arrayofvat = $tablocaltax2;
 				}
 
+				// VAT Reverse charge
+				if ($mysoc->country_code == 'FR' || getDolGlobalString('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
@@ -574,7 +654,7 @@ if ($action == 'writebookkeeping') {
 			$error++;
 			$errorforline++;
 			$errorforinvoice[$key] = 'amountsnotbalanced';
-			setEventMessages('Try to insert a non balanced transaction in book for '.$invoicestatic->ref.'. Canceled. Surely a bug.', null, 'errors');
+			setEventMessages('We tried to insert a non balanced transaction in book for '.$invoicestatic->ref.'. Canceled. Surely a bug.', null, 'errors');
 		}
 
 		if (!$errorforline) {
@@ -624,7 +704,7 @@ $form = new Form($db);
 
 // Export
 if ($action == 'exportcsv') {		// ISO and not UTF8 !
-	$sep = $conf->global->ACCOUNTING_EXPORT_SEPARATORCSV;
+	$sep = getDolGlobalString('ACCOUNTING_EXPORT_SEPARATORCSV');
 
 	$filename = 'journal';
 	$type_export = 'journal';
@@ -672,7 +752,7 @@ if ($action == 'exportcsv') {		// ISO and not UTF8 !
 				print '"'.$val["refsologest"].'"'.$sep;
 				print '"'.utf8_decode(dol_trunc($companystatic->name, 32)).'"'.$sep;
 				print '"'.length_accounta(html_entity_decode($k)).'"'.$sep;
-				print '"'.length_accountg($conf->global->ACCOUNTING_ACCOUNT_SUPPLIER).'"'.$sep;
+				print '"'.length_accountg(getDolGlobalString('ACCOUNTING_ACCOUNT_SUPPLIER')).'"'.$sep;
 				print '"'.length_accounta(html_entity_decode($k)).'"'.$sep;
 				print '"'.$langs->trans("Thirdparty").'"'.$sep;
 				print '"'.utf8_decode(dol_trunc($companystatic->name, 16)).' - '.$val["refsuppliersologest"].' - '.$langs->trans("Thirdparty").'"'.$sep;
@@ -715,6 +795,29 @@ if ($action == 'exportcsv') {		// ISO and not UTF8 !
 				$arrayofvat = $tablocaltax2;
 			}
 
+			// VAT Reverse charge
+			if ($mysoc->country_code == 'FR' || getDolGlobalString('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;
@@ -768,7 +871,7 @@ if (empty($action) || $action == 'view') {
 	$exportlink = '';
 	$builddate = dol_now();
 	$description = $langs->trans("DescJournalOnlyBindedVisible").'<br>';
-	if (!empty($conf->global->FACTURE_SUPPLIER_DEPOSITS_ARE_JUST_PAYMENTS)) {
+	if (getDolGlobalString('FACTURE_SUPPLIER_DEPOSITS_ARE_JUST_PAYMENTS')) {
 		$description .= $langs->trans("DepositsAreNotIncluded");
 	} else {
 		$description .= $langs->trans("DepositsAreIncluded");
@@ -783,7 +886,8 @@ if (empty($action) || $action == 'view') {
 	journalHead($nom, $nomlink, $period, $periodlink, $description, $builddate, $exportlink, array('action' => ''), '', $varlink);
 
 	// Button to write into Ledger
-	if (($conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == "") || $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == '-1') {
+	$acctSupplierNotConfigured = in_array(getDolGlobalString('ACCOUNTING_ACCOUNT_SUPPLIER'), ['','-1']);
+	if ($acctSupplierNotConfigured) {
 		print '<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);
@@ -791,10 +895,10 @@ if (empty($action) || $action == 'view') {
 		print '</div>';
 	}
 	print '<div class="tabsAction tabsActionNoBottom centerimp">';
-	if (!empty($conf->global->ACCOUNTING_ENABLE_EXPORT_DRAFT_JOURNAL) && $in_bookkeeping == 'notyet') {
+	if (getDolGlobalString('ACCOUNTING_ENABLE_EXPORT_DRAFT_JOURNAL') && $in_bookkeeping == 'notyet') {
 		print '<input type="button" class="butAction" name="exportcsv" value="'.$langs->trans("ExportDraftJournal").'" onclick="launch_export();" />';
 	}
-	if (($conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == "") || $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == '-1') {
+	if ($acctSupplierNotConfigured) {
 		print '<input type="button" class="butActionRefused classfortooltip" title="'.dol_escape_htmltag($langs->trans("SomeMandatoryStepsOfSetupWereNotDone")).'" value="'.$langs->trans("WriteBookKeeping").'" />';
 	} else {
 		if ($in_bookkeeping == 'notyet') {
@@ -920,7 +1024,7 @@ if (empty($action) || $action == 'view') {
 			print "<td>".$invoicestatic->getNomUrl(1)."</td>";
 			// Account
 			print "<td>";
-			$accountoshow = length_accountg($conf->global->ACCOUNTING_ACCOUNT_SUPPLIER);
+			$accountoshow = length_accountg(getDolGlobalString('ACCOUNTING_ACCOUNT_SUPPLIER'));
 			if (($accountoshow == "") || $accountoshow == 'NotDefined') {
 				print '<span class="error">'.$langs->trans("MainAccountForSuppliersNotDefined").'</span>';
 			} else {
@@ -964,7 +1068,7 @@ if (empty($action) || $action == 'view') {
 			print "</td>";
 			// Subledger account
 			print "<td>";
-			if (!empty($conf->global->ACCOUNTING_ACCOUNT_SUPPLIER_USE_AUXILIARY_ON_DEPOSIT)) {
+			if (getDolGlobalString('ACCOUNTING_ACCOUNT_SUPPLIER_USE_AUXILIARY_ON_DEPOSIT')) {
 				if ($k == getDolGlobalString('ACCOUNTING_ACCOUNT_SUPPLIER_DEPOSIT')) {
 					print length_accounta($tabcompany[$key]['code_compta']);
 				}
@@ -993,6 +1097,29 @@ if (empty($action) || $action == 'view') {
 				$arrayofvat = $tablocaltax2;
 			}
 
+			// VAT Reverse charge
+			if ($mysoc->country_code == 'FR' || getDolGlobalString('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">';
@@ -1003,7 +1130,7 @@ if (empty($action) || $action == 'view') {
 					print "<td>";
 					$accountoshow = length_accountg($k);
 					if (($accountoshow == "") || $accountoshow == 'NotDefined') {
-						print '<span class="error">'.$langs->trans("VATAccountNotDefined").' ('.$langs->trans("Purchase").')</span>';
+						print '<span class="error">'.$langs->trans("VATAccountNotDefined").' ('.$langs->trans("AccountingJournalType3").')</span>';
 					} else {
 						print $accountoshow;
 					}

+ 295 - 33
htdocs/accountancy/journal/sellsjournal.php

@@ -70,7 +70,7 @@ if (!isModEnabled('accounting')) {
 if ($user->socid > 0) {
 	accessforbidden();
 }
-if (empty($user->rights->accounting->mouvements->lire)) {
+if (!$user->hasRight('accounting', 'mouvements', 'lire')) {
 	accessforbidden();
 }
 
@@ -106,10 +106,10 @@ 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.situation_cycle_ref, f.datef as df, f.ref_client, f.date_lim_reglement as dlr, f.close_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, f.revenuestamp,";
 $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)) {
+if (getDolGlobalString('MAIN_COMPANY_PERENTITY_SHARED')) {
 	$sql .= " spe.accountancy_code_customer as code_compta,";
 	$sql .= " spe.accountancy_code_supplier as code_compta_fournisseur,";
 } else {
@@ -117,26 +117,26 @@ if (!empty($conf->global->MAIN_COMPANY_PERENTITY_SHARED)) {
 	$sql .= " s.code_compta_fournisseur,";
 }
 $sql .= " p.rowid as pid, p.ref as pref, aa.rowid as fk_compte, aa.account_number as compte, aa.label as label_compte,";
-if (!empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
+if (getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
 	$sql .= " ppe.accountancy_code_sell";
 } else {
 	$sql .= " p.accountancy_code_sell";
 }
 $sql .= " FROM ".MAIN_DB_PREFIX."facturedet as fd";
 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = fd.fk_product";
-if (!empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
+if (getDolGlobalString('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 ".MAIN_DB_PREFIX."accounting_account as aa ON aa.rowid = fd.fk_code_ventilation";
 $sql .= " JOIN ".MAIN_DB_PREFIX."facture as f ON f.rowid = fd.fk_facture";
 $sql .= " JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = f.fk_soc";
-if (!empty($conf->global->MAIN_COMPANY_PERENTITY_SHARED)) {
+if (getDolGlobalString('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 .= " WHERE fd.fk_code_ventilation > 0";
 $sql .= " AND f.entity IN (".getEntity('invoice', 0).')'; // We don't share object for accountancy, we use source object sharing
 $sql .= " AND f.fk_statut > 0";
-if (!empty($conf->global->FACTURE_DEPOSITS_ARE_JUST_PAYMENTS)) {	// Non common setup
+if (getDolGlobalString('FACTURE_DEPOSITS_ARE_JUST_PAYMENTS')) {	// Non common setup
 	$sql .= " AND f.type IN (".Facture::TYPE_STANDARD.",".Facture::TYPE_REPLACEMENT.",".Facture::TYPE_CREDIT_NOTE.",".Facture::TYPE_SITUATION.")";
 } else {
 	$sql .= " AND f.type IN (".Facture::TYPE_STANDARD.",".Facture::TYPE_REPLACEMENT.",".Facture::TYPE_CREDIT_NOTE.",".Facture::TYPE_DEPOSIT.",".Facture::TYPE_SITUATION.")";
@@ -146,8 +146,8 @@ if ($date_start && $date_end) {
 	$sql .= " AND f.datef >= '".$db->idate($date_start)."' AND f.datef <= '".$db->idate($date_end)."'";
 }
 // Define begin binding date
-if (!empty($conf->global->ACCOUNTING_DATE_START_BINDING)) {
-	$sql .= " AND f.datef >= '".$db->idate($conf->global->ACCOUNTING_DATE_START_BINDING)."'";
+if (getDolGlobalString('ACCOUNTING_DATE_START_BINDING')) {
+	$sql .= " AND f.datef >= '".$db->idate(getDolGlobalString('ACCOUNTING_DATE_START_BINDING'))."'";
 }
 // Already in bookkeeping or not
 if ($in_bookkeeping == 'already') {
@@ -159,7 +159,7 @@ if ($in_bookkeeping == 'notyet') {
 	// $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, f.ref";
-//print $sql; exit;
+//print $sql;
 
 dol_syslog('accountancy/journal/sellsjournal.php', LOG_DEBUG);
 $result = $db->query($sql);
@@ -168,6 +168,8 @@ if ($result) {
 	$tabht = array();
 	$tabtva = array();
 	$def_tva = array();
+	$tabwarranty = array();
+	$tabrevenuestamp = array();
 	$tabttc = array();
 	$tablocaltax1 = array();
 	$tablocaltax2 = array();
@@ -176,8 +178,8 @@ if ($result) {
 	$num = $db->num_rows($result);
 
 	// Variables
-	$cptcli = (($conf->global->ACCOUNTING_ACCOUNT_CUSTOMER != "")) ? $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER : 'NotDefined';
-	$cpttva = (!empty($conf->global->ACCOUNTING_VAT_SOLD_ACCOUNT)) ? $conf->global->ACCOUNTING_VAT_SOLD_ACCOUNT : 'NotDefined';
+	$cptcli = getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER', 'NotDefined');
+	$cpttva = getDolGlobalString('ACCOUNTING_VAT_SOLD_ACCOUNT', 'NotDefined');
 
 	$i = 0;
 	while ($i < $num) {
@@ -189,23 +191,25 @@ if ($result) {
 		$compta_prod = $obj->compte;
 		if (empty($compta_prod)) {
 			if ($obj->product_type == 0) {
-				$compta_prod = (!empty($conf->global->ACCOUNTING_PRODUCT_SOLD_ACCOUNT)) ? $conf->global->ACCOUNTING_PRODUCT_SOLD_ACCOUNT : 'NotDefined';
+				$compta_prod = getDolGlobalString('ACCOUNTING_PRODUCT_SOLD_ACCOUNT', 'NotDefined');
 			} else {
-				$compta_prod = (!empty($conf->global->ACCOUNTING_SERVICE_SOLD_ACCOUNT)) ? $conf->global->ACCOUNTING_SERVICE_SOLD_ACCOUNT : 'NotDefined';
+				$compta_prod = getDolGlobalString('ACCOUNTING_SERVICE_SOLD_ACCOUNT', 'NotDefined');
 			}
 		}
 
+		//$compta_revenuestamp = getDolGlobalString('ACCOUNTING_REVENUESTAMP_SOLD_ACCOUNT', 'NotDefined');
+
 		$vatdata = getTaxesFromId($obj->tva_tx.($obj->vat_src_code ? ' ('.$obj->vat_src_code.')' : ''), $mysoc, $mysoc, 0);
 		$compta_tva = (!empty($vatdata['accountancy_code_sell']) ? $vatdata['accountancy_code_sell'] : $cpttva);
 		$compta_localtax1 = (!empty($vatdata['accountancy_code_sell']) ? $vatdata['accountancy_code_sell'] : $cpttva);
 		$compta_localtax2 = (!empty($vatdata['accountancy_code_sell']) ? $vatdata['accountancy_code_sell'] : $cpttva);
 
-		// Define array to display all VAT rates that use this accounting account $compta_tva
+		// Define the array to store the detail of each vat rate and code for lines
 		if (price2num($obj->tva_tx) || !empty($obj->vat_src_code)) {
 			$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.')' : ''));
 		}
 
-		// Create a compensation rate.
+		// Create a compensation rate for situation invoice.
 		$situation_ratio = 1;
 		if (getDolGlobalInt('INVOICE_USE_SITUATION') == 1) {
 			if ($obj->situation_cycle_ref) {
@@ -224,6 +228,8 @@ if ($result) {
 			}
 		}
 
+		$revenuestamp = (double) price2num($obj->revenuestamp, 'MT');
+
 		// Invoice lines
 		$tabfac[$obj->rowid]["date"] = $db->jdate($obj->df);
 		$tabfac[$obj->rowid]["datereg"] = $db->jdate($obj->dlr);
@@ -231,6 +237,7 @@ if ($result) {
 		$tabfac[$obj->rowid]["type"] = $obj->type;
 		$tabfac[$obj->rowid]["description"] = $obj->label_compte;
 		$tabfac[$obj->rowid]["close_code"] = $obj->close_code; // close_code = 'replaced' for replacement invoices (not used in most european countries)
+		$tabfac[$obj->rowid]["revenuestamp"] = $revenuestamp;
 		//$tabfac[$obj->rowid]["fk_facturedet"] = $obj->fdid;
 
 		// Avoid warnings
@@ -255,7 +262,20 @@ if ($result) {
 		// 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.
-		$tabttc[$obj->rowid][$compta_soc] += $obj->total_ttc * $situation_ratio;
+		if (getDolGlobalInt('INVOICE_USE_SITUATION') == 1) {
+			$total_ttc = $obj->total_ttc * $situation_ratio;
+		} else {
+			$total_ttc = $obj->total_ttc;
+		}
+
+		// Move a part of the retained warrenty into the account of warranty
+		if (getDolGlobalString('INVOICE_USE_RETAINED_WARRANTY') && $obj->retained_warranty > 0) {
+			$retained_warranty = (double) price2num($total_ttc * $obj->retained_warranty / 100, 'MT');	// Calculate the amount of warrenty for this line (using the percent value)
+			$tabwarranty[$obj->rowid][$compta_soc] += $retained_warranty;
+			$total_ttc -= $retained_warranty;
+		}
+
+		$tabttc[$obj->rowid][$compta_soc] += $total_ttc;
 		$tabht[$obj->rowid][$compta_prod] += $obj->total_ht * $situation_ratio;
 		$tva_npr = (($obj->info_bits & 1 == 1) ? 1 : 0);
 		if (!$tva_npr) { // We ignore line if VAT is a NPR
@@ -264,6 +284,32 @@ if ($result) {
 		$tablocaltax1[$obj->rowid][$compta_localtax1] += $obj->total_localtax1 * $situation_ratio;
 		$tablocaltax2[$obj->rowid][$compta_localtax2] += $obj->total_localtax2 * $situation_ratio;
 
+		$compta_revenuestamp = 'NotDefined';
+		if (!empty($revenuestamp)) {
+			$sqlrevenuestamp = "SELECT accountancy_code_sell FROM ".MAIN_DB_PREFIX."c_revenuestamp";
+			$sqlrevenuestamp .= " WHERE fk_pays = ".((int) $mysoc->country_id);
+			$sqlrevenuestamp .= " AND taux = ".((double) $revenuestamp);
+			$sqlrevenuestamp .= " AND active = 1";
+			$resqlrevenuestamp = $db->query($sqlrevenuestamp);
+
+			if ($resqlrevenuestamp) {
+				$num_rows_revenuestamp = $db->num_rows($resqlrevenuestamp);
+				if ($num_rows_revenuestamp > 1) {
+					dol_print_error($db, 'Failed 2 or more lines for the revenue stamp of your country. Check the dictionary of revenue stamp.');
+				} else {
+					$objrevenuestamp = $db->fetch_object($resqlrevenuestamp);
+					if ($objrevenuestamp) {
+						$compta_revenuestamp = $objrevenuestamp->accountancy_code_sell;
+					}
+				}
+			}
+		}
+
+		if (empty($tabrevenuestamp[$obj->rowid][$compta_revenuestamp]) && !empty($revenuestamp)) {
+			// The revenue stamp was never seen for this invoice id=$obj->rowid
+			$tabttc[$obj->rowid][$compta_soc] += $obj->revenuestamp;
+			$tabrevenuestamp[$obj->rowid][$compta_revenuestamp] = $obj->revenuestamp;
+		}
 
 		$tabcompany[$obj->rowid] = array(
 			'id' => $obj->socid,
@@ -274,13 +320,15 @@ if ($result) {
 
 		$i++;
 	}
+
+	// After the loop on each line
 } else {
 	dol_print_error($db);
 }
 
 $errorforinvoice = array();
 
-// Loop in invoices to detect lines with not binding lines
+// Loop on all invoices to detect lines without binded code (fk_code_ventilation <= 0)
 foreach ($tabfac as $key => $val) {		// Loop on each invoice
 	$sql = "SELECT COUNT(fd.rowid) as nb";
 	$sql .= " FROM ".MAIN_DB_PREFIX."facturedet as fd";
@@ -308,7 +356,11 @@ if ($action == 'writebookkeeping') {
 	$invoicestatic = new Facture($db);
 	$accountingaccountcustomer = new AccountingAccount($db);
 
-	$accountingaccountcustomer->fetch(null, $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER, true);
+	$accountingaccountcustomer->fetch(null, getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER'), true);
+
+	$accountingaccountcustomerwarranty = new AccountingAccount($db);
+
+	$accountingaccountcustomerwarranty->fetch(null, getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER_RETAINED_WARRANTY'), true);
 
 	foreach ($tabfac as $key => $val) {		// Loop on each invoice
 		$errorforline = 0;
@@ -321,6 +373,7 @@ if ($action == 'writebookkeeping') {
 		$companystatic->id = $tabcompany[$key]['id'];
 		$companystatic->name = $tabcompany[$key]['name'];
 		$companystatic->code_compta = $tabcompany[$key]['code_compta'];
+		$companystatic->code_compta_client = $tabcompany[$key]['code_compta'];
 		$companystatic->code_client = $tabcompany[$key]['code_client'];
 		$companystatic->client = 3;
 
@@ -354,6 +407,55 @@ if ($action == 'writebookkeeping') {
 			setEventMessages($langs->trans('ErrorInvoiceContainsLinesNotYetBounded', $val['ref']), null, 'errors');
 		}
 
+		// Warranty
+		if (!$errorforline) {
+			foreach ($tabwarranty[$key] as $k => $mt) {
+				$bookkeeping = new BookKeeping($db);
+				$bookkeeping->doc_date = $val["date"];
+				$bookkeeping->date_lim_reglement = $val["datereg"];
+				$bookkeeping->doc_ref = $val["ref"];
+				$bookkeeping->date_creation = $now;
+				$bookkeeping->doc_type = 'customer_invoice';
+				$bookkeeping->fk_doc = $key;
+				$bookkeeping->fk_docdet = 0; // Useless, can be several lines that are source of this record to add
+				$bookkeeping->thirdparty_code = $companystatic->code_client;
+
+				$bookkeeping->subledger_account = $tabcompany[$key]['code_compta'];
+				$bookkeeping->subledger_label = $tabcompany[$key]['name'];
+
+				$bookkeeping->numero_compte = getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER_RETAINED_WARRANTY');
+				$bookkeeping->label_compte = $accountingaccountcustomerwarranty->label;
+
+				$bookkeeping->label_operation = dol_trunc($companystatic->name, 16).' - '.$invoicestatic->ref.' - '.$langs->trans("Retainedwarranty");
+				$bookkeeping->montant = $mt;
+				$bookkeeping->sens = ($mt >= 0) ? 'D' : 'C';
+				$bookkeeping->debit = ($mt >= 0) ? $mt : 0;
+				$bookkeeping->credit = ($mt < 0) ? -$mt : 0;
+				$bookkeeping->code_journal = $journal;
+				$bookkeeping->journal_label = $langs->transnoentities($journal_label);
+				$bookkeeping->fk_user_author = $user->id;
+				$bookkeeping->entity = $conf->entity;
+
+				$totaldebit += $bookkeeping->debit;
+				$totalcredit += $bookkeeping->credit;
+
+				$result = $bookkeeping->create($user);
+				if ($result < 0) {
+					if ($bookkeeping->error == 'BookkeepingRecordAlreadyExists') {	// Already exists
+						$error++;
+						$errorforline++;
+						$errorforinvoice[$key] = 'alreadyjournalized';
+						//setEventMessages('Transaction for ('.$bookkeeping->doc_type.', '.$bookkeeping->fk_doc.', '.$bookkeeping->fk_docdet.') were already recorded', null, 'warnings');
+					} else {
+						$error++;
+						$errorforline++;
+						$errorforinvoice[$key] = 'other';
+						setEventMessages($bookkeeping->error, $bookkeeping->errors, 'errors');
+					}
+				}
+			}
+		}
+
 		// Thirdparty
 		if (!$errorforline) {
 			foreach ($tabttc[$key] as $k => $mt) {
@@ -370,7 +472,7 @@ if ($action == 'writebookkeeping') {
 				$bookkeeping->subledger_account = $tabcompany[$key]['code_compta'];
 				$bookkeeping->subledger_label = $tabcompany[$key]['name'];
 
-				$bookkeeping->numero_compte = $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER;
+				$bookkeeping->numero_compte = getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER');
 				$bookkeeping->label_compte = $accountingaccountcustomer->label;
 
 				$bookkeeping->label_operation = dol_trunc($companystatic->name, 16).' - '.$invoicestatic->ref.' - '.$langs->trans("SubledgerAccount");
@@ -428,7 +530,7 @@ if ($action == 'writebookkeeping') {
 					$bookkeeping->fk_docdet = 0; // Useless, can be several lines that are source of this record to add
 					$bookkeeping->thirdparty_code = $companystatic->code_client;
 
-					if (!empty($conf->global->ACCOUNTING_ACCOUNT_CUSTOMER_USE_AUXILIARY_ON_DEPOSIT)) {
+					if (getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER_USE_AUXILIARY_ON_DEPOSIT')) {
 						if ($k == getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT')) {
 							$bookkeeping->subledger_account = $tabcompany[$key]['code_compta'];
 							$bookkeeping->subledger_label = $tabcompany[$key]['name'];
@@ -508,7 +610,12 @@ if ($action == 'writebookkeeping') {
 						$bookkeeping->numero_compte = $k;
 						$bookkeeping->label_compte = $label_account;
 
-						$bookkeeping->label_operation = dol_trunc($companystatic->name, 16).' - '.$invoicestatic->ref.' - '.$langs->trans("VAT").' '.join(', ', $def_tva[$key][$k]).' %'.($numtax ? ' - Localtax '.$numtax : '');
+
+						$bookkeeping->label_operation = dol_trunc($companystatic->name, 16).' - '.$invoicestatic->ref;
+						$tmpvatrate = (empty($def_tva[$key][$k]) ? (empty($arrayofvat[$key][$k]) ? '' : $arrayofvat[$key][$k]) : join(', ', $def_tva[$key][$k]));
+						$bookkeeping->label_operation .= ' - '.$langs->trans("Taxes").' '.$tmpvatrate.' %';
+						$bookkeeping->label_operation .= ($numtax ? ' - Localtax '.$numtax : '');
+
 						$bookkeeping->montant = $mt;
 						$bookkeeping->sens = ($mt < 0) ? 'D' : 'C';
 						$bookkeeping->debit = ($mt < 0) ? -$mt : 0;
@@ -540,12 +647,66 @@ if ($action == 'writebookkeeping') {
 			}
 		}
 
+		// Revenue stamp
+		if (!$errorforline) {
+			foreach ($tabrevenuestamp[$key] as $k => $mt) {
+				if ($mt) {
+					$accountingaccount->fetch(null, $k, true);	// TODO Use a cache for label
+					$label_account = $accountingaccount->label;
+
+					$bookkeeping = new BookKeeping($db);
+					$bookkeeping->doc_date = $val["date"];
+					$bookkeeping->date_lim_reglement = $val["datereg"];
+					$bookkeeping->doc_ref = $val["ref"];
+					$bookkeeping->date_creation = $now;
+					$bookkeeping->doc_type = 'customer_invoice';
+					$bookkeeping->fk_doc = $key;
+					$bookkeeping->fk_docdet = 0; // Useless, can be several lines that are source of this record to add
+					$bookkeeping->thirdparty_code = $companystatic->code_client;
+
+					$bookkeeping->subledger_account = '';
+					$bookkeeping->subledger_label = '';
+
+					$bookkeeping->numero_compte = $k;
+					$bookkeeping->label_compte = $label_account;
+
+					$bookkeeping->label_operation = dol_trunc($companystatic->name, 16).' - '.$invoicestatic->ref.' - '.$langs->trans("RevenueStamp");
+					$bookkeeping->montant = $mt;
+					$bookkeeping->sens = ($mt < 0) ? 'D' : 'C';
+					$bookkeeping->debit = ($mt < 0) ? -$mt : 0;
+					$bookkeeping->credit = ($mt >= 0) ? $mt : 0;
+					$bookkeeping->code_journal = $journal;
+					$bookkeeping->journal_label = $langs->transnoentities($journal_label);
+					$bookkeeping->fk_user_author = $user->id;
+					$bookkeeping->entity = $conf->entity;
+
+					$totaldebit += $bookkeeping->debit;
+					$totalcredit += $bookkeeping->credit;
+
+					$result = $bookkeeping->create($user);
+					if ($result < 0) {
+						if ($bookkeeping->error == 'BookkeepingRecordAlreadyExists') {	// Already exists
+							$error++;
+							$errorforline++;
+							$errorforinvoice[$key] = 'alreadyjournalized';
+							//setEventMessages('Transaction for ('.$bookkeeping->doc_type.', '.$bookkeeping->fk_doc.', '.$bookkeeping->fk_docdet.') were already recorded', null, 'warnings');
+						} else {
+							$error++;
+							$errorforline++;
+							$errorforinvoice[$key] = 'other';
+							setEventMessages($bookkeeping->error, $bookkeeping->errors, 'errors');
+						}
+					}
+				}
+			}
+		}
+
 		// Protection against a bug on lines before
 		if (!$errorforline && (price2num($totaldebit, 'MT') != price2num($totalcredit, 'MT'))) {
 			$error++;
 			$errorforline++;
 			$errorforinvoice[$key] = 'amountsnotbalanced';
-			setEventMessages('Try to insert a non balanced transaction in book for '.$invoicestatic->ref.'. Canceled. Surely a bug.', null, 'errors');
+			setEventMessages('We Tried to insert a non balanced transaction in book for '.$invoicestatic->ref.'. Canceled. Surely a bug.', null, 'errors');
 		}
 
 		if (!$errorforline) {
@@ -597,7 +758,8 @@ $form = new Form($db);
 
 // Export
 if ($action == 'exportcsv') {		// ISO and not UTF8 !
-	$sep = $conf->global->ACCOUNTING_EXPORT_SEPARATORCSV;
+	// Note that to have the button to get this feature enabled, you must enable ACCOUNTING_ENABLE_EXPORT_DRAFT_JOURNAL
+	$sep = getDolGlobalString('ACCOUNTING_EXPORT_SEPARATORCSV');
 
 	$filename = 'journal';
 	$type_export = 'journal';
@@ -636,6 +798,25 @@ if ($action == 'exportcsv') {		// ISO and not UTF8 !
 			continue;
 		}
 
+		// Warranty
+		foreach ($tabwarranty[$key] as $k => $mt) {
+			//if ($mt) {
+			print '"'.$key.'"'.$sep;
+			print '"'.$date.'"'.$sep;
+			print '"'.$val["ref"].'"'.$sep;
+			print '"'.utf8_decode(dol_trunc($companystatic->name, 32)).'"'.$sep;
+			print '"'.length_accounta(html_entity_decode($k)).'"'.$sep;
+			print '"'.length_accountg(getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER_RETAINED_WARRANTY')).'"'.$sep;
+			print '"'.length_accounta(html_entity_decode($k)).'"'.$sep;
+			print '"'.$langs->trans("Thirdparty").'"'.$sep;
+			print '"'.utf8_decode(dol_trunc($companystatic->name, 16)).' - '.$invoicestatic->ref.' - '.$langs->trans("Retainedwarranty").'"'.$sep;
+			print '"'.($mt >= 0 ? price($mt) : '').'"'.$sep;
+			print '"'.($mt < 0 ? price(-$mt) : '').'"'.$sep;
+			print '"'.$journal.'"';
+			print "\n";
+			//}
+		}
+
 		// Third party
 		foreach ($tabttc[$key] as $k => $mt) {
 			//if ($mt) {
@@ -644,7 +825,7 @@ if ($action == 'exportcsv') {		// ISO and not UTF8 !
 				print '"'.$val["ref"].'"'.$sep;
 				print '"'.utf8_decode(dol_trunc($companystatic->name, 32)).'"'.$sep;
 				print '"'.length_accounta(html_entity_decode($k)).'"'.$sep;
-				print '"'.length_accountg($conf->global->ACCOUNTING_ACCOUNT_CUSTOMER).'"'.$sep;
+				print '"'.length_accountg(getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER')).'"'.$sep;
 				print '"'.length_accounta(html_entity_decode($k)).'"'.$sep;
 				print '"'.$langs->trans("Thirdparty").'"'.$sep;
 				print '"'.utf8_decode(dol_trunc($companystatic->name, 16)).' - '.$invoicestatic->ref.' - '.$langs->trans("Thirdparty").'"'.$sep;
@@ -705,6 +886,25 @@ if ($action == 'exportcsv') {		// ISO and not UTF8 !
 				}
 			}
 		}
+
+		// Revenue stamp
+		foreach ($tabrevenuestamp[$key] as $k => $mt) {
+			//if ($mt) {
+			print '"'.$key.'"'.$sep;
+			print '"'.$date.'"'.$sep;
+			print '"'.$val["ref"].'"'.$sep;
+			print '"'.utf8_decode(dol_trunc($companystatic->name, 32)).'"'.$sep;
+			print '"'.length_accountg(html_entity_decode($k)).'"'.$sep;
+			print '"'.length_accountg(html_entity_decode($k)).'"'.$sep;
+			print '""'.$sep;
+			print '"'.$langs->trans("RevenueStamp").'"'.$sep;
+			print '"'.utf8_decode(dol_trunc($companystatic->name, 16)).' - '.$invoicestatic->ref.' - '.$langs->trans("RevenueStamp").'"'.$sep;
+			print '"'.($mt < 0 ? price(-$mt) : '').'"'.$sep;
+			print '"'.($mt >= 0 ? price($mt) : '').'"'.$sep;
+			print '"'.$journal.'"';
+			print "\n";
+			//}
+		}
 	}
 }
 
@@ -721,7 +921,7 @@ if (empty($action) || $action == 'view') {
 	$exportlink = '';
 	$builddate = dol_now();
 	$description = $langs->trans("DescJournalOnlyBindedVisible").'<br>';
-	if (!empty($conf->global->FACTURE_DEPOSITS_ARE_JUST_PAYMENTS)) {
+	if (getDolGlobalString('FACTURE_DEPOSITS_ARE_JUST_PAYMENTS')) {
 		$description .= $langs->trans("DepositsAreNotIncluded");
 	} else {
 		$description .= $langs->trans("DepositsAreIncluded");
@@ -736,7 +936,8 @@ if (empty($action) || $action == 'view') {
 	journalHead($nom, $nomlink, $period, $periodlink, $description, $builddate, $exportlink, array('action' => ''), '', $varlink);
 
 	// Button to write into Ledger
-	if (($conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == "") || $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == '-1') {
+	$acctCustomerNotConfigured = in_array(getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER'), ['','-1']);
+	if ($acctCustomerNotConfigured) {
 		print '<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);
@@ -744,10 +945,10 @@ if (empty($action) || $action == 'view') {
 		print '</div>';
 	}
 	print '<div class="tabsAction tabsActionNoBottom centerimp">';
-	if (!empty($conf->global->ACCOUNTING_ENABLE_EXPORT_DRAFT_JOURNAL) && $in_bookkeeping == 'notyet') {
+	if (getDolGlobalString('ACCOUNTING_ENABLE_EXPORT_DRAFT_JOURNAL') && $in_bookkeeping == 'notyet') {
 		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') {
+	if ($acctCustomerNotConfigured) {
 		print '<input type="button" class="butActionRefused classfortooltip" title="'.dol_escape_htmltag($langs->trans("SomeMandatoryStepsOfSetupWereNotDone")).'" value="'.$langs->trans("WriteBookKeeping").'" />';
 	} else {
 		if ($in_bookkeeping == 'notyet') {
@@ -800,6 +1001,7 @@ if (empty($action) || $action == 'view') {
 		$companystatic->id = $tabcompany[$key]['id'];
 		$companystatic->name = $tabcompany[$key]['name'];
 		$companystatic->code_compta = $tabcompany[$key]['code_compta'];
+		$companystatic->code_compta_client = $tabcompany[$key]['code_compta'];
 		$companystatic->code_client = $tabcompany[$key]['code_client'];
 		$companystatic->client = 3;
 
@@ -863,6 +1065,36 @@ if (empty($action) || $action == 'view') {
 			$i++;
 		}
 
+		// Warranty
+		foreach ($tabwarranty[$key] as $k => $mt) {
+			print '<tr class="oddeven">';
+			print "<!-- Thirdparty warranty -->";
+			print "<td>".$date."</td>";
+			print "<td>".$invoicestatic->getNomUrl(1)."</td>";
+			// Account
+			print "<td>";
+			$accountoshow = length_accountg(getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER_RETAINED_WARRANTY'));
+			if (($accountoshow == "") || $accountoshow == 'NotDefined') {
+				print '<span class="error">'.$langs->trans("MainAccountForRetainedWarrantyNotDefined").'</span>';
+			} else {
+				print $accountoshow;
+			}
+			print '</td>';
+			// Subledger account
+			print "<td>";
+			$accountoshow = length_accounta($k);
+			if (($accountoshow == "") || $accountoshow == 'NotDefined') {
+				print '<span class="error">'.$langs->trans("ThirdpartyAccountNotDefined").'</span>';
+			} else {
+				print $accountoshow;
+			}
+			print '</td>';
+			print "<td>".$companystatic->getNomUrl(0, 'customer', 16).' - '.$invoicestatic->ref.' - '.$langs->trans("Retainedwarranty")."</td>";
+			print '<td class="right nowraponall amount">'.($mt >= 0 ? price($mt) : '')."</td>";
+			print '<td class="right nowraponall amount">'.($mt < 0 ? price(-$mt) : '')."</td>";
+			print "</tr>";
+		}
+
 		// Third party
 		foreach ($tabttc[$key] as $k => $mt) {
 			print '<tr class="oddeven">';
@@ -871,7 +1103,7 @@ if (empty($action) || $action == 'view') {
 			print "<td>".$invoicestatic->getNomUrl(1)."</td>";
 			// Account
 			print "<td>";
-			$accountoshow = length_accountg($conf->global->ACCOUNTING_ACCOUNT_CUSTOMER);
+			$accountoshow = length_accountg(getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER'));
 			if (($accountoshow == "") || $accountoshow == 'NotDefined') {
 				print '<span class="error">'.$langs->trans("MainAccountForCustomersNotDefined").'</span>';
 			} else {
@@ -915,7 +1147,7 @@ if (empty($action) || $action == 'view') {
 			print "</td>";
 			// Subledger account
 			print "<td>";
-			if (!empty($conf->global->ACCOUNTING_ACCOUNT_CUSTOMER_USE_AUXILIARY_ON_DEPOSIT)) {
+			if (getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER_USE_AUXILIARY_ON_DEPOSIT')) {
 				if ($k == getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT')) {
 					print length_accounta($tabcompany[$key]['code_compta']);
 				}
@@ -944,6 +1176,7 @@ if (empty($action) || $action == 'view') {
 				$arrayofvat = $tablocaltax2;
 			}
 
+			// $key is id of invoice
 			foreach ($arrayofvat[$key] as $k => $mt) {
 				if ($mt) {
 					print '<tr class="oddeven">';
@@ -954,7 +1187,7 @@ if (empty($action) || $action == 'view') {
 					print "<td>";
 					$accountoshow = length_accountg($k);
 					if (($accountoshow == "") || $accountoshow == 'NotDefined') {
-						print '<span class="error">'.$langs->trans("VATAccountNotDefined").' ('.$langs->trans("Sale").')</span>';
+						print '<span class="error">'.$langs->trans("VATAccountNotDefined").' ('.$langs->trans("AccountingJournalType2").')</span>';
 					} else {
 						print $accountoshow;
 					}
@@ -962,7 +1195,12 @@ if (empty($action) || $action == 'view') {
 					// Subledger account
 					print "<td>";
 					print '</td>';
-					print "<td>".$companystatic->getNomUrl(0, 'customer', 16).' - '.$invoicestatic->ref.' - '.$langs->trans("VAT").' '.join(', ', $def_tva[$key][$k]).' %'.($numtax ? ' - Localtax '.$numtax : '');
+					print "<td>".$companystatic->getNomUrl(0, 'customer', 16).' - '.$invoicestatic->ref;
+					// $def_tva is array[invoiceid][accountancy_code_sell_of_vat_rate_found][vatrate]=vatrate
+					//var_dump($arrayofvat[$key]); var_dump($key); var_dump($k);
+					$tmpvatrate = (empty($def_tva[$key][$k]) ? (empty($arrayofvat[$key][$k]) ? '' : $arrayofvat[$key][$k]) : join(', ', $def_tva[$key][$k]));
+					print ' - '.$langs->trans("Taxes").' '.$tmpvatrate.' %';
+					print ($numtax ? ' - Localtax '.$numtax : '');
 					print "</td>";
 					print '<td class="right nowraponall amount">'.($mt < 0 ? price(-$mt) : '')."</td>";
 					print '<td class="right nowraponall amount">'.($mt >= 0 ? price($mt) : '')."</td>";
@@ -972,6 +1210,30 @@ if (empty($action) || $action == 'view') {
 				}
 			}
 		}
+
+		// Warranty
+		foreach ($tabrevenuestamp[$key] as $k => $mt) {
+			print '<tr class="oddeven">';
+			print "<!-- Thirdparty revenuestamp -->";
+			print "<td>".$date."</td>";
+			print "<td>".$invoicestatic->getNomUrl(1)."</td>";
+			// Account
+			print "<td>";
+			$accountoshow = length_accountg($k);
+			if (($accountoshow == "") || $accountoshow == 'NotDefined') {
+				print '<span class="error">'.$langs->trans("MainAccountForRevenueStampSaleNotDefined").'</span>';
+			} else {
+				print $accountoshow;
+			}
+			print '</td>';
+			// Subledger account
+			print "<td>";
+			print '</td>';
+			print "<td>".$companystatic->getNomUrl(0, 'customer', 16).' - '.$invoicestatic->ref.' - '.$langs->trans("RevenueStamp")."</td>";
+			print '<td class="right nowraponall amount">'.($mt < 0 ? price(-$mt) : '')."</td>";
+			print '<td class="right nowraponall amount">'.($mt >= 0 ? price($mt) : '')."</td>";
+			print "</tr>";
+		}
 	}
 
 	if (!$i) {

+ 15 - 4
htdocs/accountancy/journal/variousjournal.php

@@ -90,7 +90,7 @@ if (!isModEnabled('accounting')) {
 if ($user->socid > 0) {
 	accessforbidden();
 }
-if (empty($user->rights->accounting->mouvements->lire)) {
+if (!$user->hasRight('accounting', 'mouvements', 'lire')) {
 	accessforbidden();
 }
 
@@ -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>';
 

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است