Explorar el Código

Merge branch 'develop' into NEW/accountancy_massaction_preselect_account_customer_and_supplier_list

Laurent Destailleur hace 3 años
padre
commit
9a040e0521
Se han modificado 100 ficheros con 2544 adiciones y 1488 borrados
  1. 3 2
      .github/CONTRIBUTING.md
  2. 0 35
      .github/ISSUE_TEMPLATE/bug_report.md
  3. 71 0
      .github/ISSUE_TEMPLATE/bug_report.yml
  4. 0 27
      .github/ISSUE_TEMPLATE/feature_request.md
  5. 35 0
      .github/ISSUE_TEMPLATE/feature_request.yml
  6. 1 0
      .gitignore
  7. 12 6
      .travis.yml
  8. 5 5
      COPYRIGHT
  9. 240 7
      ChangeLog
  10. 1 0
      README.md
  11. 9 8
      SECURITY.md
  12. 7 9
      build/docker/Dockerfile
  13. 1 0
      build/docker/docker-compose.yml
  14. 1 0
      build/pad/README
  15. 3 1
      build/perl/virtualmin/dolibarr.pl
  16. 0 1
      build/rpm/dolibarr_fedora.spec
  17. 0 1
      build/rpm/dolibarr_generic.spec
  18. 0 1
      build/rpm/dolibarr_mandriva.spec
  19. 0 1
      build/rpm/dolibarr_opensuse.spec
  20. 2 2
      composer.json
  21. 62 64
      composer.lock
  22. 8 0
      dev/dolibarr_changes.txt
  23. 1 1
      dev/examples/code/README
  24. 1 1
      dev/examples/zapier/package.json
  25. 2 2
      dev/initdemo/initdemo.sh
  26. 4 4
      dev/initdemo/mysqldump_dolibarr_15.0.0.sql
  27. 2 2
      dev/initdemo/savedemo.sh
  28. BIN
      dev/resources/iso-normes/Intracommreport-ManuelDebXml.pdf
  29. 17 0
      dev/resources/iso-normes/QR code for invoices.txt
  30. 2 0
      dev/resources/iso-normes/address_format.txt
  31. 7 3
      dev/resources/iso-normes/currencies_iso-4217.txt
  32. 0 0
      dev/resources/iso-normes/facturx-zugferd/README.txt
  33. BIN
      dev/resources/iso-normes/format PDF - PDF A.pdf
  34. 0 0
      dev/resources/iso-normes/format_FEC - Lien_outil_de_test_agréé.pdf
  35. 0 0
      dev/resources/iso-normes/format_FEC - fie example.txt
  36. 0 0
      dev/resources/iso-normes/intracommreport/Intracommreport-ManuelDebXml.pdf
  37. 0 0
      dev/resources/iso-normes/intracommreport/Intracommreport-ManuelDesXML.pdf
  38. 0 0
      dev/resources/iso-normes/intracommreport/schema_deb.xsd
  39. 2 0
      dev/resources/iso-normes/locales.txt
  40. 0 0
      dev/resources/iso-normes/sepa/pain.001.001.03.xsd
  41. 0 0
      dev/resources/iso-normes/sepa/pain.008.001.02.xsd
  42. 0 0
      dev/resources/iso-normes/sepa/sample-credit-transfer.xml
  43. 0 0
      dev/resources/iso-normes/sepa/sample-direct-debit.xml
  44. 0 0
      dev/resources/iso-normes/sepa/text.txt
  45. 3 0
      dev/resources/iso-normes/world_tax_rates.txt
  46. 88 57
      dev/setup/apache/virtualhost
  47. 3 3
      dev/tools/fixdosfiles.sh
  48. 4 3
      dev/tools/optimize_images.sh
  49. BIN
      doc/images/appicon_128.png
  50. BIN
      doc/images/appicon_16.png
  51. BIN
      doc/images/appicon_32.png
  52. BIN
      doc/images/appicon_64.png
  53. BIN
      doc/images/background_dolibarr.jpg
  54. BIN
      doc/images/dolibarr_256x256_black.png
  55. BIN
      doc/images/dolibarr_256x256_color.png
  56. BIN
      doc/images/dolibarr_256x256_white.png
  57. BIN
      doc/images/dolibarr_512x512_color.png
  58. BIN
      doc/images/dolibarr_logo.png
  59. BIN
      doc/images/dolibarr_screenshot11_1024x768.jpg
  60. BIN
      doc/images/dolibarr_screenshot12_1920x1080.jpg
  61. BIN
      doc/images/dolibarr_screenshot1_1280x800.jpg
  62. BIN
      doc/images/dolibarr_screenshot1_1920x1080.jpg
  63. BIN
      doc/images/dolibarr_screenshot2_1280x800.jpg
  64. BIN
      doc/images/dolibarr_screenshot3_1280x800.png
  65. BIN
      doc/images/dolibarr_screenshot5_1280x800.jpg
  66. BIN
      doc/images/invoice.png
  67. 13 8
      htdocs/accountancy/admin/account.php
  68. 7 12
      htdocs/accountancy/admin/accountmodel.php
  69. 2 2
      htdocs/accountancy/admin/card.php
  70. 8 19
      htdocs/accountancy/admin/categories_list.php
  71. 2 2
      htdocs/accountancy/admin/export.php
  72. 0 1
      htdocs/accountancy/admin/fiscalyear.php
  73. 14 52
      htdocs/accountancy/admin/journals_list.php
  74. 39 13
      htdocs/accountancy/admin/productaccount.php
  75. 3 3
      htdocs/accountancy/bookkeeping/balance.php
  76. 38 11
      htdocs/accountancy/bookkeeping/card.php
  77. 40 30
      htdocs/accountancy/bookkeeping/list.php
  78. 3 3
      htdocs/accountancy/bookkeeping/listbyaccount.php
  79. 3 3
      htdocs/accountancy/bookkeeping/listbysubaccount.php
  80. 0 325
      htdocs/accountancy/bookkeeping/thirdparty_lettering_customer.php
  81. 0 322
      htdocs/accountancy/bookkeeping/thirdparty_lettering_supplier.php
  82. 11 7
      htdocs/accountancy/class/accountancyexport.class.php
  83. 78 37
      htdocs/accountancy/class/accountancyimport.class.php
  84. 44 50
      htdocs/accountancy/class/accountingaccount.class.php
  85. 705 2
      htdocs/accountancy/class/accountingjournal.class.php
  86. 52 24
      htdocs/accountancy/class/bookkeeping.class.php
  87. 34 39
      htdocs/accountancy/closure/index.php
  88. 114 49
      htdocs/accountancy/customer/index.php
  89. 7 7
      htdocs/accountancy/customer/lines.php
  90. 11 27
      htdocs/accountancy/customer/list.php
  91. 90 29
      htdocs/accountancy/expensereport/index.php
  92. 50 27
      htdocs/accountancy/expensereport/lines.php
  93. 102 46
      htdocs/accountancy/expensereport/list.php
  94. 59 57
      htdocs/accountancy/index.php
  95. 1 1
      htdocs/accountancy/journal/bankjournal.php
  96. 1 1
      htdocs/accountancy/journal/expensereportsjournal.php
  97. 1 1
      htdocs/accountancy/journal/purchasesjournal.php
  98. 15 3
      htdocs/accountancy/journal/sellsjournal.php
  99. 313 0
      htdocs/accountancy/journal/variousjournal.php
  100. 87 29
      htdocs/accountancy/supplier/index.php

+ 3 - 2
.github/CONTRIBUTING.md

@@ -26,8 +26,9 @@ Default **language here is english**. So please prepare your contributions in en
 1. [Fork](https://help.github.com/articles/fork-a-repo) the [GitHub repository](https://github.com/Dolibarr/dolibarr).
 2. Clone your fork.
 3. Choose a branch(See the [Branches](#branches) section below).
-4. Commit and push your changes.
-5. [Make a pull request](https://help.github.com/articles/creating-a-pull-request).
+4. Read our developer documentation on the [Dolibarr Wiki](https://wiki.dolibarr.org/index.php?title=Developer_documentation).
+5. Commit and push your changes.
+6. [Make a pull request](https://help.github.com/articles/creating-a-pull-request).
 
 <span id="branches" name="branches"></span>
 ### Branches

+ 0 - 35
.github/ISSUE_TEMPLATE/bug_report.md

@@ -1,35 +0,0 @@
----
-name: Bug report
-about: Create a report to help us fix something that is broken
-title: ''
-labels: Bug
-assignees: ''
-
----
-
-# Instructions
-*This is a template to help you report good issues. You may use [Github Markdown](https://help.github.com/articles/getting-started-with-writing-and-formatting-on-github/) syntax to format your issue report.*
-*Please:*
-- *replace the bracket enclosed texts with meaningful information*
-- *remove any unused sub-section*
-
-
-# Bug
-[*Short description*]
-
-## Environment
-- **Version**: [*Affected Dolibarr version(s)*]
-- **OS**: [*Server OS type and version*]
-- **Web server**: [*Webserver type and version*]
-- **PHP**: [*PHP version*]
-- **Database**: [*Database type and version*]
-- **URL(s)**: [*Affected URL(s)*]
-
-## Expected and actual behavior
-[*Verbose description*]
-
-## Steps to reproduce the behavior
-[*Verbose description*]
-
-## [Attached files](https://help.github.com/articles/issue-attachments) (Screenshots, screencasts, dolibarr.log, debugging informations…)
-[*Files*]

+ 71 - 0
.github/ISSUE_TEMPLATE/bug_report.yml

@@ -0,0 +1,71 @@
+name: Bug report
+description: Create a report to help us fix something that is broken
+labels: ["Bug"]
+
+body:
+  - type: markdown
+    attributes:
+      value: |
+        This is a template to help you report good issues. You may use [Github Markdown](https://help.github.com/articles/getting-started-with-writing-and-formatting-on-github/) syntax to format your issue report.
+        
+  - type: textarea
+    id: bug
+    attributes:
+      label: Bug
+      description: Please give a short description of the bug
+    validations:
+      required: true
+
+  - type: input
+    id: environment-version
+    attributes:
+      label: Environment Version
+      description: Affected Dolibarr version(s)
+
+  - type: input
+    id: environment-os
+    attributes:
+      label: Environment OS
+      description: Server OS type and version
+      
+  - type: input
+    id: environment-webserver
+    attributes:
+      label: Environment Web server
+      description: Webserver type and version
+      
+  - type: input
+    id: environment-php
+    attributes:
+      label: Environment PHP
+      description: PHP version
+      
+  - type: input
+    id: environment-database
+    attributes:
+      label: Environment Database
+      description: Database type and version
+
+  - type: input
+    id: environment-urls
+    attributes:
+      label: Environment URL(s)
+      description: Affected URL(s)
+
+  - type: textarea
+    id: expected-behaviour
+    attributes:
+      label: Expected and actual behavior
+      description: Verbose description
+
+  - type: textarea
+    id: reproduce
+    attributes:
+      label: Steps to reproduce the behavior
+      description: Verbose description
+      
+  - type: textarea
+    id: files
+    attributes:
+      label: Attached files
+      description: Screenshots, screencasts, dolibarr.log, debugging informations

+ 0 - 27
.github/ISSUE_TEMPLATE/feature_request.md

@@ -1,27 +0,0 @@
----
-name: Feature request
-about: Suggest a new idea for this project
-title: ''
-labels: Feature request
-assignees: ''
-
----
-
-# Instructions
-*This is a template to help you report good issues. You may use [Github Markdown](https://help.github.com/articles/getting-started-with-writing-and-formatting-on-github/) syntax to format your issue report.*
-*Please:*
-- *replace the bracket enclosed texts with meaningful information*
-- *remove any unused sub-section*
-
-
-# Feature Request
-[*Short description*]
-
-## Use case
-[*Verbose description*]
-
-## Suggested implementation
-[*Verbose description*]
-
-## Suggested steps
-[*List of tasks to achieve goal*]

+ 35 - 0
.github/ISSUE_TEMPLATE/feature_request.yml

@@ -0,0 +1,35 @@
+name: Feature request
+description: Suggest a new idea for this project
+labels: ["Feature request"]
+
+body:
+  - type: markdown
+    attributes:
+      value: |
+        This is a template to help you report good issues. You may use [Github Markdown](https://help.github.com/articles/getting-started-with-writing-and-formatting-on-github/) syntax to format your issue report.
+
+  - type: textarea
+    id: feature-request
+    attributes:
+      label: Feature Request
+      description: Short description
+    validations:
+      required: true
+
+  - type: textarea
+    id: use-case
+    attributes:
+      label: Use case
+      description: Verbose description
+      
+  - type: textarea
+    id: suggested-implementation
+    attributes:
+      label: Suggested implementation
+      description: Verbose description
+
+  - type: textarea
+    id: suggested-steps
+    attributes:
+      label: Suggested steps
+      description: List of tasks to achieve goal

+ 1 - 0
.gitignore

@@ -55,3 +55,4 @@ yarn.lock
 package-lock.json
 
 doc/install.lock
+/factory/

+ 12 - 6
.travis.yml

@@ -50,14 +50,14 @@ jobs:
       env: DB=postgresql
     - stage: PHP 5.6-7.4
       if: type = pull_request OR type = push
-      php: '7.4'
+      php: '7.4.22'
       env: DB=mysql
     - stage: PHP Dev
       if: type = push AND branch = develop
       php: nightly 
       env: DB=mysql
     - stage: PHP Dev
-      if: type = push AND branch = 14.0
+      if: type = push AND branch = 15.0
       php: nightly 
       env: DB=mysql
 
@@ -106,7 +106,7 @@ install:
                         php-parallel-lint/php-console-highlighter ^0 \
                         squizlabs/php_codesniffer ^3
   fi
-  if [ "$TRAVIS_PHP_VERSION" = '7.3' ] || [ "$TRAVIS_PHP_VERSION" = '7.4' ]; then
+  if [ "$TRAVIS_PHP_VERSION" = '7.3' ] || [ "$TRAVIS_PHP_VERSION" = '7.4' ] || [ "$TRAVIS_PHP_VERSION" = '7.4.22' ]; then
     composer -n require phpunit/phpunit ^7 \
                         php-parallel-lint/php-parallel-lint ^1.2 \
                         php-parallel-lint/php-console-highlighter ^0 \
@@ -241,7 +241,7 @@ before_script:
   # enable php-fpm
   - sudo cp ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf.default ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf
   - |
-    if [ "$TRAVIS_PHP_VERSION" = '7.0' ] || [ "$TRAVIS_PHP_VERSION" = '7.1' ] || [ "$TRAVIS_PHP_VERSION" = '7.2' ] || [ "$TRAVIS_PHP_VERSION" = '7.3' ] || [ "$TRAVIS_PHP_VERSION" = '7.4' ] || [ "$TRAVIS_PHP_VERSION" = 'nightly' ]; then
+    if [ "$TRAVIS_PHP_VERSION" = '7.0' ] || [ "$TRAVIS_PHP_VERSION" = '7.1' ] || [ "$TRAVIS_PHP_VERSION" = '7.2' ] || [ "$TRAVIS_PHP_VERSION" = '7.3' ] || [ "$TRAVIS_PHP_VERSION" = '7.4' ] || [ "$TRAVIS_PHP_VERSION" = '7.4.22' ] || [ "$TRAVIS_PHP_VERSION" = 'nightly' ]; then
       # Copy the included pool
       sudo cp ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.d/www.conf.default ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.d/www.conf
     fi
@@ -276,7 +276,7 @@ script:
   set -e
   #parallel-lint --exclude htdocs/includes --blame .
   # Exclusions are defined in the ruleset.xml file
-  if [ "$TRAVIS_PHP_VERSION" = "7.4" ]; then
+  if [ "$TRAVIS_PHP_VERSION" = "7.4.22" ]; then
     parallel-lint -e php --exclude dev/tools/test/namespacemig --exclude htdocs/includes/composer --exclude htdocs/includes/myclabs --exclude htdocs/includes/phpspec --exclude dev/initdata/dbf/includes \
       --exclude htdocs/includes/sabre --exclude htdocs/includes/phpoffice/PhpSpreadsheet --exclude htdocs/includes/sebastian \
       --exclude htdocs/includes/squizlabs/php_codesniffer --exclude htdocs/includes/jakub-onderka --exclude htdocs/includes/php-parallel-lint --exclude htdocs/includes/symfony \
@@ -291,7 +291,7 @@ script:
   # Ensure we catch errors
   set -e
   # Exclusions are defined in the ruleset.xml file
-  if [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$TRAVIS_PHP_VERSION" = "7.4" ]; then
+  if [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$TRAVIS_PHP_VERSION" = "7.4.22" ]; then
     phpcs -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 .;
   fi
   set +e
@@ -411,6 +411,12 @@ script:
   php upgrade.php 13.0.0 14.0.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade13001400.log
   php upgrade2.php 13.0.0 14.0.0 > $TRAVIS_BUILD_DIR/upgrade13001400-2.log
   php step5.php 13.0.0 14.0.0 > $TRAVIS_BUILD_DIR/upgrade13001400-3.log
+  php upgrade.php 14.0.0 15.0.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade14001500.log
+  php upgrade2.php 14.0.0 15.0.0 > $TRAVIS_BUILD_DIR/upgrade14001500-2.log
+  php step5.php 14.0.0 15.0.0 > $TRAVIS_BUILD_DIR/upgrade14001500-3.log
+  php upgrade.php 15.0.0 16.0.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade15001600.log
+  php upgrade2.php 15.0.0 16.0.0 > $TRAVIS_BUILD_DIR/upgrade15001600-2.log
+  php step5.php 15.0.0 16.0.0 > $TRAVIS_BUILD_DIR/upgrade15001600-3.log
   ls -alrt $TRAVIS_BUILD_DIR/
 
 - |

+ 5 - 5
COPYRIGHT

@@ -28,7 +28,7 @@ CKEditor               4.12.1        LGPL-2.1+                   Yes
 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
 GeoIP2                 0.2.0         Apache License 2.0          Yes             Lib to make geoip convert
-Mobiledetect           2.8.34        MIT License                 Yes             Detect mobile devices browsers
+Mobiledetect           2.8.39        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
@@ -48,10 +48,10 @@ TCPDF                  6.3.2         LGPL-3+                     Yes
 TCPDI                  1.0.0         LGPL-3+ / Apache 2.0        Yes             FPDI replacement
 
 JS libraries:
-Ace                    1.4.8         BSD                         Yes             JS library to get code syntaxique coloration in a textarea.
-ChartJS                2.9.4         MIT License                 Yes             JS library for graph
-jQuery                 3.5.1         MIT License                 Yes             JS library
-jQuery UI              1.12.1        GPL and MIT License         Yes             JS library plugin UI
+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
+jQuery                 3.6.0         MIT License                 Yes             JS library
+jQuery UI              1.13.1        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)
 jQuery Colorpicker     1.1           MIT License                 Yes             JS library for color picker for a defined list of colors

+ 240 - 7
ChangeLog

@@ -3,6 +3,66 @@ English Dolibarr ChangeLog
 --------------------------------------------------------------
 
 
+
+***** ChangeLog for 16.0.0 compared to 15.0.0 *****
+
+For users:
+---------------
+
+NEW: ...
+
+
+ Modules
+NEW: Experimental module Event Organization Management
+NEW: Experimental module Workstations Management
+NEW: Experimental module Partnership Management
+
+
+For developers:
+---------------
+
+NEW: A lot of addition of hooks.
+
+
+ 
+Following changes may create regressions for some external modules, but were necessary to make Dolibarr better:
+* The default value for MAIN_SECURITY_CSRF_WITH_TOKEN has been set to 2. It means any POST and any GET request that contains the "action" or "massaction"
+  with a value of a sensitive action must also a valid token parameter (With previous value 1, only POST was concerned). Note: With value 3, any URL
+  with parameter "action" or "massaction" need the token, whatever is the value of the action.
+* verifCond('stringtoevaluate') now return false when string contains a bad syntax content instead of true. 
+* The deprecated method thirdparty_doc_create() has been removed. You can use the generateDocument() instead.
+* All triggers with a name XXX_UPDATE have been rename with name XXX_MODIFY for code consistency purpose.
+* Rename build_path_from_id_categ() into buildPathFromId() and set method to private
+
+
+***** ChangeLog for 15.0.1 compared to 15.0.0 *****
+FIX: #19777 #20281
+FIX: bad position of extrafields for interventions
+FIX: Blocking situation when a payment was deleted in bank.
+FIX: creation of the shipment if order contains services
+FIX: Drag and drop line of files on join files tab
+FIX: Error management on mass action "Approve holiday"
+FIX: error with php8
+FIX: in case of VAT refund, negative amount must be allowed
+FIX: invoice pdf: lines originating from deposits were not detailed anymore
+FIX: Invoice - When you create an invoice for a given thirdparty, fk_account is not retrieved from company card
+FIX: list of visible type of event was not correctly filtered
+FIX: Missing or bad permissions
+FIX: Missing the field date start/end in export supplier invoice/order
+FIX: On large proposal or invoice, fix n(n+1) sql into a n sql.
+FIX: options should not exists on invoices
+FIX: payment not completed when using Paypal.
+FIX: permission to download files of expense report with readall.
+FIX- Preview icon in documents list PDF in the admin page third-party
+FIX: shipping list, e.shipping_method_id should be e.fk_shipping_method.
+FIX: Show product photo on Supplier order Cornas model.
+FIX: User name in ManufacturingOrder
+FIX: viewimage.php blocks requests with multicompany from other enties
+FIX: #yogosha9048
+FIX: #yogosha9054
+FIX: #yogosha9095
+
+
 ***** ChangeLog for 15.0.0 compared to 14.0.0 *****
 
 For users:
@@ -10,12 +70,12 @@ For users:
 
 NEW: Online proposal signature
 NEW: Can define some max limit on expense report (per period, per type or expense, ...)
+NEW: Provide a special pages for bookmarks and multicompany for a better use of some mobile applications (like DoliDroid) 
 NEW: Allow the use of __NEWREF__ to get for example the new reference a draft order will get after validation.
 NEW: Add option to disable globaly some notifications emails.
-NEW: #18326 Workflow: Close order on shipment closing.
 NEW: #18401 Add __NEWREF__ subtitute to get new object reference.
 NEW: #18403 Add __URL_SHIPMENT__ substitute to get the URL of a shipment
-NEW: #18689 REST API module: add api key generate / modify right.
+NEW: #18689 REST API module: add api key generate / modify permission.
 NEW: #18663 Make "L'Annuaire des Entreprises" the default provider for SIREN verification for French thirdparties.
 NEW: #18046 Add tags on ticket/categories
 NEW: #18326 Workflow: Close order on shipment closing.
@@ -105,6 +165,15 @@ NEW: Use an ajax call for the clicktodial feature instead of href link.
 NEW: when multiple order linked to facture, show list into note.
 NEW: when we delete several objects with massaction, if somes object has child we must see which objects are concerned and nevertheless delete objects which can be deleted
 NEW: Editing a page in website module keep old page with name .back
+NEW: External backups can be downloaded from the "About info page".
+NEW: Add massaction to switch status on sale / on purchase of a product.
+
+
+ Modules
+NEW: Stable module Knowledge Management
+NEW: Experimental module Event Organization Management
+NEW: Experimental module Workstations Management
+NEW: Development of module Partnership Management
 
 
 For developers:
@@ -114,6 +183,7 @@ NEW: Introduce method hasRight
 NEW: Can use textarea field into a confirm popup.
 NEW: Can use the result_mode of mysqli driver. Save memory for list count
 NEW: #18319 REST API - Shipment: Add 'close' action / endpoint / POST method.
+NEW: Add API /approve and /makeOrder for purchase orders. 
 NEW: add action trigger for member excluded
 NEW: add option MAIN_IBAN_IS_NEVER_MANDATORY, MAIN_IBAN_NOT_MANDATORY, PROPAL_NOT_BILLABLE, PROPAL_REOPEN_UNSIGNED_ONLY, PROPOSAL_ARE_NOT_BILLABLE, TICKETS_MESSAGE_FORCE_MAIL
 NEW: Add code codebar column on serial/lot structure
@@ -131,17 +201,179 @@ NEW: printFieldListFrom hook call on several lists
 NEW: Use lang selector when using a field key 'lang' in modulebuilder
 NEW: we need to be able to put more filters on deleteByParentField() function
 NEW: make it easier to set the `keyword`, `keywords` and `description` attributes of an ecm file object
-
-
+NEW: Experimental feature to manage user sessions in database
+NEW: Hidden option API_DISABLE_COMPRESSION is now visible in API setup page.
+NEW: Add hook printUnderHeaderPDFline on invoice PDF templates (can be used for example to add a barcode or more information on header of invoices).
  
 Following changes may create regressions for some external modules, but were necessary to make Dolibarr better:
+* ALL EXTERNAL MODULES THAT WERE NOT CORRECTLY DEVELOPPED WILL NOT WORK ON V15 (All modules that forgot to manage the security token field 
+  into forms will be broken. The security token field is expected since Dolibarr v9 but a lot of external modules did not implement it).
 * Update hook 'printOriginObjectLine', removed check on product type and special code. Need now reshook.
 * Old deprecated module "SimplePOS" has been completely removed. Use module "TakePOS" is you need a Point Of Sale.
 * The method static ActionComm::getActions($db, ...) is no more static. Use $actioncomm->getActions(...) instead (without $db param). 
 * The 'action=delete&file=...' has been replaced with 'action=deletefile&file=...' to avoid confusion with deletion of object lines.
 * Method getDictvalue has been renamed into getDictionaryValue to match camel case rule.
-
-
+* To execute shell or command line command, your code must never use method like exec, shell_exec, popen, .. but must use the built-in 
+  method executeCLI() available into core/class/utils.class.php  
+* Class file expeditionbatch.class.php renamed to expeditionlinebatch.class.php
+* ExpeditionLineBatch::fetchAll is not static anymore and first parameter $db is removed
+* ExtraFields->showOutputField parameter 4 'extrafieldsobjectkey' is now required
+* CommonObject method add_object_linked now sets targettype to 'mymodule_myobject' instead of 'myobject', 
+  you can use hook 'setLinkedObjectSourceTargetType' to set your usual targettype
+
+
+***** ChangeLog for 14.0.5 compared to 14.0.4 *****
+
+FIX: 13.0: printFieldListWhere called twice on same query
+FIX: 14.0.4 fatal error on cron list.
+FIX: #19476
+FIX: #19564
+FIX: #19651
+FIX: Accountancy - SQL error on subledger account search in journal
+FIX: apply eldy's suggestion to not overwrite existing extrafields of $line
+FIX: Can't close a down payment if paid with credit notes.
+FIX: better compatibility with multicompany
+FIX: contact card: bad colspan value for separator extrafield in creation/modification form
+FIX: discounts are applied both when fetching the best supplier price and when displaying it
+FIX: double display for contact categorie on societe create card
+FIX: fatal error on cron list.
+FIX: holiday list: only mass delete if leave request is not in draft, canceled or refused, like in card
+FIX: holiday mass deletion: correct return of record deleted
+FIX: Holiday month report
+FIX: info tab on customer invoice record not found
+FIX: line extrafields are inoperative in dispatch cards even when they exist
+FIX: list of categories in stats of supplier invoices
+FIX: missing default value for more comprehensive
+FIX: multicurrency: fields in discount unitialized when creating deposit
+FIX: Navigation on bank transaction list
+FIX: Can't edit a bank transaction due to bad permission check.
+FIX: Option MAIN_DIRECT_STATUS_UPDATE broken. Ajax on/off not saving value in DB after updating to version >=12
+FIX: postgresql compatibility, "" as is not authorized
+FIX: printFieldListWhere called twice (at different locations) for the same SQL query, can result in syntax errors
+FIX: select too large into addrights (pb of missing parenthesis)
+FIX: set optional from post, we can't untick boolean field on product card
+FIX: Take into consideration work leave over serveral months
+FIX: test if method exist on wrong object
+FIX: title for nature of third party in company list
+FIX: Urgent onglet contact inaccessible depuis une facture
+FIX: wrong syntax of sql request
+
+***** ChangeLog for 14.0.4 compared to 14.0.3 *****
+
+FIX: $totalarray is overwritten, totals were lost
+FIX: 13.0 - due to a typo in the 'mode' parameter, the "first name" column of the list of members displays the full name
+FIX: 13.0: end date required to edit a ticket message
+FIX: 13.0 feedback of PR #18993: make ticket messages punctual events with attr percentage = -1
+FIX: 13.0 PR #18993: add comment on modified part
+FIX: 13.0: sometimes firstname was mistyped as fistname
+FIX: 14.0 - civility field of private third party creation form has inadequate width
+FIX: 14.0 - civility field width inadequate due to select2 calculating the width while the field has no width (display: none)
+FIX: 14.0 - due to a typo in the 'mode' parameter, the "first name" co…
+FIX: #18634 : Problem of virtual stock with reception module enabled
+FIX: #18695 Added ref_ext to supplier invoice
+FIX: #18698 Supplier invoice list - "alert" checkbox not working
+FIX: #18735
+FIX: #18767 : Adherent delete
+FIX: #18797
+FIX: #18854
+FIX: #18875 in v14
+FIX: #18910
+FIX: #18910 : MRP List SQL query syntax error with more than one extrafileds.
+FIX: #18912 Accountancy - SQL error when custom group is added without country defined
+FIX: #18934 on-registration in the extrafieldsline database for deliveries
+FIX: #18968
+FIX: #19008
+FIX: #19014 - the properties of some fields are not updated when you submit the form
+FIX: #19210
+FIX: #19214 : PostgreSQL error on admin/limits.php
+FIX: #19241 Project - Fix display salary in overview
+FIX: #19305
+FIX: 2 columns for total labels
+FIX: Accountancy - Format Quadra export - Missing line type C to create automaticly a subledger account with label
+FIX: Accountancy - If deposit invoice is used, force binding in deposit accounting account to solve transaction
+FIX: Accountancy - Missing specific filename for export on format FEC2, Ciel & repare it
+FIX: Accountancy - Option of export popup are inverted
+FIX: Accountancy - PHP8
+FIX: Accountancy - Product admin - SQL error when we affect accounting account with product_perentity activated
+FIX: Accountancy simplified - Salaries are not present in report
+FIX: Accountancy - Some correction on export name
+FIX: Accountancy - Trunc code_journal to 2 in format XIMPORT (Ciel, Sage50)
+FIX: add warehouse in projects' overview count
+FIX: also on customer index for automatic binding
+FIX: Attachment of pdf into shipment when sending email
+FIX: autocalculation of the supplier price in main currency.
+FIX: avoid warning if $categories is an id
+FIX: bad sign of amount stored for multicurrency columns on credit notes
+FIX: Bad use of a forced contact of another company on PDF/ODT documents
+FIX: Bad use of dol_concatdesc()
+FIX: Button text on proposal card for create a invoice
+FIX: calculateCosts of BOM must not be included into fetch
+FIX: calculation of balance in conciliation page on desc sorting.
+FIX: card.php
+FIX: Change date format of the inventorycode to be equal as mass stock transfert
+FIX: check if greater 0
+FIX: close cash with some terminals in TakePOS
+FIX: compatibility with Multicompany
+FIX: consistent UX when calling a tab from the invoice card with empty ref/id
+FIX: default language defined for IN country
+FIX: Expense report - In edit mode, field qty doesn't accept decimal unlike the create mode
+FIX: fetch of product with modulebuilder load too much data
+FIX: filter for export of accounting documents
+FIX: Filter on categories
+FIX: generate documents with PDF options
+FIX: indentation
+FIX: init hookmanager after loading $conf values
+FIX: invoice: inpossible to create an invoice because of very bad check + warnings when trying to print tabs for invoice with no ID
+FIX: legal issue on expense report pdf (must also show price without tax)
+FIX: list of categories in stats of supplier invoices
+FIX: load tranlate array after setting lang
+FIX: lost superadmin grade after edit user card
+FIX: missing filter status=1 on rss feeds
+FIX: missing permission check reported by me@lainwir3d.net on product api
+FIX: missing return status
+FIX: missing sql filter by entity
+FIX: move fetch_optionnal into $ac_static->fetch()
+FIX: only a superadmin can modify entity
+FIX: only ones value is return for dictionaries
+FIX: optional visibility on create card
+FIX: payment style and html5 tags
+FIX: payment using wrong type in takepos when too many payment mode
+FIX: PR#18931 Remove useless explicit call to dol_shutdown
+FIX: Product accountancy affectation with product_perentity activated (PR #18620)
+FIX: products/services card: hidden extrafields were overridden
+FIX: project task list: extrafields could not be displayed
+FIX: Propal list - Problem of pagination on date
+FIX: reload user lang
+FIX: Remove not complete order from the virtual stock
+FIX: Replenish: SQL error when no warehouse has been created + Warning when there are no warehouses
+FIX: resource list : Use standard code to handle list filters
+FIX: restrictedArea for payment delete
+FIX: Ret PR
+FIX: second approval back in stable feature as is the setting for minimum amount (last part from PR#14286)
+FIX: selected lines on supplier invoice create
+FIX: Selection of type "people" for membership must hide the company
+FIX: select list of orders not complete when field type of company is on
+FIX: show end hours in events linked to objects
+FIX: support of localtax on expense report
+FIX: task time: can't filter by user with pgsql + show error message
+FIX: task time: keep on using natural_search
+FIX: tcpdf vulnerability to roman numeral bomb, cf. tecnickom/TCPDF issue #315
+FIX: Test when date of invoie is in future (pb with TZ and offset)
+FIX: Ticket - Card - Wrong font awesome library
+FIX: Ticket - Duplicate field project when we create ticket from project
+FIX: translation into email for member at membership validation.
+FIX: Travis Sanitize SQL
+FIX: unprivileged user can see task associated with a not allowed project
+FIX: URGENT: impossible to create an invoice
+FIX: Use of accent into filename of GED
+FIX: user date timezone offset
+FIX: User salary card - translation problem
+FIX: user without permission can set ticket subject
+FIX: We need a default price base type in variant creation case with multiprices when parent has been created with only one level price
+FIX: wrong array key value
+FIX: wrong check
+FIX: wrong position of error message
+Sync transifex.
 
 ***** ChangeLog for 14.0.3 compared to 14.0.2 *****
 
@@ -497,7 +729,8 @@ Following changes may create regressions for some external modules, but were nec
 * Removed deprecated substitution key  __REFCLIENT__  (replaced with __REF_CLIENT__)
 * Removed constant MAIN_COUNTRIES_IN_EEC. You can now set if country is in Europe or not from the dictionary of countries.
 * v14 seems to work correctly on PHP v8 but it generates a lot of verbose warnings. Currently, v14 i snot yet officialy supported with PHP 8.
-
+* To execute shell or command line command, your code must never use method like exec, shell_exec, popen, .. but must use the built-in 
+  method executeCLI() available into core/class/utils.class.php  
 
 
 ***** ChangeLog for 13.0.5 compared to 13.0.4 *****

+ 1 - 0
README.md

@@ -4,6 +4,7 @@
 ![Build status](https://img.shields.io/travis/Dolibarr/dolibarr/develop.svg)
 [![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%205.6-8892BF.svg?style=flat-square)](https://php.net/)
 [![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)
 
 Dolibarr ERP & CRM is a modern software package that helps manage your organization's activity (contacts, suppliers, invoices, orders, stocks, agenda…).
 

+ 9 - 8
SECURITY.md

@@ -6,17 +6,18 @@ This file contains some policies about the security reports on Dolibarr ERP CRM
 
 | Version    | Supported              |
 | ---------- | ---------------------- |
-| <= 14.0.1  | :x:                    |
-| >= 14.0.2+ | :white_check_mark: except CSRF attacks|
+| <= 14.0.4  | :x:                    |
+| >= 14.0.5+ | :white_check_mark: except CSRF attacks|
 | >= develop | :white_check_mark:     |
 
 ## Reporting a Vulnerability
 
-To report a vulnerability, please use GitHub security advisory at [https://github.com/Dolibarr/dolibarr/security/advisories/new](https://github.com/Dolibarr/dolibarr/security/advisories/new) (if you have permissions) or alternatively send an email to security@dolibarr.org (for everybody)
+To report a vulnerability, for a private report, please use GitHub security advisory at [https://github.com/Dolibarr/dolibarr/security/advisories/new](https://github.com/Dolibarr/dolibarr/security/advisories/new) (if you have permissions).
+Alternatively send an email to security@dolibarr.org (for everybody)
 
 ## Hunting vulnerabilities on Dolibarr
 
-We believe that future of software is online SaaS. This means software are more and more critical and no technology is perfect. Working with skilled security researchers is crucial in identifying weaknesses in our technology.
+We believe that the future of software is online SaaS. This means software are more and more critical and no technology is perfect. Working with skilled security researchers is crucial in identifying weaknesses in our technology.
 
 If you believe you've found a security bug in our service, we are happy to work with you to resolve the issue promptly and ensure you are fairly rewarded for your discovery.
 
@@ -34,13 +35,13 @@ You can install the web application yourself on your own platform/server so you
 
 ## Eligibility and Responsible Disclosure
 
-We are happy to thank everyone who submits valid reports which help us improve the security of Dolibarr however, only those that meet the following eligibility requirements will be "validated reports" (if not, we may close the report without any answer):
+We are happy to thank everyone who submits valid reports which help us improve the security of Dolibarr, however only those that meet the following eligibility requirements will be "validated reports" (if not, we may close the report without any answer):
 
 You must be the first reporter of the vulnerability (duplicate reports are closed).
 
 You must send a clear textual description of the report along with steps to reproduce the issue, include attachments such as screenshots or proof of concept code as necessary.
 
-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 softwate on your own platform.
+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.
 
 You must not leak, manipulate, or destroy any user data of third parties to find your vulnerability.
 
@@ -55,7 +56,7 @@ ONLY vulnerabilities discovered, when the following setup on test platform is us
 * The module DebugBar and ModuleBuilder must NOT be enabled (by default, these modules are not enabled. They are developer tools)
 * 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 only the documents directory is in write mode. The root directory called htdocs must be readonly.
+* 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.
 * 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.
 
@@ -66,7 +67,7 @@ Scope is the web application (back office) and the APIs.
 * Remote code execution (RCE)
 * Local files access and manipulation (LFI, RFI, XXE, SSRF, XSPA)
 * Code injections (HTML, JS, SQL, PHP, ...)
-* Cross-Site Scripting (XSS), except from setup page of module "External web site" (allowing any content here, editable by admin user only, is accepted on purpose or into module "Web site" when permission to edit website content is allowed).
+* Cross-Site Scripting (XSS), except from setup page of module "External web site" (allowing any content here, editable by admin user only, is accepted on purpose) and except into module "Web site" when permission to edit website content is allowed (injecting any data in this case is allowed too).
 * Cross-Site Requests Forgery (CSRF) with real security impact (when using GET URLs, CSRF are qualified only for creating, updating or deleting data from pages restricted to admin users)
 * Open redirect
 * Broken authentication & session management

+ 7 - 9
build/docker/Dockerfile

@@ -39,15 +39,13 @@ RUN chmod +x /usr/local/bin/docker-run.sh
 
 RUN pecl install xdebug && docker-php-ext-enable xdebug
 RUN echo 'zend_extension="/usr/local/lib/php/extensions/no-debug-non-zts-20180731/xdebug.so"' >> ${PHP_INI_DIR}/php.ini
-RUN echo 'xdebug.remote_autostart=1' >> ${PHP_INI_DIR}/php.ini
-RUN echo 'xdebug.remote_enable=1' >> ${PHP_INI_DIR}/php.ini
-RUN echo 'xdebug.default_enable=1' >> ${PHP_INI_DIR}/php.ini
-#RUN echo 'xdebug.remote_host=docker.host' >> ${PHP_INI_DIR}/php.ini
-RUN echo 'xdebug.remote_port=9000' >> ${PHP_INI_DIR}/php.ini
-RUN echo 'xdebug.remote_connect_back=1' >> ${PHP_INI_DIR}/php.ini
-RUN echo 'xdebug.profiler_enable=0' >> ${PHP_INI_DIR}/php.ini
-RUN echo 'xdebug.remote_log="/tmp/xdebug.log"' >> ${PHP_INI_DIR}/php.ini
-#RUN echo 'localhost docker.host' >> /etc/hosts
+RUN echo 'xdebug.mode=debug' >> ${PHP_INI_DIR}/php.ini
+RUN echo 'xdebug.start_with_request=yes' >> ${PHP_INI_DIR}/php.ini
+RUN echo 'xdebug.client_host=host.docker.internal' >> ${PHP_INI_DIR}/php.ini
+RUN echo 'xdebug.client_port=9003' >> ${PHP_INI_DIR}/php.ini
+RUN echo 'xdebug.discover_client_host=true' >> ${PHP_INI_DIR}/php.ini
+#RUN echo 'xdebug.log="/tmp/xdebug.log"' >> ${PHP_INI_DIR}/php.ini
+RUN echo 'xdebug.idekey="netbeans-xdebug"' >> ${PHP_INI_DIR}/php.ini
 
 # set up sendmail config, to use maildev
 RUN echo "account default" > /etc/msmtprc

+ 1 - 0
build/docker/docker-compose.yml

@@ -48,6 +48,7 @@ services:
             - external-pod
         extra_hosts:
             - "localhost.localdomain:127.0.0.1"
+            - "host.docker.internal:host-gateway"
 
     mail:
         image: maildev/maildev

+ 1 - 0
build/pad/README

@@ -1,6 +1,7 @@
 README (English)
 ##################################################
 Building PAD files
+http://pad.asp-software.org/padgen.php
 ##################################################
 
 This directory contains files and docs used to build

+ 3 - 1
build/perl/virtualmin/dolibarr.pl

@@ -30,7 +30,7 @@ return "Regis Houssin";
 # script_dolibarr_versions()
 sub script_dolibarr_versions
 {
-return ( "12.0.3", "11.0.5", "10.0.7", "9.0.4", "8.0.6", "7.0.5" );
+return ( "14.0.5", "13.0.5", "12.0.5", "11.0.5", "10.0.7", "9.0.4", "8.0.6", "7.0.5" );
 }
 
 sub script_dolibarr_release
@@ -400,6 +400,8 @@ sub script_dolibarr_check_latest
 {
 local ($ver) = @_;
 local @vers = &osdn_package_versions("dolibarr",
+				$ver >= 14.0 ? "dolibarr\\-(12\\.0\\.[0-9\\.]+)\\.tgz" :
+				$ver >= 13.0 ? "dolibarr\\-(12\\.0\\.[0-9\\.]+)\\.tgz" :
 				$ver >= 12.0 ? "dolibarr\\-(12\\.0\\.[0-9\\.]+)\\.tgz" :
 				$ver >= 11.0 ? "dolibarr\\-(11\\.0\\.[0-9\\.]+)\\.tgz" :
 				$ver >= 10.0 ? "dolibarr\\-(10\\.0\\.[0-9\\.]+)\\.tgz" :

+ 0 - 1
build/rpm/dolibarr_fedora.spec

@@ -166,7 +166,6 @@ done >>%{name}.lang
 %_datadir/dolibarr/htdocs/blockedlog
 %_datadir/dolibarr/htdocs/bookmarks
 %_datadir/dolibarr/htdocs/bom
-%_datadir/dolibarr/htdocs/cashdesk
 %_datadir/dolibarr/htdocs/categories
 %_datadir/dolibarr/htdocs/collab
 %_datadir/dolibarr/htdocs/comm

+ 0 - 1
build/rpm/dolibarr_generic.spec

@@ -247,7 +247,6 @@ done >>%{name}.lang
 %_datadir/dolibarr/htdocs/blockedlog
 %_datadir/dolibarr/htdocs/bookmarks
 %_datadir/dolibarr/htdocs/bom
-%_datadir/dolibarr/htdocs/cashdesk
 %_datadir/dolibarr/htdocs/categories
 %_datadir/dolibarr/htdocs/collab
 %_datadir/dolibarr/htdocs/comm

+ 0 - 1
build/rpm/dolibarr_mandriva.spec

@@ -163,7 +163,6 @@ done >>%{name}.lang
 %_datadir/dolibarr/htdocs/blockedlog
 %_datadir/dolibarr/htdocs/bookmarks
 %_datadir/dolibarr/htdocs/bom
-%_datadir/dolibarr/htdocs/cashdesk
 %_datadir/dolibarr/htdocs/categories
 %_datadir/dolibarr/htdocs/collab
 %_datadir/dolibarr/htdocs/comm

+ 0 - 1
build/rpm/dolibarr_opensuse.spec

@@ -174,7 +174,6 @@ done >>%{name}.lang
 %_datadir/dolibarr/htdocs/blockedlog
 %_datadir/dolibarr/htdocs/bookmarks
 %_datadir/dolibarr/htdocs/bom
-%_datadir/dolibarr/htdocs/cashdesk
 %_datadir/dolibarr/htdocs/categories
 %_datadir/dolibarr/htdocs/collab
 %_datadir/dolibarr/htdocs/comm

+ 2 - 2
composer.json

@@ -28,7 +28,7 @@
 		"ext-curl" : "*",
 		"ckeditor/ckeditor" : "4.12.1",
 		"mike42/escpos-php" : "2.2",
-		"mobiledetect/mobiledetectlib" : "2.8.34",
+		"mobiledetect/mobiledetectlib" : "2.8.39",
 		"phpoffice/phpexcel" : "1.8.2",
 		"restler/framework" : "3.0.0-RC6",
 		"tecnickcom/tcpdf" : "6.3.2",
@@ -59,4 +59,4 @@
 		"ext-zip" : "ODT, Excel and file compression support",
 		"ext-xml" : "Excel support"
 	}
-}
+}

+ 62 - 64
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "1dbd2d05cc0836acfca5988f29005cf2",
+    "content-hash": "ea80667c2a5aaabe388fbae4716d7dfb",
     "packages": [
         {
             "name": "ckeditor/ckeditor",
@@ -179,16 +179,16 @@
         },
         {
             "name": "mobiledetect/mobiledetectlib",
-            "version": "2.8.34",
+            "version": "2.8.39",
             "source": {
                 "type": "git",
                 "url": "https://github.com/serbanghita/Mobile-Detect.git",
-                "reference": "6f8113f57a508494ca36acbcfa2dc2d923c7ed5b"
+                "reference": "0fd6753003fc870f6e229bae869cc1337c99bc45"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/6f8113f57a508494ca36acbcfa2dc2d923c7ed5b",
-                "reference": "6f8113f57a508494ca36acbcfa2dc2d923c7ed5b",
+                "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/0fd6753003fc870f6e229bae869cc1337c99bc45",
+                "reference": "0fd6753003fc870f6e229bae869cc1337c99bc45",
                 "shasum": ""
             },
             "require": {
@@ -199,12 +199,12 @@
             },
             "type": "library",
             "autoload": {
-                "classmap": [
-                    "Mobile_Detect.php"
-                ],
                 "psr-0": {
                     "Detection": "namespaced/"
-                }
+                },
+                "classmap": [
+                    "Mobile_Detect.php"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -229,9 +229,9 @@
             ],
             "support": {
                 "issues": "https://github.com/serbanghita/Mobile-Detect/issues",
-                "source": "https://github.com/serbanghita/Mobile-Detect/tree/2.8.34"
+                "source": "https://github.com/serbanghita/Mobile-Detect/tree/2.8.39"
             },
-            "time": "2019-09-18T18:44:20+00:00"
+            "time": "2022-02-17T19:24:25+00:00"
         },
         {
             "name": "nnnick/chartjs",
@@ -279,56 +279,6 @@
             "time": "2020-10-19T12:22:11+00:00"
         },
         {
-            "name": "psr/log",
-            "version": "1.1.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/php-fig/log.git",
-                "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc",
-                "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.1.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Psr\\Log\\": "Psr/Log/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "PHP-FIG",
-                    "homepage": "http://www.php-fig.org/"
-                }
-            ],
-            "description": "Common interface for logging libraries",
-            "homepage": "https://github.com/php-fig/log",
-            "keywords": [
-                "log",
-                "psr",
-                "psr-3"
-            ],
-            "support": {
-                "source": "https://github.com/php-fig/log/tree/1.1.3"
-            },
-            "time": "2020-03-23T09:12:05+00:00"
-        },
- 		{
             "name": "phpoffice/phpexcel",
             "version": "1.8.2",
             "source": {
@@ -352,8 +302,6 @@
                 "squizlabs/php_codesniffer": "2.*"
             },
             "type": "library",
-            "autoload": {
-            },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
                 "LGPL-2.1"
@@ -384,7 +332,57 @@
             ],
             "abandoned": "phpoffice/phpspreadsheet",
             "time": "2018-11-22T23:07:24+00:00"
-        },        
+        },
+        {
+            "name": "psr/log",
+            "version": "1.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/log.git",
+                "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc",
+                "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Log\\": "Psr/Log/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for logging libraries",
+            "homepage": "https://github.com/php-fig/log",
+            "keywords": [
+                "log",
+                "psr",
+                "psr-3"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/log/tree/1.1.3"
+            },
+            "time": "2020-03-23T09:12:05+00:00"
+        },
         {
             "name": "restler/framework",
             "version": "3.0.0-RC6",

+ 8 - 0
dev/dolibarr_changes.txt

@@ -111,6 +111,10 @@ with
 				// DOL CHANGE If we keep this, the image is not visible on pages after the first one.
 				//var_dump($file.' '.(!@TCPDF_STATIC::file_exists($file)));
 				//return false;	
+                $tfile = str_replace(' ', '%20', $file);
+				if (@TCPDF_STATIC::file_exists($tfile)) {
+					$file = $tfile;
+				}
 			}
 			
 * Replace in tcpdf.php
@@ -205,6 +209,8 @@ with
 with
 	foreach ($value[1] as $k => $v) {
 
+
+
 JSGANTT:
 --------
 * Replace in function JSGantt.taskLink
@@ -233,11 +239,13 @@ JCROP:
 * Remove analytics tag into file index.html
 
 
+
 JQUERYFILETREE:
 ---------------
 * Remove directory htdocs/includes/jquery/plugins/jqueryFileTree/connectors
 
 
+
 RESTLER:
 --------
 

+ 1 - 1
dev/examples/code/README

@@ -5,4 +5,4 @@ This directory contains samples of code to use Dolibarr business classes to buil
 external interfaces that need to read/update data from/into Dolibarr.
 
 You can also have a look at the Dolibarr doxygen doc that describes all files and classes:
-http://www.dolibarr.org/html_doxygen/index.html
+https://doxygen.dolibarr.org/

+ 1 - 1
dev/examples/zapier/package.json

@@ -15,7 +15,7 @@
     "npm": ">=5.6.0"
   },
   "dependencies": {
-    "zapier-platform-core": "11.0.1"
+    "zapier-platform-core": "11.3.1"
   },
   "devDependencies": {
     "mocha": "^5.2.0",

+ 2 - 2
dev/initdemo/initdemo.sh

@@ -132,7 +132,7 @@ then
 	fichtemp=`tempfile 2>/dev/null` || fichtemp=/tmp/test$$
 	trap "rm -f $fichtemp" 0 1 2 5 15
 	$DIALOG --title "Init Dolibarr with demo values" --clear \
-	        --inputbox "Password for Mysql user login :" 16 55 2> $fichtemp
+	        --passwordbox "Password for Mysql user login :" 16 55 2> $fichtemp
 	
 	valret=$?
 	
@@ -153,7 +153,7 @@ then
 	# ---------------------------- confirmation
 	DIALOG=${DIALOG=dialog}
 	$DIALOG --title "Init Dolibarr with demo values" --clear \
-	        --yesno "Do you confirm ? \n Dump file : '$dumpfile' \n Dump dir : '$mydir' \n Document dir : '$documentdir' \n Mysql database : '$base' \n Mysql port : '$port' \n Mysql login: '$admin' \n Mysql password : '$passwd'" 15 55
+	        --yesno "Do you confirm ? \n Dump file : '$dumpfile' \n Dump dir : '$mydir' \n Document dir : '$documentdir' \n Mysql database : '$base' \n Mysql port : '$port' \n Mysql login: '$admin' \n Mysql password : --hidden--" 15 55
 	
 	case $? in
 	        0)      echo "Ok, start process...";;

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 4 - 4
dev/initdemo/mysqldump_dolibarr_15.0.0.sql


+ 2 - 2
dev/initdemo/savedemo.sh

@@ -116,7 +116,7 @@ then
 	fichtemp=`tempfile 2>/dev/null` || fichtemp=/tmp/test$$
 	trap "rm -f $fichtemp" 0 1 2 5 15
 	$DIALOG --title "Save Dolibarr with demo values" --clear \
-	        --inputbox "Password for Mysql root login :" 16 55 2> $fichtemp
+	        --passwordbox "Password for Mysql root login :" 16 55 2> $fichtemp
 	
 	valret=$?
 	
@@ -150,7 +150,7 @@ then
 	# ---------------------------- confirmation
 	DIALOG=${DIALOG=dialog}
 	$DIALOG --title "Save Dolibarr with demo values" --clear \
-	        --yesno "Do you confirm ? \n Dump file : '$dumpfile' \n Dump dir : '$mydir' \n Mysql database : '$base' \n Mysql port : '$port' \n Mysql login: '$admin' \n Mysql password : '$passwd'" 15 55
+	        --yesno "Do you confirm ? \n Dump file : '$dumpfile' \n Dump dir : '$mydir' \n Mysql database : '$base' \n Mysql port : '$port' \n Mysql login: '$admin' \n Mysql password : --hidden--" 15 55
 	
 	case $? in
 	        0)      echo "Ok, start process...";;

BIN
dev/resources/iso-normes/Intracommreport-ManuelDebXml.pdf


+ 17 - 0
dev/resources/iso-normes/QR code for invoices.txt

@@ -0,0 +1,17 @@
+List of QR Code format we found on some invoices
+------------------------------------------------
+
+
+* For SEPA QR payment Code format (Europe)
+------------------------------------------
+https://en.wikipedia.org/wiki/EPC_QR_code#Generators
+
+
+
+* For ZATCA QR Code format (Saudi Arabia). Used when INVOICE_ADD_ZATCA_QR_CODE is set
+-------------------------------------------------------------------------------------
+https://www.pwc.com/m1/en/services/tax/me-tax-legal-news/2021/saudi-arabia-guide-to-develop-compliant-qr-code-for-simplified-einvoices.html
+
+https://www.tecklenborgh.com/post/ksa-zatca-publishes-guide-on-how-to-develop-a-fatoora-compliant-qr-code
+
+Method to encode/decode ZATCA string is available in test/phpunit/BarcodeTest.php 

+ 2 - 0
dev/resources/iso-normes/address_format.txt

@@ -1,3 +1,5 @@
+Address format
+
 https://bitboost.com/ref/international-address-formats.html#Formats
 
 https://www.upu.int/en/Postal-Solutions/Programmes-Services/Addressing-Solutions

+ 7 - 3
dev/resources/iso-normes/currencies_iso-4217.txt

@@ -1,8 +1,12 @@
 # File of all ISO-4217 currencies codes
-# http://en.wikipedia.org/wiki/ISO_4217
-# http://fx.sauder.ubc.ca/currency_table.html for symbols for 2 letter code
 #
-# Code,Name,Nb decimals
+# https://en.wikipedia.org/wiki/ISO_4217
+# https://en.wikipedia.org/wiki/Currency_symbol   for symbols for 2 letter code
+#
+
+
+# Code, Currency Name, Nb decimals
+
 AED,UAE Dirham,2
 AFN,Afghanistan Afghani,2
 ALL,Albanian Lek,2

+ 0 - 0
dev/resources/facturx-zugferd/README.txt → dev/resources/iso-normes/facturx-zugferd/README.txt


BIN
dev/resources/iso-normes/format PDF - PDF A.pdf


+ 0 - 0
dev/resources/iso-normes/format_FEC-Lien_outil_de_test_agréé.pdf → dev/resources/iso-normes/format_FEC - Lien_outil_de_test_agréé.pdf


+ 0 - 0
dev/resources/iso-normes/sample_FEC_file.txt → dev/resources/iso-normes/format_FEC - fie example.txt


+ 0 - 0
dev/resources/intracommreport/manuelDebXml1.2.pdf → dev/resources/iso-normes/intracommreport/Intracommreport-ManuelDebXml.pdf


+ 0 - 0
dev/resources/iso-normes/Intracommreport-ManuelDesXML.pdf → dev/resources/iso-normes/intracommreport/Intracommreport-ManuelDesXML.pdf


+ 0 - 0
dev/resources/intracommreport/schema_deb.xsd → dev/resources/iso-normes/intracommreport/schema_deb.xsd


+ 2 - 0
dev/resources/iso-normes/locales.txt

@@ -1,3 +1,5 @@
+Date and number format
+----------------------
 
 For languages:
 https://icu4c-demos.unicode.org/icu-bin/icudemos - Locale Explorer -> Error 404

+ 0 - 0
dev/resources/sepa/pain.001.001.03.xsd → dev/resources/iso-normes/sepa/pain.001.001.03.xsd


+ 0 - 0
dev/resources/sepa/pain.008.001.02.xsd → dev/resources/iso-normes/sepa/pain.008.001.02.xsd


+ 0 - 0
dev/resources/sepa/sample-credit-transfer.xml → dev/resources/iso-normes/sepa/sample-credit-transfer.xml


+ 0 - 0
dev/resources/sepa/sample-direct-debit.xml → dev/resources/iso-normes/sepa/sample-direct-debit.xml


+ 0 - 0
dev/resources/sepa/text.txt → dev/resources/iso-normes/sepa/text.txt


+ 3 - 0
dev/resources/iso-normes/world_tax_rates.txt

@@ -1,3 +1,6 @@
+VAT Rates
+---------
+
 http://www.taxrates.cc/index.html
 https://en.wikipedia.org/wiki/List_of_countries_by_tax_rates
 

+ 88 - 57
dev/setup/apache/virtualhost

@@ -1,62 +1,93 @@
 <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 open_basedir /tmp/:/home/../htdocs
+	#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 open_basedir /tmp/:/home/.../htdocs:/home/.../dolibarr_documents:
+	
+	ServerName myvirtualalias
+	ServerAlias myvirtualalias
+	
+	UseCanonicalName On
+	
+	KeepAlive On
+	KeepAliveTimeout 5
+	MaxKeepAliveRequests 20
+	
+	AddDefaultCharset UTF-8
+	
+	DocumentRoot "/home/.../htdocs"
+	
+	<Directory /home/.../htdocs/>
+	   	AllowOverride None
+	   	Options       -Indexes -MultiViews +FollowSymLinks -ExecCGI
+	   	Require all granted
 
-ServerName myvirtualalias
-ServerAlias myvirtualalias
+		# To restrict access by a HTTP basic auth	   
+	   	#AuthType Basic
+       	#AuthName "Authenticate to backoffice"
+       	#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
+    <Directory /home/admin/wwwroot/dolibarr/htdocs/public/>
+        AuthType None
+        Require all granted
+        Satisfy any
+    </Directory>
+    <Directory /home/admin/wwwroot/dolibarr/htdocs/api/>
+        AuthType None
+        Require all granted
+        Satisfy any
+    </Directory>
+    <Directory /home/admin/wwwroot/dolibarr/htdocs/dav/>
+        AuthType None
+        Require all granted
+        Satisfy any
+    </Directory>
+    <Directory /home/admin/wwwroot/dolibarr/htdocs/.well-known/>
+        AuthType None
+        Require all granted
+        Satisfy any
+    </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
+    </Files>
 
-UseCanonicalName On
-
-AddDefaultCharset UTF-8
-
-DocumentRoot "/home/.../htdocs"
-
-<Directory /home/.../htdocs/>
-   AllowOverride None
-   Options       -Indexes -MultiViews +FollowSymLinks -ExecCGI
-   Require all granted
-</Directory>
-
-<Directory "/home/../htdocs/cache">
-   Deny from all
-   RemoveHandler .phtml .php .php3 .php4 .php5 .php6 .phps .cgi .exe .pl .asp .aspx .shtml .shtm .fcgi .fpl .jsp .htm .html .wml
-   AddType application/x-httpd-php-source .phtml .php .php3 .php4 .php5 .php6 .phps .cgi .exe .pl .asp .aspx .shtml .shtm .fcgi .fpl .jsp .htm .html .wml
-</Directory>
-
-
-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
-AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/x-javascript
-
-AddType text/javascript .jgz
-AddEncoding gzip .jgz
-ExpiresActive On
-ExpiresByType image/x-icon A2592000
-ExpiresByType image/gif A2592000
-ExpiresByType image/png A2592000
-ExpiresByType image/jpeg A2592000
-ExpiresByType text/css A2592000
-ExpiresByType text/javascript A2592000
-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
-
-#RewriteEngine   on
-#RewriteCond     %{SERVER_PORT} ^80$
-#RewriteRule     ^(.*)$ https://%{SERVER_NAME}$1 [L,R]
+        
+	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
+	AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/x-javascript
+	
+	AddType text/javascript .jgz
+	AddEncoding gzip .jgz
+	ExpiresActive On
+	ExpiresByType image/x-icon A2592000
+	ExpiresByType image/gif A2592000
+	ExpiresByType image/png A2592000
+	ExpiresByType image/jpeg A2592000
+	ExpiresByType text/css A2592000
+	ExpiresByType text/javascript A2592000
+	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
+	
+	#RewriteEngine   on
+	#RewriteCond     %{SERVER_PORT} ^80$
+	#RewriteRule     ^(.*)$ https://%{SERVER_NAME}$1 [L,R]
 
 </VirtualHost>

+ 3 - 3
dev/tools/fixdosfiles.sh

@@ -17,14 +17,14 @@ fi
 # To detec
 if [ "x$1" = "xlist" ]
 then
-	find . \( -iname "functions" -o -iname "*.md" -o -iname "*.html" -o -iname "*.htm" -o -iname "*.php" -o -iname "*.sh" -o -iname "*.cml" -o -iname "*.css" -o -iname "*.js" -o -iname "*.lang" -o -iname "*.pl" -o -iname "*.sql" -o -iname "*.txt" -o -iname "*.xml" -o -iname "*.pml" \) -exec file "{}" + | grep -v 'documents\/website' | grep -v 'documents\/mdedias' | grep CRLF
-#	find . \( -iname "*.md" -o -iname "*.html" -o -iname "*.htm" -o -iname "*.php" -o -iname "*.sh" -o -iname "*.cml" -o -iname "*.css" -o -iname "*.js" -o -iname "*.lang" -o -iname "*.pl" -o -iname "*.sql" -o -iname "*.txt" -o -iname "*.xml" \) -exec file "{}" + | grep -v 'documents\/website' | grep -v 'documents\/mdedias' | grep -v 'htdocs\/includes' | grep CRLF
+	find . \( -iname "functions" -o -iname "*.md" -o -iname "*.html" -o -iname "*.htm" -o -iname "*.php" -o -iname "*.sh" -o -iname "*.cml" -o -iname "*.css" -o -iname "*.js" -o -iname "*.lang" -o -iname "*.pl" -o -iname "*.sql" -o -iname "*.txt" -o -iname "*.xml" -o -iname "*.pml" \) -exec file "{}" + | grep -v 'custom\/' | grep -v 'documents\/website' | grep -v 'documents\/medias' | grep -v 'documents\/sellyoursaas' | grep CRLF
+#	find . \( -iname "*.md" -o -iname "*.html" -o -iname "*.htm" -o -iname "*.php" -o -iname "*.sh" -o -iname "*.cml" -o -iname "*.css" -o -iname "*.js" -o -iname "*.lang" -o -iname "*.pl" -o -iname "*.sql" -o -iname "*.txt" -o -iname "*.xml" \) -exec file "{}" + | grep -v 'custom\/' | grep -v 'documents\/website' | grep -v 'documents\/medias' | grep -v 'documents\/sellyoursaas' | grep -v 'htdocs\/includes' | grep CRLF
 fi
 
 # To convert
 if [ "x$1" = "xfix" ]
 then
-	for fic in `find . \( -iname "functions" -o -iname "*.md" -o -iname "*.html" -o -iname "*.htm" -o -iname "*.php" -o -iname "*.sh" -o -iname "*.cml" -o -iname "*.css" -o -iname "*.js" -o -iname "*.lang" -o -iname "*.pl" -o -iname "*.sql" -o -iname "*.txt" -o -iname "*.xml" -o -iname "*.pml" \) -exec file "{}" + | grep -v 'documents\/website' | grep -v 'documents\/mdedias' | grep CRLF | awk -F':' '{ print $1 }' `
+	for fic in `find . \( -iname "functions" -o -iname "*.md" -o -iname "*.html" -o -iname "*.htm" -o -iname "*.php" -o -iname "*.sh" -o -iname "*.cml" -o -iname "*.css" -o -iname "*.js" -o -iname "*.lang" -o -iname "*.pl" -o -iname "*.sql" -o -iname "*.txt" -o -iname "*.xml" -o -iname "*.pml" \) -exec file "{}" + | grep -v 'custom\/' | grep -v 'documents\/website' | grep -v 'documents\/medias' | grep -v 'documents\/sellyoursaas' | grep CRLF | awk -F':' '{ print $1 }' `
 	do
 		echo "Fix file $fic"
 		dos2unix "$fic"

+ 4 - 3
dev/tools/optimize_images.sh

@@ -14,7 +14,8 @@ max_output_size=0
 usage()
 {
   cat <<EO
-Usage: $PROGNAME [options]
+Usage:   $PROGNAME (list|fix) [options]
+Example: optimize_images.sh (list|fix) -i dirtoscan
 
 Script to optimize JPG and PNG images in a directory.
 
@@ -183,8 +184,8 @@ ARGS=$(getopt -s bash --options $SHORTOPTS --longoptions $LONGOPTS --name $PROGN
 # Syntax
 if [ "x$1" != "xlist" -a "x$1" != "xfix" ]
 then
-	echo "Usage: optimize_images.sh (list|fix) -i dirtoscan"
-	exit
+	usage
+	exit 0
 fi
 
 eval set -- "$ARGS"

BIN
doc/images/appicon_128.png


BIN
doc/images/appicon_16.png


BIN
doc/images/appicon_32.png


BIN
doc/images/appicon_64.png


BIN
doc/images/background_dolibarr.jpg


BIN
doc/images/dolibarr_256x256_black.png


BIN
doc/images/dolibarr_256x256_color.png


BIN
doc/images/dolibarr_256x256_white.png


BIN
doc/images/dolibarr_512x512_color.png


BIN
doc/images/dolibarr_logo.png


BIN
doc/images/dolibarr_screenshot11_1024x768.jpg


BIN
doc/images/dolibarr_screenshot12_1920x1080.jpg


BIN
doc/images/dolibarr_screenshot1_1280x800.jpg


BIN
doc/images/dolibarr_screenshot1_1920x1080.jpg


BIN
doc/images/dolibarr_screenshot2_1280x800.jpg


BIN
doc/images/dolibarr_screenshot3_1280x800.png


BIN
doc/images/dolibarr_screenshot5_1280x800.jpg


BIN
doc/images/invoice.png


+ 13 - 8
htdocs/accountancy/admin/account.php

@@ -363,7 +363,7 @@ if ($resql) {
 
 	$newcardbutton .= dolGetButtonTitle($langs->trans("New"), $langs->trans("Addanaccount"), 'fa fa-plus-circle', './card.php?action=create');
 	include DOL_DOCUMENT_ROOT.'/core/tpl/massactions_pre.tpl.php';
-	print_barre_liste($langs->trans('ListAccounts'), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, 'title_accountancy', 0, $newcardbutton, '', $limit, 0, 0, 1);
+	print_barre_liste($langs->trans('ListAccounts'), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, 'accounting_account', 0, $newcardbutton, '', $limit, 0, 0, 1);
 
 	// Box to select active chart of account
 	print $langs->trans("Selectchartofaccounts")." : ";
@@ -404,6 +404,11 @@ if ($resql) {
 
 	$moreforfilter = '';
 
+	$accountstatic = new AccountingAccount($db);
+	$accountparent = new AccountingAccount($db);
+	$totalarray = array();
+	$totalarray['nbfield'] = 0;
+
 	print '<div class="div-table-responsive">';
 	print '<table class="tagtable liste'.($moreforfilter ? " listwithfilterbefore" : "").'">'."\n";
 
@@ -466,11 +471,6 @@ if ($resql) {
 	print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', '', $sortfield, $sortorder, 'center maxwidthsearch ');
 	print "</tr>\n";
 
-	$accountstatic = new AccountingAccount($db);
-	$accountparent = new AccountingAccount($db);
-	$totalarray = array();
-	$totalarray['nbfield'] = 0;
-
 	$i = 0;
 	while ($i < min($num, $limit)) {
 		$obj = $db->fetch_object($resql);
@@ -615,8 +615,13 @@ if ($resql) {
 	}
 
 	if ($num == 0) {
-		$totalarray['nbfield']++;
-		print '<tr><td colspan="'.$totalarray['nbfield'].'"><span class="opacitymedium">'.$langs->trans("None").'</span></td></tr>';
+		$colspan = 1;
+		foreach ($arrayfields as $key => $val) {
+			if (!empty($val['checked'])) {
+				$colspan++;
+			}
+		}
+		print '<tr><td colspan="'.$colspan.'"><span class="opacitymedium">'.$langs->trans("None").'</span></td></tr>';
 	}
 
 	print "</table>";

+ 7 - 12
htdocs/accountancy/admin/accountmodel.php

@@ -185,11 +185,6 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) {
 			$ok = 0;
 			setEventMessages($langs->transnoentities('ErrorCodeCantContainZero'), null, 'errors');
 		}
-		/*if (!is_numeric($_POST['code']))	// disabled, code may not be in numeric base
-		{
-			$ok = 0;
-			$msg .= $langs->transnoentities('ErrorFieldFormat', $langs->transnoentities('Code')).'<br>';
-		}*/
 	}
 	if (GETPOSTISSET("country") && (GETPOST("country") == '0') && ($id != 2)) {
 		$ok = 0;
@@ -228,17 +223,17 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) {
 		$i = 0;
 		foreach ($listfieldinsert as $f => $value) {
 			if ($value == 'price' || preg_match('/^amount/i', $value) || $value == 'taux') {
-				$_POST[$listfieldvalue[$i]] = price2num($_POST[$listfieldvalue[$i]], 'MU');
+				$_POST[$listfieldvalue[$i]] = price2num(GETPOST($listfieldvalue[$i]), 'MU');
 			} elseif ($value == 'entity') {
 				$_POST[$listfieldvalue[$i]] = $conf->entity;
 			}
 			if ($i) {
 				$sql .= ",";
 			}
-			if ($_POST[$listfieldvalue[$i]] == '') {
+			if (GETPOST($listfieldvalue[$i]) == '') {
 				$sql .= "null";
 			} else {
-				$sql .= "'".$db->escape($_POST[$listfieldvalue[$i]])."'";
+				$sql .= "'".$db->escape(GETPOST($listfieldvalue[$i]))."'";
 			}
 			$i++;
 		}
@@ -276,7 +271,7 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) {
 		$i = 0;
 		foreach ($listfieldmodify as $field) {
 			if ($field == 'price' || preg_match('/^amount/i', $field) || $field == 'taux') {
-				$_POST[$listfieldvalue[$i]] = price2num($_POST[$listfieldvalue[$i]], 'MU');
+				$_POST[$listfieldvalue[$i]] = price2num(GETPOST($listfieldvalue[$i]), 'MU');
 			} elseif ($field == 'entity') {
 				$_POST[$listfieldvalue[$i]] = $conf->entity;
 			}
@@ -284,10 +279,10 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) {
 				$sql .= ",";
 			}
 			$sql .= $field."=";
-			if ($_POST[$listfieldvalue[$i]] == '') {
+			if (GETPOST($listfieldvalue[$i]) == '') {
 				$sql .= "null";
 			} else {
-				$sql .= "'".$db->escape($_POST[$listfieldvalue[$i]])."'";
+				$sql .= "'".$db->escape(GETPOST($listfieldvalue[$i]))."'";
 			}
 			$i++;
 		}
@@ -495,7 +490,7 @@ if ($id) {
 			if ($valuetoshow != '') {
 				print '<td class="'.$class.'">';
 				if (!empty($tabhelp[$id][$value]) && preg_match('/^http(s*):/i', $tabhelp[$id][$value])) {
-					print '<a href="'.$tabhelp[$id][$value].'" target="_blank">'.$valuetoshow.' '.img_help(1, $valuetoshow).'</a>';
+					print '<a href="'.$tabhelp[$id][$value].'">'.$valuetoshow.' '.img_help(1, $valuetoshow).'</a>';
 				} elseif (!empty($tabhelp[$id][$value])) {
 					print $form->textwithpicto($valuetoshow, $tabhelp[$id][$value]);
 				} else {

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

@@ -417,13 +417,13 @@ if ($action == 'create') {
 			print '<div class="tabsAction">';
 
 			if (!empty($user->rights->accounting->chartofaccount)) {
-				print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?action=update&token='.newToken().'&id='.$id.'">'.$langs->trans('Modify').'</a>';
+				print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?action=update&token='.newToken().'&id='.$object->id.'">'.$langs->trans('Modify').'</a>';
 			} else {
 				print '<a class="butActionRefused classfortooltip" href="#" title="'.dol_escape_htmltag($langs->trans("NotAllowed")).'">'.$langs->trans('Modify').'</a>';
 			}
 
 			if (!empty($user->rights->accounting->chartofaccount)) {
-				print '<a class="butActionDelete" href="'.$_SERVER["PHP_SELF"].'?action=delete&token='.newToken().'&id='.$id.'">'.$langs->trans('Delete').'</a>';
+				print '<a class="butActionDelete" href="'.$_SERVER["PHP_SELF"].'?action=delete&token='.newToken().'&id='.$object->id.'">'.$langs->trans('Delete').'</a>';
 			} else {
 				print '<a class="butActionRefused classfortooltip" href="#" title="'.dol_escape_htmltag($langs->trans("NotAllowed")).'">'.$langs->trans('Delete').'</a>';
 			}

+ 8 - 19
htdocs/accountancy/admin/categories_list.php

@@ -148,10 +148,10 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) {
 		if ($value == 'formula' && !GETPOST('formula')) {
 			continue;
 		}
-		if ($value == 'range_account' && empty($_POST['range_account'])) {
+		if ($value == 'range_account' && !GETPOST('range_account')) {
 			continue;
 		}
-		if (($value == 'country' || $value == 'country_id') && (!empty($_POST['country_id']))) {
+		if (($value == 'country' || $value == 'country_id') && GETPOST('country_id')) {
 			continue;
 		}
 		if (!GETPOSTISSET($value) || GETPOST($value) == '') {
@@ -195,17 +195,6 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) {
 		setEventMessages($langs->transnoentities('ErrorFieldMustBeANumeric', $langs->transnoentities("Position")), null, 'errors');
 	}
 
-	// Clean some parameters
-	if ($_POST["accountancy_code"] <= 0) {
-		$_POST["accountancy_code"] = ''; // If empty, we force to null
-	}
-	if ($_POST["accountancy_code_sell"] <= 0) {
-		$_POST["accountancy_code_sell"] = ''; // If empty, we force to null
-	}
-	if ($_POST["accountancy_code_buy"] <= 0) {
-		$_POST["accountancy_code_buy"] = ''; // If empty, we force to null
-	}
-
 	// Si verif ok et action add, on ajoute la ligne
 	if ($ok && GETPOST('actionadd', 'alpha')) {
 		if ($tabrowid[$id]) {
@@ -243,7 +232,7 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) {
 			if ($i) {
 				$sql .= ",";
 			}
-			if ($_POST[$listfieldvalue[$i]] == '' && !$listfieldvalue[$i] == 'formula') {
+			if (GETPOST($listfieldvalue[$i]) == '' && !$listfieldvalue[$i] == 'formula') {
 				$sql .= "null"; // For vat, we want/accept code = ''
 			} else {
 				$sql .= "'".$db->escape(GETPOST($listfieldvalue[$i]))."'";
@@ -283,8 +272,8 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) {
 		}
 		$i = 0;
 		foreach ($listfieldmodify as $field) {
-			if ($field == 'fk_country' && $_POST['country'] > 0) {
-				$_POST[$listfieldvalue[$i]] = $_POST['country'];
+			if ($field == 'fk_country' && GETPOST('country') > 0) {
+				$_POST[$listfieldvalue[$i]] = GETPOST('country');
 			} elseif ($field == 'entity') {
 				$_POST[$listfieldvalue[$i]] = $conf->entity;
 			}
@@ -292,10 +281,10 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) {
 				$sql .= ",";
 			}
 			$sql .= $field."=";
-			if ($_POST[$listfieldvalue[$i]] == '' && !$listfieldvalue[$i] == 'range_account') {
+			if (GETPOST($listfieldvalue[$i]) == '' && !$listfieldvalue[$i] == 'range_account') {
 				$sql .= "null"; // For range_account, we want/accept code = ''
 			} else {
-				$sql .= "'".$db->escape($_POST[$listfieldvalue[$i]])."'";
+				$sql .= "'".$db->escape(GETPOST($listfieldvalue[$i]))."'";
 			}
 			$i++;
 		}
@@ -520,7 +509,7 @@ if ($tabname[$id]) {
 		if ($valuetoshow != '') {
 			print '<td class="'.$class.'">';
 			if (!empty($tabhelp[$id][$value]) && preg_match('/^http(s*):/i', $tabhelp[$id][$value])) {
-				print '<a href="'.$tabhelp[$id][$value].'" target="_blank">'.$valuetoshow.' '.img_help(1, $valuetoshow).'</a>';
+				print '<a href="'.$tabhelp[$id][$value].'">'.$valuetoshow.' '.img_help(1, $valuetoshow).'</a>';
 			} elseif (!empty($tabhelp[$id][$value])) {
 				print $form->textwithpicto($valuetoshow, $tabhelp[$id][$value]);
 			} else {

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

@@ -24,7 +24,7 @@
 /**
  * \file 		htdocs/accountancy/admin/export.php
  * \ingroup 	Accountancy (Double entries)
- * \brief 		Setup page to configure accounting expert module
+ * \brief 		Setup page to configure accounting export module
  */
 require '../../main.inc.php';
 
@@ -142,7 +142,7 @@ $linkback = '';
 print load_fiche_titre($langs->trans('ExportOptions'), $linkback, 'accountancy');
 
 
-print "\n".'<script type="text/javascript" language="javascript">'."\n";
+print "\n".'<script type="text/javascript">'."\n";
 print 'jQuery(document).ready(function () {'."\n";
 print '    function initfields()'."\n";
 print '    {'."\n";

+ 0 - 1
htdocs/accountancy/admin/fiscalyear.php

@@ -127,7 +127,6 @@ if ($result) {
 	$title = $langs->trans('AccountingPeriods');
 	print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $params, $sortfield, $sortorder, '', $num, $nbtotalofrecords, 'title_accountancy', 0, $addbutton, '', $limit, 1);
 
-	// Load attribute_label
 	print '<div class="div-table-responsive">';
 	print '<table class="tagtable liste centpercent">';
 	print '<tr class="liste_titre">';

+ 14 - 52
htdocs/accountancy/admin/journals_list.php

@@ -58,8 +58,8 @@ $listoffset = GETPOST('listoffset', 'alpha');
 $listlimit = GETPOST('listlimit', 'int') > 0 ?GETPOST('listlimit', 'int') : 1000;
 $active = 1;
 
-$sortfield = GETPOST("sortfield", 'alpha');
-$sortorder = GETPOST("sortorder", 'alpha');
+$sortfield = GETPOST('sortfield', 'aZ09comma');
+$sortorder = GETPOST('sortorder', 'aZ09comma');
 $page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int');
 if (empty($page) || $page == -1) {
 	$page = 0;
@@ -165,45 +165,19 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) {
 
 	// Check that all fields are filled
 	$ok = 1;
-	foreach ($listfield as $f => $value) {
-		if ($fieldnamekey == 'libelle' || ($fieldnamekey == 'label')) {
-			$fieldnamekey = 'Label';
-		}
-		if ($fieldnamekey == 'code') {
-			$fieldnamekey = 'Code';
-		}
-		if ($fieldnamekey == 'nature') {
-			$fieldnamekey = 'NatureOfJournal';
-		}
-	}
+
 	// Other checks
 	if (GETPOSTISSET("code")) {
 		if (GETPOST("code") == '0') {
 			$ok = 0;
 			setEventMessages($langs->transnoentities('ErrorCodeCantContainZero'), null, 'errors');
 		}
-		/*if (!is_numeric($_POST['code']))	// disabled, code may not be in numeric base
-		{
-			$ok = 0;
-			$msg .= $langs->transnoentities('ErrorFieldFormat', $langs->transnoentities('Code')).'<br>';
-		}*/
 	}
 	if (!GETPOST('label', 'alpha')) {
 		setEventMessages($langs->transnoentities("ErrorFieldRequired", $langs->transnoentitiesnoconv("Label")), null, 'errors');
 		$ok = 0;
 	}
 
-	// Clean some parameters
-	if ($_POST["accountancy_code"] <= 0) {
-		$_POST["accountancy_code"] = ''; // If empty, we force to null
-	}
-	if ($_POST["accountancy_code_sell"] <= 0) {
-		$_POST["accountancy_code_sell"] = ''; // If empty, we force to null
-	}
-	if ($_POST["accountancy_code_buy"] <= 0) {
-		$_POST["accountancy_code_buy"] = ''; // If empty, we force to null
-	}
-
 	// Si verif ok et action add, on ajoute la ligne
 	if ($ok && GETPOST('actionadd', 'alpha')) {
 		if ($tabrowid[$id]) {
@@ -235,16 +209,13 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) {
 		}
 		$i = 0;
 		foreach ($listfieldinsert as $f => $value) {
-			if ($value == 'entity') {
-				$_POST[$listfieldvalue[$i]] = $conf->entity;
-			}
 			if ($i) {
 				$sql .= ",";
 			}
-			if ($_POST[$listfieldvalue[$i]] == '') {
+			if (GETPOST($listfieldvalue[$i]) == '') {
 				$sql .= "null"; // For vat, we want/accept code = ''
 			} else {
-				$sql .= "'".$db->escape($_POST[$listfieldvalue[$i]])."'";
+				$sql .= "'".$db->escape(GETPOST($listfieldvalue[$i]))."'";
 			}
 			$i++;
 		}
@@ -254,7 +225,7 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) {
 		$result = $db->query($sql);
 		if ($result) {	// Add is ok
 			setEventMessages($langs->transnoentities("RecordSaved"), null, 'mesgs');
-			$_POST = array('id'=>$id); // Clean $_POST array, we keep only
+			$_POST = array('id'=>$id); // Clean $_POST array, we keep only id
 		} else {
 			if ($db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
 				setEventMessages($langs->transnoentities("ErrorRecordAlreadyExists"), null, 'errors');
@@ -281,24 +252,15 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) {
 		}
 		$i = 0;
 		foreach ($listfieldmodify as $field) {
-			if ($field == 'price' || preg_match('/^amount/i', $field) || $field == 'taux') {
-				$_POST[$listfieldvalue[$i]] = price2num($_POST[$listfieldvalue[$i]], 'MU');
-			} elseif ($field == 'entity') {
-				$_POST[$listfieldvalue[$i]] = $conf->entity;
-			}
 			if ($i) {
 				$sql .= ",";
 			}
-			$sql .= $field."=";
-			if ($_POST[$listfieldvalue[$i]] == '' && !($listfieldvalue[$i] == 'code' && $id == 10)) {
-				$sql .= "null"; // For vat, we want/accept code = ''
-			} else {
-				$sql .= "'".$db->escape($_POST[$listfieldvalue[$i]])."'";
-			}
+			$sql .= $field." = ";
+			$sql .= "'".$db->escape(GETPOST($listfieldvalue[$i]))."'";
 			$i++;
 		}
 		$sql .= " WHERE ".$rowidcol." = ".((int) $rowid);
-		$sql .= " AND entity = ".$conf->entity;
+		$sql .= " AND entity = ".((int) $conf->entity);
 
 		dol_syslog("actionmodify", LOG_DEBUG);
 		//print $sql;
@@ -323,7 +285,7 @@ if ($action == 'confirm_delete' && $confirm == 'yes') {       // delete
 	}
 
 	$sql = "DELETE from ".$tabname[$id]." WHERE ".$rowidcol." = ".((int) $rowid);
-	$sql .= " AND entity = ".$conf->entity;
+	$sql .= " AND entity = ".((int) $conf->entity);
 
 	dol_syslog("delete", LOG_DEBUG);
 	$result = $db->query($sql);
@@ -410,7 +372,7 @@ if ($action == 'delete') {
 if ($id) {
 	// Complete requete recherche valeurs avec critere de tri
 	$sql = $tabsql[$id];
-	$sql .= " WHERE a.entity = ".$conf->entity;
+	$sql .= " WHERE a.entity = ".((int) $conf->entity);
 
 	// If sort order is "country", we use country_code instead
 	if ($sortfield == 'country') {
@@ -453,7 +415,7 @@ if ($id) {
 			if ($valuetoshow != '') {
 				print '<td class="'.$class.'">';
 				if (!empty($tabhelp[$id][$value]) && preg_match('/^http(s*):/i', $tabhelp[$id][$value])) {
-					print '<a href="'.$tabhelp[$id][$value].'" target="_blank">'.$valuetoshow.' '.img_help(1, $valuetoshow).'</a>';
+					print '<a href="'.$tabhelp[$id][$value].'">'.$valuetoshow.' '.img_help(1, $valuetoshow).'</a>';
 				} elseif (!empty($tabhelp[$id][$value])) {
 					print $form->textwithpicto($valuetoshow, $tabhelp[$id][$value]);
 				} else {
@@ -510,7 +472,7 @@ if ($id) {
 		$num = $db->num_rows($resql);
 		$i = 0;
 
-		$param = '&id='.$id;
+		$param = '&id='.((int) $id);
 		if ($search_country_id > 0) {
 			$param .= '&search_country_id='.urlencode($search_country_id);
 		}
@@ -635,7 +597,7 @@ if ($id) {
 							$class = 'tddict';
 							// Show value for field
 							if ($showfield) {
-								print '<!-- '.$fieldlist[$field].' --><td class="'.$class.'">'.$valuetoshow.'</td>';
+								print '<!-- '.$fieldlist[$field].' --><td class="'.$class.'">'.dol_escape_htmltag($valuetoshow).'</td>';
 							}
 						}
 					}

+ 39 - 13
htdocs/accountancy/admin/productaccount.php

@@ -4,6 +4,7 @@
  * Copyright (C) 2014 	   Florian Henry		<florian.henry@open-concept.pro>
  * Copyright (C) 2014 	   Juanjo Menent		<jmenent@2byte.es>
  * Copyright (C) 2015      Ari Elbaz (elarifr)	<github@accedinfo.com>
+ * Copyright (C) 2021      Gauthier VERDOL	<gauthier.verdol@atm-consulting.fr>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -47,9 +48,12 @@ if (empty($user->rights->accounting->bind->write)) {
 
 // search & action GETPOST
 $action = GETPOST('action', 'aZ09');
+$massaction = GETPOST('massaction', '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');
@@ -68,7 +72,6 @@ $search_onsell = GETPOST('search_onsell', 'alpha');
 $search_onpurchase = GETPOST('search_onpurchase', 'alpha');
 
 $accounting_product_mode = GETPOST('accounting_product_mode', 'alpha');
-$btn_changeaccount = GETPOST('changeaccount', 'alpha');
 $btn_changetype = GETPOST('changetype', 'alpha');
 $optioncss = GETPOST('optioncss', 'alpha');
 
@@ -77,8 +80,8 @@ if (empty($accounting_product_mode)) {
 }
 
 $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", 'alpha');
-$sortorder = GETPOST("sortorder", 'alpha');
+$sortfield = GETPOST('sortfield', 'aZ09comma');
+$sortorder = GETPOST('sortorder', 'aZ09comma');
 $page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int');
 if (empty($page) || $page == -1) {
 	$page = 0;
@@ -165,7 +168,7 @@ if ($action == 'update') {
 		}
 	}
 
-	if (!empty($btn_changeaccount)) {
+	if (!empty($chk_prod) && $massaction === 'changeaccount') {
 		//$msg = '<div><span class="accountingprocessing">' . $langs->trans("Processing") . '...</span></div>';
 		if (!empty($chk_prod) && in_array($accounting_product_mode, $accounting_product_modes)) {
 			$accounting = new AccountingAccount($db);
@@ -436,11 +439,27 @@ if ($result) {
 	$varpage = empty($contextpage) ? $_SERVER["PHP_SELF"] : $contextpage;
 	$selectedfields = $form->multiSelectArrayWithCheckbox('selectedfields', $arrayfields, $varpage); // This also change content of $arrayfields
 
+	if ($massaction !== 'set_default_account') {
+		$arrayofmassactions = array(
+			'changeaccount'=>img_picto('', 'check', 'class="pictofixedwidth"').$langs->trans("Save")
+			,'set_default_account'=>img_picto('', 'check', 'class="pictofixedwidth"').$langs->trans("ConfirmPreselectAccount")
+		);
+		$massactionbutton = $form->selectMassAction('', $arrayofmassactions, 1);
+	}
+
 	$buttonsave = '<input type="submit" class="button button-save" id="changeaccount" name="changeaccount" value="'.$langs->trans("Save").'">';
 	//print '<br><div class="center">'.$buttonsave.'</div>';
 
 	$texte = $langs->trans("ListOfProductsServices");
-	print_barre_liste($texte, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $buttonsave, $num, $nbtotalofrecords, '', 0, '', '', $limit, 0, 0, 1);
+	print_barre_liste($texte, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, '', 0, '', '', $limit, 0, 0, 1);
+
+	if ($massaction == 'set_default_account') {
+		$formquestion[]=array('type' => 'other',
+			'name' => 'set_default_account',
+			'label' => $langs->trans("AccountancyCode"),
+			'value' => $form->select_account('', 'default_account', 1, array(), 0, 0, 'maxwidth200 maxwidthonsmartphone', 'cachewithshowemptyone'));
+		print $form->formconfirm($_SERVER["PHP_SELF"], $langs->trans("ConfirmPreselectAccount"), $langs->trans("ConfirmPreselectAccountQuestion", count($chk_prod)), "confirm_set_default_account", $formquestion, 1, 0, 200, 500, 1);
+	}
 
 	print '<div class="div-table-responsive">';
 	print '<table class="liste '.($moreforfilter ? "listwithfilterbefore" : "").'">';
@@ -645,7 +664,7 @@ if ($result) {
 			if (!empty($obj->aaid)) {
 				$defaultvalue = ''; // Do not suggest default new value is code is already valid
 			}
-			print $form->select_account($defaultvalue, 'codeventil_'.$product_static->id, 1, array(), 1, 0, 'maxwidth300 maxwidthonsmartphone productforselect');
+			print $form->select_account(($default_account > 0 && $confirm === 'yes' && in_array($product_static->id, $chk_prod)) ? $default_account : $defaultvalue, 'codeventil_'.$product_static->id, 1, array(), 1, 0, 'maxwidth300 maxwidthonsmartphone productforselect');
 			print '</td>';
 		} elseif ($accounting_product_mode == 'ACCOUNTANCY_BUY_INTRA') {
 			// Accounting account buy intra (In EEC)
@@ -659,7 +678,7 @@ if ($result) {
 			if (!empty($obj->aaid)) {
 				$defaultvalue = ''; // Do not suggest default new value is code is already valid
 			}
-			print $form->select_account($defaultvalue, 'codeventil_'.$product_static->id, 1, array(), 1, 0, 'maxwidth300 maxwidthonsmartphone productforselect');
+			print $form->select_account(($default_account > 0 && $confirm === 'yes' && in_array($product_static->id, $chk_prod)) ? $default_account : $defaultvalue, 'codeventil_'.$product_static->id, 1, array(), 1, 0, 'maxwidth300 maxwidthonsmartphone productforselect');
 			print '</td>';
 		} elseif ($accounting_product_mode == 'ACCOUNTANCY_BUY_EXPORT') {
 			// Accounting account buy export (Out of EEC)
@@ -673,7 +692,7 @@ if ($result) {
 			if (!empty($obj->aaid)) {
 				$defaultvalue = ''; // Do not suggest default new value is code is already valid
 			}
-			print $form->select_account($defaultvalue, 'codeventil_'.$product_static->id, 1, array(), 1, 0, 'maxwidth300 maxwidthonsmartphone productforselect');
+			print $form->select_account(($default_account > 0 && $confirm === 'yes' && in_array($product_static->id, $chk_prod)) ? $default_account : $defaultvalue, 'codeventil_'.$product_static->id, 1, array(), 1, 0, 'maxwidth300 maxwidthonsmartphone productforselect');
 			print '</td>';
 		} elseif ($accounting_product_mode == 'ACCOUNTANCY_SELL') {
 			// Accounting account sell
@@ -687,7 +706,7 @@ if ($result) {
 			if (!empty($obj->aaid)) {
 				$defaultvalue = ''; // Do not suggest default new value is code is already valid
 			}
-			print $form->select_account($defaultvalue, 'codeventil_'.$product_static->id, 1, array(), 1, 0, 'maxwidth300 maxwidthonsmartphone productforselect');
+			print $form->select_account(($default_account > 0 && $confirm === 'yes' && in_array($product_static->id, $chk_prod)) ? $default_account : $defaultvalue, 'codeventil_'.$product_static->id, 1, array(), 1, 0, 'maxwidth300 maxwidthonsmartphone productforselect');
 			print '</td>';
 		} elseif ($accounting_product_mode == 'ACCOUNTANCY_SELL_INTRA') {
 			// Accounting account sell intra (In EEC)
@@ -701,7 +720,7 @@ if ($result) {
 			if (!empty($obj->aaid)) {
 				$defaultvalue = ''; // Do not suggest default new value is code is already valid
 			}
-			print $form->select_account($defaultvalue, 'codeventil_'.$product_static->id, 1, array(), 1, 0, 'maxwidth300 maxwidthonsmartphone productforselect');
+			print $form->select_account(($default_account > 0 && $confirm === 'yes' && in_array($product_static->id, $chk_prod)) ? $default_account : $defaultvalue, 'codeventil_'.$product_static->id, 1, array(), 1, 0, 'maxwidth300 maxwidthonsmartphone productforselect');
 			print '</td>';
 		} else {
 			// Accounting account sell export (Out of EEC)
@@ -714,20 +733,27 @@ if ($result) {
 			if (!empty($obj->aaid)) {
 				$defaultvalue = ''; // Do not suggest default new value is code is already valid
 			}
-			print $form->select_account($defaultvalue, 'codeventil_'.$product_static->id, 1, array(), 1, 0, 'maxwidth300 maxwidthonsmartphone productforselect');
+			print $form->select_account(($default_account > 0 && $confirm === 'yes' && in_array($product_static->id, $chk_prod)) ? $default_account : $defaultvalue, 'codeventil_'.$product_static->id, 1, array(), 1, 0, 'maxwidth300 maxwidthonsmartphone productforselect');
 			print '</td>';
 		}
 
+		if (!empty($chk_prod)) {
+			$ischecked = 0;
+			if (in_array($product_static->id, $chk_prod)) {
+				$ischecked=true;
+			}
+		}
+
 		// Checkbox select
 		print '<td class="center">';
-		print '<input type="checkbox" class="checkforselect productforselectcodeventil_'.$product_static->id.'" name="chk_prod[]" value="'.$obj->rowid.'"/></td>';
+		print '<input type="checkbox" class="checkforselect productforselectcodeventil_'.$product_static->id.'" name="chk_prod[]" '.($ischecked ? "checked" : "").' value="'.$obj->rowid.'"/></td>';
 		print "</tr>";
 		$i++;
 	}
 	print '</table>';
 	print '</div>';
 
-	print '<script type="text/javascript" language="javascript">
+	print '<script type="text/javascript">
         jQuery(document).ready(function() {
         	function init_savebutton()
         	{

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

@@ -237,11 +237,11 @@ if ($action != 'export_csv') {
 	print '<input type="hidden" name="page" value="'.$page.'">';
 
 	$parameters = array();
-	$reshook = $hookmanager->executeHooks('addMoreActionsButtons', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+	$reshook = $hookmanager->executeHooks('addMoreActionsButtonsList', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 	if (empty($reshook)) {
 		$button = '<input type="button" id="exportcsvbutton" name="exportcsvbutton" class="butAction" value="'.$langs->trans("Export").' ('.$conf->global->ACCOUNTING_EXPORT_FORMAT.')" />';
 
-		print '<script type="text/javascript" language="javascript">
+		print '<script type="text/javascript">
 		jQuery(document).ready(function() {
 			jQuery("#exportcsvbutton").click(function() {
 				event.preventDefault();
@@ -417,7 +417,7 @@ if ($action != 'export_csv') {
 
 				// Show first line of a break
 				print '<tr class="trforbreak">';
-				print '<td colspan="'.($colspan+1).'" style="font-weight:bold; border-bottom: 1pt solid black;">'.$line->numero_compte.($root_account_description ? ' - '.$root_account_description : '').'</td>';
+				print '<td colspan="'.($colspan+1).'" class="tdforbreak">'.$line->numero_compte.($root_account_description ? ' - '.$root_account_description : '').'</td>';
 				print '</tr>';
 
 				$displayed_account = $root_account_number;

+ 38 - 11
htdocs/accountancy/bookkeeping/card.php

@@ -38,6 +38,8 @@ require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
 $langs->loadLangs(array("accountancy", "bills", "compta"));
 
 $action = GETPOST('action', 'aZ09');
+$cancel = GETPOST('cancel', 'aZ09');
+
 $optioncss = GETPOST('optioncss', 'aZ'); // Option for the css output (always '' except when 'print')
 
 $id = GETPOST('id', 'int'); // id of record
@@ -92,6 +94,11 @@ if (empty($user->rights->accounting->mouvements->lire)) {
  * Actions
  */
 
+if ($cancel) {
+	header("Location: ".DOL_URL_ROOT.'/accountancy/bookkeeping/list.php');
+	exit;
+}
+
 if ($action == "confirm_update") {
 	$error = 0;
 
@@ -618,9 +625,10 @@ if ($action == 'create') {
 			print '<input type="hidden" name="fk_docdet" value="'.$object->fk_docdet.'">'."\n";
 			print '<input type="hidden" name="mode" value="'.$mode.'">'."\n";
 
-			print '<table class="noborder centpercent">';
-
 			if (count($object->linesmvt) > 0) {
+				print '<div class="div-table-responsive-no-min">';
+				print '<table class="noborder centpercent">';
+
 				$total_debit = 0;
 				$total_credit = 0;
 
@@ -655,7 +663,7 @@ if ($action == 'create') {
 						// Also, it is not possible to use a value that is not in the list.
 						// Also, the label is not automatically filled when a value is selected.
 						if (!empty($conf->global->ACCOUNTANCY_COMBO_FOR_AUX)) {
-							print $formaccounting->select_auxaccount((GETPOSTISSET("subledger_account") ? GETPOST("subledger_account", "alpha") : $line->subledger_account), 'subledger_account', 1);
+							print $formaccounting->select_auxaccount((GETPOSTISSET("subledger_account") ? GETPOST("subledger_account", "alpha") : $line->subledger_account), 'subledger_account', 1, 'maxwidth250', '', 'subledger_label');
 						} else {
 							print '<input type="text" class="maxwidth150" name="subledger_account" value="'.(GETPOSTISSET("subledger_account") ? GETPOST("subledger_account", "alpha") : $line->subledger_account).'" placeholder="'.dol_escape_htmltag($langs->trans("SubledgerAccount")).'">';
 						}
@@ -670,8 +678,14 @@ if ($action == 'create') {
 						print '<input type="submit" class="button" name="update" value="'.$langs->trans("Update").'">';
 						print '</td>';
 					} else {
-						$accountingaccount->fetch(null, $line->numero_compte, true);
-						print '<td>'.$accountingaccount->getNomUrl(0, 1, 1, '', 0).'</td>';
+						$resultfetch = $accountingaccount->fetch(null, $line->numero_compte, true);
+						print '<td>';
+						if ($resultfetch > 0) {
+							print $accountingaccount->getNomUrl(0, 1, 1, '', 0);
+						} else {
+							print $line->numero_compte.' <span class="warning">('.$langs->trans("AccountRemovedFromCurrentChartOfAccount").')</span>';
+						}
+						print '</td>';
 						print '<td>'.length_accounta($line->subledger_account);
 						if ($line->subledger_label) {
 							print ' - <span class="opacitymedium">'.$line->subledger_label.'</span>';
@@ -681,11 +695,15 @@ if ($action == 'create') {
 						print '<td class="right nowraponall amount">'.price($line->debit).'</td>';
 						print '<td class="right nowraponall amount">'.price($line->credit).'</td>';
 
-						print '<td class="center">';
+						print '<td class="center nowraponall">';
 						if (empty($line->date_export) && empty($line->date_validation)) {
 							print '<a class="editfielda reposition" href="' . $_SERVER["PHP_SELF"] . '?action=update&id=' . $line->id . '&piece_num=' . urlencode($line->piece_num) . '&mode=' . urlencode($mode) . '&token=' . urlencode(newToken()) . '">';
 							print img_edit('', 0, 'class="marginrightonly"');
 							print '</a> &nbsp;';
+						} else {
+							print '<a class="editfielda nohover cursornotallowed reposition disabled" href="#" title="'.dol_escape_htmltag($langs->trans("ForbiddenTransactionAlreadyExported")).'">';
+							print img_edit($langs->trans("ForbiddenTransactionAlreadyExported"), 0, 'class="marginrightonly"');
+							print '</a> &nbsp;';
 						}
 
 						if (empty($line->date_validation)) {
@@ -696,9 +714,13 @@ if ($action == 'create') {
 
 							print '<a href="' . $_SERVER["PHP_SELF"] . '?action=' . $actiontodelete . '&id=' . $line->id . '&piece_num=' . urlencode($line->piece_num) . '&mode=' . urlencode($mode) . '&token=' . urlencode(newToken()) . '">';
 							print img_delete();
+							print '</a>';
+						} else {
+							print '<a class="editfielda nohover cursornotallowed disabled" href="#" title="'.dol_escape_htmltag($langs->trans("ForbiddenTransactionAlreadyExported")).'">';
+							print img_delete($langs->trans("ForbiddenTransactionAlreadyValidated"));
+							print '</a>';
 						}
 
-						print '</a>';
 						print '</td>';
 					}
 					print "</tr>\n";
@@ -724,7 +746,7 @@ if ($action == 'create') {
 						// Also, it is not possible to use a value that is not in the list.
 						// Also, the label is not automatically filled when a value is selected.
 						if (!empty($conf->global->ACCOUNTANCY_COMBO_FOR_AUX)) {
-							print $formaccounting->select_auxaccount('', 'subledger_account', 1);
+							print $formaccounting->select_auxaccount('', 'subledger_account', 1, 'maxwidth250', '', 'subledger_label');
 						} else {
 							print '<input type="text" class="maxwidth150" name="subledger_account" value="" placeholder="' . dol_escape_htmltag($langs->trans("SubledgerAccount")) . '">';
 						}
@@ -733,12 +755,16 @@ if ($action == 'create') {
 						print '<td><input type="text" class="minwidth200" name="label_operation" value="' . $label_operation . '"/></td>';
 						print '<td class="right"><input type="text" size="6" class="right" name="debit" value=""/></td>';
 						print '<td class="right"><input type="text" size="6" class="right" name="credit" value=""/></td>';
-						print '<td><input type="submit" class="button" name="save" value="' . $langs->trans("Add") . '"></td>';
+						print '<td>';
+						print '<input type="submit" class="button" name="save" value="' . $langs->trans("Add") . '">';
+						print '</td>';
 						print '</tr>';
 					}
-					print '</table>';
 				}
 
+				print '</table>';
+				print '</div>';
+
 				if ($mode == '_tmp' && $action == '') {
 					print '<br>';
 					print '<div class="center">';
@@ -753,8 +779,9 @@ if ($action == 'create') {
 
 					print "</div>";
 				}
-				print '</form>';
 			}
+
+			print '</form>';
 		}
 	} else {
 		print load_fiche_titre($langs->trans("NoRecords"));

+ 40 - 30
htdocs/accountancy/bookkeeping/list.php

@@ -24,6 +24,7 @@
  * \ingroup		Accountancy (Double entries)
  * \brief 		List operation of book keeping
  */
+
 require '../../main.inc.php';
 require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountancyexport.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
@@ -189,7 +190,7 @@ $arrayfields = array(
 	'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("DateValidation"), 'checked'=>1),
+	't.date_validated'=>array('label'=>$langs->trans("DateValidationAndLock"), 'checked'=>1),
 );
 
 if (empty($conf->global->ACCOUNTING_ENABLE_LETTERING)) {
@@ -333,10 +334,6 @@ if (empty($reshook)) {
 		$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);
@@ -533,11 +530,11 @@ if (count($filter) > 0) {
 			$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<=') {
+		} 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.numero_compte') {
+		} 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);
@@ -681,24 +678,35 @@ $formconfirm = '';
 if ($action == 'export_file') {
 	$form_question = array();
 
+	// 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'),
+		'value' => $checked,
 	);
+
+	$form_question['separator'] = array('name'=>'separator', 'type'=>'separator');
+
+	// 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'),
-		'value' => (!empty($conf->global->ACCOUNTING_DEFAULT_NOT_NOTIFIED_VALIDATION_DATE) ? 'false' : 'true'),
+		'value' => $checked,
 	);
 
+	$form_question['separator2'] = array('name'=>'separator2', 'type'=>'separator');
+
 	$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?'.$param, $langs->trans("ExportFilteredList").' ('.$listofformat[$formatexportset].')', $langs->trans('ConfirmExportFile'), 'export_fileconfirm', $form_question, '', 1, 300, 600);
 }
+
 if ($action == 'delmouv') {
-	$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?mvt_num='.GETPOST('mvt_num').$param, $langs->trans('DeleteMvt'), $langs->trans('ConfirmDeleteMvtPartial'), 'delmouvconfirm', '', 0, 1);
+	$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?mvt_num='.urlencode(GETPOST('mvt_num')).$param, $langs->trans('DeleteMvt'), $langs->trans('ConfirmDeleteMvtPartial'), 'delmouvconfirm', '', 0, 1);
 }
+
 if ($action == 'delbookkeepingyear') {
 	$form_question = array();
 	$delyear = GETPOST('delyear', 'int');
@@ -719,6 +727,7 @@ if ($action == 'delbookkeepingyear') {
 		'type' => 'select',
 		'label' => $langs->trans('DelMonth'),
 		'values' => $month_array,
+		'morecss' => 'minwidth150',
 		'default' => ''
 	);
 	$form_question['delyear'] = array(
@@ -736,7 +745,7 @@ if ($action == 'delbookkeepingyear') {
 			'default' => $deljournal
 	);
 
-	$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?'.$param, $langs->trans('DeleteMvt'), $langs->trans('ConfirmDeleteMvt', $langs->transnoentitiesnoconv("RegistrationInAccounting")), 'delbookkeepingyearconfirm', $form_question, '', 1, 300);
+	$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?'.$param, $langs->trans('DeleteMvt'), $langs->trans('ConfirmDeleteMvt', $langs->transnoentitiesnoconv("RegistrationInAccounting")), 'delbookkeepingyearconfirm', $form_question, '', 1, 320);
 }
 
 // Print form confirm
@@ -769,7 +778,7 @@ if (count($filter)) {
 }
 
 $parameters = array();
-$reshook = $hookmanager->executeHooks('addMoreActionsButtons', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+$reshook = $hookmanager->executeHooks('addMoreActionsButtonsList', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 if (empty($reshook)) {
 	// Button re-export
 	if (!empty($conf->global->ACCOUNTING_REEXPORT)) {
@@ -823,7 +832,7 @@ if (!empty($arrayfields['t.piece_num']['checked'])) {
 // 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, 'maxwidth150');
+	print $formaccounting->multi_select_journal($search_ledger_code, 'search_ledger_code', 0, 1, 1, 1, 'small maxwidth150');
 	print '</td>';
 }
 // Date document
@@ -845,10 +854,10 @@ if (!empty($arrayfields['t.doc_ref']['checked'])) {
 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, 'maxwidth200', 'account');
+	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, 'maxwidth200', 'account');
+	print $formaccounting->select_account($search_accountancy_code_end, 'search_accountancy_code_end', $langs->trans('to'), array(), 1, 1, 'maxwidth150', 'account');
 	print '</div>';
 	print '</td>';
 }
@@ -1125,24 +1134,25 @@ while ($i < min($num, $limit)) {
 			// Other type
 		}
 
-		print '<td class="nowrap">';
-
-		print '<table class="nobordernopadding"><tr class="nocellnopadd">';
-		// Picto + Ref
-		print '<td class="nobordernopadding nowrap">';
-
+		$labeltoshow = '';
+		$labeltoshowalt = '';
 		if ($line->doc_type == 'customer_invoice' || $line->doc_type == 'supplier_invoice' || $line->doc_type == 'expense_report') {
-			print $objectstatic->getNomUrl(1, '', 0, 0, '', 0, -1, 1);
-			print $documentlink;
+			$labeltoshow .= $objectstatic->getNomUrl(1, '', 0, 0, '', 0, -1, 1);
+			$labeltoshow .= $documentlink;
+			$labeltoshowalt .= $objectstatic->ref;
 		} elseif ($line->doc_type == 'bank') {
-			print $objectstatic->getNomUrl(1);
+			$labeltoshow .= $objectstatic->getNomUrl(1);
+			$labeltoshowalt .= $objectstatic->ref;
 			$bank_ref = strstr($line->doc_ref, '-');
-			print " " . $bank_ref;
+			$labeltoshow .= " " . $bank_ref;
+			$labeltoshowalt .= " " . $bank_ref;
 		} else {
-			print $line->doc_ref;
+			$labeltoshow .= $line->doc_ref;
+			$labeltoshowalt .= $line->doc_ref;
 		}
-		print '</td></tr></table>';
 
+		print '<td class="nowraponall tdoverflowmax200" title="'.dol_escape_htmltag($labeltoshowalt).'">';
+		print $labeltoshow;
 		print "</td>\n";
 		if (!$i) {
 			$totalarray['nbfield']++;
@@ -1167,7 +1177,7 @@ while ($i < min($num, $limit)) {
 
 	// Label operation
 	if (!empty($arrayfields['t.label_operation']['checked'])) {
-		print '<td>'.$line->label_operation.'</td>';
+		print '<td class="small tdoverflowmax200" title="'.dol_escape_htmltag($line->label_operation).'">'.dol_escape_htmltag($line->label_operation).'</td>';
 		if (!$i) {
 			$totalarray['nbfield']++;
 		}
@@ -1228,7 +1238,7 @@ while ($i < min($num, $limit)) {
 
 	// Exported operation date
 	if (!empty($arrayfields['t.date_export']['checked'])) {
-		print '<td class="center">'.dol_print_date($line->date_export, 'dayhour').'</td>';
+		print '<td class="center nowraponall">'.dol_print_date($line->date_export, 'dayhour').'</td>';
 		if (!$i) {
 			$totalarray['nbfield']++;
 		}
@@ -1236,7 +1246,7 @@ while ($i < min($num, $limit)) {
 
 	// Validated operation date
 	if (!empty($arrayfields['t.date_validated']['checked'])) {
-		print '<td class="center">'.dol_print_date($line->date_validation, 'dayhour').'</td>';
+		print '<td class="center nowraponall">'.dol_print_date($line->date_validation, 'dayhour').'</td>';
 		if (!$i) {
 			$totalarray['nbfield']++;
 		}

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

@@ -473,7 +473,7 @@ print '<input type="hidden" name="sortfield" value="'.$sortfield.'">';
 print '<input type="hidden" name="sortorder" value="'.$sortorder.'">';
 
 $parameters = array();
-$reshook = $hookmanager->executeHooks('addMoreActionsButtons', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+$reshook = $hookmanager->executeHooks('addMoreActionsButtonsList', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 if (empty($reshook)) {
 	$newcardbutton = dolGetButtonTitle($langs->trans('ViewFlatList'), '', 'fa fa-list paddingleft imgforviewmode', DOL_URL_ROOT.'/accountancy/bookkeeping/list.php?'.$param);
 	$newcardbutton .= dolGetButtonTitle($langs->trans('GroupByAccountAccounting'), '', 'fa fa-stream paddingleft imgforviewmode', DOL_URL_ROOT.'/accountancy/bookkeeping/listbyaccount.php?'.$param, '', 1, array('morecss' => 'marginleftonly btnTitleSelected'));
@@ -711,8 +711,8 @@ while ($i < min($num, $limit)) {
 		}
 
 		// Show the break account
-		print "<tr>";
-		print '<td colspan="'.($totalarray['nbfield'] ? $totalarray['nbfield'] : 10).'" style="font-weight:bold; border-bottom: 1pt solid black;">';
+		print '<tr class="trforbreak">';
+		print '<td colspan="'.($totalarray['nbfield'] ? $totalarray['nbfield'] : 10).'" class="tdforbreak">';
 		if ($line->numero_compte != "" && $line->numero_compte != '-1') {
 			print length_accountg($line->numero_compte).' : '.$object->get_compte_desc($line->numero_compte);
 		} else {

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

@@ -473,7 +473,7 @@ print '<input type="hidden" name="sortfield" value="'.$sortfield.'">';
 print '<input type="hidden" name="sortorder" value="'.$sortorder.'">';
 
 $parameters = array();
-$reshook = $hookmanager->executeHooks('addMoreActionsButtons', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+$reshook = $hookmanager->executeHooks('addMoreActionsButtonsList', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 if (empty($reshook)) {
 	$newcardbutton = dolGetButtonTitle($langs->trans('ViewFlatList'), '', 'fa fa-list paddingleft imgforviewmode', DOL_URL_ROOT.'/accountancy/bookkeeping/list.php?'.$param);
 	$newcardbutton .= dolGetButtonTitle($langs->trans('GroupByAccountAccounting'), '', 'fa fa-stream paddingleft imgforviewmode', DOL_URL_ROOT.'/accountancy/bookkeeping/listbyaccount.php?'.$param, '', 1, array('morecss' => 'marginleftonly'));
@@ -713,8 +713,8 @@ while ($i < min($num, $limit)) {
 		}
 
 		// Show the break account
-		print "<tr>";
-		print '<td colspan="'.($totalarray['nbfield'] ? $totalarray['nbfield'] : 10).'" style="font-weight:bold; border-bottom: 1pt solid black;">';
+		print '<tr class="trforbreak">';
+		print '<td colspan="'.($totalarray['nbfield'] ? $totalarray['nbfield'] : 10).'" class="tdforbreak">';
 		if ($line->subledger_account != "" && $line->subledger_account != '-1') {
 			print $line->subledger_label.' : '.length_accounta($line->subledger_account);
 		} else {

+ 0 - 325
htdocs/accountancy/bookkeeping/thirdparty_lettering_customer.php

@@ -1,325 +0,0 @@
-<?php
-/* Copyright (C) 2004-2005 Rodolphe Quiedeville <rodolphe@quiedeville.org>
- * Copyright (C) 2005      Laurent Destailleur  <eldy@users.sourceforge.net>
- * Copyright (C) 2013      Olivier Geffroy      <jeff@jeffinfo.com>
- * Copyright (C) 2013      Florian Henry	    <florian.henry@open-concept.pro>
- * Copyright (C) 2013-2019 Alexandre Spangaro   <aspangaro@open-dsi.fr>
- * Copyright (C) 2018-2020 Frédéric France      <frederic.france@netlogic.fr>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-/**
- * \file        htdocs/accountancy/bookkeeping/thirdparty_lettering_customer.php
- * \ingroup     accountancy
- * \brief       Tab to manage customer lettering
- */
-require '../../main.inc.php';
-require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
-require_once DOL_DOCUMENT_ROOT.'/accountancy/class/bookkeeping.class.php';
-require_once DOL_DOCUMENT_ROOT.'/accountancy/class/lettering.class.php';
-require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingjournal.class.php';
-require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
-require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
-
-// Load translation files required by the page
-$langs->loadLangs(array("compta", "accountancy"));
-
-$action = GETPOST('action', 'aZ09');
-$massaction = GETPOST('massaction', 'alpha');
-$show_files = GETPOST('show_files', 'int');
-$confirm = GETPOST('confirm', 'alpha');
-$toselect = GETPOST('toselect', 'array');
-// $socid = GETPOST('socid', 'int') ?GETPOST('socid', 'int') : GETPOST('id', 'int');
-// Security check
-$socid = GETPOSTINT("socid");
-// if ($user->socid) $socid=$user->socid;
-
-$limit = GETPOSTISSET('limit') ? GETPOST('limit', 'int') : $conf->liste_limit;
-$sortfield = GETPOST("sortfield", 'alpha');
-$sortorder = GETPOST("sortorder", 'alpha');
-$page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int');
-if (empty($page) || $page == - 1) {
-	$page = 0;
-} // If $page is not defined, or '' or -1
-$offset = $limit * $page;
-$pageprev = $page - 1;
-$pagenext = $page + 1;
-if ($sortorder == "") {
-	$sortorder = "ASC";
-}
-if ($sortfield == "") {
-	$sortfield = "bk.doc_date";
-}
-
-/*
-$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(0, 0, 0, GETPOST('date_endmonth', 'int'), GETPOST('date_endday', 'int'), GETPOST('date_endyear', 'int'));
-//$search_doc_type = GETPOST("search_doc_type", 'alpha');
-$search_doc_ref = GETPOST("search_doc_ref", 'alpha');
-*/
-
-$lettering = GETPOST('lettering', 'alpha');
-if (!empty($lettering)) {
-	$action = $lettering;
-}
-
-/*
-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_date_start = '';
-	$search_date_end = '';
-	//$search_doc_type = '';
-	$search_doc_ref = '';
-}
-*/
-
-$lettering = new Lettering($db);
-$object = new Societe($db);
-$object->id = $socid;
-$result = $object->fetch($socid);
-if ($result < 0) {
-	setEventMessages($object->error, $object->errors, 'errors');
-}
-
-if (empty($conf->accounting->enabled)) {
-	accessforbidden();
-}
-if ($user->socid > 0) {
-	accessforbidden();
-}
-if (empty($user->rights->accounting->mouvements->lire)) {
-	accessforbidden();
-}
-
-
-/*
- * Action
- */
-
-if ($action == 'lettering') {
-	$result = $lettering->updateLettering($toselect);
-
-	if ($result < 0) {
-		setEventMessages('', $lettering->errors, 'errors');
-		$error++;
-	}
-}
-
-/*
-if ($action == 'autolettrage') {
-
-	$result = $lettering->letteringThirdparty($socid);
-
-	if ($result < 0) {
-		setEventMessages('', $lettering->errors, 'errors');
-		$error++;
-	}
-}
-*/
-
-/*
- * View
- */
-
-$form = new Form($db);
-$formaccounting = new FormAccounting($db);
-
-$title = $object->name." - ".$langs->trans('TabLetteringCustomer');
-$help_url = 'EN:Module_Third_Parties|FR:Module_Tiers|ES:Empresas|DE:Modul_Geschäftspartner';
-llxHeader('', $title, $help_url);
-
-$head = societe_prepare_head($object);
-
-dol_htmloutput_mesg(is_numeric($error) ? '' : $error, $errors, 'error');
-
-print dol_get_fiche_head($head, 'lettering_customer', $langs->trans("ThirdParty"), 0, 'company');
-
-$linkback = '<a href="'.DOL_URL_ROOT.'/societe/list.php?restore_lastsearch_values=1">'.$langs->trans("BackToList").'</a>';
-
-dol_banner_tab($object, 'socid', $linkback, ($user->socid ? 0 : 1), 'rowid', 'nom', '', '', 0, '', '', 'arearefnobottom');
-
-print dol_get_fiche_end();
-
-$sql = "SELECT bk.rowid, bk.doc_date, bk.doc_type, bk.doc_ref, ";
-$sql .= " bk.subledger_account, bk.numero_compte , bk.label_compte, bk.debit, ";
-$sql .= " bk.credit, bk.montant, bk.sens, bk.code_journal, bk.piece_num, bk.lettering_code";
-$sql .= " FROM ".MAIN_DB_PREFIX."accounting_bookkeeping as bk";
-$sql .= " WHERE (bk.subledger_account =  '".$db->escape($object->code_compta)."' AND bk.numero_compte = '".$db->escape($conf->global->ACCOUNTING_ACCOUNT_CUSTOMER)."' )";
-
-/*
-if (dol_strlen($search_date_start) || dol_strlen($search_date_end)) {
-	$sql .= " AND ( bk.doc_date BETWEEN  '" . $db->idate($search_date_start) . "' AND  '" . $db->idate($search_date_end) . "' )";
-}
-*/
-
-$sql .= ' AND bk.entity IN ('.getEntity('accountingbookkeeping').')';
-$sql .= $db->order($sortfield, $sortorder);
-
-$debit = 0;
-$credit = 0;
-$solde = 0;
-// Count total nb of records and calc total sum
-$nbtotalofrecords = '';
-$resql = $db->query($sql);
-if (!$resql) {
-	dol_print_error($db);
-	exit();
-}
-$nbtotalofrecords = $db->num_rows($resql);
-
-while ($obj = $db->fetch_object($resql)) {
-	$debit += $obj->debit;
-	$credit += $obj->credit;
-
-	$solde += ($obj->credit - $obj->debit);
-}
-
-$sql .= $db->plimit($limit + 1, $offset);
-
-dol_syslog("/accountancy/bookkeeping/thirdparty_lettering_customer.php", LOG_DEBUG);
-$resql = $db->query($sql);
-if (!$resql) {
-	dol_print_error($db);
-	exit();
-}
-
-$param = '';
-$param .= "&socid=".urlencode($socid);
-
-$num = $db->num_rows($resql);
-
-dol_syslog("/accountancy/bookkeeping/thirdparty_lettering_customer.php", LOG_DEBUG);
-if ($resql) {
-	$i = 0;
-
-	$param = "&socid=".$socid;
-	print '<form name="add" action="'.$_SERVER["PHP_SELF"].'?socid='.$object->id.'" method="POST">';
-	print '<input type="hidden" name="token" value="'.newToken().'">';
-	print '<input type="hidden" name="socid" value="'.$object->id.'">';
-
-	$letteringbutton = '<a class="divButAction"><span class="valignmiddle"><input class="butAction" type="submit" value="lettering" name="lettering" id="lettering"></span></a>';
-
-	print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num, $nbtotalofrecords, 'title_companies', 0, $letteringbutton, '', $limit);
-
-	print '<div class="div-table-responsive-no-min">';
-	print '<table class="liste centpercent">'."\n";
-
-	/*
-	print '<tr class="liste_titre">';
-	//print '<td><input type="text" name="search_doc_type" value="' . $search_doc_type . '"></td>';
-
-	// Date
-	print '<td class="liste_titre center">';
-	print '<div class="nowrap">';
-	print $langs->trans('From') . ' ';
-	print $form->selectDate($search_date_start, 'date_creation_start', 0, 0, 1);
-	print '</div>';
-	print '<div class="nowrap">';
-	print $langs->trans('to') . ' ';
-	print $form->selectDate($search_date_end, 'date_creation_end', 0, 0, 1);
-	print '</div>';
-	print '</td>';
-
-	// Piece
-	print '<td><input type="text" name="search_doc_ref" value="' . $search_doc_ref . '"></td>';
-	print '<td colspan="6">&nbsp;</td>';
-	print '<td class="right">';
-	$searchpicto = $form->showFilterButtons();
-	print $searchpicto;
-	print '</td>';
-	print '</tr>';
-	*/
-
-	print '<tr class="liste_titre">';
-	//print_liste_field_titre("Doctype", $_SERVER["PHP_SELF"], "bk.doc_type", "", $param, "", $sortfield, $sortorder);
-	print_liste_field_titre("Docdate", $_SERVER["PHP_SELF"], "bk.doc_date", "", $param, "", $sortfield, $sortorder, 'center ');
-	print_liste_field_titre("Piece", $_SERVER["PHP_SELF"], "bk.doc_ref", "", $param, "", $sortfield, $sortorder);
-	print_liste_field_titre("LabelAccount", $_SERVER["PHP_SELF"], "bk.label_compte", "", $param, "", $sortfield, $sortorder);
-	print_liste_field_titre("Debit", $_SERVER["PHP_SELF"], "bk.debit", "", $param, "", $sortfield, $sortorder);
-	print_liste_field_titre("Credit", $_SERVER["PHP_SELF"], "bk.credit", "", $param, "", $sortfield, $sortorder);
-	print_liste_field_titre("Balancing", $_SERVER["PHP_SELF"], "", "", $param, "", $sortfield, $sortorder);
-	print_liste_field_titre("Codejournal", $_SERVER["PHP_SELF"], "bk.code_journal", "", $param, "", $sortfield, $sortorder, 'center ');
-	print_liste_field_titre("LetteringCode", $_SERVER["PHP_SELF"], "bk.lettering_code", "", $param, "", $sortfield, $sortorder, 'center ');
-	print_liste_field_titre("", "", "", '', '', "", $sortfield, $sortorder, 'maxwidthsearch center ');
-	print "</tr>\n";
-
-	$solde = 0;
-	$tmp = '';
-
-	while ($obj = $db->fetch_object($resql)) {
-		if ($tmp != $obj->lettering_code || empty($tmp)) {
-			$tmp = $obj->lettering_code;
-		}
-		/*if ($tmp != $obj->lettering_code || empty($obj->lettering_code))*/	$solde += ($obj->credit - $obj->debit);
-
-		print '<tr class="oddeven">';
-
-		//print '<td>' . $obj->doc_type . '</td>' . "\n";
-		print '<td class="center">'.dol_print_date($db->jdate($obj->doc_date), 'day').'</td>';
-		print '<td>'.$obj->doc_ref.'</td>';
-		print '<td>'.$obj->label_compte.'</td>';
-		print '<td class="nowrap right">'.price($obj->debit).'</td>';
-		print '<td class="nowrap right">'.price($obj->credit).'</td>';
-		print '<td class="nowrap right">'.price(round($solde, 2)).'</td>';
-
-		// Journal
-		$accountingjournal = new AccountingJournal($db);
-		$result = $accountingjournal->fetch('', $obj->code_journal);
-		$journaltoshow = (($result > 0) ? $accountingjournal->getNomUrl(0, 0, 0, '', 0) : $obj->code_journal);
-		print '<td class="center">'.$journaltoshow.'</td>';
-
-		if (empty($obj->lettering_code) && empty($obj->date_validated)) {
-			print '<td class="nowrap center"><input type="checkbox" class="flat checkforselect" name="toselect[]" id="toselect[]" value="'.$obj->rowid.'" /></td>';
-			print '<td><a href="'.DOL_URL_ROOT.'/accountancy/bookkeeping/card.php?piece_num='.$obj->piece_num.'">';
-			print img_edit();
-			print '</a></td>'."\n";
-		} else {
-			print '<td class="center">'.$obj->lettering_code.'</td>';
-			print '<td></td>';
-		}
-
-		print "</tr>\n";
-	}
-
-	print '<tr class="oddeven">';
-	print '<td class="right" colspan="3">'.$langs->trans("Total").':</td>'."\n";
-	print '<td class="right nowraponall amount"><strong>'.price($debit).'</strong></td>';
-	print '<td class="right nowraponall amount"><strong>'.price($credit).'</strong></td>';
-	print '<td colspan="4"></td>';
-	print "</tr>\n";
-
-	print '<tr class="oddeven">';
-	print '<td class="right" colspan="3">'.$langs->trans("Balancing").':</td>'."\n";
-	print '<td colspan="2">&nbsp;</td>';
-	print '<td class="right nowraponall amount"><strong>'.price($credit - $debit).'</strong></td>';
-	print '<td colspan="6"></td>';
-	print "</tr>\n";
-
-	print "</table>";
-
-	print '<div class="tabsAction tabsActionNoBottom">'."\n";
-	print $letteringbutton;
-	print '</div>';
-
-	print "</form>";
-	$db->free($resql);
-} else {
-	dol_print_error($db);
-}
-
-// End of page
-llxFooter();
-$db->close();

+ 0 - 322
htdocs/accountancy/bookkeeping/thirdparty_lettering_supplier.php

@@ -1,322 +0,0 @@
-<?php
-/* Copyright (C) 2004-2005 Rodolphe Quiedeville <rodolphe@quiedeville.org>
- * Copyright (C) 2005      Laurent Destailleur  <eldy@users.sourceforge.net>
- * Copyright (C) 2013      Olivier Geffroy      <jeff@jeffinfo.com>
- * Copyright (C) 2013      Florian Henry	    <florian.henry@open-concept.pro>
- * Copyright (C) 2013-2019 Alexandre Spangaro   <aspangaro@open-dsi.fr>
- * Copyright (C) 2018-2020 Frédéric France      <frederic.france@netlogic.fr>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
-
-/**
- * \file    	htdocs/accountancy/bookkeeping/thirdparty_lettering_supplier.php
- * \ingroup 	Accountancy (Double entries)
- * \brief 		Tab to setup lettering
- */
-require '../../main.inc.php';
-require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
-require_once DOL_DOCUMENT_ROOT.'/accountancy/class/bookkeeping.class.php';
-require_once DOL_DOCUMENT_ROOT.'/accountancy/class/lettering.class.php';
-require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingjournal.class.php';
-require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
-require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
-
-// Load translation files required by the page
-$langs->loadLangs(array("compta", "accountancy"));
-
-$action     = GETPOST('action', 'aZ09');
-$massaction = GETPOST('massaction', 'alpha');
-$show_files = GETPOST('show_files', 'int');
-$confirm    = GETPOST('confirm', 'alpha');
-$toselect   = GETPOST('toselect', 'array');
-// $socid = GETPOST('socid', 'int') ? ((int) GETPOST('socid', 'int')) : ((int) GETPOST('id', 'int'));
-// Security check
-$socid = GETPOSTINT("socid");
-// if ($user->socid) $socid=$user->socid;
-
-
-$limit = GETPOST('limit', 'int') ? GETPOST('limit', 'int') : $conf->liste_limit;
-$sortfield = GETPOST("sortfield", 'alpha');
-$sortorder = GETPOST("sortorder", 'alpha');
-$page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int');
-if (empty($page) || $page == - 1) {
-	$page = 0;
-} // If $page is not defined, or '' or -1
-$offset = $limit * $page;
-$pageprev = $page - 1;
-$pagenext = $page + 1;
-if ($sortorder == "") {
-	$sortorder = "ASC";
-}
-if ($sortfield == "") {
-	$sortfield = "bk.doc_date";
-}
-
-/*
-$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(0, 0, 0, GETPOST('date_endmonth', 'int'), GETPOST('date_endday', 'int'), GETPOST('date_endyear', 'int'));
-//$search_doc_type = GETPOST("search_doc_type",'alpha');
-$search_doc_ref = GETPOST("search_doc_ref",'alpha');
-*/
-
-$lettering = GETPOST('lettering', 'alpha');
-if (!empty($lettering)) {
-	$action = $lettering;
-}
-
-/*
-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_date_start = '';
-	$search_date_end = '';
-	//$search_doc_type='';
-	$search_doc_ref='';
-}
-*/
-
-$lettering = new Lettering($db);
-$object = new Societe($db);
-$object->id = $socid;
-$result = $object->fetch($socid);
-if ($result < 0) {
-	setEventMessages($object->error, $object->errors, 'errors');
-}
-
-if (empty($conf->accounting->enabled)) {
-	accessforbidden();
-}
-if ($user->socid > 0) {
-	accessforbidden();
-}
-if (empty($user->rights->accounting->mouvements->lire)) {
-	accessforbidden();
-}
-
-
-/*
- * Action
- */
-
-if ($action == 'lettering') {
-	$result = $lettering->updateLettering($toselect);
-
-	if ($result < 0) {
-		setEventMessages('', $lettering->errors, 'errors');
-		$error++;
-	}
-}
-
-/*
-if ($action == 'autolettrage') {
-
-	$result = $lettering->letteringThirdparty($socid);
-
-	if ($result < 0) {
-		setEventMessages('', $lettering->errors, 'errors');
-		$error++;
-	}
-}
-*/
-
-/*
- * View
- */
-
-$form = new Form($db);
-$formaccounting = new FormAccounting($db);
-
-$title = $object->name." - ".$langs->trans('TabLetteringSupplier');
-$help_url = 'EN:Module_Third_Parties|FR:Module_Tiers|ES:Empresas|DE:Modul_Geschäftspartner';
-llxHeader('', $title, $help_url);
-
-$head = societe_prepare_head($object);
-
-dol_htmloutput_mesg(is_numeric($error) ? '' : $error, $errors, 'error');
-
-print dol_get_fiche_head($head, 'lettering_supplier', $langs->trans("ThirdParty"), 0, 'company');
-
-$linkback = '<a href="'.DOL_URL_ROOT.'/societe/list.php?restore_lastsearch_values=1">'.$langs->trans("BackToList").'</a>';
-
-dol_banner_tab($object, 'socid', $linkback, ($user->socid ? 0 : 1), 'rowid', 'nom', '', '', 0, '', '', 'arearefnobottom');
-
-print dol_get_fiche_end();
-
-$sql = "SELECT bk.rowid, bk.doc_date, bk.doc_type, bk.doc_ref, ";
-$sql .= " bk.subledger_account, bk.numero_compte , bk.label_compte, bk.debit, ";
-$sql .= " bk.credit, bk.montant, bk.sens, bk.code_journal, bk.piece_num, bk.lettering_code, bk.date_validated ";
-$sql .= " FROM ".MAIN_DB_PREFIX."accounting_bookkeeping as bk";
-$sql .= " WHERE (bk.subledger_account =  '".$db->escape($object->code_compta_fournisseur)."' AND bk.numero_compte = '".$db->escape($conf->global->ACCOUNTING_ACCOUNT_SUPPLIER)."' )";
-if (dol_strlen($search_date_start) || dol_strlen($search_date_end)) {
-	$sql .= " AND (bk.doc_date BETWEEN '".$db->idate($search_date_start)."' AND  '".$db->idate($search_date_end)."' )";
-}
-$sql .= ' AND bk.entity IN ('.getEntity('accountingbookkeeping').')';
-$sql .= $db->order($sortfield, $sortorder);
-
-$debit = 0;
-$credit = 0;
-$solde = 0;
-// Count total nb of records and calc total sum
-$nbtotalofrecords = '';
-$resql = $db->query($sql);
-if (!$resql) {
-	dol_print_error($db);
-	exit;
-}
-$nbtotalofrecords = $db->num_rows($resql);
-
-while ($obj = $db->fetch_object($resql)) {
-	$debit += $obj->debit;
-	$credit += $obj->credit;
-
-	$solde += ($obj->credit - $obj->debit);
-}
-
-$sql .= $db->plimit($limit + 1, $offset);
-
-dol_syslog("/accountancy/bookkeeping/thirdparty_lettering_supplier.php", LOG_DEBUG);
-$resql = $db->query($sql);
-if (!$resql) {
-		dol_print_error($db);
-		exit;
-}
-
-$param = '';
-$param .= "&socid=".urlencode($socid);
-
-$num = $db->num_rows($resql);
-
-dol_syslog("/accountancy/bookkeeping/thirdparty_lettering_supplier.php", LOG_DEBUG);
-$resql = $db->query($sql);
-if ($resql) {
-	$num = $db->num_rows($resql);
-	$i = 0;
-
-	$param = "&socid=".$socid;
-	print '<form name="add" action="'.$_SERVER["PHP_SELF"].'?socid='.$object->id.'" method="POST">';
-	print '<input type="hidden" name="token" value="'.newToken().'">';
-	print '<input type="hidden" name="socid" value="'.$object->id.'">';
-
-	$letteringbutton = '<a class="divButAction"><span class="valignmiddle"><input class="butAction" type="submit" value="lettering" name="lettering" id="lettering"></span></a>';
-
-	print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num, $nbtotalofrecords, 'title_companies', 0, $letteringbutton, '', $limit);
-
-	print '<div class="div-table-responsive-no-min">';
-	print '<table class="liste centpercent">'."\n";
-
-	/*
-	print '<tr class="liste_titre">';
-	//print '<td><input type="text" name="search_doc_type" value="' . $search_doc_type . '"></td>';
-
-	// Date
-	print '<td class="liste_titre center">';
-	print '<div class="nowrap">';
-	print $langs->trans('From') . ' ';
-	print $form->selectDate($search_date_start, 'date_creation_start', 0, 0, 1);
-	print '</div>';
-	print '<div class="nowrap">';
-	print $langs->trans('to') . ' ';
-	print $form->selectDate($search_date_end, 'date_creation_end', 0, 0, 1);
-	print '</div>';
-	print '</td>';
-
-	// Piece
-	print '<td><input type="text" name="search_doc_ref" value="' . $search_doc_ref . '"></td>';
-	print '<td colspan="6">&nbsp;</td>';
-	print '<td class="right">';
-	$searchpicto = $form->showFilterButtons();
-	print $searchpicto;
-	print '</td>';
-	print '</tr>';
-	*/
-
-	print '<tr class="liste_titre">';
-	//print_liste_field_titre("Doctype", $_SERVER["PHP_SELF"], "bk.doc_type", "", $param, "", $sortfield, $sortorder);
-	print_liste_field_titre("Docdate", $_SERVER["PHP_SELF"], "bk.doc_date", "", $param, "", $sortfield, $sortorder, 'center ');
-	print_liste_field_titre("Piece", $_SERVER["PHP_SELF"], "bk.doc_ref", "", $param, "", $sortfield, $sortorder);
-	print_liste_field_titre("LabelAccount", $_SERVER["PHP_SELF"], "bk.label_compte", "", $param, "", $sortfield, $sortorder);
-	print_liste_field_titre("Debit", $_SERVER["PHP_SELF"], "bk.debit", "", $param, "", $sortfield, $sortorder);
-	print_liste_field_titre("Credit", $_SERVER["PHP_SELF"], "bk.credit", "", $param, "", $sortfield, $sortorder);
-	print_liste_field_titre("Balancing", $_SERVER["PHP_SELF"], "", "", $param, "", $sortfield, $sortorder);
-	print_liste_field_titre("Codejournal", $_SERVER["PHP_SELF"], "bk.code_journal", "", $param, "", $sortfield, $sortorder, 'center ');
-	print_liste_field_titre("LetteringCode", $_SERVER["PHP_SELF"], "bk.lettering_code", "", $param, "", $sortfield, $sortorder, 'center ');
-	print_liste_field_titre("", "", "", '', '', "", $sortfield, $sortorder, 'maxwidthsearch center ');
-	print "</tr>\n";
-
-	$solde = 0;
-	$tmp = '';
-	while ($obj = $db->fetch_object($resql)) {
-		if ($tmp != $obj->lettering_code || empty($tmp)) {
-			$tmp = $obj->lettering_code;
-		}
-		/*if ($tmp != $obj->lettering_code || empty($obj->lettering_code))*/	$solde += ($obj->credit - $obj->debit);
-
-		print '<tr class="oddeven">';
-
-		//print '<td>' . $obj->doc_type . '</td>' . "\n";
-		print '<td class="center">'.dol_print_date($db->jdate($obj->doc_date), 'day').'</td>';
-		print '<td>'.$obj->doc_ref.'</td>';
-		print '<td>'.$obj->label_compte.'</td>';
-		print '<td class="nowrap right">'.price($obj->debit).'</td>';
-		print '<td class="nowrap right">'.price($obj->credit).'</td>';
-		print '<td class="nowrap right">'.price(round($solde, 2)).'</td>';
-
-		// Journal
-		$accountingjournal = new AccountingJournal($db);
-		$result = $accountingjournal->fetch('', $obj->code_journal);
-		$journaltoshow = (($result > 0) ? $accountingjournal->getNomUrl(0, 0, 0, '', 0) : $obj->code_journal);
-		print '<td class="center">'.$journaltoshow.'</td>';
-
-		if (empty($obj->lettering_code) && empty($obj->date_validated)) {
-			print '<td class="nowrap center"><input type="checkbox" class="flat checkforselect" name="toselect[]" id="toselect[]" value="'.$obj->rowid.'" /></td>';
-			print '<td><a href="'.DOL_URL_ROOT.'/accountancy/bookkeeping/card.php?piece_num='.$obj->piece_num.'">';
-			print img_edit();
-			print '</a></td>'."\n";
-		} else {
-			print '<td class="center">'.$obj->lettering_code.'</td>';
-			print '<td></td>';
-		}
-
-		print "</tr>\n";
-	}
-
-	print '<tr class="oddeven">';
-	print '<td class="right" colspan="3">'.$langs->trans("Total").':</td>'."\n";
-	print '<td class="right nowraponall amount"><strong>'.price($debit).'</strong></td>';
-	print '<td class="right nowraponall amount"><strong>'.price($credit).'</strong></td>';
-	print '<td colspan="6"></td>';
-	print "</tr>\n";
-
-	print '<tr class="oddeven">';
-	print '<td class="right" colspan="3">'.$langs->trans("Balancing").':</td>'."\n";
-	print '<td colspan="2">&nbsp;</td>';
-	print '<td class="right nowraponall amount"><strong>'.price($credit - $debit).'</strong></td>';
-	print '<td colspan="4"></td>';
-	print "</tr>\n";
-
-	print "</table>";
-
-	print '<div class="tabsAction tabsActionNoBottom">'."\n";
-	print $letteringbutton;
-	print '</div>';
-
-	print "</form>";
-	$db->free($resql);
-} else {
-	dol_print_error($db);
-}
-
-// End of page
-llxFooter();
-$db->close();

+ 11 - 7
htdocs/accountancy/class/accountancyexport.class.php

@@ -64,6 +64,10 @@ class AccountancyExport
 	public static $EXPORT_TYPE_FEC = 1000;
 	public static $EXPORT_TYPE_FEC2 = 1010;
 
+	/**
+	 * @var DoliDB	Database handler
+	 */
+	public $db;
 
 	/**
 	 * @var string[] Error codes (or messages)
@@ -915,7 +919,7 @@ class AccountancyExport
 		print "Montantdevise".$separator;
 		print "Idevise".$separator;
 		print "DateLimitReglmt".$separator;
-		print "NumFacture".$separator;
+		print "NumFacture";
 		print $end_line;
 
 		foreach ($objectLines as $line) {
@@ -997,13 +1001,13 @@ class AccountancyExport
 				print $line->multicurrency_amount . $separator;
 
 				// FEC:Idevise
-				print $line->multicurrency_code.$separator;
+				print $line->multicurrency_code . $separator;
 
 				// FEC_suppl:DateLimitReglmt
-				print $date_limit_payment;
+				print $date_limit_payment . $separator;
 
 				// FEC_suppl:NumFacture
-				print dol_trunc(self::toAnsi($refInvoice), 17, 'right', 'UTF-8', 1) . $separator;
+				print dol_trunc(self::toAnsi($refInvoice), 17, 'right', 'UTF-8', 1);
 
 				print $end_line;
 			}
@@ -1042,7 +1046,7 @@ class AccountancyExport
 		print "Montantdevise".$separator;
 		print "Idevise".$separator;
 		print "DateLimitReglmt".$separator;
-		print "NumFacture".$separator;
+		print "NumFacture";
 		print $end_line;
 
 		foreach ($objectLines as $line) {
@@ -1127,10 +1131,10 @@ class AccountancyExport
 				print $line->multicurrency_code . $separator;
 
 				// FEC_suppl:DateLimitReglmt
-				print $date_limit_payment;
+				print $date_limit_payment . $separator;
 
 				// FEC_suppl:NumFacture
-				print dol_trunc(self::toAnsi($refInvoice), 17, 'right', 'UTF-8', 1) . $separator;
+				print dol_trunc(self::toAnsi($refInvoice), 17, 'right', 'UTF-8', 1);
 
 
 				print $end_line;

+ 78 - 37
htdocs/accountancy/class/accountancyimport.class.php

@@ -29,7 +29,7 @@
 /**
  * \file		htdocs/accountancy/class/accountancyimport.class.php
  * \ingroup		Accountancy (Double entries)
- * \brief 		Class accountancy import
+ * \brief 		Class with methods for accountancy import
  */
 
 
@@ -39,63 +39,104 @@
  */
 class AccountancyImport
 {
+	/**
+	 * @var DoliDB	Database handler
+	 */
+	public $db;
+
+
+	/**
+	 * Constructor
+	 *
+	 * @param DoliDb $db Database handler
+	 */
+	public function __construct(DoliDB $db)
+	{
+		$this->db = $db;
+	}
+
+	/**
+	 *  Clean amount
+	 *
+	 * @param   array       $arrayrecord        Array of read values: [fieldpos] => (['val']=>val, ['type']=>-1=null,0=blank,1=string), [fieldpos+1]...
+	 * @param   array       $listfields         Fields list to add
+	 * @param 	int			$record_key         Record key
+	 * @return  mixed							Value
+	 */
+	public function cleanAmount(&$arrayrecord, $listfields, $record_key)
+	{
+		$value_trim = trim($arrayrecord[$record_key]['val']);
+		return floatval($value_trim);
+	}
+
+	/**
+	 *  Clean value with trim
+	 *
+	 * @param   array       $arrayrecord        Array of read values: [fieldpos] => (['val']=>val, ['type']=>-1=null,0=blank,1=string), [fieldpos+1]...
+	 * @param   array       $listfields         Fields list to add
+	 * @param 	int			$record_key         Record key
+	 * @return  mixed							Value
+	 */
+	public function cleanValue(&$arrayrecord, $listfields, $record_key)
+	{
+		return trim($arrayrecord[$record_key]['val']);
+	}
+
 	/**
 	 *  Compute amount
 	 *
 	 * @param   array       $arrayrecord        Array of read values: [fieldpos] => (['val']=>val, ['type']=>-1=null,0=blank,1=string), [fieldpos+1]...
-	 * @param   string      $fieldname          Field name with alias
 	 * @param   array       $listfields         Fields list to add
-	 * @param   array       $listvalues         Values list to add
-	 * @return  int         <0 if KO, >0 if OK
+	 * @param 	int			$record_key         Record key
+	 * @return  mixed							Value
 	 */
-	public function computeAmount(&$arrayrecord, $fieldname, &$listfields, &$listvalues)
+	public function computeAmount(&$arrayrecord, $listfields, $record_key)
 	{
-		$fieldArr = explode('.', $fieldname);
-		if (count($fieldArr) > 0) {
-			$fieldname = $fieldArr[1];
-		}
+		// get fields indexes
+		$field_index_list = array_flip($listfields);
+		if (isset($field_index_list['debit']) && isset($field_index_list['credit'])) {
+			$debit_index = $field_index_list['debit'];
+			$credit_index = $field_index_list['credit'];
 
-		$debit  = trim($arrayrecord[11]['val']);
-		$credit = trim($arrayrecord[12]['val']);
-		if (!empty($debit)) {
-			$amount = $debit;
-		} else {
-			$amount = $credit;
-		}
+			$debit  = floatval($arrayrecord[$debit_index]['val']);
+			$credit = floatval($arrayrecord[$credit_index]['val']);
+			if (!empty($debit)) {
+				$amount = $debit;
+			} else {
+				$amount = $credit;
+			}
 
-		$listfields[] = $fieldname;
-		$listvalues[] = "'" . abs($amount) . "'";
+			return "'" . $this->db->escape(abs($amount)) . "'";
+		}
 
-		return 1;
+		return "''";
 	}
 
 
 	/**
-	 *  Compute sens
+	 *  Compute direction
 	 *
 	 * @param   array       $arrayrecord        Array of read values: [fieldpos] => (['val']=>val, ['type']=>-1=null,0=blank,1=string), [fieldpos+1]...
-	 * @param   string      $fieldname          Field name with alias
 	 * @param   array       $listfields         Fields list to add
-	 * @param   array       $listvalues         Values list to add
-	 * @return  int         <0 if KO, >0 if OK
+	 * @param 	int			$record_key         Record key
+	 * @return  mixed							Value
 	 */
-	public function computeDirection(&$arrayrecord, $fieldname, &$listfields, &$listvalues)
+	public function computeDirection(&$arrayrecord, $listfields, $record_key)
 	{
-		$fieldArr = explode('.', $fieldname);
-		if (count($fieldArr) > 0) {
-			$fieldname = $fieldArr[1];
-		}
+		$field_index_list = array_flip($listfields);
+		if (isset($field_index_list['debit'])) {
+			$debit_index = $field_index_list['debit'];
 
-		$debit = trim($arrayrecord[11]['val']);
-		if (!empty($debit)) {
-			$sens = 'D';
-		} else {
-			$sens = 'C';
-		}
+			$debit = floatval($arrayrecord[$debit_index]['val']);
+			if (!empty($debit)) {
+				$sens = 'D';
+			} else {
+				$sens = 'C';
+			}
 
-		$listfields[] = $fieldname;
-		$listvalues[] = "'" . $sens . "'";
+			return "'" . $this->db->escape($sens) . "'";
+		}
 
-		return 1;
+		return "''";
 	}
 }

+ 44 - 50
htdocs/accountancy/class/accountingaccount.class.php

@@ -29,6 +29,7 @@
 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
 require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
 require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
+
 /**
  * Class to manage accounting accounts
  */
@@ -108,6 +109,11 @@ class AccountingAccount extends CommonObject
 	 */
 	public $account_category;
 
+	/**
+	 * @var int Label category account
+	 */
+	public $account_category_label;
+
 	/**
 	 * @var int Status
 	 */
@@ -148,6 +154,11 @@ class AccountingAccount extends CommonObject
 	 */
 	private $accountingaccount_codetotid_cache = array();
 
+
+	const STATUS_ENABLED = 1;
+	const STATUS_DISABLED = 0;
+
+
 	/**
 	 * Constructor
 	 *
@@ -158,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) $conf->global->CHARTOFACCOUNTS).")"; // Used to add a filter in Form::showrefnav method
 	}
 
 	/**
@@ -166,7 +177,7 @@ class AccountingAccount extends CommonObject
 	 *
 	 * @param 	int 	       $rowid 				    Id
 	 * @param 	string 	       $account_number 	        Account number
-	 * @param 	int|boolean    $limittocurrentchart     1 or true=Load record only if it is into current active char of account
+	 * @param 	int|boolean    $limittocurrentchart     1 or true=Load record only if it is into current active chart of account
 	 * @param   string         $limittoachartaccount    'ABC'=Load record only if it is into chart account with code 'ABC' (better and faster than previous parameter if you have chart of account code).
 	 * @return 	int                                     <0 if KO, 0 if not found, Id of record if OK and found
 	 */
@@ -187,13 +198,14 @@ 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='.$this->db->escape($conf->global->CHARTOFACCOUNTS).')';
+				$sql .= ' AND a.fk_pcg_version IN (SELECT pcg_version FROM '.MAIN_DB_PREFIX.'accounting_system WHERE rowid = '.((int) $conf->global->CHARTOFACCOUNTS).')';
 			}
 			if (!empty($limittoachartaccount)) {
 				$sql .= " AND a.fk_pcg_version = '".$this->db->escape($limittoachartaccount)."'";
 			}
 
-			dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
+			dol_syslog(get_class($this)."::fetch rowid=".$rowid." account_number=".$account_number, LOG_DEBUG);
+
 			$result = $this->db->query($sql);
 			if ($result) {
 				$obj = $this->db->fetch_object($result);
@@ -464,7 +476,7 @@ class AccountingAccount extends CommonObject
 	 */
 	public function getNomUrl($withpicto = 0, $withlabel = 0, $nourl = 0, $moretitle = '', $notooltip = 0, $save_lastsearch_value = -1, $withcompletelabel = 0, $option = '')
 	{
-		global $langs, $conf;
+		global $langs, $conf, $hookmanager;
 		require_once DOL_DOCUMENT_ROOT . '/core/lib/accounting.lib.php';
 
 		if (!empty($conf->dol_no_mouse_hover)) {
@@ -495,7 +507,7 @@ class AccountingAccount extends CommonObject
 			$url .= '&save_lastsearch_values=1';
 		}
 
-		$picto = 'billr';
+		$picto = 'accounting_account';
 		$label = '';
 
 		if (empty($this->labelshort) || $withcompletelabel == 1) {
@@ -549,13 +561,22 @@ class AccountingAccount extends CommonObject
 		if ($withpicto != 2) {
 			$result .= $linkstart . $label_link . $linkend;
 		}
+		global $action;
+		$hookmanager->initHooks(array($this->element . 'dao'));
+		$parameters = array('id'=>$this->id, 'getnomurl' => &$result);
+		$reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
+		if ($reshook > 0) {
+			$result = $hookmanager->resPrint;
+		} else {
+			$result .= $hookmanager->resPrint;
+		}
 		return $result;
 	}
 
 	/**
 	 * Information on record
 	 *
-	 * @param int $id of record
+	 * @param int 	$id 	ID of record
 	 * @return void
 	 */
 	public function info($id)
@@ -685,49 +706,22 @@ class AccountingAccount extends CommonObject
 	public function LibStatut($status, $mode = 0)
 	{
 		// phpcs:enable
-		global $langs;
-		$langs->loadLangs(array("users"));
+		if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
+			global $langs;
+			$langs->load("users");
+			$this->labelStatus[self::STATUS_ENABLED] = $langs->transnoentitiesnoconv('Enabled');
+			$this->labelStatus[self::STATUS_DISABLED] = $langs->transnoentitiesnoconv('Disabled');
+			$this->labelStatusShort[self::STATUS_ENABLED] = $langs->transnoentitiesnoconv('Enabled');
+			$this->labelStatusShort[self::STATUS_DISABLED] = $langs->transnoentitiesnoconv('Disabled');
+		}
 
-		if ($mode == 0) {
-			if ($status == 1) {
-				return $langs->trans('Enabled');
-			} elseif ($status == 0) {
-				return $langs->trans('Disabled');
-			}
-		} elseif ($mode == 1) {
-			if ($status == 1) {
-				return $langs->trans('Enabled');
-			} elseif ($status == 0) {
-				return $langs->trans('Disabled');
-			}
-		} elseif ($mode == 2) {
-			if ($status == 1) {
-				return img_picto($langs->trans('Enabled'), 'statut4') . ' ' . $langs->trans('Enabled');
-			} elseif ($status == 0) {
-				return img_picto($langs->trans('Disabled'), 'statut5') . ' ' . $langs->trans('Disabled');
-			}
-		} elseif ($mode == 3) {
-			if ($status == 1) {
-				return img_picto($langs->trans('Enabled'), 'statut4');
-			} elseif ($status == 0) {
-				return img_picto($langs->trans('Disabled'), 'statut5');
-			}
-		} elseif ($mode == 4) {
-			if ($status == 1) {
-				return img_picto($langs->trans('Enabled'), 'statut4') . ' ' . $langs->trans('Enabled');
-			} elseif ($status == 0) {
-				return img_picto($langs->trans('Disabled'), 'statut5') . ' ' . $langs->trans('Disabled');
-			}
-		} elseif ($mode == 5) {
-			if ($status == 1) {
-				return $langs->trans('Enabled') . ' ' . img_picto($langs->trans('Enabled'), 'statut4');
-			} elseif ($status == 0) {
-				return $langs->trans('Disabled') . ' ' . img_picto($langs->trans('Disabled'), 'statut5');
-			}
+		$statusType = 'status4';
+		if ($status == self::STATUS_DISABLED) {
+			$statusType = 'status5';
 		}
-	}
 
-	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
+		return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
+	}
 
 	/**
 	 * Return Suggest accounting accounts to bind
@@ -866,15 +860,15 @@ 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)) {
-					$code_t = $buyer->code_compta;
+				if (!empty($buyer->code_compta_product)) {
+					$code_t = $buyer->code_compta_product;
 					$suggestedid = $accountingAccount['thirdparty'];
 					$suggestedaccountingaccountfor = 'thridparty';
 				}
 			}
 
 			// Manage Deposit
-			if ($factureDet->desc == "(DEPOSIT)") {
+			if ($factureDet->desc == "(DEPOSIT)" || $facture->type == $facture::TYPE_DEPOSIT) {
 				$accountdeposittoventilated = new self($this->db);
 				$result = $accountdeposittoventilated->fetch('', $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT, 1);
 				if ($result < 0) {

+ 705 - 2
htdocs/accountancy/class/accountingjournal.class.php

@@ -1,5 +1,5 @@
 <?php
-/* Copyright (C) 2017		Alexandre Spangaro   <aspangaro@open-dsi.fr>
+/* Copyright (C) 2017-2022  OpenDSI     <support@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
@@ -81,6 +81,24 @@ class AccountingJournal extends CommonObject
 	 */
 	public $lines;
 
+	/**
+	 * @var array 		Accounting account cached
+	 */
+	static public $accounting_account_cached = array();
+
+	/**
+	 * @var array 		Nature mapping
+	 */
+	static public $nature_maps = array(
+		1 => 'variousoperations',
+		2 => 'sells',
+		3 => 'purchases',
+		4 => 'bank',
+		5 => 'expensereports',
+		8 => 'inventories',
+		9 => 'hasnew',
+	);
+
 	/**
 	 * Constructor
 	 *
@@ -221,7 +239,7 @@ class AccountingJournal extends CommonObject
 	 */
 	public function getNomUrl($withpicto = 0, $withlabel = 0, $nourl = 0, $moretitle = '', $notooltip = 0)
 	{
-		global $langs, $conf, $user;
+		global $langs, $conf, $user, $hookmanager;
 
 		if (!empty($conf->dol_no_mouse_hover)) {
 			$notooltip = 1; // Force disable tooltips
@@ -276,6 +294,15 @@ class AccountingJournal extends CommonObject
 		}
 		$result .= $linkend;
 
+		global $action;
+		$hookmanager->initHooks(array('accountingjournaldao'));
+		$parameters = array('id'=>$this->id, 'getnomurl' => &$result);
+		$reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
+		if ($reshook > 0) {
+			$result = $hookmanager->resPrint;
+		} else {
+			$result .= $hookmanager->resPrint;
+		}
 		return $result;
 	}
 
@@ -336,4 +363,680 @@ class AccountingJournal extends CommonObject
 			}
 		}
 	}
+
+
+	/**
+	 *  Get journal data
+	 *
+	 * @param 	User			$user				User who get infos
+	 * @param 	string			$type				Type data returned ('view', 'bookkeeping', 'csv')
+	 * @param 	int				$date_start			Filter 'start date'
+	 * @param 	int				$date_end			Filter 'end date'
+	 * @param 	string			$in_bookkeeping		Filter 'in bookkeeping' ('already', 'notyet')
+	 * @return 	array|int							<0 if KO, >0 if OK
+	 */
+	public function getData(User $user, $type = 'view', $date_start = null, $date_end = null, $in_bookkeeping = 'notyet')
+	{
+		global $hookmanager;
+
+		// Clean parameters
+		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'));
+		$parameters = array('data' => &$data, 'user' => $user, 'type' => $type, 'date_start' => $date_start, 'date_end' => $date_end, 'in_bookkeeping' => $in_bookkeeping);
+		$reshook = $hookmanager->executeHooks('getData', $parameters, $this); // Note that $action and $object may have been
+		if ($reshook < 0) {
+			$this->error = $hookmanager->error;
+			$this->errors = $hookmanager->errors;
+			return -1;
+		} elseif (empty($reshook)) {
+			switch ($this->nature) {
+				case 1: // Various Journal
+					$data = $this->getAssetData($user, $type, $date_start, $date_end, $in_bookkeeping);
+					break;
+				//              case 2: // Sells Journal
+				//              case 3: // Purchases Journal
+				//              case 4: // Bank Journal
+				//              case 5: // Expense reports Journal
+				//              case 8: // Inventory Journal
+				//              case 9: // hasnew Journal
+			}
+		}
+
+		return $data;
+	}
+
+	/**
+	 *  Get asset data for various journal
+	 *
+	 * @param 	User			$user				User who get infos
+	 * @param 	string			$type				Type data returned ('view', 'bookkeeping', 'csv')
+	 * @param 	int				$date_start			Filter 'start date'
+	 * @param 	int				$date_end			Filter 'end date'
+	 * @param 	string			$in_bookkeeping		Filter 'in bookkeeping' ('already', 'notyet')
+	 * @return 	array|int							<0 if KO, >0 if OK
+	 */
+	public function getAssetData(User $user, $type = 'view', $date_start = null, $date_end = null, $in_bookkeeping = 'notyet')
+	{
+		global $conf, $langs;
+
+		if (empty($conf->asset->enabled)) {
+			return array();
+		}
+
+		require_once DOL_DOCUMENT_ROOT . '/core/lib/accounting.lib.php';
+		require_once DOL_DOCUMENT_ROOT . '/asset/class/asset.class.php';
+		require_once DOL_DOCUMENT_ROOT . '/asset/class/assetaccountancycodes.class.php';
+		require_once DOL_DOCUMENT_ROOT . '/asset/class/assetdepreciationoptions.class.php';
+
+		$langs->loadLangs(array("assets"));
+
+		// Clean parameters
+		if (empty($type)) $type = 'view';
+		if (empty($in_bookkeeping)) $in_bookkeeping = 'notyet';
+
+		$sql = "";
+		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";
+		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
+		$sql .= " AND ad.ref != ''"; // not reversal lines
+		if ($date_start && $date_end) {
+			$sql .= " AND ad.depreciation_date >= '" . $this->db->idate($date_start) . "' AND ad.depreciation_date <= '" . $this->db->idate($date_end) . "'";
+		}
+		// Define begin binding date
+		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
+		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);
+		$resql = $this->db->query($sql);
+		if (!$resql) {
+			$this->errors[] = $this->db->lasterror();
+			return -1;
+		}
+
+		$pre_data = array(
+			'elements' => array(),
+		);
+		while ($obj = $this->db->fetch_object($resql)) {
+			if (!isset($pre_data['elements'][$obj->rowid])) {
+				$pre_data['elements'][$obj->rowid] = array(
+					'ref' => $obj->asset_ref,
+					'label' => $obj->asset_label,
+					'acquisition_value_ht' => $obj->asset_acquisition_value_ht,
+					'depreciation' => array(),
+				);
+
+				// Disposal infos
+				if (isset($obj->asset_disposal_date)) {
+					$pre_data['elements'][$obj->rowid]['disposal'] = array(
+						'date' => $this->db->jdate($obj->asset_disposal_date),
+						'amount' => $obj->asset_disposal_amount_ht,
+						'subject_to_vat' => !empty($obj->asset_disposal_subject_to_vat),
+					);
+				}
+			}
+
+			$compta_debit = empty($obj->accountancy_code_debit) ? 'NotDefined' : $obj->accountancy_code_debit;
+			$compta_credit = empty($obj->accountancy_code_credit) ? 'NotDefined' : $obj->accountancy_code_credit;
+
+			$pre_data['elements'][$obj->rowid]['depreciation'][$obj->depreciation_id] = array(
+				'date' => $this->db->jdate($obj->depreciation_date),
+				'ref' => $obj->depreciation_ref,
+				'lines' => array(
+					$compta_debit => -$obj->depreciation_ht,
+					$compta_credit => $obj->depreciation_ht,
+				),
+			);
+		}
+
+		$disposal_ref = $langs->transnoentitiesnoconv('AssetDisposal');
+		$journal = $this->code;
+		$journal_label = $this->label;
+		$journal_label_formatted = $langs->transnoentities($journal_label);
+		$now = dol_now();
+
+		$element_static = new Asset($this->db);
+
+		$journal_data = array();
+		foreach ($pre_data['elements'] as $pre_data_id => $pre_data_info) {
+			$element_static->id = $pre_data_id;
+			$element_static->ref = (string) $pre_data_info["ref"];
+			$element_static->label = (string) $pre_data_info["label"];
+			$element_static->acquisition_value_ht = $pre_data_info["acquisition_value_ht"];
+			$element_link = $element_static->getNomUrl(1, 'with_label');
+
+			$element_name_formatted_0 = dol_trunc($element_static->label, 16);
+			$element_name_formatted_1 = utf8_decode(dol_trunc($element_static->label, 32));
+			$element_name_formatted_2 = utf8_decode(dol_trunc($element_static->label, 16));
+			$label_operation = $element_static->getNomUrl(0, 'label', 16);
+
+			$element = array(
+				'ref' => dol_trunc($element_static->ref, 16, 'right', 'UTF-8', 1),
+				'error' => $pre_data_info['error'],
+				'blocks' => array(),
+			);
+
+			// Depreciation lines
+			//--------------------
+			foreach ($pre_data_info['depreciation'] as $depreciation_id => $line) {
+				$depreciation_ref = $line["ref"];
+				$depreciation_date = $line["date"];
+				$depreciation_date_formatted = dol_print_date($depreciation_date, 'day');
+
+				// lines
+				$blocks = array();
+				foreach ($line['lines'] as $account => $mt) {
+					$account_infos = $this->getAccountingAccountInfos($account);
+
+					if ($type == 'view') {
+						$account_to_show = length_accounta($account);
+						if (($account_to_show == "") || $account_to_show == 'NotDefined') {
+							$account_to_show = '<span class="error">' . $langs->trans("AssetInAccountNotDefined") . '</span>';
+						}
+
+						$blocks[] = array(
+							'date' => $depreciation_date_formatted,
+							'piece' => $element_link,
+							'account_accounting' => $account_to_show,
+							'subledger_account' => '',
+							'label_operation' => $label_operation . ' - ' . $depreciation_ref,
+							'debit' => $mt < 0 ? price(-$mt) : '',
+							'credit' => $mt >= 0 ? price($mt) : '',
+						);
+					} elseif ($type == 'bookkeeping') {
+						if ($account_infos['found']) {
+							$blocks[] = array(
+								'doc_date' => $depreciation_date,
+								'date_lim_reglement' => '',
+								'doc_ref' => $element_static->ref,
+								'date_creation' => $now,
+								'doc_type' => 'asset',
+								'fk_doc' => $element_static->id,
+								'fk_docdet' => $depreciation_id, // Useless, can be several lines that are source of this record to add
+								'thirdparty_code' => '',
+								'subledger_account' => '',
+								'subledger_label' => '',
+								'numero_compte' => $account,
+								'label_compte' => $account_infos['label'],
+								'label_operation' => $element_name_formatted_0 . ' - ' . $depreciation_ref,
+								'montant' => $mt,
+								'sens' => $mt < 0 ? 'D' : 'C',
+								'debit' => $mt < 0 ? -$mt : 0,
+								'credit' => $mt >= 0 ? $mt : 0,
+								'code_journal' => $journal,
+								'journal_label' => $journal_label_formatted,
+								'piece_num' => '',
+								'import_key' => '',
+								'fk_user_author' => $user->id,
+								'entity' => $conf->entity,
+							);
+						}
+					} else { // $type == 'csv'
+						$blocks[] = array(
+							$depreciation_date,                                   	// Date
+							$element_static->ref,                                	// Piece
+							$account_infos['code_formatted_1'],                		// AccountAccounting
+							$element_name_formatted_0 . ' - ' . $depreciation_ref,  // LabelOperation
+							$mt < 0 ? price(-$mt) : '',                        		// Debit
+							$mt >= 0 ? price($mt) : '',                        		// Credit
+						);
+					}
+				}
+				$element['blocks'][] = $blocks;
+			}
+
+			// Disposal line
+			//--------------------
+			if (!empty($pre_data_info['disposal'])) {
+				$disposal_date = $pre_data_info['disposal']['date'];
+
+				if ((!($date_start && $date_end) || ($date_start <= $disposal_date && $disposal_date <= $date_end)) &&
+					(empty($conf->global->ACCOUNTING_DATE_START_BINDING) || $conf->global->ACCOUNTING_DATE_START_BINDING <= $disposal_date)
+				) {
+					$disposal_amount = $pre_data_info['disposal']['amount'];
+					$disposal_subject_to_vat = $pre_data_info['disposal']['subject_to_vat'];
+					$disposal_date_formatted = dol_print_date($disposal_date, 'day');
+					$disposal_vat = $conf->global->ASSET_DISPOSAL_VAT > 0 ? $conf->global->ASSET_DISPOSAL_VAT : 20;
+
+					// Get accountancy codes
+					//---------------------------
+					require_once DOL_DOCUMENT_ROOT . '/asset/class/assetaccountancycodes.class.php';
+					$accountancy_codes = new AssetAccountancyCodes($this->db);
+					$result = $accountancy_codes->fetchAccountancyCodes($element_static->id);
+					if ($result < 0) {
+						$element['error'] = $accountancy_codes->errorsToString();
+					} else {
+						// Get last depreciation cumulative amount
+						$element_static->fetchDepreciationLines();
+						foreach ($element_static->depreciation_lines as $mode_key => $depreciation_lines) {
+							$accountancy_codes_list = $accountancy_codes->accountancy_codes[$mode_key];
+
+							if (!isset($accountancy_codes_list['value_asset_sold'])) {
+								continue;
+							}
+
+							$accountancy_code_value_asset_sold = empty($accountancy_codes_list['value_asset_sold']) ? 'NotDefined' : $accountancy_codes_list['value_asset_sold'];
+							$accountancy_code_depreciation_asset = empty($accountancy_codes_list['depreciation_asset']) ? 'NotDefined' : $accountancy_codes_list['depreciation_asset'];
+							$accountancy_code_asset = empty($accountancy_codes_list['asset']) ? 'NotDefined' : $accountancy_codes_list['asset'];
+							$accountancy_code_receivable_on_assignment = empty($accountancy_codes_list['receivable_on_assignment']) ? 'NotDefined' : $accountancy_codes_list['receivable_on_assignment'];
+							$accountancy_code_vat_collected = empty($accountancy_codes_list['vat_collected']) ? 'NotDefined' : $accountancy_codes_list['vat_collected'];
+							$accountancy_code_proceeds_from_sales = empty($accountancy_codes_list['proceeds_from_sales']) ? 'NotDefined' : $accountancy_codes_list['proceeds_from_sales'];
+
+							$last_cumulative_amount_ht = 0;
+							$depreciated_ids = array_keys($pre_data_info['depreciation']);
+							foreach ($depreciation_lines as $line) {
+								$last_cumulative_amount_ht = $line['cumulative_depreciation_ht'];
+								if (!in_array($line['id'], $depreciated_ids) && empty($line['bookkeeping']) && !empty($line['ref'])) {
+									break;
+								}
+							}
+
+							$lines = array();
+							$lines[0][$accountancy_code_value_asset_sold] = -($element_static->acquisition_value_ht - $last_cumulative_amount_ht);
+							$lines[0][$accountancy_code_depreciation_asset] = -$last_cumulative_amount_ht;
+							$lines[0][$accountancy_code_asset] = $element_static->acquisition_value_ht;
+
+							$disposal_amount_vat = $disposal_subject_to_vat ? (double) price2num($disposal_amount * $disposal_vat / 100, 'MT') : 0;
+							$lines[1][$accountancy_code_receivable_on_assignment] = -($disposal_amount + $disposal_amount_vat);
+							if ($disposal_subject_to_vat) $lines[1][$accountancy_code_vat_collected] = $disposal_amount_vat;
+							$lines[1][$accountancy_code_proceeds_from_sales] = $disposal_amount;
+
+							foreach ($lines as $lines_block) {
+								$blocks = array();
+								foreach ($lines_block as $account => $mt) {
+									$account_infos = $this->getAccountingAccountInfos($account);
+
+									if ($type == 'view') {
+										$account_to_show = length_accounta($account);
+										if (($account_to_show == "") || $account_to_show == 'NotDefined') {
+											$account_to_show = '<span class="error">' . $langs->trans("AssetInAccountNotDefined") . '</span>';
+										}
+
+										$blocks[] = array(
+											'date' => $disposal_date_formatted,
+											'piece' => $element_link,
+											'account_accounting' => $account_to_show,
+											'subledger_account' => '',
+											'label_operation' => $label_operation . ' - ' . $disposal_ref,
+											'debit' => $mt < 0 ? price(-$mt) : '',
+											'credit' => $mt >= 0 ? price($mt) : '',
+										);
+									} elseif ($type == 'bookkeeping') {
+										if ($account_infos['found']) {
+											$blocks[] = array(
+												'doc_date' => $disposal_date,
+												'date_lim_reglement' => '',
+												'doc_ref' => $element_static->ref,
+												'date_creation' => $now,
+												'doc_type' => 'asset',
+												'fk_doc' => $element_static->id,
+												'fk_docdet' => 0, // Useless, can be several lines that are source of this record to add
+												'thirdparty_code' => '',
+												'subledger_account' => '',
+												'subledger_label' => '',
+												'numero_compte' => $account,
+												'label_compte' => $account_infos['label'],
+												'label_operation' => $element_name_formatted_0 . ' - ' . $disposal_ref,
+												'montant' => $mt,
+												'sens' => $mt < 0 ? 'D' : 'C',
+												'debit' => $mt < 0 ? -$mt : 0,
+												'credit' => $mt >= 0 ? $mt : 0,
+												'code_journal' => $journal,
+												'journal_label' => $journal_label_formatted,
+												'piece_num' => '',
+												'import_key' => '',
+												'fk_user_author' => $user->id,
+												'entity' => $conf->entity,
+											);
+										}
+									} else { // $type == 'csv'
+										$blocks[] = array(
+											$disposal_date,                                    // Date
+											$element_static->ref,                              // Piece
+											$account_infos['code_formatted_1'],                // AccountAccounting
+											$element_name_formatted_0 . ' - ' . $disposal_ref, // LabelOperation
+											$mt < 0 ? price(-$mt) : '',                        // Debit
+											$mt >= 0 ? price($mt) : '',                        // Credit
+										);
+									}
+								}
+								$element['blocks'][] = $blocks;
+							}
+						}
+					}
+				}
+			}
+
+			$journal_data[$pre_data_id] = $element;
+		}
+		unset($pre_data);
+
+		return $journal_data;
+	}
+
+	/**
+	 *  Write bookkeeping
+	 *
+	 * @param	User		$user				User who write in the bookkeeping
+	 * @param	array		$journal_data		Journal data to write in the bookkeeping
+	 * 											$journal_data = array(
+	 *                                          id_element => array(
+	 *                                          'ref' => 'ref',
+	 *                                          'error' => '',
+	 *                                          'blocks' => array(
+	 *                                          pos_block => array(
+	 *                                          num_line => array(
+	 *                                          'doc_date' => '',
+	 *                                          'date_lim_reglement' => '',
+	 *                                          'doc_ref' => '',
+	 *                                          'date_creation' => '',
+	 *                                          'doc_type' => '',
+	 *                                          'fk_doc' => '',
+	 *                                          'fk_docdet' => '',
+	 *                                          'thirdparty_code' => '',
+	 *                                          'subledger_account' => '',
+	 *                                          'subledger_label' => '',
+	 *                                          'numero_compte' => '',
+	 *                                          'label_compte' => '',
+	 *                                          'label_operation' => '',
+	 *                                          'montant' => '',
+	 *                                          'sens' => '',
+	 *                                          'debit' => '',
+	 *                                          'credit' => '',
+	 *                                          'code_journal' => '',
+	 *                                          'journal_label' => '',
+	 *                                          'piece_num' => '',
+	 *                                          'import_key' => '',
+	 *                                          'fk_user_author' => '',
+	 *                                          'entity' => '',
+	 *                                          ),
+	 *                                          ),
+	 *                                          ),
+	 *                                          ),
+	 * 											);
+	 * @param	int		$max_nb_errors			Nb error authorized before stop the process
+	 * @return 	int								<0 if KO, >0 if OK
+	 */
+	public function writeIntoBookkeeping(User $user, &$journal_data = array(), $max_nb_errors = 10)
+	{
+		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'));
+		$parameters = array('journal_data' => &$journal_data);
+		$reshook = $hookmanager->executeHooks('writeBookkeeping', $parameters, $this); // Note that $action and $object may have been
+		if ($reshook < 0) {
+			$this->error = $hookmanager->error;
+			$this->errors = $hookmanager->errors;
+			return -1;
+		} elseif (empty($reshook)) {
+			// Clean parameters
+			$journal_data = is_array($journal_data) ? $journal_data : array();
+
+			foreach ($journal_data as $element_id => $element) {
+				$error_for_line = 0;
+				$total_credit = 0;
+				$total_debit = 0;
+
+				$this->db->begin();
+
+				if ($element['error'] == 'somelinesarenotbound') {
+					$error++;
+					$error_for_line++;
+					$this->errors[] = $langs->trans('ErrorInvoiceContainsLinesNotYetBounded', $element['ref']);
+				}
+
+				if (!$error_for_line) {
+					foreach ($element['blocks'] as $lines) {
+						foreach ($lines as $line) {
+							$bookkeeping = new BookKeeping($this->db);
+							$bookkeeping->doc_date = $line['doc_date'];
+							$bookkeeping->date_lim_reglement = $line['date_lim_reglement'];
+							$bookkeeping->doc_ref = $line['doc_ref'];
+							$bookkeeping->date_creation = $line['date_creation']; // not used
+							$bookkeeping->doc_type = $line['doc_type'];
+							$bookkeeping->fk_doc = $line['fk_doc'];
+							$bookkeeping->fk_docdet = $line['fk_docdet'];
+							$bookkeeping->thirdparty_code = $line['thirdparty_code'];
+							$bookkeeping->subledger_account = $line['subledger_account'];
+							$bookkeeping->subledger_label = $line['subledger_label'];
+							$bookkeeping->numero_compte = $line['numero_compte'];
+							$bookkeeping->label_compte = $line['label_compte'];
+							$bookkeeping->label_operation = $line['label_operation'];
+							$bookkeeping->montant = $line['montant'];
+							$bookkeeping->sens = $line['sens'];
+							$bookkeeping->debit = $line['debit'];
+							$bookkeeping->credit = $line['credit'];
+							$bookkeeping->code_journal = $line['code_journal'];
+							$bookkeeping->journal_label = $line['journal_label'];
+							$bookkeeping->piece_num = $line['piece_num'];
+							$bookkeeping->import_key = $line['import_key'];
+							$bookkeeping->fk_user_author = $user->id;
+							$bookkeeping->entity = $conf->entity;
+
+							$total_debit += $bookkeeping->debit;
+							$total_credit += $bookkeeping->credit;
+
+							$result = $bookkeeping->create($user);
+							if ($result < 0) {
+								if ($bookkeeping->error == 'BookkeepingRecordAlreadyExists') {   // Already exists
+									$error++;
+									$error_for_line++;
+									$journal_data[$element_id]['error'] = 'alreadyjournalized';
+								} else {
+									$error++;
+									$error_for_line++;
+									$journal_data[$element_id]['error'] = 'other';
+									$this->errors[] = $bookkeeping->errorsToString();
+								}
+							}
+							//
+							//                          if (!$error_for_line && !empty($conf->asset->enabled) && $this->nature == 1 && $bookkeeping->fk_doc > 0) {
+							//                              // Set last cumulative depreciation
+							//                              require_once DOL_DOCUMENT_ROOT . '/asset/class/asset.class.php';
+							//                              $asset = new Asset($this->db);
+							//                              $result = $asset->setLastCumulativeDepreciation($bookkeeping->fk_doc);
+							//                              if ($result < 0) {
+							//                                  $error++;
+							//                                  $error_for_line++;
+							//                                  $journal_data[$element_id]['error'] = 'other';
+							//                                  $this->errors[] = $asset->errorsToString();
+							//                              }
+							//                          }
+						}
+
+						if ($error_for_line) {
+							break;
+						}
+					}
+				}
+
+				// Protection against a bug on lines before
+				if (!$error_for_line && (price2num($total_debit, 'MT') != price2num($total_credit, 'MT'))) {
+					$error++;
+					$error_for_line++;
+					$journal_data[$element_id]['error'] = 'amountsnotbalanced';
+					$this->errors[] = 'Try to insert a non balanced transaction in book for ' . $element['blocks'] . '. Canceled. Surely a bug.';
+				}
+
+				if (!$error_for_line) {
+					$this->db->commit();
+				} else {
+					$this->db->rollback();
+
+					if ($error >= $max_nb_errors) {
+						$this->errors[] = $langs->trans("ErrorTooManyErrorsProcessStopped");
+						break; // Break in the foreach
+					}
+				}
+			}
+		}
+
+		return $error ? -$error : 1;
+	}
+
+	/**
+	 *	Export journal CSV
+	 * 	ISO and not UTF8 !
+	 *
+	 * @param	array			$journal_data			Journal data to write in the bookkeeping
+	 * 													$journal_data = array(
+	 *                                                  id_element => array(
+	 *                                                  'continue' => false,
+	 *                                                  'blocks' => array(
+	 *                                                  pos_block => array(
+	 *                                                  num_line => array(
+	 *                                                  data to write in the CSV line
+	 *                                                  ),
+	 *                                                  ),
+	 *                                                  ),
+	 *                                                  ),
+	 * 													);
+	 * @param	int				$search_date_end		Search date end
+	 * @param	string			$sep					CSV separator
+	 * @return 	int|string								<0 if KO, >0 if OK
+	 */
+	public function exportCsv(&$journal_data = array(), $search_date_end = 0, $sep = '')
+	{
+		global $conf, $langs, $hookmanager;
+
+		if (empty($sep)) $sep = $conf->global->ACCOUNTING_EXPORT_SEPARATORCSV;
+		$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
+		if ($reshook < 0) {
+			$this->error = $hookmanager->error;
+			$this->errors = $hookmanager->errors;
+			return -1;
+		} elseif (empty($reshook)) {
+			// Clean parameters
+			$journal_data = is_array($journal_data) ? $journal_data : array();
+
+			// CSV header line
+			$header = array();
+			if ($this->nature == 4) {
+				$header = array(
+					$langs->transnoentitiesnoconv("BankId"),
+					$langs->transnoentitiesnoconv("Date"),
+					$langs->transnoentitiesnoconv("PaymentMode"),
+					$langs->transnoentitiesnoconv("AccountAccounting"),
+					$langs->transnoentitiesnoconv("LedgerAccount"),
+					$langs->transnoentitiesnoconv("SubledgerAccount"),
+					$langs->transnoentitiesnoconv("Label"),
+					$langs->transnoentitiesnoconv("Debit"),
+					$langs->transnoentitiesnoconv("Credit"),
+					$langs->transnoentitiesnoconv("Journal"),
+					$langs->transnoentitiesnoconv("Note"),
+				);
+			} elseif ($this->nature == 5) {
+				$header = array(
+					$langs->transnoentitiesnoconv("Date"),
+					$langs->transnoentitiesnoconv("Piece"),
+					$langs->transnoentitiesnoconv("AccountAccounting"),
+					$langs->transnoentitiesnoconv("LabelOperation"),
+					$langs->transnoentitiesnoconv("Debit"),
+					$langs->transnoentitiesnoconv("Credit"),
+				);
+			} elseif ($this->nature == 1) {
+				$header = array(
+					$langs->transnoentitiesnoconv("Date"),
+					$langs->transnoentitiesnoconv("Piece"),
+					$langs->transnoentitiesnoconv("AccountAccounting"),
+					$langs->transnoentitiesnoconv("LabelOperation"),
+					$langs->transnoentitiesnoconv("Debit"),
+					$langs->transnoentitiesnoconv("Credit"),
+				);
+			}
+
+			if (!empty($header)) $out .= '"' . implode('"' . $sep . '"', $header) . '"' . "\n";
+			foreach ($journal_data as $element_id => $element) {
+				foreach ($element['blocks'] as $lines) {
+					foreach ($lines as $line) {
+						$out .= '"' . implode('"' . $sep . '"', $line) . '"' . "\n";
+					}
+				}
+			}
+		}
+
+		return $out;
+	}
+
+	/**
+	 *  Get accounting account infos
+	 *
+	 * @param string	$account	Accounting account number
+	 * @return array				Accounting account infos
+	 */
+	public function getAccountingAccountInfos($account)
+	{
+		if (!isset(self::$accounting_account_cached[$account])) {
+			require_once DOL_DOCUMENT_ROOT . '/core/lib/accounting.lib.php';
+			require_once DOL_DOCUMENT_ROOT . '/accountancy/class/accountingaccount.class.php';
+			$accountingaccount = new AccountingAccount($this->db);
+			$result = $accountingaccount->fetch(null, $account, true);
+			if ($result > 0) {
+				self::$accounting_account_cached[$account] = array(
+					'found' => true,
+					'label' => $accountingaccount->label,
+					'code_formatted_1' => length_accounta(html_entity_decode($account)),
+					'label_formatted_1' => utf8_decode(dol_trunc($accountingaccount->label, 32)),
+					'label_formatted_2' => dol_trunc($accountingaccount->label, 32),
+				);
+			} else {
+				self::$accounting_account_cached[$account] = array(
+					'found' => false,
+					'label' => '',
+					'code_formatted_1' => length_accounta(html_entity_decode($account)),
+					'label_formatted_1' => '',
+					'label_formatted_2' => '',
+				);
+			}
+		}
+
+		return self::$accounting_account_cached[$account];
+	}
 }

+ 52 - 24
htdocs/accountancy/class/bookkeeping.class.php

@@ -331,7 +331,7 @@ class BookKeeping extends CommonObject
 				if (empty($this->piece_num)) {
 					$sqlnum = "SELECT MAX(piece_num)+1 as maxpiecenum";
 					$sqlnum .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
-					$sqlnum .= " WHERE entity = ".$conf->entity; // Do not use getEntity for accounting features
+					$sqlnum .= " WHERE entity = " . ((int) $conf->entity); // Do not use getEntity for accounting features
 
 					$resqlnum = $this->db->query($sqlnum);
 					if ($resqlnum) {
@@ -460,7 +460,7 @@ class BookKeeping extends CommonObject
 	{
 		global $db, $conf, $langs;
 		global $dolibarr_main_authentication, $dolibarr_main_demo;
-		global $menumanager;
+		global $menumanager, $hookmanager;
 
 		if (!empty($conf->dol_no_mouse_hover)) {
 			$notooltip = 1; // Force disable tooltips
@@ -512,6 +512,15 @@ class BookKeeping extends CommonObject
 		$result .= $linkend;
 		//if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
 
+		global $action;
+		$hookmanager->initHooks(array($this->element . 'dao'));
+		$parameters = array('id'=>$this->id, 'getnomurl' => &$result);
+		$reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
+		if ($reshook > 0) {
+			$result = $hookmanager->resPrint;
+		} else {
+			$result .= $hookmanager->resPrint;
+		}
 		return $result;
 	}
 
@@ -736,7 +745,7 @@ class BookKeeping extends CommonObject
 		$sql .= " t.date_validated as date_validation";
 		$sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.$mode.' as t';
 		$sql .= ' WHERE 1 = 1';
-		$sql .= " AND entity IN (".getEntity('accountancy').")";
+		$sql .= " AND entity = " . ((int) $conf->entity); // Do not use getEntity for accounting features
 		if (null !== $ref) {
 			$sql .= " AND t.ref = '".$this->db->escape($ref)."'";
 		} else {
@@ -881,7 +890,7 @@ class BookKeeping extends CommonObject
 		}
 		$sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
 		$sql .= ' WHERE 1 = 1';
-		$sql .= " AND entity IN (".getEntity('accountancy').")";
+		$sql .= " AND entity = " . ((int) $conf->entity); // Do not use getEntity for accounting features
 		if (count($sqlwhere) > 0) {
 			$sql .= " AND ".implode(" ".$filtermode." ", $sqlwhere);
 		}
@@ -1013,7 +1022,7 @@ class BookKeeping extends CommonObject
 				} elseif ($key == 't.numero_compte>=' || $key == 't.numero_compte<=' || $key == 't.subledger_account>=' || $key == 't.subledger_account<=') {
 					$sqlwhere[] = $key.'\''.$this->db->escape($value).'\'';
 				} elseif ($key == 't.fk_doc' || $key == 't.fk_docdet' || $key == 't.piece_num') {
-					$sqlwhere[] = $key.'='.$value;
+					$sqlwhere[] = $key.'='.((int) $value);
 				} elseif ($key == 't.subledger_account' || $key == 't.numero_compte') {
 					$sqlwhere[] = $key.' LIKE \''.$this->db->escape($value).'%\'';
 				} elseif ($key == 't.date_creation>=' || $key == 't.date_creation<=') {
@@ -1037,7 +1046,7 @@ class BookKeeping extends CommonObject
 				}
 			}
 		}
-		$sql .= ' WHERE t.entity IN ('.getEntity('accountancy').')';
+		$sql .= ' WHERE t.entity = ' . ((int) $conf->entity); // Do not use getEntity for accounting features
 		if ($showAlreadyExportMovements == 0) {
 			$sql .= " AND t.date_export IS NULL";
 		}
@@ -1157,7 +1166,7 @@ class BookKeeping extends CommonObject
 				}
 			}
 		}
-		$sql .= ' WHERE entity IN ('.getEntity('accountancy').')';
+		$sql .= ' WHERE entity = ' . ((int) $conf->entity); // Do not use getEntity for accounting features
 		if (count($sqlwhere) > 0) {
 			$sql .= " AND ".implode(" ".$filtermode." ", $sqlwhere);
 		}
@@ -1455,7 +1464,7 @@ class BookKeeping extends CommonObject
 	 */
 	public function deleteByYearAndJournal($delyear = 0, $journal = '', $mode = '', $delmonth = 0)
 	{
-		global $langs;
+		global $conf, $langs;
 
 		if (empty($delyear) && empty($journal)) {
 			$this->error = 'ErrorOneFieldRequired';
@@ -1476,7 +1485,7 @@ class BookKeeping extends CommonObject
 		if (!empty($journal)) {
 			$sql .= " AND code_journal = '".$this->db->escape($journal)."'";
 		}
-		$sql .= " AND entity IN (".getEntity('accountancy').")";
+		$sql .= " AND entity = " . ((int) $conf->entity); // Do not use getEntity for accounting features
 		// Exclusion of validated entries at the time of deletion
 		$sql .= " AND date_validated IS NULL";
 
@@ -1515,7 +1524,7 @@ class BookKeeping extends CommonObject
 		$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
 		$sql .= " WHERE piece_num = ".(int) $piecenum;
 		$sql .= " AND date_validated IS NULL";		// For security, exclusion of validated entries at the time of deletion
-		$sql .= " AND entity IN (".getEntity('accountancy').")";
+		$sql .= " AND entity = " . ((int) $conf->entity); // Do not use getEntity for accounting features
 
 		$resql = $this->db->query($sql);
 
@@ -1637,7 +1646,7 @@ class BookKeeping extends CommonObject
 		}
 		$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element.$mode;
 		$sql .= " WHERE piece_num = ".((int) $piecenum);
-		$sql .= " AND entity IN (".getEntity('accountancy').")";
+		$sql .= " AND entity = " . ((int) $conf->entity); // Do not use getEntity for accounting features
 
 		dol_syslog(__METHOD__, LOG_DEBUG);
 		$result = $this->db->query($sql);
@@ -1675,9 +1684,10 @@ class BookKeeping extends CommonObject
 		global $conf;
 
 		$sql = "SELECT MAX(piece_num)+1 as max FROM ".MAIN_DB_PREFIX.$this->table_element.$mode;
-		$sql .= " WHERE entity IN (".getEntity('accountancy').")";
+		$sql .= " WHERE entity = " . ((int) $conf->entity); // Do not use getEntity for accounting features
+
+		dol_syslog(get_class($this)."::getNextNumMvt", LOG_DEBUG);
 
-		dol_syslog(get_class($this)."getNextNumMvt", LOG_DEBUG);
 		$result = $this->db->query($sql);
 
 		if ($result) {
@@ -1718,7 +1728,7 @@ class BookKeeping extends CommonObject
 		}
 		$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element.$mode;
 		$sql .= " WHERE piece_num = ".((int) $piecenum);
-		$sql .= " AND entity IN (".getEntity('accountancy').")";
+		$sql .= " AND entity = " . ((int) $conf->entity); // Do not use getEntity for accounting features
 
 		dol_syslog(__METHOD__, LOG_DEBUG);
 		$result = $this->db->query($sql);
@@ -1781,7 +1791,7 @@ class BookKeeping extends CommonObject
 		$sql .= " montant as amount, sens, fk_user_author, import_key, code_journal, piece_num,";
 		$sql .= " date_validated as date_validation";
 		$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
-		$sql .= " WHERE entity IN (".getEntity('accountancy').")";
+		$sql .= " WHERE entity = " . ((int) $conf->entity); // Do not use getEntity for accounting features
 
 		dol_syslog(get_class($this)."::export_bookkeeping", LOG_DEBUG);
 
@@ -1837,6 +1847,8 @@ class BookKeeping extends CommonObject
 	 */
 	public function transformTransaction($direction = 0, $piece_num = '')
 	{
+		global $conf;
+
 		$error = 0;
 
 		$this->db->begin();
@@ -1856,14 +1868,14 @@ class BookKeeping extends CommonObject
 			$sql .= ' doc_ref, fk_doc, fk_docdet, entity, thirdparty_code, subledger_account, subledger_label,';
 			$sql .= ' numero_compte, label_compte, label_operation, debit, credit,';
 			$sql .= ' montant, sens, fk_user_author, import_key, code_journal, journal_label, '.((int) $next_piecenum).", '".$this->db->idate($now)."'";
-			$sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.'_tmp WHERE piece_num = '.((int) $piece_num);
+			$sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.'_tmp WHERE piece_num = '.((int) $piece_num).' AND entity = ' .((int) $conf->entity);
 			$resql = $this->db->query($sql);
 			if (!$resql) {
 				$error++;
 				$this->errors[] = 'Error '.$this->db->lasterror();
 				dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
 			}
-			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.$this->table_element.'_tmp WHERE piece_num = '.((int) $piece_num);
+			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.$this->table_element.'_tmp WHERE piece_num = '.((int) $piece_num).' AND entity = ' .((int) $conf->entity);
 			$resql = $this->db->query($sql);
 			if (!$resql) {
 				$error++;
@@ -1871,7 +1883,7 @@ class BookKeeping extends CommonObject
 				dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
 			}
 		} elseif ($direction == 1) {
-			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.$this->table_element.'_tmp WHERE piece_num = '.((int) $piece_num);
+			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.$this->table_element.'_tmp WHERE piece_num = '.((int) $piece_num).' AND entity = ' .((int) $conf->entity);
 			$resql = $this->db->query($sql);
 			if (!$resql) {
 				$error++;
@@ -1886,14 +1898,14 @@ class BookKeeping extends CommonObject
 			$sql .= ' doc_ref, fk_doc, fk_docdet, thirdparty_code, subledger_account, subledger_label,';
 			$sql .= ' numero_compte, label_compte, label_operation, debit, credit,';
 			$sql .= ' montant, sens, fk_user_author, import_key, code_journal, journal_label, piece_num';
-			$sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' WHERE piece_num = '.((int) $piece_num);
+			$sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' WHERE piece_num = '.((int) $piece_num).' AND entity = ' .((int) $conf->entity);
 			$resql = $this->db->query($sql);
 			if (!$resql) {
 				$error++;
 				$this->errors[] = 'Error '.$this->db->lasterror();
 				dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
 			}
-			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.$this->table_element.'_tmp WHERE piece_num = '.((int) $piece_num);
+			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.$this->table_element.'_tmp WHERE piece_num = '.((int) $piece_num).' AND entity = ' .((int) $conf->entity);
 			$resql = $this->db->query($sql);
 			if (!$resql) {
 				$error++;
@@ -1948,7 +1960,7 @@ class BookKeeping extends CommonObject
 		$sql .= " AND aa.active = 1";
 		$sql .= " INNER JOIN ".MAIN_DB_PREFIX."accounting_system as asy ON aa.fk_pcg_version = asy.pcg_version";
 		$sql .= " AND asy.rowid = ".((int) $pcgver);
-		$sql .= " AND ab.entity IN (".getEntity('accountancy').")";
+		$sql .= " AND ab.entity = " . ((int) $conf->entity); // Do not use getEntity for accounting features
 		$sql .= " ORDER BY account_number ASC";
 
 		dol_syslog(get_class($this)."::select_account", LOG_DEBUG);
@@ -2012,7 +2024,7 @@ class BookKeeping extends CommonObject
 		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as parent ON aa.account_parent = parent.rowid AND parent.active = 1";
 		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as root ON parent.account_parent = root.rowid AND root.active = 1";
 		$sql .= " WHERE aa.account_number = '".$this->db->escape($account)."'";
-		$sql .= " AND aa.entity IN (".getEntity('accountancy').")";
+		$sql .= " AND aa.entity = " . ((int) $conf->entity); // Do not use getEntity for accounting features
 
 		dol_syslog(get_class($this)."::select_account", LOG_DEBUG);
 		$resql = $this->db->query($sql);
@@ -2052,7 +2064,7 @@ class BookKeeping extends CommonObject
 		$sql .= " AND asy.rowid = ".((int) $pcgver);
 		$sql .= " AND aa.active = 1";
 		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_accounting_category as cat ON aa.fk_accounting_category = cat.rowid";
-		$sql .= " WHERE aa.entity IN (".getEntity('accountancy').")";
+		$sql .= " WHERE aa.entity = " . ((int) $conf->entity); // Do not use getEntity for accounting features
 
 		dol_syslog(get_class($this)."::select_account", LOG_DEBUG);
 		$resql = $this->db->query($sql);
@@ -2114,15 +2126,26 @@ class BookKeepingLine
 	public $montant;
 
 	/**
-	 * @var float Amount
+	 * @var float 	Amount
 	 */
 	public $amount;
 
+	/**
+	 * @var float 	Multicurrency amount
+	 */
+	public $multicurrency_amount;
+
+	/**
+	 * @var float 	Multicurrency code
+	 */
+	public $multicurrency_code;
+
 	/**
 	 * @var string Sens
 	 */
 	public $sens;
 	public $lettering_code;
+	public $date_lettering;
 
 	/**
 	 * @var int ID
@@ -2153,4 +2176,9 @@ class BookKeepingLine
 	 * @var integer|string $date_validation;
 	 */
 	public $date_validation;
+
+	/**
+	 * @var integer|string $date_lim_reglement;
+	 */
+	public $date_lim_reglement;
 }

+ 34 - 39
htdocs/accountancy/closure/index.php

@@ -13,7 +13,6 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
  */
 
 /**
@@ -31,7 +30,8 @@ require_once DOL_DOCUMENT_ROOT.'/accountancy/class/bookkeeping.class.php';
 // Load translation files required by the page
 $langs->loadLangs(array("compta", "bills", "other", "accountancy"));
 
-$socid = GETPOST('socid', 'int');
+$validatemonth = GETPOST('validatemonth', 'int');
+$validateyear = GETPOST('validateyear', 'int');
 
 $action = GETPOST('action', 'aZ09');
 
@@ -68,52 +68,47 @@ if (empty($user->rights->accounting->fiscalyear->write)) {
 }
 
 
+
 /*
  * Actions
  */
 
+$now = dol_now();
+
 if ($action == 'validate_movements_confirm' && !empty($user->rights->accounting->fiscalyear->write)) {
-	$result = $object->fetchAll();
+	$date_start = dol_mktime(0, 0, 0, GETPOST('date_startmonth', 'int'), GETPOST('date_startday', 'int'), GETPOST('date_startyear', 'int'));
+	$date_end = dol_mktime(23, 59, 59, GETPOST('date_endmonth', 'int'), GETPOST('date_endday', 'int'), GETPOST('date_endyear', 'int'));
 
-	if ($result < 0) {
-		setEventMessages($object->error, $object->errors, 'errors');
-	} else {
-		// Specify as export : update field date_validated on selected month/year
-		$error = 0;
-		$db->begin();
-
-		$date_start = dol_mktime(0, 0, 0, GETPOST('date_startmonth', 'int'), GETPOST('date_startday', 'int'), GETPOST('date_startyear', 'int'));
-		$date_end = dol_mktime(23, 59, 59, GETPOST('date_endmonth', 'int'), GETPOST('date_endday', 'int'), GETPOST('date_endyear', 'int'));
-
-		if (is_array($object->lines)) {
-			foreach ($object->lines as $movement) {
-				$now = dol_now();
-
-				$sql = " UPDATE ".MAIN_DB_PREFIX."accounting_bookkeeping";
-				$sql .= " SET date_validated = '".$db->idate($now)."'";
-				$sql .= " WHERE rowid = ".((int) $movement->id);
-				$sql .= " AND doc_date >= '" . $db->idate($date_start) . "'";
-				$sql .= " AND doc_date <= '" . $db->idate($date_end) . "'";
-
-				dol_syslog("/accountancy/closure/index.php :: Function validate_movement_confirm Specify movements as validated", LOG_DEBUG);
-				$result = $db->query($sql);
-				if (!$result) {
-					$error++;
-					break;
-				}
-			}
-		}
+	$error = 0;
+
+	$db->begin();
+
+	// Specify as export : update field date_validated on selected month/year
+	$sql = " UPDATE ".MAIN_DB_PREFIX."accounting_bookkeeping";
+	$sql .= " SET date_validated = '".$db->idate($now)."'";
+	$sql .= " WHERE entity = " . ((int) $conf->entity);
+	$sql .= " AND doc_date >= '" . $db->idate($date_start) . "'";
+	$sql .= " AND doc_date <= '" . $db->idate($date_end) . "'";
+	$sql .= " AND date_validated IS NULL";
+
+	dol_syslog("/accountancy/closure/index.php action=validate_movement_confirm -> Set movements as validated", LOG_DEBUG);
+	$result = $db->query($sql);
+	if (!$result) {
+		$error++;
+	}
+
+	if (!$error) {
+		$db->commit();
+
+		setEventMessages($langs->trans("AllMovementsWereRecordedAsValidated"), null, 'mesgs');
 
-		if (!$error) {
-			$db->commit();
-			setEventMessages($langs->trans("AllMovementsWereRecordedAsValidated"), null, 'mesgs');
-		} else {
-			$error++;
-			$db->rollback();
-			setEventMessages($langs->trans("NotAllMovementsCouldBeRecordedAsValidated"), null, 'errors');
-		}
 		header("Location: ".$_SERVER['PHP_SELF']."?year=".$year_start);
 		exit;
+	} else {
+		$db->rollback();
+
+		setEventMessages($langs->trans("NotAllMovementsCouldBeRecordedAsValidated"), null, 'errors');
+		$action = '';
 	}
 }
 

+ 114 - 49
htdocs/accountancy/customer/index.php

@@ -36,6 +36,9 @@ require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
 // Load translation files required by the page
 $langs->loadLangs(array("compta", "bills", "other", "accountancy"));
 
+$validatemonth = GETPOST('validatemonth', 'int');
+$validateyear = GETPOST('validateyear', 'int');
+
 // Security check
 if (empty($conf->accounting->enabled)) {
 	accessforbidden();
@@ -90,7 +93,7 @@ if (empty($user->rights->accounting->mouvements->lire)) {
  */
 
 if (($action == 'clean' || $action == 'validatehistory') && $user->rights->accounting->bind->write) {
-	// Clean database
+	// 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 .= " SET fk_code_ventilation = 0";
@@ -116,6 +119,9 @@ if (($action == 'clean' || $action == 'validatehistory') && $user->rights->accou
 
 if ($action == 'validatehistory') {
 	$error = 0;
+	$nbbinddone = 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
@@ -147,18 +153,17 @@ if ($action == 'validatehistory') {
 	$sql .= " co.code as country_code, co.label as country_label,";
 	$sql .= " s.tva_intra,";
 	if (!empty($conf->global->MAIN_COMPANY_PERENTITY_SHARED)) {
-		$sql .= " spe.accountancy_code_sell as company_code_sell";
+		$sql .= " spe.accountancy_code_sell as company_code_sell";	// accounting code for product but stored on thirdparty
 	} else {
-		$sql .= " s.accountancy_code_sell as company_code_sell";
+		$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";
 	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 ".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";
+	$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";
 	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);
@@ -169,11 +174,13 @@ if ($action == 'validatehistory') {
 	$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 .= " WHERE f.fk_statut > 0 AND l.fk_code_ventilation <= 0";
-	$sql .= " AND l.product_type <= 2";
+	$sql .= " WHERE f.fk_statut > 0 AND l.fk_code_ventilation <= 0 AND l.product_type <= 2 AND f.entity = ".((int) $conf->entity);
 	if (!empty($conf->global->ACCOUNTING_DATE_START_BINDING)) {
 		$sql .= " AND f.datef >= '".$db->idate($conf->global->ACCOUNTING_DATE_START_BINDING)."'";
 	}
+	if ($validatemonth && $validateyear) {
+		$sql .= dolSqlDateFilter('f.datef', 0, $validatemonth, $validateyear);
+	}
 
 	dol_syslog('htdocs/accountancy/customer/index.php');
 
@@ -208,7 +215,7 @@ if ($action == 'validatehistory') {
 			$thirdpartystatic->email = $objp->email;
 			$thirdpartystatic->country_code = $objp->country_code;
 			$thirdpartystatic->tva_intra = $objp->tva_intra;
-			$thirdpartystatic->code_compta = $objp->company_code_sell;
+			$thirdpartystatic->code_compta_product = $objp->company_code_sell;		// The accounting account for product stored on thirdparty object (for level3 suggestion)
 
 			$product_static->ref = $objp->product_ref;
 			$product_static->id = $objp->product_id;
@@ -245,8 +252,10 @@ if ($action == 'validatehistory') {
 			$code_sell_p_notset = '';
 			$code_sell_t_notset = '';
 
+			$suggestedid = 0;
+
 			$return=$accountingAccount->getAccountingCodeToBind($thirdpartystatic, $mysoc, $product_static, $facture_static, $facture_static_det, $accountingAccountArray, 'customer');
-			if (!is_array($return) && $return<0) {
+			if (!is_array($return) && $return < 0) {
 				setEventMessage($accountingAccount->error, 'errors');
 			} else {
 				$suggestedid = $return['suggestedid'];
@@ -259,28 +268,7 @@ if ($action == 'validatehistory') {
 				}
 			}
 
-			if (!empty($conf->global->ACCOUNTANCY_USE_PRODUCT_ACCOUNT_ON_THIRDPARTY)) {
-				// Level 3: Search suggested account for this thirdparty (similar code exists in page index.php to make automatic binding)
-				if (!empty($objp->company_code_sell)) {
-					$objp->code_sell_t = $objp->company_code_sell;
-					$objp->aarowid_suggest = $objp->aarowid_thirdparty;
-					$suggestedaccountingaccountfor = '';
-				}
-			}
-
-			// Manage Deposit
-			if (!empty($conf->global->ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT)) {
-				if ($objp->description == "(DEPOSIT)" || $objp->ftype == $facture_static::TYPE_DEPOSIT) {
-					$accountdeposittoventilated = new AccountingAccount($db);
-					$accountdeposittoventilated->fetch('', $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT, 1);
-					$objp->code_sell_l = $accountdeposittoventilated->ref;
-					$objp->code_sell_p = '';
-					$objp->code_sell_t = '';
-					$objp->aarowid_suggest = $accountdeposittoventilated->rowid;
-				}
-			}
-
-			if ($objp->aarowid_suggest > 0) {
+			if ($suggestedid > 0) {
 				$sqlupdate = "UPDATE ".MAIN_DB_PREFIX."facturedet";
 				$sqlupdate .= " SET fk_code_ventilation = ".((int) $suggestedid);
 				$sqlupdate .= " WHERE fk_code_ventilation <= 0 AND product_type <= 2 AND rowid = ".((int) $facture_static_det->id);
@@ -290,18 +278,25 @@ if ($action == 'validatehistory') {
 					$error++;
 					setEventMessages($db->lasterror(), null, 'errors');
 					break;
+				} else {
+					$nbbinddone++;
 				}
+			} else {
+				$notpossible++;
 			}
 
 			$i++;
 		}
+		if ($num_lines > 10000) {
+			$notpossible += ($num_lines - 10000);
+		}
 	}
 
 	if ($error) {
 		$db->rollback();
 	} else {
 		$db->commit();
-		setEventMessages($langs->trans('AutomaticBindingDone'), null, 'mesgs');
+		setEventMessages($langs->trans('AutomaticBindingDone', 	$nbbinddone, $notpossible), null, 'mesgs');
 	}
 }
 
@@ -325,7 +320,7 @@ print '</span><br>';
 
 $y = $year_current;
 
-$buttonbind = '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?year='.$year_current.'&action=validatehistory">'.$langs->trans("ValidateHistory").'</a>';
+$buttonbind = '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?action=validatehistory&token='.newToken().'">'.$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 load_fiche_titre($langs->trans("OverviewOfAmountOfLinesNotBound"), $buttonbind, '');
@@ -339,7 +334,24 @@ for ($i = 1; $i <= 12; $i++) {
 	if ($j > 12) {
 		$j -= 12;
 	}
-	print '<td width="60" class="right">'.$langs->trans('MonthShort'.str_pad($j, 2, '0', STR_PAD_LEFT)).'</td>';
+	$cursormonth = $j;
+	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 width="60" class="right">';
+	if (!empty($tmp['mday'])) {
+		$param = 'search_date_startday=1&search_date_startmonth='.$cursormonth.'&search_date_startyear='.$cursoryear;
+		$param .= '&search_date_endday='.$tmp['mday'].'&search_date_endmonth='.$tmp['mon'].'&search_date_endyear='.$tmp['year'];
+		print '<a href="'.DOL_URL_ROOT.'/accountancy/customer/list.php?'.$param.'">';
+	}
+	print $langs->trans('MonthShort'.str_pad($j, 2, '0', STR_PAD_LEFT));
+	if (!empty($tmp['mday'])) {
+		print '</a>';
+	}
+	print '</td>';
 }
 print '<td width="60" class="right"><b>'.$langs->trans("Total").'</b></td></tr>';
 
@@ -379,7 +391,8 @@ if ($resql) {
 	$num = $db->num_rows($resql);
 
 	while ($row = $db->fetch_row($resql)) {
-		print '<tr class="oddeven"><td>';
+		print '<tr class="oddeven">';
+		print '<td>';
 		if ($row[0] == 'tobind') {
 			print '<span class="opacitymedium">'.$langs->trans("Unknown").'</span>';
 		} else {
@@ -388,19 +401,33 @@ if ($resql) {
 		print '</td>';
 		print '<td>';
 		if ($row[0] == 'tobind') {
-			print $langs->trans("UseMenuToSetBindindManualy", DOL_URL_ROOT.'/accountancy/customer/list.php?search_year='.$y, $langs->transnoentitiesnoconv("ToBind"));
+			print $langs->trans("UseMenuToSetBindindManualy", DOL_URL_ROOT.'/accountancy/customer/list.php?search_year='.((int) $y), $langs->transnoentitiesnoconv("ToBind"));
 		} else {
 			print $row[1];
 		}
 		print '</td>';
-		for ($i = 2; $i <= 12; $i++) {
-			print '<td class="right nowraponall amount">'.price($row[$i]).'</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]);
+			print '</td>';
 		}
-		print '<td class="right nowraponall amount">'.price($row[13]).'</td>';
 		print '<td class="right nowraponall amount"><b>'.price($row[14]).'</b></td>';
 		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
 }
@@ -423,7 +450,24 @@ for ($i = 1; $i <= 12; $i++) {
 	if ($j > 12) {
 		$j -= 12;
 	}
-	print '<td width="60" class="right">'.$langs->trans('MonthShort'.str_pad($j, 2, '0', STR_PAD_LEFT)).'</td>';
+	$cursormonth = $j;
+	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 width="60" class="right">';
+	if (!empty($tmp['mday'])) {
+		$param = 'search_date_startday=1&search_date_startmonth='.$cursormonth.'&search_date_startyear='.$cursoryear;
+		$param .= '&search_date_endday='.$tmp['mday'].'&search_date_endmonth='.$tmp['mon'].'&search_date_endyear='.$tmp['year'];
+		print '<a href="'.DOL_URL_ROOT.'/accountancy/customer/lines.php?'.$param.'">';
+	}
+	print $langs->trans('MonthShort'.str_pad($j, 2, '0', STR_PAD_LEFT));
+	if (!empty($tmp['mday'])) {
+		print '</a>';
+	}
+	print '</td>';
 }
 print '<td width="60" class="right"><b>'.$langs->trans("Total").'</b></td></tr>';
 
@@ -456,6 +500,7 @@ if (!empty($conf->global->FACTURE_DEPOSITS_ARE_JUST_PAYMENTS)) {
 }
 $sql .= " AND aa.account_number IS NOT NULL";
 $sql .= " GROUP BY fd.fk_code_ventilation,aa.account_number,aa.label";
+$sql .= ' ORDER BY aa.account_number';
 
 dol_syslog('htdocs/accountancy/customer/index.php');
 $resql = $db->query($sql);
@@ -463,7 +508,8 @@ if ($resql) {
 	$num = $db->num_rows($resql);
 
 	while ($row = $db->fetch_row($resql)) {
-		print '<tr class="oddeven"><td>';
+		print '<tr class="oddeven">';
+		print '<td>';
 		if ($row[0] == 'tobind') {
 			print $langs->trans("Unknown");
 		} else {
@@ -473,20 +519,34 @@ if ($resql) {
 
 		print '<td>';
 		if ($row[0] == 'tobind') {
-			print $langs->trans("UseMenuToSetBindindManualy", DOL_URL_ROOT.'/accountancy/customer/list.php?search_year='.$y, $langs->transnoentitiesnoconv("ToBind"));
+			print $langs->trans("UseMenuToSetBindindManualy", DOL_URL_ROOT.'/accountancy/customer/list.php?search_year='.((int) $y), $langs->transnoentitiesnoconv("ToBind"));
 		} else {
 			print $row[1];
 		}
 		print '</td>';
 
-		for ($i = 2; $i <= 12; $i++) {
-			print '<td class="right nowraponall amount">'.price($row[$i]).'</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]);
+			print '</td>';
 		}
-		print '<td class="right nowraponall amount">'.price($row[13]).'</td>';
 		print '<td class="right nowraponall amount"><b>'.price($row[14]).'</b></td>';
 		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
 }
@@ -573,16 +633,22 @@ if ($conf->global->MAIN_FEATURES_LEVEL > 0) { // This part of code looks strange
 			print '<td width="60" class="right">'.$langs->trans('MonthShort'.str_pad($j, 2, '0', STR_PAD_LEFT)).'</td>';
 		}
 		print '<td width="60" class="right"><b>'.$langs->trans("Total").'</b></td></tr>';
-
 		$sql = "SELECT '".$db->escape($langs->trans("Vide"))."' AS marge,";
 		for ($i = 1; $i <= 12; $i++) {
 			$j = $i + ($conf->global->SOCIETE_FISCAL_MONTH_START ? $conf->global->SOCIETE_FISCAL_MONTH_START : 1) - 1;
 			if ($j > 12) {
 				$j -= 12;
 			}
-			$sql .= "  SUM(".$db->ifsql("MONTH(f.datef)=".$j, "(fd.total_ht-(fd.qty * fd.buy_price_ht))", "0").") AS month".str_pad($j, 2, "0", STR_PAD_LEFT).",";
+			$sql .= " SUM(".$db->ifsql("MONTH(f.datef)=".$j,
+						" (".$db->ifsql("fd.total_ht < 0",
+							" (-1 * (abs(fd.total_ht) - (fd.buy_price_ht * fd.qty * (fd.situation_percent / 100))))",
+							"  (fd.total_ht - (fd.buy_price_ht * fd.qty * (fd.situation_percent / 100)))").")",
+						 0).") AS month".str_pad($j, 2, '0', STR_PAD_LEFT).",";
 		}
-		$sql .= "  SUM((fd.total_ht-(fd.qty * fd.buy_price_ht))) as total";
+		$sql .= "  SUM(".$db->ifsql("fd.total_ht < 0",
+							" (-1 * (abs(fd.total_ht) - (fd.buy_price_ht * fd.qty * (fd.situation_percent / 100))))",
+							"  (fd.total_ht - (fd.buy_price_ht * fd.qty * (fd.situation_percent / 100)))").") as total";
+
 		$sql .= " FROM ".MAIN_DB_PREFIX."facturedet as fd";
 		$sql .= "  LEFT JOIN ".MAIN_DB_PREFIX."facture as f ON f.rowid = fd.fk_facture";
 		$sql .= " WHERE f.datef >= '".$db->idate($search_date_start)."'";
@@ -599,7 +665,6 @@ if ($conf->global->MAIN_FEATURES_LEVEL > 0) { // This part of code looks strange
 		} else {
 			$sql .= " AND f.type IN (".Facture::TYPE_STANDARD.", ".Facture::TYPE_REPLACEMENT.", ".Facture::TYPE_CREDIT_NOTE.", ".Facture::TYPE_DEPOSIT.", ".Facture::TYPE_SITUATION.")";
 		}
-
 		dol_syslog('htdocs/accountancy/customer/index.php');
 		$resql = $db->query($sql);
 		if ($resql) {

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

@@ -68,7 +68,7 @@ $search_tvaintra = GETPOST('search_tvaintra', 'alpha');
 $limit = GETPOST('limit', 'int') ?GETPOST('limit', 'int') : (empty($conf->global->ACCOUNTING_LIMIT_LIST_VENTILATION) ? $conf->liste_limit : $conf->global->ACCOUNTING_LIMIT_LIST_VENTILATION);
 $sortfield = GETPOST('sortfield', 'aZ09comma');
 $sortorder = GETPOST('sortorder', 'aZ09comma');
-$page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int');
+$page = GETPOSTISSET('pageplusonPour le détail de la facture ref…e') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int');
 if (empty($page) || $page < 0) {
 	$page = 0;
 }
@@ -137,9 +137,9 @@ if (is_array($changeaccount) && count($changeaccount) > 0 && $user->rights->acco
 	if (!$error) {
 		$db->begin();
 
-		$sql1 = "UPDATE ".MAIN_DB_PREFIX."facturedet as l";
-		$sql1 .= " SET l.fk_code_ventilation=".(GETPOST('account_parent', 'int') > 0 ? GETPOST('account_parent', 'int') : '0');
-		$sql1 .= ' WHERE l.rowid IN ('.$db->sanitize(implode(',', $changeaccount)).')';
+		$sql1 = "UPDATE ".MAIN_DB_PREFIX."facturedet";
+		$sql1 .= " SET fk_code_ventilation=".(GETPOST('account_parent', 'int') > 0 ? GETPOST('account_parent', 'int') : '0');
+		$sql1 .= ' WHERE rowid IN ('.$db->sanitize(implode(',', $changeaccount)).')';
 
 		dol_syslog('accountancy/customer/lines.php::changeaccount sql= '.$sql1);
 		$resql1 = $db->query($sql1);
@@ -496,8 +496,8 @@ if ($result) {
 		}
 		print '</td>';
 
-		print '<td class="tdoverflowonsmartphone">';
-		$text = dolGetFirstLineOfText(dol_string_nohtmltag($objp->description));
+		print '<td class="tdoverflowonsmartphone small">';
+		$text = dolGetFirstLineOfText(dol_string_nohtmltag($objp->description, 1));
 		$trunclength = empty($conf->global->ACCOUNTING_LENGTH_DESCRIPTION) ? 32 : $conf->global->ACCOUNTING_LENGTH_DESCRIPTION;
 		print $form->textwithtooltip(dol_trunc($text, $trunclength), $objp->description);
 		print '</td>';
@@ -516,7 +516,7 @@ if ($result) {
 		}
 		print '</td>';
 
-		print '<td>'.$objp->tva_intra.'</td>';
+		print '<td class="tdoverflowmax80" title="'.dol_escape_htmltag($objp->tva_intra).'">'.dol_escape_htmltag($objp->tva_intra).'</td>';
 
 		print '<td>';
 		print $accountingaccountstatic->getNomUrl(0, 1, 1, '', 1);

+ 11 - 27
htdocs/accountancy/customer/list.php

@@ -545,19 +545,21 @@ if ($result) {
 		// issue : if we change product_type value in product DB it should differ from the value stored in facturedet DB !
 		$code_sell_l = '';
 		$code_sell_p = '';
+		$code_sell_t = '';
 
 		$thirdpartystatic->id = $objp->socid;
 		$thirdpartystatic->name = $objp->name;
 		$thirdpartystatic->client = $objp->client;
 		$thirdpartystatic->fournisseur = $objp->fournisseur;
 		$thirdpartystatic->code_client = $objp->code_client;
+		$thirdpartystatic->code_compta = $objp->code_compta_client;		// For backward compatibility
 		$thirdpartystatic->code_compta_client = $objp->code_compta_client;
 		$thirdpartystatic->code_fournisseur = $objp->code_fournisseur;
 		$thirdpartystatic->code_compta_fournisseur = $objp->code_compta_fournisseur;
 		$thirdpartystatic->email = $objp->email;
 		$thirdpartystatic->country_code = $objp->country_code;
 		$thirdpartystatic->tva_intra = $objp->tva_intra;
-		$thirdpartystatic->code_compta_company = $objp->company_code_sell;
+		$thirdpartystatic->code_compta_product = $objp->company_code_sell;		// The accounting account for product stored on thirdparty object (for level3 suggestion)
 
 		$product_static->ref = $objp->product_ref;
 		$product_static->id = $objp->product_id;
@@ -594,6 +596,8 @@ if ($result) {
 		$code_sell_p_notset = '';
 		$code_sell_t_notset = '';
 
+		$suggestedid = 0;
+
 		$return=$accountingAccount->getAccountingCodeToBind($thirdpartystatic, $mysoc, $product_static, $facture_static, $facture_static_det, $accountingAccountArray, 'customer');
 		if (!is_array($return) && $return<0) {
 			setEventMessage($accountingAccount->error, 'errors');
@@ -607,28 +611,7 @@ if ($result) {
 		}
 		//var_dump($return);
 
-		// Level 3: 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($objp->company_code_sell)) {
-				$objp->code_sell_t = $objp->company_code_sell;
-				$objp->aarowid_suggest = $objp->aarowid_thirdparty;
-				$suggestedaccountingaccountfor = '';
-			}
-		}
-
-		// Manage Deposit
-		if (!empty($conf->global->ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT)) {
-			if ($objp->description == "(DEPOSIT)" || $objp->ftype == $facture_static::TYPE_DEPOSIT) {
-				$accountdeposittoventilated = new AccountingAccount($db);
-				$accountdeposittoventilated->fetch('', $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT, 1);
-				$objp->code_sell_l = $accountdeposittoventilated->ref;
-				$objp->code_sell_p = '';
-				$objp->code_sell_t = '';
-				$objp->aarowid_suggest = $accountdeposittoventilated->rowid;
-			}
-		}
-
-		if (!empty($objp->code_sell_p)) {
+		if (!empty($code_sell_p)) {
 			// Value was defined previously
 		} else {
 			$code_sell_p_notset = 'color:orange';
@@ -643,6 +626,7 @@ if ($result) {
 		// $code_sell_l is now default code of product/service
 		// $code_sell_p is now code of product/service
 		// $code_sell_t is now code of thirdparty
+		//var_dump($code_sell_l.' - '.$code_sell_p.' - '.$code_sell_t.' -> '.$suggestedid.' ('.$suggestedaccountingaccountbydefaultfor.' '.$suggestedaccountingaccountfor.')');
 
 		print '<tr class="oddeven">';
 
@@ -660,13 +644,13 @@ if ($result) {
 			print $product_static->getNomUrl(1);
 		}
 		if ($product_static->label) {
-			print '<br><span class="opacitymedium small">'.$product_static->label.'</span>';
+			print '<br><span class="opacitymedium small">'.dol_escape_htmltag($product_static->label).'</span>';
 		}
 		print '</td>';
 
 		// Description
 		print '<td class="tdoverflowonsmartphone small">';
-		$text = dolGetFirstLineOfText(dol_string_nohtmltag($facture_static_det->desc));
+		$text = dolGetFirstLineOfText(dol_string_nohtmltag($facture_static_det->desc, 1));
 		$trunclength = empty($conf->global->ACCOUNTING_LENGTH_DESCRIPTION) ? 32 : $conf->global->ACCOUNTING_LENGTH_DESCRIPTION;
 		print $form->textwithtooltip(dol_trunc($text, $trunclength), $facture_static_det->desc);
 		print '</td>';
@@ -694,7 +678,7 @@ if ($result) {
 		print '</td>';
 
 		// VAT Num
-		print '<td class="tdoverflowmax100" title="'.dol_escape_htmltag($objp->tva_intra).'">'.dol_escape_htmltag($objp->tva_intra).'</td>';
+		print '<td class="tdoverflowmax80" title="'.dol_escape_htmltag($objp->tva_intra).'">'.dol_escape_htmltag($objp->tva_intra).'</td>';
 
 		// Found accounts
 		print '<td class="small">';
@@ -776,7 +760,7 @@ if ($db->type == 'mysqli') {
 }
 
 // Add code to auto check the box when we select an account
-print '<script type="text/javascript" language="javascript">
+print '<script type="text/javascript">
 jQuery(document).ready(function() {
 	jQuery(".codeventil").change(function() {
 		var s=$(this).attr("id").replace("codeventil", "")

+ 90 - 29
htdocs/accountancy/expensereport/index.php

@@ -32,6 +32,9 @@ require_once DOL_DOCUMENT_ROOT.'/expensereport/class/expensereport.class.php';
 // Load translation files required by the page
 $langs->loadLangs(array("compta", "bills", "other", "accountancy"));
 
+$validatemonth = GETPOST('validatemonth', 'int');
+$validateyear = GETPOST('validateyear', 'int');
+
 $month_start = ($conf->global->SOCIETE_FISCAL_MONTH_START ? ($conf->global->SOCIETE_FISCAL_MONTH_START) : 1);
 if (GETPOST("year", 'int')) {
 	$year_start = GETPOST("year", 'int');
@@ -96,34 +99,69 @@ if (($action == 'clean' || $action == 'validatehistory') && $user->rights->accou
 
 if ($action == 'validatehistory') {
 	$error = 0;
+	$nbbinddone = 0;
+	$notpossible = 0;
+
 	$db->begin();
 
 	// Now make the binding
-	if ($db->type == 'pgsql') {
-		$sql1 = "UPDATE ".MAIN_DB_PREFIX."expensereport_det";
-		$sql1 .= " SET fk_code_ventilation = accnt.rowid";
-		$sql1 .= " FROM ".MAIN_DB_PREFIX."c_type_fees as t, ".MAIN_DB_PREFIX."accounting_account as accnt , ".MAIN_DB_PREFIX."accounting_system as syst";
-		$sql1 .= " WHERE ".MAIN_DB_PREFIX."expensereport_det.fk_c_type_fees = t.id  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 t.accountancy_code = accnt.account_number";
-		$sql1 .= " AND ".MAIN_DB_PREFIX."expensereport_det.fk_code_ventilation = 0";
-	} else {
-		$sql1 = "UPDATE ".MAIN_DB_PREFIX."expensereport_det as erd, ".MAIN_DB_PREFIX."c_type_fees as t, ".MAIN_DB_PREFIX."accounting_account as accnt , ".MAIN_DB_PREFIX."accounting_system as syst";
-		$sql1 .= " SET erd.fk_code_ventilation = accnt.rowid";
-		$sql1 .= " WHERE erd.fk_c_type_fees = t.id 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 t.accountancy_code=accnt.account_number";
-		$sql1 .= " AND erd.fk_code_ventilation = 0";
+	$sql1 = "SELECT erd.rowid, accnt.rowid as suggestedid";
+	$sql1 .= " FROM ".MAIN_DB_PREFIX."expensereport_det as erd";
+	$sql1 .= " LEFT JOIN ".MAIN_DB_PREFIX."c_type_fees as t ON erd.fk_c_type_fees = t.id";
+	$sql1 .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as accnt ON t.accountancy_code = accnt.account_number AND accnt.active = 1 AND accnt.entity =".((int) $conf->entity);
+	$sql1 .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_system as syst ON accnt.fk_pcg_version = syst.pcg_version AND syst.rowid = ".((int) $conf->global->CHARTOFACCOUNTS).' AND syst.active = 1,';
+	$sql1 .= " ".MAIN_DB_PREFIX."expensereport as er";
+	$sql1 .= " WHERE erd.fk_expensereport = er.rowid AND er.entity = ".((int) $conf->entity);
+	$sql1 .= " AND er.fk_statut IN (".ExpenseReport::STATUS_APPROVED.", ".ExpenseReport::STATUS_CLOSED.") AND erd.fk_code_ventilation <= 0";
+	if ($validatemonth && $validateyear) {
+		$sql1 .= dolSqlDateFilter('erd.date', 0, $validatemonth, $validateyear);
 	}
 
 	dol_syslog('htdocs/accountancy/expensereport/index.php');
 
-	$resql1 = $db->query($sql1);
-	if (!$resql1) {
+	$result = $db->query($sql1);
+	if (!$result) {
 		$error++;
-		$db->rollback();
 		setEventMessages($db->lasterror(), null, 'errors');
+	} else {
+		$num_lines = $db->num_rows($result);
+
+		$i = 0;
+		while ($i < min($num_lines, 10000)) {	// No more than 10000 at once
+			$objp = $db->fetch_object($result);
+
+			$lineid = $objp->rowid;
+			$suggestedid = $objp->suggestedid;
+
+			if ($suggestedid > 0) {
+				$sqlupdate = "UPDATE ".MAIN_DB_PREFIX."expensereport_det";
+				$sqlupdate .= " SET fk_code_ventilation = ".((int) $suggestedid);
+				$sqlupdate .= " WHERE fk_code_ventilation <= 0 AND rowid = ".((int) $lineid);
+
+				$resqlupdate = $db->query($sqlupdate);
+				if (!$resqlupdate) {
+					$error++;
+					setEventMessages($db->lasterror(), null, 'errors');
+					break;
+				} else {
+					$nbbinddone++;
+				}
+			} else {
+				$notpossible++;
+			}
+
+			$i++;
+		}
+		if ($num_lines > 10000) {
+			$notpossible += ($num_lines - 10000);
+		}
+	}
+
+	if ($error) {
+		$db->rollback();
 	} else {
 		$db->commit();
-		setEventMessages($langs->trans('AutomaticBindingDone'), null, 'mesgs');
+		setEventMessages($langs->trans('AutomaticBindingDone', $nbbinddone, $notpossible), null, 'mesgs');
 	}
 }
 
@@ -146,7 +184,7 @@ print '</span><br>';
 
 $y = $year_current;
 
-$buttonbind = '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?year='.$year_current.'&action=validatehistory">'.$langs->trans("ValidateHistory").'</a>';
+$buttonbind = '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?action=validatehistory&token='.newToken().'&year='.$year_current.'">'.$langs->trans("ValidateHistory").'</a>';
 
 
 print_barre_liste(img_picto('', 'unlink', 'class="paddingright fa-color-unset"').$langs->trans("OverviewOfAmountOfLinesNotBound"), '', '', '', '', '', '', -1, '', '', 0, $buttonbind, '', 0, 1, 1);
@@ -161,7 +199,25 @@ for ($i = 1; $i <= 12; $i++) {
 	if ($j > 12) {
 		$j -= 12;
 	}
-	print '<td width="60" class="right">'.$langs->trans('MonthShort'.str_pad($j, 2, '0', STR_PAD_LEFT)).'</td>';
+	$cursormonth = $j;
+	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 width="60" class="right">';
+	if (!empty($tmp['mday'])) {
+		$param = 'search_date_startday=1&search_date_startmonth='.$cursormonth.'&search_date_startyear='.$cursoryear;
+		$param .= '&search_date_endday='.$tmp['mday'].'&search_date_endmonth='.$tmp['mon'].'&search_date_endyear='.$tmp['year'];
+		$param .= '&search_month='.$tmp['mon'].'&search_year='.$tmp['year'];
+		print '<a href="'.DOL_URL_ROOT.'/accountancy/expensereport/list.php?'.$param.'">';
+	}
+	print $langs->trans('MonthShort'.str_pad($j, 2, '0', STR_PAD_LEFT));
+	if (!empty($tmp['mday'])) {
+		print '</a>';
+	}
+	print '</td>';
 }
 print '<td width="60" class="right"><b>'.$langs->trans("Total").'</b></td></tr>';
 
@@ -188,6 +244,7 @@ $sql .= " AND er.fk_statut IN (".ExpenseReport::STATUS_APPROVED.", ".ExpenseRepo
 $sql .= " AND er.entity IN (".getEntity('expensereport', 0).")"; // We don't share object for accountancy
 $sql .= " AND aa.account_number IS NULL";
 $sql .= " GROUP BY erd.fk_code_ventilation,aa.account_number,aa.label";
+$sql .= ' ORDER BY aa.account_number';
 
 dol_syslog('/accountancy/expensereport/index.php:: sql='.$sql);
 $resql = $db->query($sql);
@@ -195,7 +252,8 @@ if ($resql) {
 	$num = $db->num_rows($resql);
 
 	while ($row = $db->fetch_row($resql)) {
-		print '<tr class="oddeven"><td>';
+		print '<tr class="oddeven">';
+		print '<td>';
 		if ($row[0] == 'tobind') {
 			print '<span class="opacitymedium">'.$langs->trans("Unknown").'</span>';
 		} else {
@@ -204,15 +262,16 @@ if ($resql) {
 		print '</td>';
 		print '<td>';
 		if ($row[0] == 'tobind') {
-			print $langs->trans("UseMenuToSetBindindManualy", DOL_URL_ROOT.'/accountancy/expensereport/list.php?search_year='.$y, $langs->transnoentitiesnoconv("ToBind"));
+			print $langs->trans("UseMenuToSetBindindManualy", DOL_URL_ROOT.'/accountancy/expensereport/list.php?search_year='.((int) $y), $langs->transnoentitiesnoconv("ToBind"));
 		} else {
 			print $row[1];
 		}
 		print '</td>';
-		for ($i = 2; $i <= 12; $i++) {
-			print '<td class="right nowraponall amount">'.price($row[$i]).'</td>';
+		for ($i = 2; $i <= 13; $i++) {
+			print '<td class="right nowraponall amount">';
+			print price($row[$i]);
+			print '</td>';
 		}
-		print '<td class="right nowraponall amount">'.price($row[13]).'</td>';
 		print '<td class="right nowraponall amount"><b>'.price($row[14]).'</b></td>';
 		print '</tr>';
 	}
@@ -274,7 +333,8 @@ if ($resql) {
 	$num = $db->num_rows($resql);
 
 	while ($row = $db->fetch_row($resql)) {
-		print '<tr class="oddeven"><td>';
+		print '<tr class="oddeven">';
+		print '<td>';
 		if ($row[0] == 'tobind') {
 			print '<span class="opacitymedium">'.$langs->trans("Unknown").'</span>';
 		} else {
@@ -284,15 +344,16 @@ if ($resql) {
 
 		print '<td>';
 		if ($row[0] == 'tobind') {
-			print $langs->trans("UseMenuToSetBindindManualy", DOL_URL_ROOT.'/accountancy/expensereport/list.php?search_year='.$y, $langs->transnoentitiesnoconv("ToBind"));
+			print $langs->trans("UseMenuToSetBindindManualy", DOL_URL_ROOT.'/accountancy/expensereport/list.php?search_year='.((int) $y), $langs->transnoentitiesnoconv("ToBind"));
 		} else {
 			print $row[1];
 		}
 		print '</td>';
-		for ($i = 2; $i <= 12; $i++) {
-			print '<td class="right nowraponall amount">'.price($row[$i]).'</td>';
+		for ($i = 2; $i <= 13; $i++) {
+			print '<td class="right nowraponall amount">';
+			print price($row[$i]);
+			print '</td>';
 		}
-		print '<td class="right nowraponall amount">'.price($row[13]).'</td>';
 		print '<td class="right nowraponall amount"><b>'.price($row[14]).'</b></td>';
 		print '</tr>';
 	}

+ 50 - 27
htdocs/accountancy/expensereport/lines.php

@@ -1,6 +1,6 @@
 <?php
 /* Copyright (C) 2013-2016	Olivier Geffroy		<jeff@jeffinfo.com>
- * Copyright (C) 2013-2017	Alexandre Spangaro	<aspangaro@open-dsi.fr>
+ * Copyright (C) 2013-2022	Alexandre Spangaro	<aspangaro@open-dsi.fr>
  * Copyright (C) 2014-2015	Ari Elbaz (elarifr)	<github@accedinfo.com>
  * Copyright (C) 2013-2016	Florian Henry		<florian.henry@open-concept.pro>
  * Copyright (C) 2014		Juanjo Menent		<jmenent@2byte.es>
@@ -26,8 +26,8 @@
  */
 require '../../main.inc.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/class/html.formother.class.php';
 require_once DOL_DOCUMENT_ROOT.'/expensereport/class/expensereport.class.php';
 require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
 require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
@@ -49,9 +49,14 @@ $search_desc = GETPOST('search_desc', 'alpha');
 $search_amount = GETPOST('search_amount', 'alpha');
 $search_account = GETPOST('search_account', 'alpha');
 $search_vat = GETPOST('search_vat', 'alpha');
-$search_day = GETPOST("search_day", "int");
-$search_month = GETPOST("search_month", "int");
-$search_year = GETPOST("search_year", "int");
+$search_date_startday = GETPOST('search_date_startday', 'int');
+$search_date_startmonth = GETPOST('search_date_startmonth', 'int');
+$search_date_startyear = GETPOST('search_date_startyear', 'int');
+$search_date_endday = GETPOST('search_date_endday', 'int');
+$search_date_endmonth = GETPOST('search_date_endmonth', 'int');
+$search_date_endyear = GETPOST('search_date_endyear', 'int');
+$search_date_start = dol_mktime(0, 0, 0, $search_date_startmonth, $search_date_startday, $search_date_startyear);	// Use tzserver
+$search_date_end = dol_mktime(23, 59, 59, $search_date_endmonth, $search_date_endday, $search_date_endyear);
 
 // 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);
@@ -61,9 +66,9 @@ $page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("pa
 if (empty($page) || $page < 0) {
 	$page = 0;
 }
+$offset = $limit * $page;
 $pageprev = $page - 1;
 $pagenext = $page + 1;
-$offset = $limit * $page;
 if (!$sortfield) {
 	$sortfield = "erd.date, erd.rowid";
 }
@@ -101,9 +106,14 @@ if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x'
 	$search_amount = '';
 	$search_account = '';
 	$search_vat = '';
-	$search_day = '';
-	$search_month = '';
-	$search_year = '';
+	$search_date_startday = '';
+	$search_date_startmonth = '';
+	$search_date_startyear = '';
+	$search_date_endday = '';
+	$search_date_endmonth = '';
+	$search_date_endyear = '';
+	$search_date_start = '';
+	$search_date_end = '';
 }
 
 if (is_array($changeaccount) && count($changeaccount) > 0 && $user->rights->accounting->bind->write) {
@@ -204,7 +214,12 @@ if (strlen(trim($search_account))) {
 if (strlen(trim($search_vat))) {
 	$sql .= natural_search("erd.tva_tx", price2num($search_vat), 1);
 }
-$sql .= dolSqlDateFilter('erd.date', $search_day, $search_month, $search_year);
+if ($search_date_start) {
+	$sql .= " AND erd.date >= '".$db->idate($search_date_start)."'";
+}
+if ($search_date_end) {
+	$sql .= " AND erd.date <= '".$db->idate($search_date_end)."'";
+}
 $sql .= " AND er.entity IN (".getEntity('expensereport', 0).")"; // We don't share object for accountancy
 
 $sql .= $db->order($sortfield, $sortorder);
@@ -222,9 +237,8 @@ if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
 
 $sql .= $db->plimit($limit + 1, $offset);
 
-dol_syslog('accountancy/expensereport/lines.php::list');
+dol_syslog("accountancy/expensereport/lines.php", LOG_DEBUG);
 $result = $db->query($sql);
-
 if ($result) {
 	$num_lines = $db->num_rows($result);
 	$i = 0;
@@ -254,14 +268,23 @@ if ($result) {
 	if ($search_vat) {
 		$param .= "&search_vat=".urlencode($search_vat);
 	}
-	if ($search_day) {
-		$param .= '&search_day='.urlencode($search_day);
+	if ($search_date_startday) {
+		$param .= '&search_date_startday='.urlencode($search_date_startday);
+	}
+	if ($search_date_startmonth) {
+		$param .= '&search_date_startmonth='.urlencode($search_date_startmonth);
+	}
+	if ($search_date_startyear) {
+		$param .= '&search_date_startyear='.urlencode($search_date_startyear);
 	}
-	if ($search_month) {
-		$param .= '&search_month='.urlencode($search_month);
+	if ($search_date_endday) {
+		$param .= '&search_date_endday='.urlencode($search_date_endday);
 	}
-	if ($search_year) {
-		$param .= '&search_year='.urlencode($search_year);
+	if ($search_date_endmonth) {
+		$param .= '&search_date_endmonth='.urlencode($search_date_endmonth);
+	}
+	if ($search_date_endyear) {
+		$param .= '&search_date_endyear='.urlencode($search_date_endyear);
 	}
 
 	print '<form action="'.$_SERVER["PHP_SELF"].'" method="post">'."\n";
@@ -276,12 +299,11 @@ if ($result) {
 	print '<input type="hidden" name="page" value="'.$page.'">';
 
 	print_barre_liste($langs->trans("ExpenseReportLinesDone"), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num_lines, $nbtotalofrecords, 'title_accountancy', 0, '', '', $limit);
-
 	print '<span class="opacitymedium">'.$langs->trans("DescVentilDoneExpenseReport").'</span><br>';
 
-	print '<br><div class="inline-block divButAction">'.$langs->trans("ChangeAccount").'<br>';
+	print '<br><div class="inline-block divButAction paddingbottom">'.$langs->trans("ChangeAccount").' ';
 	print $formaccounting->select_account($account_parent, 'account_parent', 2, array(), 0, 0, 'maxwidth300 maxwidthonsmartphone valignmiddle');
-	print '<input type="submit" class="button valignmiddle" value="'.$langs->trans("ChangeBinding").'" /></div>';
+	print '<input type="submit" class="button small valignmiddle" value="'.$langs->trans("ChangeBinding").'"/></div>';
 
 	$moreforfilter = '';
 
@@ -296,11 +318,12 @@ if ($result) {
 		print '<td class="liste_titre"></td>';
 	}
 	print '<td class="liste_titre center">';
-	if (!empty($conf->global->MAIN_LIST_FILTER_ON_DAY)) {
-		print '<input class="flat valignmiddle maxwidth25" type="text" maxlength="2" name="search_day" value="'.$search_day.'">';
-	}
-	print '<input class="flat valignmiddle maxwidth25" type="text" maxlength="2" name="search_month" value="'.$search_month.'">';
-	$formother->select_year($search_year, 'search_year', 1, 20, 5);
+	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>';
 	print '<td class="liste_titre"><input type="text" class="flat maxwidth50" name="search_label" value="'.dol_escape_htmltag($search_label).'"></td>';
 	print '<td class="liste_titre"><input type="text" class="flat maxwidth50" name="search_desc" value="'.dol_escape_htmltag($search_desc).'"></td>';
@@ -382,7 +405,7 @@ if ($result) {
 
 		// Fees description -- Can be null
 		print '<td>';
-		$text = dolGetFirstLineOfText(dol_string_nohtmltag($objp->comments));
+		$text = dolGetFirstLineOfText(dol_string_nohtmltag($objp->comments, 1));
 		$trunclength = empty($conf->global->ACCOUNTING_LENGTH_DESCRIPTION) ? 32 : $conf->global->ACCOUNTING_LENGTH_DESCRIPTION;
 		print $form->textwithtooltip(dol_trunc($text, $trunclength), $objp->comments);
 		print '</td>';

+ 102 - 46
htdocs/accountancy/expensereport/list.php

@@ -1,6 +1,6 @@
 <?php
 /* Copyright (C) 2013-2014	Olivier Geffroy			<jeff@jeffinfo.com>
- * Copyright (C) 2013-2017	Alexandre Spangaro		<aspangaro@open-dsi.fr>
+ * Copyright (C) 2013-2022	Alexandre Spangaro		<aspangaro@open-dsi.fr>
  * Copyright (C) 2014-2015	Ari Elbaz (elarifr)		<github@accedinfo.com>
  * Copyright (C) 2013-2014	Florian Henry			<florian.henry@open-concept.pro>
  * Copyright (C) 2014		Juanjo Menent			<jmenent@2byte.es>s
@@ -30,9 +30,9 @@ require '../../main.inc.php';
 require_once DOL_DOCUMENT_ROOT.'/expensereport/class/expensereport.class.php';
 require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
 require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
-require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
 
 // Load translation files required by the page
@@ -59,9 +59,14 @@ $search_desc = GETPOST('search_desc', 'alpha');
 $search_amount = GETPOST('search_amount', 'alpha');
 $search_account = GETPOST('search_account', 'alpha');
 $search_vat = GETPOST('search_vat', 'alpha');
-$search_day = GETPOST("search_day", "int");
-$search_month = GETPOST("search_month", "int");
-$search_year = GETPOST("search_year", "int");
+$search_date_startday = GETPOST('search_date_startday', 'int');
+$search_date_startmonth = GETPOST('search_date_startmonth', 'int');
+$search_date_startyear = GETPOST('search_date_startyear', 'int');
+$search_date_endday = GETPOST('search_date_endday', 'int');
+$search_date_endmonth = GETPOST('search_date_endmonth', 'int');
+$search_date_endyear = GETPOST('search_date_endyear', 'int');
+$search_date_start = dol_mktime(0, 0, 0, $search_date_startmonth, $search_date_startday, $search_date_startyear);	// Use tzserver
+$search_date_end = dol_mktime(23, 59, 59, $search_date_endmonth, $search_date_endday, $search_date_endyear);
 
 // 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);
@@ -83,6 +88,9 @@ if (!$sortorder) {
 	}
 }
 
+// Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context
+$hookmanager->initHooks(array('accountancyexpensereportlist'));
+
 $formaccounting = new FormAccounting($db);
 $accounting = new AccountingAccount($db);
 
@@ -101,7 +109,7 @@ if (empty($user->rights->accounting->mouvements->lire)) {
 
 
 /*
- * Action
+ * Actions
  */
 
 if (GETPOST('cancel', 'alpha')) {
@@ -111,30 +119,47 @@ if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massa
 	$massaction = '';
 }
 
-// Purge search criteria
-if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All test are required to be compatible with all browsers
-	$search_login = '';
-	$search_expensereport = '';
-	$search_label = '';
-	$search_desc = '';
-	$search_amount = '';
-	$search_account = '';
-	$search_vat = '';
-	$search_day = '';
-	$search_month = '';
-	$search_year = '';
+$parameters = array();
+$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)) {
+	// Purge search criteria
+	if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All test are required to be compatible with all browsers
+		$search_login = '';
+		$search_expensereport = '';
+		$search_label = '';
+		$search_desc = '';
+		$search_amount = '';
+		$search_account = '';
+		$search_vat = '';
+		$search_date_startday = '';
+		$search_date_startmonth = '';
+		$search_date_startyear = '';
+		$search_date_endday = '';
+		$search_date_endmonth = '';
+		$search_date_endyear = '';
+		$search_date_start = '';
+		$search_date_end = '';
+		$search_country = '';
+		$search_tvaintra = '';
+	}
+
+	// Mass actions
+	$objectclass = 'ExpenseReport';
+	$objectlabel = 'ExpenseReport';
+	$permissiontoread = $user->rights->expensereport->read;
+	$permissiontodelete = $user->rights->expensereport->delete;
+	$uploaddir = $conf->expensereport->dir_output;
+	include DOL_DOCUMENT_ROOT . '/core/actions_massactions.inc.php';
 }
 
-// Mass actions
-$objectclass = 'ExpenseReport';
-$objectlabel = 'ExpenseReport';
-$permissiontoread = $user->rights->expensereport->read;
-$permissiontodelete = $user->rights->expensereport->delete;
-$uploaddir = $conf->expensereport->dir_output;
-include DOL_DOCUMENT_ROOT.'/core/actions_massactions.inc.php';
 
 if ($massaction == 'ventil' && $user->rights->accounting->bind->write) {
 	$msg = '';
+
 	//print '<div><span style="color:red">' . $langs->trans("Processing") . '...</span></div>';
 	if (!empty($mesCasesCochees)) {
 		$msg = '<div>'.$langs->trans("SelectedLines").': '.count($mesCasesCochees).'</div>';
@@ -159,7 +184,7 @@ if ($massaction == 'ventil' && $user->rights->accounting->bind->write) {
 				$accountventilated = new AccountingAccount($db);
 				$accountventilated->fetch($monCompte, '', 1);
 
-				dol_syslog('accountancy/expensereport/list.php', LOG_DEBUG);
+				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>';
 					$ok++;
@@ -201,6 +226,9 @@ $sql .= " erd.rowid, erd.fk_c_type_fees, erd.comments, erd.total_ht as price, er
 $sql .= " f.id as type_fees_id, f.code as type_fees_code, f.label as type_fees_label, f.accountancy_code as code_buy,";
 $sql .= " u.rowid as userid, u.login, u.lastname, u.firstname, u.email, u.gender, u.employee, u.photo, u.statut,";
 $sql .= " aa.rowid as aarowid";
+$parameters = array();
+$reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters); // Note that $action and $object may have been modified by hook
+$sql .= $hookmanager->resPrint;
 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport as er";
 $sql .= " INNER JOIN ".MAIN_DB_PREFIX."expensereport_det as erd ON er.rowid = erd.fk_expensereport";
 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_type_fees as f ON f.id = erd.fk_c_type_fees";
@@ -233,9 +261,19 @@ if (strlen(trim($search_account))) {
 if (strlen(trim($search_vat))) {
 	$sql .= natural_search("erd.tva_tx", $search_vat, 1);
 }
-$sql .= dolSqlDateFilter('erd.date', $search_day, $search_month, $search_year);
+if ($search_date_start) {
+	$sql .= " AND erd.date >= '".$db->idate($search_date_start)."'";
+}
+if ($search_date_end) {
+	$sql .= " AND erd.date <= '".$db->idate($search_date_end)."'";
+}
 $sql .= " AND er.entity IN (".getEntity('expensereport', 0).")"; // We don't share object for accountancy
 
+// Add where from hooks
+$parameters = array();
+$reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters); // Note that $action and $object may have been modified by hook
+$sql .= $hookmanager->resPrint;
+
 $sql .= $db->order($sortfield, $sortorder);
 
 // Count total nb of records
@@ -251,7 +289,13 @@ if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
 
 $sql .= $db->plimit($limit + 1, $offset);
 
-dol_syslog('accountancy/expensereport/list.php');
+dol_syslog("accountancy/expensereport/list.php", LOG_DEBUG);
+// MAX_JOIN_SIZE can be very low (ex: 300000) on some limited configurations (ex: https://www.online.net/fr/hosting/online-perso)
+// This big SELECT command may exceed the MAX_JOIN_SIZE limit => Therefore we use SQL_BIG_SELECTS=1 to disable the MAX_JOIN_SIZE security
+if ($db->type == 'mysqli') {
+	$db->query("SET SQL_BIG_SELECTS=1");
+}
+
 $result = $db->query($sql);
 if ($result) {
 	$num_lines = $db->num_rows($result);
@@ -272,14 +316,23 @@ if ($result) {
 	if ($search_lineid) {
 		$param .= '&search_lineid='.urlencode($search_lineid);
 	}
-	if ($search_day) {
-		$param .= '&search_day='.urlencode($search_day);
+	if ($search_date_startday) {
+		$param .= '&search_date_startday='.urlencode($search_date_startday);
+	}
+	if ($search_date_startmonth) {
+		$param .= '&search_date_startmonth='.urlencode($search_date_startmonth);
+	}
+	if ($search_date_startyear) {
+		$param .= '&search_date_startyear='.urlencode($search_date_startyear);
 	}
-	if ($search_month) {
-		$param .= '&search_month='.urlencode($search_month);
+	if ($search_date_endday) {
+		$param .= '&search_date_endday='.urlencode($search_date_endday);
 	}
-	if ($search_year) {
-		$param .= '&search_year='.urlencode($search_year);
+	if ($search_date_endmonth) {
+		$param .= '&search_date_endmonth='.urlencode($search_date_endmonth);
+	}
+	if ($search_date_endyear) {
+		$param .= '&search_date_endyear='.urlencode($search_date_endyear);
 	}
 	if ($search_expensereport) {
 		$param .= '&search_expensereport='.urlencode($search_expensereport);
@@ -302,7 +355,6 @@ if ($result) {
 	);
 	$massactionbutton = $form->selectMassAction('ventil', $arrayofmassactions, 1);
 
-
 	print '<form action="'.$_SERVER["PHP_SELF"].'" method="post">'."\n";
 	print '<input type="hidden" name="action" value="ventil">';
 	if ($optioncss != '') {
@@ -335,20 +387,21 @@ if ($result) {
 	if (!empty($conf->global->ACCOUNTANCY_USE_EXPENSE_REPORT_VALIDATION_DATE)) {
 		print '<td class="liste_titre"></td>';
 	}
-	print '<td class="liste_titre center nowraponall minwidth100imp">';
-	if (!empty($conf->global->MAIN_LIST_FILTER_ON_DAY)) {
-		print '<input class="flat valignmiddle maxwidth25" type="text" maxlength="2" name="search_day" value="'.$search_day.'">';
-	}
-	print '<input class="flat valignmiddle maxwidth25" type="text" maxlength="2" name="search_month" value="'.$search_month.'">';
-	$formother->select_year($search_year, 'search_year', 1, 20, 5);
+	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>';
 	print '<td class="liste_titre"><input type="text" class="flat maxwidth50" name="search_label" value="'.dol_escape_htmltag($search_label).'"></td>';
 	print '<td class="liste_titre"><input type="text" class="flat maxwidthonsmartphone" name="search_desc" value="'.dol_escape_htmltag($search_desc).'"></td>';
-	print '<td class="liste_titre right"><input type="text" class="right flat maxwidth50" name="search_amount" value="'.dol_escape_htmltag($search_amount).'"></td>';
-	print '<td class="liste_titre right"><input type="text" class="right flat maxwidth50" name="search_vat" placeholder="%" size="1" value="'.dol_escape_htmltag($search_vat).'"></td>';
+	print '<td class="liste_titre right"><input type="text" class="flat maxwidth50 right" name="search_amount" value="'.dol_escape_htmltag($search_amount).'"></td>';
+	print '<td class="liste_titre right"><input type="text" class="flat maxwidth50 right" name="search_vat" placeholder="%" size="1" value="'.dol_escape_htmltag($search_vat).'"></td>';
 	print '<td class="liste_titre"></td>';
 	print '<td class="liste_titre"></td>';
-	print '<td class="center" class="liste_titre">';
+	print '<td class="center liste_titre">';
 	$searchpicto = $form->showFilterButtons();
 	print $searchpicto;
 	print '</td>';
@@ -428,7 +481,7 @@ if ($result) {
 
 		// Fees description -- Can be null
 		print '<td>';
-		$text = dolGetFirstLineOfText(dol_string_nohtmltag($objp->comments));
+		$text = dolGetFirstLineOfText(dol_string_nohtmltag($objp->comments, 1));
 		$trunclength = empty($conf->global->ACCOUNTING_LENGTH_DESCRIPTION) ? 32 : $conf->global->ACCOUNTING_LENGTH_DESCRIPTION;
 		print $form->textwithtooltip(dol_trunc($text, $trunclength), $objp->comments);
 		print '</td>';
@@ -468,9 +521,12 @@ if ($result) {
 } else {
 	print $db->error();
 }
+if ($db->type == 'mysqli') {
+	$db->query("SET SQL_BIG_SELECTS=0"); // Enable MAX_JOIN_SIZE limitation
+}
 
 // Add code to auto check the box when we select an account
-print '<script type="text/javascript" language="javascript">
+print '<script type="text/javascript">
 jQuery(document).ready(function() {
 	jQuery(".codeventil").change(function() {
 		var s=$(this).attr("id").replace("codeventil", "")

+ 59 - 57
htdocs/accountancy/index.php

@@ -100,7 +100,7 @@ if (!empty($conf->global->INVOICE_USE_SITUATION) && $conf->global->INVOICE_USE_S
 		$showtutorial .= ' '.$langs->trans("ShowTutorial");
 		$showtutorial .= '</a></div>';
 
-		$showtutorial .= '<script type="text/javascript" language="javascript">
+		$showtutorial .= '<script type="text/javascript">
 	    jQuery(document).ready(function() {
 	        jQuery("#show_hide").click(function () {
 	            jQuery( "#idfaq" ).toggle({
@@ -117,76 +117,78 @@ if (!empty($conf->global->INVOICE_USE_SITUATION) && $conf->global->INVOICE_USE_S
 	print '<div class="'.($helpisexpanded ? '' : 'hideobject').'" id="idfaq">'; // hideobject is to start hidden
 	print "<br>\n";
 	print '<span class="opacitymedium">'.$langs->trans("AccountancyAreaDescIntro")."</span><br>\n";
-	print "<br>\n"; print "<br>\n";
+	if (!empty($user->rights->accounting->chartofaccount)) {
+		print "<br>\n"; print "<br>\n";
 
-	print load_fiche_titre('<span class="fa fa-calendar-check-o"></span> '.$langs->trans("AccountancyAreaDescActionOnce"), '', '')."\n";
-	print '<hr>';
-	print "<br>\n";
-
-	// STEPS
-	$step++;
-	$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescJournalSetup", $step, '{s}');
-	$s = str_replace('{s}', '<a href="'.DOL_URL_ROOT.'/accountancy/admin/journals_list.php?id=35"><strong>'.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("AccountingJournals").'</strong></a>', $s);
-	print $s;
-	print "<br>\n";
-	$step++;
-	$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescChartModel", $step, '{s}');
-	$s = str_replace('{s}', '<a href="'.DOL_URL_ROOT.'/accountancy/admin/accountmodel.php"><strong>'.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("Pcg_version").'</strong></a>', $s);
-	print $s;
-	print "<br>\n";
-	$step++;
-	$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescChart", $step, '{s}');
-	$s = str_replace('{s}', '<a href="'.DOL_URL_ROOT.'/accountancy/admin/account.php"><strong>'.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("Chartofaccounts").'</strong></a>', $s);
-	print $s;
-	print "<br>\n";
+		print load_fiche_titre('<span class="fa fa-calendar-check-o"></span> '.$langs->trans("AccountancyAreaDescActionOnce"), '', '')."\n";
+		print '<hr>';
+		print "<br>\n";
 
-	print "<br>\n";
-	print $langs->trans("AccountancyAreaDescActionOnceBis");
-	print "<br>\n";
-	print "<br>\n";
+		// STEPS
+		$step++;
+		$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescJournalSetup", $step, '{s}');
+		$s = str_replace('{s}', '<a href="'.DOL_URL_ROOT.'/accountancy/admin/journals_list.php?id=35"><strong>'.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("AccountingJournals").'</strong></a>', $s);
+		print $s;
+		print "<br>\n";
+		$step++;
+		$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescChartModel", $step, '{s}');
+		$s = str_replace('{s}', '<a href="'.DOL_URL_ROOT.'/accountancy/admin/accountmodel.php"><strong>'.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("Pcg_version").'</strong></a>', $s);
+		print $s;
+		print "<br>\n";
+		$step++;
+		$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescChart", $step, '{s}');
+		$s = str_replace('{s}', '<a href="'.DOL_URL_ROOT.'/accountancy/admin/account.php"><strong>'.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("Chartofaccounts").'</strong></a>', $s);
+		print $s;
+		print "<br>\n";
 
-	$step++;
-	$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescDefault", $step, '{s}');
-	$s = str_replace('{s}', '<a href="'.DOL_URL_ROOT.'/accountancy/admin/defaultaccounts.php"><strong>'.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("MenuDefaultAccounts").'</strong></a>', $s);
-	print $s;
-	print "<br>\n";
+		print "<br>\n";
+		print $langs->trans("AccountancyAreaDescActionOnceBis");
+		print "<br>\n";
+		print "<br>\n";
 
-	$step++;
-	$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescBank", $step, '{s}')."\n";
-	$s = str_replace('{s}', '<a href="'.DOL_URL_ROOT.'/compta/bank/list.php"><strong>'.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("MenuBankAccounts").'</strong></a>', $s);
-	print $s;
-	print "<br>\n";
+		$step++;
+		$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescDefault", $step, '{s}');
+		$s = str_replace('{s}', '<a href="'.DOL_URL_ROOT.'/accountancy/admin/defaultaccounts.php"><strong>'.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("MenuDefaultAccounts").'</strong></a>', $s);
+		print $s;
+		print "<br>\n";
 
-	$step++;
-	$textlink = '<a href="'.DOL_URL_ROOT.'/admin/dict.php?id=10&from=accountancy"><strong>'.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("MenuVatAccounts").'</strong></a>';
-	$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescVat", $step, '{s}');
-	$s = str_replace('{s}', $textlink, $s);
-	print $s;
-	print "<br>\n";
-	if (!empty($conf->tax->enabled)) {
-		$textlink = '<a href="'.DOL_URL_ROOT.'/admin/dict.php?id=7&from=accountancy"><strong>'.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("MenuTaxAccounts").'</strong></a>';
 		$step++;
-		$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescContrib", $step, '{s}');
-		$s = str_replace('{s}', $textlink, $s);
+		$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescBank", $step, '{s}')."\n";
+		$s = str_replace('{s}', '<a href="'.DOL_URL_ROOT.'/compta/bank/list.php"><strong>'.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("MenuBankAccounts").'</strong></a>', $s);
 		print $s;
 		print "<br>\n";
-	}
-	if (!empty($conf->expensereport->enabled)) {  // TODO Move this in the default account page because this is only one accounting account per purpose, not several.
+
 		$step++;
-		$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescExpenseReport", $step, '{s}');
-		$s = str_replace('{s}', '<a href="'.DOL_URL_ROOT.'/admin/dict.php?id=17&from=accountancy"><strong>'.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("MenuExpenseReportAccounts").'</strong></a>', $s);
+		$textlink = '<a href="'.DOL_URL_ROOT.'/admin/dict.php?id=10&from=accountancy"><strong>'.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("MenuVatAccounts").'</strong></a>';
+		$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescVat", $step, '{s}');
+		$s = str_replace('{s}', $textlink, $s);
 		print $s;
 		print "<br>\n";
-	}
 
-	$step++;
-	$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescProd", $step, '{s}');
-	$s = str_replace('{s}', '<a href="'.DOL_URL_ROOT.'/accountancy/admin/productaccount.php"><strong>'.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("ProductsBinding").'</strong></a>', $s);
-	print $s;
-	print "<br>\n";
+		if (!empty($conf->tax->enabled)) {
+			$textlink = '<a href="'.DOL_URL_ROOT.'/admin/dict.php?id=7&from=accountancy"><strong>'.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("MenuTaxAccounts").'</strong></a>';
+			$step++;
+			$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescContrib", $step, '{s}');
+			$s = str_replace('{s}', $textlink, $s);
+			print $s;
+			print "<br>\n";
+		}
+		if (!empty($conf->expensereport->enabled)) {  // TODO Move this in the default account page because this is only one accounting account per purpose, not several.
+			$step++;
+			$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescExpenseReport", $step, '{s}');
+			$s = str_replace('{s}', '<a href="'.DOL_URL_ROOT.'/admin/dict.php?id=17&from=accountancy"><strong>'.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("MenuExpenseReportAccounts").'</strong></a>', $s);
+			print $s;
+			print "<br>\n";
+		}
 
+		$step++;
+		$s = img_picto('', 'puce').' '.$langs->trans("AccountancyAreaDescProd", $step, '{s}');
+		$s = str_replace('{s}', '<a href="'.DOL_URL_ROOT.'/accountancy/admin/productaccount.php"><strong>'.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("ProductsBinding").'</strong></a>', $s);
+		print $s;
+		print "<br>\n";
 
-	print '<br>';
+		print '<br>';
+	}
 
 	// Step A - E
 

+ 1 - 1
htdocs/accountancy/journal/bankjournal.php

@@ -1057,7 +1057,7 @@ if (empty($action) || $action == 'view') {
 	}
 
 
-	print '<div class="tabsAction tabsActionNoBottom">';
+	print '<div class="tabsAction tabsActionNoBottom centerimp">';
 
 	if (!empty($conf->global->ACCOUNTING_ENABLE_EXPORT_DRAFT_JOURNAL) && $in_bookkeeping == 'notyet') {
 		print '<input type="button" class="butAction" name="exportcsv" value="'.$langs->trans("ExportDraftJournal").'" onclick="launch_export();" />';

+ 1 - 1
htdocs/accountancy/journal/expensereportsjournal.php

@@ -521,7 +521,7 @@ if (empty($action) || $action == 'view') {
 		print $desc;
 		print '</div>';
 	}
-	print '<div class="tabsAction tabsActionNoBottom">';
+	print '<div class="tabsAction tabsActionNoBottom centerimp">';
 
 	if (!empty($conf->global->ACCOUNTING_ENABLE_EXPORT_DRAFT_JOURNAL) && $in_bookkeeping == 'notyet') {
 		print '<input type="button" class="butAction" name="exportcsv" value="'.$langs->trans("ExportDraftJournal").'" onclick="launch_export();" />';

+ 1 - 1
htdocs/accountancy/journal/purchasesjournal.php

@@ -774,7 +774,7 @@ if (empty($action) || $action == 'view') {
 		print $desc;
 		print '</div>';
 	}
-	print '<div class="tabsAction tabsActionNoBottom">';
+	print '<div class="tabsAction tabsActionNoBottom centerimp">';
 	if (!empty($conf->global->ACCOUNTING_ENABLE_EXPORT_DRAFT_JOURNAL) && $in_bookkeeping == 'notyet') {
 		print '<input type="button" class="butAction" name="exportcsv" value="'.$langs->trans("ExportDraftJournal").'" onclick="launch_export();" />';
 	}

+ 15 - 3
htdocs/accountancy/journal/sellsjournal.php

@@ -412,8 +412,13 @@ 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;
 
-					$bookkeeping->subledger_account = '';
-					$bookkeeping->subledger_label = '';
+					if ($k == getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT')) {
+						$bookkeeping->subledger_account = $tabcompany[$key]['code_compta'];
+						$bookkeeping->subledger_label = $tabcompany[$key]['name'];
+					} else {
+						$bookkeeping->subledger_account = '';
+						$bookkeeping->subledger_label = '';
+					}
 
 					$bookkeeping->numero_compte = $k;
 					$bookkeeping->label_compte = $label_account;
@@ -716,7 +721,7 @@ if (empty($action) || $action == 'view') {
 		print $desc;
 		print '</div>';
 	}
-	print '<div class="tabsAction tabsActionNoBottom">';
+	print '<div class="tabsAction tabsActionNoBottom centerimp">';
 	if (!empty($conf->global->ACCOUNTING_ENABLE_EXPORT_DRAFT_JOURNAL) && $in_bookkeeping == 'notyet') {
 		print '<input type="button" class="butAction" name="exportcsv" value="'.$langs->trans("ExportDraftJournal").'" onclick="launch_export();" />';
 	}
@@ -886,6 +891,13 @@ if (empty($action) || $action == 'view') {
 			print "</td>";
 			// Subledger account
 			print "<td>";
+			if ($k == getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT')) {
+				if (($accountoshow == "") || $accountoshow == 'NotDefined') {
+					print '<span class="error">'.$langs->trans("ThirdpartyAccountNotDefined").'</span>';
+				} else {
+					print length_accounta($tabcompany[$key]['code_compta']);
+				}
+			}
 			print '</td>';
 			$companystatic->id = $tabcompany[$key]['id'];
 			$companystatic->name = $tabcompany[$key]['name'];

+ 313 - 0
htdocs/accountancy/journal/variousjournal.php

@@ -0,0 +1,313 @@
+<?php
+/* Copyright (C) 2021-2022  Open-DSI            <support@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/>.
+ */
+
+/**
+ * \file		htdocs/accountancy/journal/variousjournal.php
+ * \ingroup		Accountancy (Double entries)
+ * \brief		Page of a journal
+ */
+
+require '../../main.inc.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.'/accountancy/class/accountingjournal.class.php';
+
+// Load translation files required by the page
+$langs->loadLangs(array("banks", "accountancy", "compta", "other", "errors"));
+
+$id_journal = GETPOST('id_journal', 'int');
+$action = GETPOST('action', 'aZ09');
+
+$date_startmonth = GETPOST('date_startmonth');
+$date_startday = GETPOST('date_startday');
+$date_startyear = GETPOST('date_startyear');
+$date_endmonth = GETPOST('date_endmonth');
+$date_endday = GETPOST('date_endday');
+$date_endyear = GETPOST('date_endyear');
+$in_bookkeeping = GETPOST('in_bookkeeping');
+if ($in_bookkeeping == '') {
+	$in_bookkeeping = 'notyet';
+}
+
+// Security check
+if (empty($conf->accounting->enabled)) {
+	accessforbidden();
+}
+if ($user->socid > 0) {
+	accessforbidden();
+}
+if (empty($user->rights->accounting->mouvements->lire)) {
+	accessforbidden();
+}
+
+// Get information of journal
+$object = new AccountingJournal($db);
+$result = $object->fetch($id_journal);
+if ($result > 0) {
+	$id_journal = $object->id;
+} elseif ($result < 0) {
+	dol_print_error('', $object->error, $object->errors);
+} elseif ($result == 0) {
+	accessforbidden($langs->trans('ErrorRecordNotFound'));
+}
+
+$hookmanager->initHooks(array('globaljournal', $object->nature_text . 'journal'));
+$parameters = array();
+
+$date_start = dol_mktime(0, 0, 0, $date_startmonth, $date_startday, $date_startyear);
+$date_end = dol_mktime(23, 59, 59, $date_endmonth, $date_endday, $date_endyear);
+
+if (empty($date_startmonth) || empty($date_endmonth)) {
+	// Period by default on transfer
+	$dates = getDefaultDatesForTransfer();
+	$date_start = $dates['date_start'];
+	$date_end = $dates['date_end'];
+	$pastmonthyear = $dates['pastmonthyear'];
+	$pastmonth = $dates['pastmonth'];
+}
+
+if (!GETPOSTISSET('date_startmonth') && (empty($date_start) || empty($date_end))) { // We define date_start and date_end, only if we did not submit the form
+	$date_start = dol_get_first_day($pastmonthyear, $pastmonth, false);
+	$date_end = dol_get_last_day($pastmonthyear, $pastmonth, false);
+}
+
+$data_type = 'view';
+if ($action == 'writebookkeeping') $data_type = 'bookkeeping';
+if ($action == 'exportcsv') $data_type = 'csv';
+$journal_data = $object->getData($user, $data_type, $date_start, $date_end, $in_bookkeeping);
+if (!is_array($journal_data)) {
+	setEventMessages($object->error, $object->errors, 'errors');
+}
+
+/*
+ * Actions
+ */
+
+$reshook = $hookmanager->executeHooks('doActions', $parameters, $user, $action); // Note that $action and $object may have been modified by some hooks
+
+$reload = false;
+
+// Bookkeeping Write
+if ($action == 'writebookkeeping') {
+	$error = 0;
+
+	$result = $object->writeIntoBookkeeping($user, $journal_data);
+	if ($result < 0) {
+		setEventMessages($object->error, $object->errors, 'errors');
+		$error = abs($result);
+	}
+
+	$nb_elements = count($journal_data);
+	if (empty($error) && $nb_elements > 0) {
+		setEventMessages($langs->trans("GeneralLedgerIsWritten"), null, 'mesgs');
+	} elseif ($nb_elements == $error) {
+		setEventMessages($langs->trans("NoNewRecordSaved"), null, 'warnings');
+	} else {
+		setEventMessages($langs->trans("GeneralLedgerSomeRecordWasNotRecorded"), null, 'warnings');
+	}
+
+	$reload = true;
+} elseif ($action == 'exportcsv') {
+	// Export CSV
+	$result = $object->exportCsv($journal_data, $date_end);
+	if ($result < 0) {
+		setEventMessages($object->error, $object->errors, 'errors');
+		$reload = true;
+	} else {
+		$filename = 'journal';
+		$type_export = 'journal';
+
+		require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
+		include DOL_DOCUMENT_ROOT.'/accountancy/tpl/export_journal.tpl.php';
+
+		print $result;
+
+		$db->close();
+		exit();
+	}
+}
+
+// Must reload data, so we make a redirect
+if ($reload) {
+	$param = 'id_journal=' . $id_journal;
+	$param .= '&date_startday=' . $date_startday;
+	$param .= '&date_startmonth=' . $date_startmonth;
+	$param .= '&date_startyear=' . $date_startyear;
+	$param .= '&date_endday=' . $date_endday;
+	$param .= '&date_endmonth=' . $date_endmonth;
+	$param .= '&date_endyear=' . $date_endyear;
+	$param .= '&in_bookkeeping=' . $in_bookkeeping;
+	header("Location: " . $_SERVER['PHP_SELF'] . ($param ? '?' . $param : ''));
+	exit;
+}
+
+
+/*
+ * View
+ */
+
+$form = new Form($db);
+
+if ($object->nature == 2) {
+	$title = $langs->trans("SellsJournal");
+	$some_mandatory_steps_of_setup_were_not_done = $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == "" || $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == '-1';
+	$account_accounting_not_defined = $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == "" || $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == '-1';
+} elseif ($object->nature == 3) {
+	$title = $langs->trans("PurchasesJournal");
+	$some_mandatory_steps_of_setup_were_not_done = $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == "" || $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == '-1';
+	$account_accounting_not_defined = $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == "" || $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == '-1';
+} elseif ($object->nature == 4) {
+	$title = $langs->trans("FinanceJournal");
+	$some_mandatory_steps_of_setup_were_not_done = $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';
+	$account_accounting_not_defined = $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == "" || $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == '-1'
+		|| $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == "" || $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == '-1';
+} elseif ($object->nature == 5) {
+	$title = $langs->trans("ExpenseReportsJournal");
+	$some_mandatory_steps_of_setup_were_not_done = empty($conf->global->SALARIES_ACCOUNTING_ACCOUNT_PAYMENT) || $conf->global->SALARIES_ACCOUNTING_ACCOUNT_PAYMENT == '-1';
+	$account_accounting_not_defined = empty($conf->global->SALARIES_ACCOUNTING_ACCOUNT_PAYMENT) || $conf->global->SALARIES_ACCOUNTING_ACCOUNT_PAYMENT == '-1';
+} else {
+	$title = $object->getLibType();
+	$some_mandatory_steps_of_setup_were_not_done = false;
+	$account_accounting_not_defined = false;
+}
+
+
+$nom = $title . ' | ' . $object->getNomUrl(0, 1, 1, '', 1);
+$nomlink = '';
+$periodlink = '';
+$exportlink = '';
+$builddate = dol_now();
+$description = $langs->trans("DescJournalOnlyBindedVisible") . '<br>';
+if ($object->nature == 2 || $object->nature == 3) {
+	if (!empty($conf->global->FACTURE_DEPOSITS_ARE_JUST_PAYMENTS)) {
+		$description .= $langs->trans("DepositsAreNotIncluded");
+	} else {
+		$description .= $langs->trans("DepositsAreIncluded");
+	}
+}
+
+$listofchoices = array('notyet' => $langs->trans("NotYetInGeneralLedger"), 'already' => $langs->trans("AlreadyInGeneralLedger"));
+$period = $form->selectDate($date_start ? $date_start : -1, 'date_start', 0, 0, 0, '', 1, 0) . ' - ' . $form->selectDate($date_end ? $date_end : -1, 'date_end', 0, 0, 0, '', 1, 0);
+$period .= ' -  ' . $langs->trans("JournalizationInLedgerStatus") . ' ' . $form->selectarray('in_bookkeeping', $listofchoices, $in_bookkeeping, 1);
+
+$varlink = 'id_journal=' . $id_journal;
+
+llxHeader('', $title);
+
+journalHead($nom, $nomlink, $period, $periodlink, $description, $builddate, $exportlink, array('action' => ''), '', $varlink);
+
+if ($object->nature == 4) { // Bank journal
+	// Test that setup is complete (we are in accounting, so test on entity is always on $conf->entity only, no sharing allowed)
+	$sql = "SELECT COUNT(rowid) as nb";
+	$sql .= " FROM " . MAIN_DB_PREFIX . "bank_account";
+	$sql .= " WHERE entity = " . (int) $conf->entity;
+	$sql .= " AND fk_accountancy_journal IS NULL";
+	$sql .= " AND clos=0";
+	$resql = $db->query($sql);
+	if ($resql) {
+		$obj = $db->fetch_object($resql);
+		if ($obj->nb > 0) {
+			print '<br>' . img_warning() . ' ' . $langs->trans("TheJournalCodeIsNotDefinedOnSomeBankAccount");
+			print ' : ' . $langs->trans("AccountancyAreaDescBank", 9, '<strong>' . $langs->transnoentitiesnoconv("MenuAccountancy") . '-' . $langs->transnoentitiesnoconv("Setup") . "-" . $langs->transnoentitiesnoconv("BankAccounts") . '</strong>');
+		}
+	} else dol_print_error($db);
+}
+
+// Button to write into Ledger
+if ($some_mandatory_steps_of_setup_were_not_done) {
+	print '<br><div class="warning">' . img_warning() . ' ' . $langs->trans("SomeMandatoryStepsOfSetupWereNotDone");
+	print ' : ' . $langs->trans("AccountancyAreaDescMisc", 4, '<strong>' . $langs->transnoentitiesnoconv("MenuAccountancy") . '-' . $langs->transnoentitiesnoconv("Setup") . "-" . $langs->transnoentitiesnoconv("MenuDefaultAccounts") . '</strong>');
+	print '</div>';
+}
+print '<div class="tabsAction tabsActionNoBottom">';
+if (!empty($conf->global->ACCOUNTING_ENABLE_EXPORT_DRAFT_JOURNAL) && $in_bookkeeping == 'notyet') {
+	print '<input type="button" class="butAction" name="exportcsv" value="' . $langs->trans("ExportDraftJournal") . '" onclick="launch_export();" />';
+}
+if ($account_accounting_not_defined) {
+	print '<input type="button" class="butActionRefused classfortooltip" title="' . dol_escape_htmltag($langs->trans("SomeMandatoryStepsOfSetupWereNotDone")) . '" value="' . $langs->trans("WriteBookKeeping") . '" />';
+} else {
+	if ($in_bookkeeping == 'notyet') {
+		print '<input type="button" class="butAction" name="writebookkeeping" value="' . $langs->trans("WriteBookKeeping") . '" onclick="writebookkeeping();" />';
+	} else {
+		print '<a href="#" class="butActionRefused classfortooltip" name="writebookkeeping">' . $langs->trans("WriteBookKeeping") . '</a>';
+	}
+}
+print '</div>';
+
+// TODO Avoid using js. We can use a direct link with $param
+print '
+	<script type="text/javascript">
+		function launch_export() {
+			$("div.fiche form input[name=\"action\"]").val("exportcsv");
+			$("div.fiche form input[type=\"submit\"]").click();
+			$("div.fiche form input[name=\"action\"]").val("");
+		}
+		function writebookkeeping() {
+			console.log("click on writebookkeeping");
+			$("div.fiche form input[name=\"action\"]").val("writebookkeeping");
+			$("div.fiche form input[type=\"submit\"]").click();
+			$("div.fiche form input[name=\"action\"]").val("");
+		}
+	</script>';
+
+$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
+ */
+print '<br>';
+
+print '<div class="div-table-responsive">';
+print '<table class="noborder centpercent">';
+print '<tr class="liste_titre">';
+print '<td>' . $langs->trans("Date") . '</td>';
+print '<td>' . $langs->trans("Piece") . ' (' . $object_label . ')</td>';
+print '<td>' . $langs->trans("AccountAccounting") . '</td>';
+print '<td>' . $langs->trans("SubledgerAccount") . '</td>';
+print '<td>' . $langs->trans("LabelOperation") . '</td>';
+if ($object->nature == 4) print '<td class="center">' . $langs->trans("PaymentMode") . '</td>'; // bank
+print '<td class="right">' . $langs->trans("Debit") . '</td>';
+print '<td class="right">' . $langs->trans("Credit") . '</td>';
+print "</tr>\n";
+
+foreach ($journal_data as $element_id => $element) {
+	foreach ($element['blocks'] as $lines) {
+		foreach ($lines as $line) {
+			print '<tr class="oddeven">';
+			print '<td>' . $line['date'] . '</td>';
+			print '<td>' . $line['piece'] . '</td>';
+			print '<td>' . $line['account_accounting'] . '</td>';
+			print '<td>' . $line['subledger_account'] . '</td>';
+			print '<td>' . $line['label_operation'] . '</td>';
+			if ($object->nature == 4) print '<td class="center">' . $line['payment_mode'] . '</td>';
+			print '<td class="right nowraponall">' . $line['debit'] . '</td>';
+			print '<td class="right nowraponall">' . $line['credit'] . '</td>';
+			print '</tr>';
+		}
+	}
+}
+
+print '</table>';
+print '</div>';
+
+llxFooter();
+
+$db->close();

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

@@ -34,6 +34,9 @@ require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
 // Load translation files required by the page
 $langs->loadLangs(array("compta", "bills", "other", "accountancy"));
 
+$validatemonth = GETPOST('validatemonth', 'int');
+$validateyear = GETPOST('validateyear', 'int');
+
 // Security check
 if (empty($conf->accounting->enabled)) {
 	accessforbidden();
@@ -114,6 +117,9 @@ if (($action == 'clean' || $action == 'validatehistory') && $user->rights->accou
 
 if ($action == 'validatehistory') {
 	$error = 0;
+	$nbbinddone = 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
@@ -149,7 +155,6 @@ if ($action == 'validatehistory') {
 	} else {
 		$sql .= " s.accountancy_code_buy as company_code_buy";
 	}
-
 	$sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f";
 	$sql .= " INNER JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = f.fk_soc";
 	if (!empty($conf->global->MAIN_COMPANY_PERENTITY_SHARED)) {
@@ -167,11 +172,13 @@ if ($action == 'validatehistory') {
 	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa2 ON " . $alias_product_perentity . ".accountancy_code_buy_intra = aa2.account_number  AND aa2.active = 1 AND aa2.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa2.entity = ".$conf->entity;
 	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa3 ON " . $alias_product_perentity . ".accountancy_code_buy_export = aa3.account_number AND aa3.active = 1 AND aa3.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa3.entity = ".$conf->entity;
 	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa4 ON " . $alias_societe_perentity . ".accountancy_code_buy = aa4.account_number        AND aa4.active = 1 AND aa4.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa4.entity = ".$conf->entity;
-	$sql .= " WHERE f.fk_statut > 0 AND l.fk_code_ventilation <= 0";
-	$sql .= " AND l.product_type <= 2";
+	$sql .= " WHERE f.fk_statut > 0 AND l.fk_code_ventilation <= 0 AND l.product_type <= 2 AND f.entity = ".((int) $conf->entity);
 	if (!empty($conf->global->ACCOUNTING_DATE_START_BINDING)) {
 		$sql .= " AND f.datef >= '".$db->idate($conf->global->ACCOUNTING_DATE_START_BINDING)."'";
 	}
+	if ($validatemonth && $validateyear) {
+		$sql .= dolSqlDateFilter('f.datef', 0, $validatemonth, $validateyear);
+	}
 
 	dol_syslog('htdocs/accountancy/supplier/index.php');
 
@@ -204,7 +211,7 @@ if ($action == 'validatehistory') {
 			$thirdpartystatic->email = $objp->email;
 			$thirdpartystatic->country_code = $objp->country_code;
 			$thirdpartystatic->tva_intra = $objp->tva_intra;
-			$thirdpartystatic->code_compta = $objp->company_code_sell;
+			$thirdpartystatic->code_compta_product = $objp->company_code_buy;		// The accounting account for product stored on thirdparty object (for level3 suggestion)
 
 			$product_static->ref = $objp->product_ref;
 			$product_static->id = $objp->product_id;
@@ -223,7 +230,7 @@ if ($action == 'validatehistory') {
 			$facture_static->ref = $objp->ref;
 			$facture_static->id = $objp->facid;
 			$facture_static->type = $objp->ftype;
-			$facture_static->datef = $objp->datef;
+			$facture_static->date = $objp->datef;
 
 			$facture_static_det->id = $objp->rowid;
 			$facture_static_det->total_ht = $objp->total_ht;
@@ -241,6 +248,8 @@ if ($action == 'validatehistory') {
 			$code_buy_p_notset = '';
 			$code_buy_t_notset = '';
 
+			$suggestedid = 0;
+
 			$return = $accountingAccount->getAccountingCodeToBind($mysoc, $thirdpartystatic, $product_static, $facture_static, $facture_static_det, $accountingAccountArray, 'supplier');
 			if (!is_array($return) && $return<0) {
 				setEventMessage($accountingAccount->error, 'errors');
@@ -255,16 +264,7 @@ if ($action == 'validatehistory') {
 				}
 			}
 
-			if (!empty($conf->global->ACCOUNTANCY_USE_PRODUCT_ACCOUNT_ON_THIRDPARTY)) {
-				// Level 3: Search suggested account for this thirdparty (similar code exists in page index.php to make automatic binding)
-				if (!empty($objp->company_code_buy)) {
-					$objp->code_buy_t = $objp->company_code_buy;
-					$objp->aarowid_suggest = $objp->aarowid_thirdparty;
-					$suggestedaccountingaccountfor = '';
-				}
-			}
-
-			if ($objp->aarowid_suggest > 0) {
+			if ($suggestedid > 0) {
 				$sqlupdate = "UPDATE ".MAIN_DB_PREFIX."facture_fourn_det";
 				$sqlupdate .= " SET fk_code_ventilation = ".((int) $suggestedid);
 				$sqlupdate .= " WHERE fk_code_ventilation <= 0 AND product_type <= 2 AND rowid = ".((int) $facture_static_det->id);
@@ -274,18 +274,25 @@ if ($action == 'validatehistory') {
 					$error++;
 					setEventMessages($db->lasterror(), null, 'errors');
 					break;
+				} else {
+					$nbbinddone++;
 				}
+			} else {
+				$notpossible++;
 			}
 
 			$i++;
 		}
+		if ($num_lines > 10000) {
+			$notpossible += ($num_lines - 10000);
+		}
 	}
 
 	if ($error) {
 		$db->rollback();
 	} else {
 		$db->commit();
-		setEventMessages($langs->trans('AutomaticBindingDone'), null, 'mesgs');
+		setEventMessages($langs->trans('AutomaticBindingDone', 	$nbbinddone, $notpossible), null, 'mesgs');
 	}
 }
 
@@ -307,7 +314,7 @@ print '</span><br>';
 
 $y = $year_current;
 
-$buttonbind = '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?year='.$year_current.'&action=validatehistory">'.$langs->trans("ValidateHistory").'</a>';
+$buttonbind = '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?action=validatehistory&token='.newToken().'">'.$langs->trans("ValidateHistory").'</a>';
 
 
 print_barre_liste(img_picto('', 'unlink', 'class="paddingright fa-color-unset"').$langs->trans("OverviewOfAmountOfLinesNotBound"), '', '', '', '', '', '', -1, '', '', 0, $buttonbind, '', 0, 1, 1);
@@ -322,7 +329,24 @@ for ($i = 1; $i <= 12; $i++) {
 	if ($j > 12) {
 		$j -= 12;
 	}
-	print '<td width="60" class="right">'.$langs->trans('MonthShort'.str_pad($j, 2, '0', STR_PAD_LEFT)).'</td>';
+	$cursormonth = $j;
+	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 width="60" class="right">';
+	if (!empty($tmp['mday'])) {
+		$param = 'search_date_startday=1&search_date_startmonth='.$cursormonth.'&search_date_startyear='.$cursoryear;
+		$param .= '&search_date_endday='.$tmp['mday'].'&search_date_endmonth='.$tmp['mon'].'&search_date_endyear='.$tmp['year'];
+		print '<a href="'.DOL_URL_ROOT.'/accountancy/supplier/list.php?'.$param.'">';
+	}
+	print $langs->trans('MonthShort'.str_pad($j, 2, '0', STR_PAD_LEFT));
+	if (!empty($tmp['mday'])) {
+		print '</a>';
+	}
+	print '</td>';
 }
 print '<td width="60" class="right"><b>'.$langs->trans("Total").'</b></td></tr>';
 
@@ -350,6 +374,7 @@ $sql .= "  AND ffd.product_type <= 2";
 $sql .= " AND ff.entity IN (".getEntity('facture_fourn', 0).")"; // We don't share object for accountancy
 $sql .= " AND aa.account_number IS NULL";
 $sql .= " GROUP BY ffd.fk_code_ventilation,aa.account_number,aa.label";
+$sql .= ' ORDER BY aa.account_number';
 
 dol_syslog('htdocs/accountancy/supplier/index.php');
 $resql = $db->query($sql);
@@ -357,7 +382,8 @@ if ($resql) {
 	$num = $db->num_rows($resql);
 
 	while ($row = $db->fetch_row($resql)) {
-		print '<tr class="oddeven"><td>';
+		print '<tr class="oddeven">';
+		print '<td>';
 		if ($row[0] == 'tobind') {
 			print '<span class="opacitymedium">'.$langs->trans("Unknown").'</span>';
 		} else {
@@ -366,19 +392,26 @@ if ($resql) {
 		print '</td>';
 		print '<td>';
 		if ($row[0] == 'tobind') {
-			print $langs->trans("UseMenuToSetBindindManualy", DOL_URL_ROOT.'/accountancy/supplier/list.php?search_year='.$y, $langs->transnoentitiesnoconv("ToBind"));
+			print $langs->trans("UseMenuToSetBindindManualy", DOL_URL_ROOT.'/accountancy/supplier/list.php?search_year='.((int) $y), $langs->transnoentitiesnoconv("ToBind"));
 		} else {
 			print $row[1];
 		}
 		print '</td>';
-		for ($i = 2; $i <= 12; $i++) {
-			print '<td class="right nowraponall amount">'.price($row[$i]).'</td>';
+		for ($i = 2; $i <= 13; $i++) {
+			print '<td class="right nowraponall amount">';
+			print price($row[$i]);
+			print '</td>';
 		}
-		print '<td class="right nowraponall amount">'.price($row[13]).'</td>';
 		print '<td class="right nowraponall amount"><b>'.price($row[14]).'</b></td>';
 		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
 }
@@ -401,7 +434,24 @@ for ($i = 1; $i <= 12; $i++) {
 	if ($j > 12) {
 		$j -= 12;
 	}
-	print '<td width="60" class="right">'.$langs->trans('MonthShort'.str_pad($j, 2, '0', STR_PAD_LEFT)).'</td>';
+	$cursormonth = $j;
+	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 width="60" class="right">';
+	if (!empty($tmp['mday'])) {
+		$param = 'search_date_startday=1&search_date_startmonth='.$cursormonth.'&search_date_startyear='.$cursoryear;
+		$param .= '&search_date_endday='.$tmp['mday'].'&search_date_endmonth='.$tmp['mon'].'&search_date_endyear='.$tmp['year'];
+		print '<a href="'.DOL_URL_ROOT.'/accountancy/supplier/lines.php?'.$param.'">';
+	}
+	print $langs->trans('MonthShort'.str_pad($j, 2, '0', STR_PAD_LEFT));
+	if (!empty($tmp['mday'])) {
+		print '</a>';
+	}
+	print '</td>';
 }
 print '<td width="60" class="right"><b>'.$langs->trans("Total").'</b></td></tr>';
 
@@ -436,7 +486,8 @@ if ($resql) {
 	$num = $db->num_rows($resql);
 
 	while ($row = $db->fetch_row($resql)) {
-		print '<tr class="oddeven"><td>';
+		print '<tr class="oddeven">';
+		print '<td>';
 		if ($row[0] == 'tobind') {
 			print $langs->trans("Unknown");
 		} else {
@@ -445,19 +496,26 @@ if ($resql) {
 		print '</td>';
 		print '<td>';
 		if ($row[0] == 'tobind') {
-			print $langs->trans("UseMenuToSetBindindManualy", DOL_URL_ROOT.'/accountancy/supplier/list.php?search_year='.$y, $langs->transnoentitiesnoconv("ToBind"));
+			print $langs->trans("UseMenuToSetBindindManualy", DOL_URL_ROOT.'/accountancy/supplier/list.php?search_year='.((int) $y), $langs->transnoentitiesnoconv("ToBind"));
 		} else {
 			print $row[1];
 		}
 		print '</td>';
-		for ($i = 2; $i <= 12; $i++) {
-			print '<td class="right nowraponall amount">'.price($row[$i]).'</td>';
+		for ($i = 2; $i <= 13; $i++) {
+			print '<td class="right nowraponall amount">';
+			print price($row[$i]);
+			print '</td>';
 		}
-		print '<td class="right nowraponall amount">'.price($row[13]).'</td>';
 		print '<td class="right nowraponall amount"><b>'.price($row[14]).'</b></td>';
 		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
 }

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio