浏览代码

Merge branch 'develop' into NEW_exped_ObjectLink

atm-GregM 3 年之前
父节点
当前提交
e8a5d4f008
共有 100 个文件被更改,包括 4009 次插入5360 次删除
  1. 2 1
      .scrutinizer.yml
  2. 25 6
      .travis.yml
  3. 6 6
      COPYRIGHT
  4. 358 21
      ChangeLog
  5. 13 14
      README.md
  6. 7 6
      SECURITY.md
  7. 4 59
      build/exe/doliwamp/doliwamp.iss
  8. 1 3
      build/makepack-dolibarr.pl
  9. 12 12
      build/makepack-howto.txt
  10. 1 0
      build/pad/README
  11. 3 1
      build/perl/virtualmin/dolibarr.pl
  12. 0 1
      build/rpm/dolibarr_fedora.spec
  13. 0 1
      build/rpm/dolibarr_generic.spec
  14. 0 1
      build/rpm/dolibarr_mandriva.spec
  15. 0 1
      build/rpm/dolibarr_opensuse.spec
  16. 2 2
      composer.json.disabled
  17. 0 2349
      composer.lock
  18. 11 0
      dev/dolibarr_changes.txt
  19. 1 1
      dev/examples/code/README
  20. 1 1
      dev/examples/zapier/package.json
  21. 0 13
      dev/resources/iso-normes/QR code for invoices.txt
  22. 8 8
      dev/resources/iso-normes/accountancy/accountancy_rules.txt
  23. 0 0
      dev/resources/iso-normes/banking/banknumber_format.txt
  24. 0 0
      dev/resources/iso-normes/banking/iban_iso-13616_fr.txt
  25. 0 129
      dev/resources/iso-normes/barcode_EAN13.txt
  26. 7 3
      dev/resources/iso-normes/currencies_iso-4217.txt
  27. 29 0
      dev/resources/iso-normes/qr-bar-codes/QR code for invoices.txt
  28. 129 0
      dev/resources/iso-normes/qr-bar-codes/barcode_EAN13.txt
  29. 1 1
      dev/setup/git/hooks/pre-commit
  30. 1 1
      dev/tools/test/namespacemig/main.inc.php
  31. 2 0
      htdocs/accountancy/admin/account.php
  32. 6 11
      htdocs/accountancy/admin/accountmodel.php
  33. 2 2
      htdocs/accountancy/admin/card.php
  34. 7 18
      htdocs/accountancy/admin/categories_list.php
  35. 61 11
      htdocs/accountancy/admin/defaultaccounts.php
  36. 14 12
      htdocs/accountancy/admin/export.php
  37. 1 1
      htdocs/accountancy/admin/index.php
  38. 14 50
      htdocs/accountancy/admin/journals_list.php
  39. 108 15
      htdocs/accountancy/admin/productaccount.php
  40. 1 1
      htdocs/accountancy/bookkeeping/balance.php
  41. 42 35
      htdocs/accountancy/bookkeeping/card.php
  42. 271 144
      htdocs/accountancy/bookkeeping/list.php
  43. 331 128
      htdocs/accountancy/bookkeeping/listbyaccount.php
  44. 0 979
      htdocs/accountancy/bookkeeping/listbysubaccount.php
  45. 0 325
      htdocs/accountancy/bookkeeping/thirdparty_lettering_customer.php
  46. 0 322
      htdocs/accountancy/bookkeeping/thirdparty_lettering_supplier.php
  47. 45 7
      htdocs/accountancy/class/accountancyexport.class.php
  48. 78 37
      htdocs/accountancy/class/accountancyimport.class.php
  49. 9 8
      htdocs/accountancy/class/accountingaccount.class.php
  50. 695 1
      htdocs/accountancy/class/accountingjournal.class.php
  51. 103 70
      htdocs/accountancy/class/bookkeeping.class.php
  52. 461 16
      htdocs/accountancy/class/lettering.class.php
  53. 0 1
      htdocs/accountancy/closure/index.php
  54. 8 6
      htdocs/accountancy/customer/index.php
  55. 6 6
      htdocs/accountancy/customer/lines.php
  56. 27 6
      htdocs/accountancy/customer/list.php
  57. 51 29
      htdocs/accountancy/expensereport/lines.php
  58. 102 46
      htdocs/accountancy/expensereport/list.php
  59. 59 57
      htdocs/accountancy/index.php
  60. 50 25
      htdocs/accountancy/journal/bankjournal.php
  61. 6 0
      htdocs/accountancy/journal/purchasesjournal.php
  62. 25 2
      htdocs/accountancy/journal/sellsjournal.php
  63. 314 0
      htdocs/accountancy/journal/variousjournal.php
  64. 29 11
      htdocs/accountancy/supplier/index.php
  65. 33 14
      htdocs/accountancy/supplier/lines.php
  66. 52 14
      htdocs/accountancy/supplier/list.php
  67. 68 51
      htdocs/adherents/admin/member.php
  68. 1 1
      htdocs/adherents/admin/member_extrafields.php
  69. 1 1
      htdocs/adherents/admin/member_type_extrafields.php
  70. 3 2
      htdocs/adherents/admin/website.php
  71. 3 3
      htdocs/adherents/agenda.php
  72. 18 25
      htdocs/adherents/card.php
  73. 2 2
      htdocs/adherents/cartes/carte.php
  74. 8 9
      htdocs/adherents/class/adherent.class.php
  75. 4 1
      htdocs/adherents/class/adherent_type.class.php
  76. 1 1
      htdocs/adherents/class/api_members.class.php
  77. 1 1
      htdocs/adherents/class/api_memberstypes.class.php
  78. 2 2
      htdocs/adherents/class/api_subscriptions.class.php
  79. 6 6
      htdocs/adherents/class/subscription.class.php
  80. 14 14
      htdocs/adherents/index.php
  81. 90 54
      htdocs/adherents/list.php
  82. 12 5
      htdocs/adherents/partnership.php
  83. 30 30
      htdocs/adherents/subscription.php
  84. 5 5
      htdocs/adherents/subscription/card.php
  85. 4 4
      htdocs/adherents/subscription/list.php
  86. 1 1
      htdocs/adherents/tpl/linkedobjectblock.tpl.php
  87. 7 5
      htdocs/adherents/type.php
  88. 21 21
      htdocs/admin/accountant.php
  89. 3 1
      htdocs/admin/agenda.php
  90. 1 1
      htdocs/admin/agenda_extrafields.php
  91. 4 14
      htdocs/admin/bank.php
  92. 1 1
      htdocs/admin/bank_extrafields.php
  93. 12 11
      htdocs/admin/bom.php
  94. 1 1
      htdocs/admin/bom_extrafields.php
  95. 1 0
      htdocs/admin/boxes.php
  96. 3 2
      htdocs/admin/commande.php
  97. 1 1
      htdocs/admin/commande_fournisseur_dispatch_extrafields.php
  98. 35 25
      htdocs/admin/company.php
  99. 9 7
      htdocs/admin/debugbar.php
  100. 1 1
      htdocs/admin/defaultvalues.php

+ 2 - 1
.scrutinizer.yml

@@ -18,9 +18,10 @@ filter:
         - dev/*
         - doc/*
         - documents/*
-        - htdocs/includes/*
         - node_modules/*
         - test/*
+    dependency_paths:
+        - htdocs/includes/*
     paths: 
         - htdocs/*
         - scripts/*

+ 25 - 6
.travis.yml

@@ -50,7 +50,7 @@ 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
@@ -93,23 +93,26 @@ install:
   echo
 
 - |
-  echo "Installing Composer dependencies - PHP Unit, Parallel Lint, PHP CodeSniffer - for $TRAVIS_PHP_VERSION"
+  echo "Installing Composer dependencies - PHP Unit, Parallel Lint, PHP CodeSniffer, PHP Vardump check - for $TRAVIS_PHP_VERSION"
   if [ "$TRAVIS_PHP_VERSION" = '5.6' ]; then
     composer -n require phpunit/phpunit ^5 \
                         php-parallel-lint/php-parallel-lint ^1 \
                         php-parallel-lint/php-console-highlighter ^0 \
+                        php-parallel-lint/php-var-dump-check ~0.4 \
                         squizlabs/php_codesniffer ^3
   fi
   if [ "$TRAVIS_PHP_VERSION" = '7.0' ] || [ "$TRAVIS_PHP_VERSION" = '7.1' ] || [ "$TRAVIS_PHP_VERSION" = '7.2' ]; then
     composer -n require phpunit/phpunit ^6 \
                         php-parallel-lint/php-parallel-lint ^1 \
                         php-parallel-lint/php-console-highlighter ^0 \
+                        php-parallel-lint/php-var-dump-check ~0.4 \
                         squizlabs/php_codesniffer ^3
   fi
-  if [ "$TRAVIS_PHP_VERSION" = '7.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 \
+                        php-parallel-lint/php-var-dump-check ~0.4 \
                         squizlabs/php_codesniffer ^3
   fi
   # phpunit 9 is required for php 8
@@ -117,6 +120,7 @@ install:
       composer -n require --ignore-platform-reqs phpunit/phpunit ^7 \
                                                  php-parallel-lint/php-parallel-lint ^1.2 \
                                                  php-parallel-lint/php-console-highlighter ^0 \
+                                                 php-parallel-lint/php-var-dump-check ~0.4 \
                                                  squizlabs/php_codesniffer ^3
   fi
   echo
@@ -166,6 +170,10 @@ before_script:
     which phpcs
     phpcs --version | head -
     phpcs -i | head -
+    # Check PHP Vardump check version
+    echo "PHP Vardump check version"
+    which var_dump_check
+    var_dump_check --version
     # Check PHPUnit version
     echo "PHPUnit version"
     which phpunit
@@ -241,7 +249,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 +284,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,12 +299,23 @@ 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
   echo
 
+- |
+  echo "Checking missing debug"
+  # Ensure we catch errors
+  set -e
+  # Exclusions are defined in the ruleset.xml file
+  if [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$TRAVIS_PHP_VERSION" = "7.4.22" ]; then
+     var-dump-check --extensions php --tracy --exclude htdocs/includes --exclude test/ --exclude htdocs/public/test/ --exclude htdocs/core/lib/functions.lib.php .
+  fi
+  set +e
+  echo
+
 - |
   export INSTALL_FORCED_FILE=htdocs/install/install.forced.php
   echo "Setting up Dolibarr $INSTALL_FORCED_FILE to test installation"

+ 6 - 6
COPYRIGHT

@@ -24,11 +24,10 @@ Component              Version       License                     GPL Compatible
 -------------------------------------------------------------------------------------
 PHP libraries:
 ADOdb-Date             0.36          Modified BSD License        Yes             Date convertion (not into rpm package)
-CKEditor               4.12.1        LGPL-2.1+                   Yes             Editor WYSIWYG
 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 +47,11 @@ 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
+CKEditor               4.18          LGPL-2.1+                   Yes             Editor WYSIWYG
+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

+ 358 - 21
ChangeLog

@@ -3,6 +3,329 @@ English Dolibarr ChangeLog
 --------------------------------------------------------------
 
 
+
+***** ChangeLog for 16.0.0 compared to 15.0.0 *****
+
+For users:
+---------------
+
+NEW: PHP 8.1 compatibility
+NEW: Support for recurring purchase invoices.
+NEW: #20292 Include German public holidays
+NEW: Can show ZATCA QRCode on PDFs
+NEW: Can show Swiss QR Code on PDFs
+NEW: #17123 added ExtraFields for Stock Mouvement
+NEW: #20609 : new massaction to assign a sale representatives on a selection of thirdparties
+NEW: #20653 edit discount pourcentage for all lines in one shot
+NEW: Accept 'auto' for ref of object on import of purchase order/proposal
+NEW: Accountancy - Add more filters and info on page to bind accounting accounts
+NEW: Accountancy - Add subledger account when we generate a transaction with a deposit invoice
+NEW: Accountancy - Add a massaction to preselect an account (customer and supplier list)
+NEW: ACE Editor is restored at same cursor position after a save.
+NEW: Add "addMoreActionsButtons" hook to subscription form
+NEW: Add an option in GUI to show a Quick add button into top menu bar
+NEW: Module Recruitment - Add a public page with all list of open job positions.
+NEW: Module Recruitment - Add a tab with list of application on the jobposition file.
+NEW: Add a workflow to auto link contract on a ticket
+NEW: Add column date of Signature on proposal list
+NEW: Add column template invoice in invoice list
+NEW: Add column "Total HT" to products array on document creation card
+NEW: ADD configuration for text color of button action
+NEW: Add constant to hide categories in TakePos
+NEW: Add constant to show category description in TakePos
+NEW: Add constant to show only the products in stock in TakePos
+NEW: Add entity filter in exports
+NEW: Show the event block on recurring invoices #20870
+NEW: Add filter "opportunity status" on statistics of projects.
+NEW: Add firstname, lastname and max number of attendees for module "Event Organization"
+NEW: add margin info in proposal and order list
+NEW: Add massaction "Edit Extrafield" for Product
+NEW: Add more fields to detect duplicate during import of thirdparties
+NEW: Add option to foce delivery on email for purchase order receipt to yes
+NEW: Add param boder table for md theme
+NEW: Add param color button action
+NEW: Add possibility to create contract from invoice
+NEW: Add possibility with constant MAIN_LOGIN_BADCHARUNAUTHORIZED to define bad character unauthorized into login name
+NEW: Add private and public notes on tax files.
+NEW: Add status "Obsolete" to KM articles
+NEW: Add substitutions "user numbers"
+NEW: Add the possibility to add sub-BOMs to BOM
+NEW: allow a ticket to be automatically marked as read when created from backend.
+NEW: allow cut&paste as real numeric value to excel
+NEW: A public form to send a message and create a lead is available
+NEW: automatically set totally received status in reception
+NEW: Auto set invoice paid when adding credit not and remain to pay is 0
+NEW: Availibility dictionnary has a new column unit and number
+NEW: barcode rule to insert product in takepos
+NEW: Can change value of AWP during the inventory
+NEW: Can enter price with tax for predefined products on purchase objects
+NEW: Can filter on a thirdparty on product statistics
+NEW: Can removed doc templates from setup page of thirdparty
+NEW: Can set the parent company during the creation of thirdparty (action=add of societe/card.php)
+NEW: Can use ! to make a search that exclude a string
+NEW: Change in theme colors does not need to use the refresh button
+NEW: clean values and amount in FEC import
+NEW: const MAIL_MASS_ACTION_ADD_LAST_IF_MAIN_DOC_NOT_FOUND for mailing mass action
+NEW: Contact filter project list
+NEW: Create contract from invoice
+NEW: create third-party with contact if not found on public ticket
+NEW: Default value for MAIN_SECURITY_CSRF_WITH_TOKEN is now 2 (GET are also protected agains CSRF attacks)
+NEW: deposit payment terms: add field into dictionary admin page to define default percentage of deposit. 
+NEW: Dictionaries - add possibility to manage countries in EEC
+NEW: display errors in a message box after generating documents
+NEW: Display physical and virtual stock of the products when creating OF from a BOM
+NEW: Display product ref in "Object link" product tab for BOM
+NEW: Enhance the import. Can use 'auto' for the ref (import of orders)
+NEW: Events on Proposal to Return to Draft
+NEW: Page to list expense report payments
+NEW: JS inventory autocalc input
+NEW: language support for more emailing target selectors
+NEW: leave requests: add field into type dictionary to block request if balance is negative
+NEW: MAIN_MAIL_AUTOCOPY_TO can accept several email and special keys
+NEW: MAIN_SEARCH_CAT_OR_BY_DEFAULT const for search by category
+NEW: Mass action "Close shipments"
+NEW: Module website now supports the multicompany module
+NEW: More mode for THEME_TOPMENU_DISABLE_IMAGE (2, 3, ...)
+NEW: Add option to move checkbox column as first column on Thirdparty list (only few screens)
+NEW: Add tabs for nets Bom
+NEW: on redirect of page in website module, GET parameters are kept.
+NEW: optional display warning icons on ticket list
+NEW: option to default check "notify tier at creation" in ticket module
+NEW: option update prices on proposal cloning
+NEW: payment conditions enabling semi-automatic deposit creation (Issue #18439)
+NEW: possibility to consume multiple batch
+NEW: Reverse movement product consumption
+NEW: Send email to the supplier order contact
+NEW: New permission to report time on timesheet.
+NEW: SEPA XML - option to place payment Type Info at Credit transfer Transaction level
+NEW: Show number of votes into the label of tab "Results" of a survey
+NEW: Show product reference in Takepos
+NEW: Some core tables are created only at module activation
+NEW: split consumption line on MO
+NEW: stock filter in reassort lists
+NEW: stock limit in stock export CSV
+NEW: Sub-bom are availables
+NEW: Supplier order - Show ref supplier of reception in linked object block
+NEW: support user_modif in order
+NEW: TakePos - pagination on search results
+NEW: The backup tools has an "lowmemory" option for mysqldump on large database
+NEW: The 'reposition' class works on ajax constantonoff that make redirects
+NEW: Thirdparty - Add rules "customer accountancy code" is mandatory to validate invoice
+NEW: thumbnail field in product list
+NEW: total mark rate in list
+NEW: uncheck "send message" by default on a ticket when private messages has been checked
+NEW: VAT Report by month - Show detail by rate and also by code
+NEW: Ticket triggers: allow to automatically send messages on new tickets
+NEW: Accountancy - Add hidden feature for accounting reconciliation
+NEW: Can store the session into database (instead of beeing managed by PHP)
+
+ Modules
+NEW: Module Partnership Management
+NEW: Experimental module Event Organization Management
+
+
+For developers or integrators:
+------------------------------
+NEW: dol_uncompress() supports more extensions (.gz, .bz2, .zstd). Only .zip was supported before.
+NEW: Implement a generic method for Kaban views
+NEW: Upgrade chartjs library to 3.7.1
+NEW: update rank line is possible on API for customer invoices, sales orders and supplier invoice
+NEW: stripe element with more gateways
+NEW: solde() function evolution to be able to get solde until a chosen date
+NEW: Suggest a way to run upgrade per entities.
+NEW: Support html content for multiselect component.
+NEW: ModuleBuilder - Add tabs view in module builder
+NEW: ModuleBuilder - More feature that can be modifed after module generation
+NEW: Hook getNomUrl available everywhere in tooltip of ref links
+NEW: Identification of tr is possible with by attribute data-id on some pages
+NEW: Import with select boxes V2
+NEW: Can update rank of invoice, proposal and order lines with API update
+NEW: Can use current entity filter on 'chkbxlst'
+NEW: Creation of the function select_bom() used to display bom select list
+NEW: add printFieldListWhere hook in product reassort card
+NEW: Add trigger and event on completely received status change
+NEW: Add utility function send backup by mail
+NEW: add WordPress OAuth to save a token (not SSO)
+NEW: A module can embed a sql script run at each Dolibarr upgrade
+NEW: API Proposals - Add POST lines
+NEW: API REST filter states by country
+NEW: Add option INVOICEREC_SET_AUTOFILL_DATE_START/END
+NEW: Add option MAIN_API_DEBUG to save API logs into a file
+NEW: Add param to keep the robot=index meta tag on public pages
+NEW: Add method hintindex() in database handlers.
+NEW: add modifications for new function "$db->prefix()"
+NEW: addMoreActionsButtonsList hook for button in list
+NEW: Add API to get a template invoice
+NEW: Standardize a lot of code.
+NEW: #20736 Allow extrafields SQL filters on REST API product lookup
+NEW: #19294 implement detailed timespent in task of project API
+NEW: Add a protection into PHPunit to avoid to forget a var_dump
+NEW: Add datem and type parameters to API to create movements
+NEW: Add hidden option on contract PDF line to hide qty and price
+NEW: Option MAIL_MASS_ACTION_ADD_LAST_IF_MAIN_DOC_NOT_FOUND to send last document in mass mailing action
+NEW: Add hooks: selectContactListWhere hook, selectThirdpartyListWhere hook 
+NEW: TakePos - add hooks complete product display
+NEW: TakePos - add hooks for cart display
+NEW: TakePos - add hooks to complete ajax return array
+NEW: Add hook before the public ticket list
+NEW: Add hook doaction in takepos invoice
+NEW: Add Hook for Notif
+NEW: Add hook for more buttons
+NEW: Add hook printFieldListWhere in "show_contacts" function
+NEW: Add hook printFieldWhere in load_state_board function
+NEW: Add hooks contact tab badge and hooks parameter for avoid conflicts
+NEW: Add hook selectProductsListWhere in select_produits_list function
+NEW: Add hooks in commercial index
+NEW: Add hooks in customers and products boxes
+NEW: Add hooks in thirdparty index page
+NEW: Add hooks on project task time page
+NEW: Add hooks on salaries and sociales card
+NEW: Add hooks select product list and select thirdparty list function
+NEW: Add hook to getSellPrice function
+
+
+Following changes may create regressions for some external modules, but were necessary to make Dolibarr better:
+* There is a new specific permission to be allowed to enter timesheets. If you use timesheet, don't forget to give the new permission (disable and 
+  enable the module project if it is not visible).
+* 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 renamed with name XXX_MODIFY for code consistency purpose.
+* Rename build_path_from_id_categ() into buildPathFromId() and set method to private.
+* Move massaction 'confirm_createbills' from actions_massactions.inc.php to commande/list.php
+* Method fetch_all_resources(), fetch_all_used(), fetch_all_available() of DolResource has been removed (they were not used by core code).
+* Method fetch_all of DolResource has been renamed into fetchAll() to match naming conventions.
+* The hook 'upgrade' and 'doUpgrade2" has been renamed 'doUpgradeBefore' and 'doUpgradeAfterDB'. A new trigger 'doUpgradeAfterFiles' has been introduced.
+* The context hook 'suppliercard' when on the supplier tab of a thirdparty has been renamed into 'thirdpartysupplier'
+
+
+***** ChangeLog for 15.0.2 compared to 15.0.1 *****
+
+FIX: #19777 #20281
+FIX: #20140 #20301
+FIX: #20279 Accountancy - PostGreSQL - Error on mass update lines already binded
+FIX: #20476 migration postgresql 14.0.x to 15.0.x packaging type
+FIX: #20733 Inventory: Do not use batch qty even if present if batch module is disabled.
+FIX: action comm list: holiday last day not included + handle duration with halfdays
+FIX: Add missing entity on salary's payment
+FIX: Add 'recruitment' into check array
+FIX: add tools to fix bad bank amount in accounting with multicurrency
+FIX: assign member cateogry to a member
+FIX: backport
+FIX: bad bank amount in accounting with multicurrency
+FIX: Bad condition on remx
+FIX: Bad filter on date on salary list
+FIX: bad link to add a customer price (token duplicated)
+FIX: bad status of member on widget by type and status
+FIX: better error management at product selling price update
+FIX: Can't edit bank record
+FIX: check mandatory thirdparty fields for mass action
+FIX: check thirdparty object loaded and properties exist
+FIX: comment
+FIX: compatibility for ticket number sharing
+FIX: compatibility with multicompany sharings
+FIX: contact card: single extrafield update failed
+FIX: country not visible into list of states
+FIX: Delete an extrafield where type is double
+FIX: deprecated module are not more viewed as external modules
+FIX: Disable customer type by default if type prospect/customer is disabled
+FIX: each time we create a supplier order, we need to give it a ref_supplier
+FIX: Error management
+FIX: fatal error for $db  usage in tpl
+FIX: filter into the list of product lots
+FIX: Filter on Object Referent page give CRSF page
+FIX: Fix default options ($hidedetails, $hidedesc, $hideref) with globales when generate PDF in mass actions
+FIX: Fix search by filters
+FIX: Fix the adding of lines in the create invoice functions
+FIX: forgotten form confirm before various payment delete
+FIX: holiday/leave requests: write status change emails in HTML
+FIX: include discount price for PMP after a reception (Issue #20029)
+FIX: incrementation
+FIX: in salary stats and payment list, we must check right perms as well as salary list
+FIX: intervention entity missing
+FIX: label tax cat trad
+FIX: Mass action ship orders
+FIX: missing advanced perms
+FIX: missing call to executeHooks()
+FIX: Missing entity on adding new VAT
+FIX: missing hook for row ordering
+FIX: missing hook parameter ($possiblelinks)
+FIX: missing parenthesis
+FIX: missing picto in combo of mass actions of thirdparties.
+FIX: missing signature library when ODT model is used
+FIX: Missing unset fields after updateline expensereport
+FIX: ModuileBuilder - Fix getLinesArray() error reporting
+FIX: Move delete task time trigger position
+FIX: Navigation between invoices
+FIX: No empty line inserted into accounting_bookkeeping
+FIX: Numbering of sepa files
+FIX: object cloning: set unique extrafield values to null to prevent duplicates
+FIX: on update with action reminder in future there is user key error
+FIX: originproductline array td identification data-id
+FIX: out of memory when more than 100 000 invoices.
+FIX: permit access to medias when logged in a different entity
+FIX: phpcs
+FIX: project creation prevented if PROJECTLEADER contact role renamed, de-activated or deleted
+FIX: project timesheet by week: cleanup unused code
+FIX: project timesheet: public holidays offset by 1 day
+FIX: project timesheets: assume Saturday and Sunday as default weekend days when working days conf is empty or badly formed
+FIX: propal list: bad error management when setting "not signed" mass action
+FIX: propal list mass action translations and error management (v14 edition)
+FIX: propal list: missing not signed massaction translation keys for transifex
+FIX: PR returns
+FIX: ref_client doesn't exists on supplier invoice, then ref_fourn needs to have a default value when we want to bill several supplier orders
+FIX: replenish and manage product stock by warhouse
+FIX: sending email on payment of registration of event
+FIX: SEPA ICS is not mandatory for bank transfer
+FIX: Set datec when add time spent on a project task
+FIX: status filter on supplierOrder stats doesn't work
+FIX: stickler-ci
+FIX: still prevent project creation if PROJECTLEADER role unavailable, but with a specific error message
+FIX: Supplier order stats
+FIX: Tabulation must be allowed for HTML content
+FIX: tool to fix bank account not in main currency for vendor invoice
+FIX: translations
+FIX: Travis + Update dev
+FIX: truncate Customer Reference too long on PDF header (PR #20718)
+FIX: uniformize code
+FIX: Update of sale price (log not correctly updated)
+FIX: user actions rights when mulit-company transverse mode is enabled
+FIX: user employee tab: offset in open days messes up holiday length calculation
+FIX: We need to have a different default_ref_supplier for each new fourn invoice
+FIX: "WHERE" clause missing on resource export
+FIX: #yogosha9754
+
+
+***** 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,6 +333,7 @@ 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: #18401 Add __NEWREF__ subtitute to get new object reference.
@@ -83,8 +407,9 @@ NEW: Increase size of params of actions for emailcollector
 NEW: Invoice list - Use complete country select field with EEC or not
 NEW: mass action delete, no more break if at least one object has child
 NEW: mass action paid on customer invoice list
-NEW: massaction validate on supplier orders list
-NEW: Mass action send email to all attendees of an event.
+NEW: mass action validate on supplier orders list
+NEW: mass action send email to all attendees of an event
+NEW: mass action to switch status on sale / on purchase of a product
 NEW: expense reports: conf to pre-fill start/end dates with bounds of current month
 NEW: Option "Add a link on the PDF to make the online payment"
 NEW: More options to generate PDF (show Frame option, width of picture option)
@@ -105,7 +430,7 @@ 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
@@ -113,36 +438,49 @@ NEW: Stable module Knowledge Management
 NEW: Experimental module Event Organization Management
 NEW: Experimental module Workstations Management
 NEW: Development of module Partnership Management
+OLD: module SimplePOS has been completely removed -> use TakePOS
 
 
 For developers:
 ---------------
 
-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
+API:
 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
-NEW: Add date_valid and date_approve columns in the list of supplier orders
-NEW: add hook `beforeBodyClose`
-NEW: Add hook hookGetEntity.
-NEW: add hookmanager on note pages
-NEW: add hook 'menuLeftMenuItems' to filter the leftmenu items
-NEW: Add the property "copytoclipboard" in modulebuilder
-NEW: api for knowledgemanagement
+NEW: add API /approve and /makeOrder for purchase orders 
+NEW: API for knowledgemanagement
 NEW: API get list of legal form of business
 NEW: API list of staff units
+NEW: Hidden option API_DISABLE_COMPRESSION is now visible in API setup page.
+
+Hook:
+NEW: add hook 'beforeBodyClose'
+NEW: add hook 'hookGetEntity'
+NEW: add hook 'menuLeftMenuItems' to filter the leftmenu items
+NEW: add hook 'printUnderHeaderPDFline' on invoice PDF templates (can be used for example to add a barcode or more information on header of invoices).
+NEW: add hookmanager on note pages
 NEW: hook after rank update
-NEW: printFieldListFrom hook call on several lists
+NEW: 'printFieldListFrom' hook call on several lists
+
+ModuleBuilder:
+NEW: add the property "copytoclipboard" in modulebuilder
 NEW: Use lang selector when using a field key 'lang' in modulebuilder
+
+Options:
+NEW: add options MAIN_IBAN_IS_NEVER_MANDATORY, MAIN_IBAN_NOT_MANDATORY, PROPAL_NOT_BILLABLE, PROPAL_REOPEN_UNSIGNED_ONLY, PROPOSAL_ARE_NOT_BILLABLE, TICKETS_MESSAGE_FORCE_MAIL
+
+Trigger:
+NEW: add action trigger for member excluded
+
+
+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: add code codebar column on serial/lot structure
+NEW: add date_valid and date_approve columns in the list of supplier orders
 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 
@@ -999,7 +1337,6 @@ NEW: introduce constant FACTUREFOURN_REUSE_NOTES_ON_CREATE_FROM
 NEW: introducing new modal boxes in TakePOS
 NEW: keep TakePOS terminal when login/logout
 NEW: link on balance to the ledger
-NEW: MAIN_EMAILCOLLECTOR_MAIL_WITHOUT_HEADER const in email collector
 NEW: manage errors on update extra fields in ticket card
 NEW: mass-actions for the event list view
 NEW: more filter for "View change logs"

+ 13 - 14
README.md

@@ -32,7 +32,7 @@ Other licenses apply for some included dependencies. See [COPYRIGHT](https://git
 
 If you have low technical skills and you're looking to install Dolibarr ERP/CRM in just a few clicks, you can use one of the packaged versions:
 
-- [DoliWamp for Windows](https://wiki.dolibarr.org/index.php/Dolibarr_for_Windows_DoliWamp)
+- [DoliWamp for Windows](https://wiki.dolibarr.org/index.php/Dolibarr_for_Windows_(DoliWamp))
 - [DoliDeb for Debian](https://wiki.dolibarr.org/index.php/Dolibarr_for_Ubuntu_or_Debian)
 - DoliRpm for Redhat, Fedora, OpenSuse, Mandriva or Mageia
 
@@ -111,7 +111,7 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
 
 - Customers/Prospects + Contacts management
 - Opportunities or Leads management
-- Commercial proposals management
+- Commercial proposals management (online signing)
 - Customer Orders management
 - Contracts/Subscription management
 - Interventions management
@@ -129,11 +129,11 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
 - Supplier Invoices/credit notes and payment management
 - INCOTERMS
 
- Finance / Accounting
+ Finance/Accounting
 
-- Invoices / Payments
+- Invoices/Payments
 - Bank accounts management
-- Direct debit orders management (European SEPA)
+- Direct debit and Credit transfer management (European SEPA)
 - Accounting management
 - Donations management
 - Loan management
@@ -142,14 +142,14 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
 
  Collaboration
 
-- Shared calendar/agenda (with ical and vcal export for third party tools integration)
+- Shared calendar/agenda (with ical and vcal import/export for third party tools integration)
 - Projects & Tasks management
 - Ticket System
 - Surveys
 
  HR
 
-- Employee's leave requests management
+- Employee's leaves management
 - Expense reports
 - Recruitment management
 - Timesheets
@@ -157,16 +157,14 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
 ### Other application/modules
 
 - Electronic Document Management (EDM)
-- Bookmarks management
+- Bookmarks
 - Reporting
 - Data export/import
 - Barcodes
-- Margin calculations
 - LDAP connectivity
 - ClickToDial integration
 - Mass emailing
 - RSS integration
-- Skype integration
 - Social platforms linking
 - Payment platforms integration (PayPal, Stripe, Paybox...)
 - Email-Collector
@@ -175,13 +173,12 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
 
 ### Other general features
 
-- Localization in most major languages
-- Multi-Language Support
+- Multi-Language Support (Localization in most major languages)
 - Multi-Users and groups with finely grained rights
 - Multi-Currency
 - Multi-Company (by adding of an external module)
 - Very user friendly and easy to use
-- customizable Dashboard
+- Customizable dashboards
 - Highly customizable: enable only the modules you need, add user personalized fields, choose your skin, several menu managers (can be used by internal users as a back-office with a particular menu, or by external users as a front-office with another one)
 - APIs (REST, SOAP)
 - Code that is easy to understand, maintain and develop (PHP with no heavy framework; trigger and hook architecture)
@@ -191,8 +188,9 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog)
   - Canadian double taxes (federal/province) and other countries using cumulative VAT
   - Tunisian tax stamp
   - Argentina invoice numbering using A,B,C...
+  - ZATCA e-invoicing QR-Code
   - Compatible with [European directives](https://europa.eu/legislation_summaries/taxation/l31057_en.htm) (2006/112/CE ... 2010/45/UE)
-  - Compatible with European GDPR rules
+  - Compatible with data privacy rules (europe GDPR, ...)
   - ...
 - Flexible PDF & ODT generation for invoices, proposals, orders...
 - ...
@@ -244,6 +242,7 @@ Follow Dolibarr project on:
 - [Facebook](https://www.facebook.com/dolibarr)
 - [Twitter](https://www.twitter.com/dolibarr)
 - [LinkedIn](https://www.linkedin.com/company/association-dolibarr)
+- [Reddit](https://www.reddit.com/r/Dolibarr_ERP_CRM/)
 - [YouTube](https://www.youtube.com/user/DolibarrERPCRM)
 - [GitHub](https://github.com/Dolibarr/dolibarr)
 

+ 7 - 6
SECURITY.md

@@ -6,8 +6,9 @@ This file contains some policies about the security reports on Dolibarr ERP CRM
 
 | Version    | Supported              |
 | ---------- | ---------------------- |
-| <= 14.0.4  | :x:                    |
-| >= 14.0.5+ | :white_check_mark: except CSRF attacks|
+| <= 15.0.0  | :x:                    |
+| >= 15.0.1+ | :white_check_mark: except CSRF attacks|
+| >= 16.0.0  | :white_check_mark:     |
 | >= develop | :white_check_mark:     |
 
 ## Reporting a Vulnerability
@@ -17,7 +18,7 @@ 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.
 
@@ -35,13 +36,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.
 
@@ -56,7 +57,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.
 

+ 4 - 59
build/exe/doliwamp/doliwamp.iss

@@ -27,12 +27,12 @@ OutputBaseFilename=__FILENAMEEXEDOLIWAMP__
 ;SourceDir=Z:\home\ldestailleur\git\dolibarrxxx
 SourceDir=..\..\..
 AppId=doliwamp
-AppPublisher=NLTechno
-AppPublisherURL=https://www.nltechno.com
+AppPublisher=DoliCloud
+AppPublisherURL=https://www.dolicloud.com
 AppSupportURL=https://www.dolibarr.org
 AppUpdatesURL=https://www.dolibarr.org
 AppComments=DoliWamp includes Dolibarr, Apache, PHP and Mysql software.
-AppCopyright=Copyright (C) 2008-2020 Laurent Destailleur (NLTechno), Fabian Rodriguez (Le Goût du Libre)
+AppCopyright=Copyright (C) 2008-2022 Laurent Destailleur (NLTechno), Fabian Rodriguez (Le Goût du Libre)
 DefaultDirName=c:\dolibarr
 DefaultGroupName=Dolibarr
 ;LicenseFile=COPYING
@@ -99,13 +99,12 @@ Source: "build\exe\doliwamp\startdoliwamp_manual_donotuse.bat.install"; DestDir:
 Source: "build\exe\doliwamp\builddemosslfiles.bat.install"; DestDir: "{app}\"; Flags: ignoreversion;
 Source: "build\exe\doliwamp\UsedPort.exe"; DestDir: "{app}\"; Flags: ignoreversion;
 
-; PhpMyAdmin, Apache, Php, Mysql
+; Apache, Php, Mysql
 ; Put here path of Wampserver applications
 ; Value OK: apache 2.2.6,  php 5.2.5 (5.2.11, 5.3.0 and 5.3.1 fails if php_exif, php_pgsql, php_zip is on), mysql 5.0.45
 ; Value OK: apache 2.2.11, php 5.3.0 (if no php_exif, php_pgsql, php_zip), mysql 5.0.45
 ; Value OK: apache 2.4.9,  php 5.5.12, mysql 5.0.45 instead of 5.6.17 (wampserver2.5-Apache-2.4.9-Mysql-5.6.17-php5.5.12-32b.exe)
 ; Value OK: apache 2.4.41, php 7.3.12, mariadb10.4.10 (wampserver3.2.0_x64.exe)
-Source: "C:\wamp64\apps\phpmyadmin4.9.2\*.*"; DestDir: "{app}\apps\phpmyadmin4.9.2"; Flags: ignoreversion recursesubdirs; Excludes: "config.inc.php,wampserver.conf,*.log,*_log,darkblue_orange"
 ;Source: "C:\Program Files\Wamp\bin\apache\apache2.4.9\*.*"; DestDir: "{app}\bin\apache\apache2.4.9"; Flags: ignoreversion recursesubdirs; Excludes: "php.ini,httpd.conf,wampserver.conf,*.log,*_log"
 Source: "C:\wamp64\bin\apache\apache2.4.41\*.*"; DestDir: "{app}\bin\apache\apache2.4.41"; Flags: ignoreversion recursesubdirs; Excludes: "php.ini,httpd.conf,wampserver.conf,*.log,*_log"
 ;Source: "C:\Program Files\Wamp\bin\php\php5.5.12\*.*"; DestDir: "{app}\bin\php\php5.5.12"; Flags: ignoreversion recursesubdirs; Excludes: "php.ini,phpForApache.ini,wampserver.conf,*.log,*_log"
@@ -125,9 +124,7 @@ Source: "scripts\*.*"; DestDir: "{app}\www\dolibarr\scripts"; Flags: ignoreversi
 Source: "*.*"; DestDir: "{app}\www\dolibarr"; Flags: ignoreversion; Excludes: ".gitignore,.project,CVS\*,Thumbs.db,default.properties,install.lock"
 
 ; Config files
-Source: "build\exe\doliwamp\phpmyadmin.conf.install"; DestDir: "{app}\alias"; Flags: ignoreversion;
 Source: "build\exe\doliwamp\dolibarr.conf.install"; DestDir: "{app}\alias"; Flags: ignoreversion;
-Source: "build\exe\doliwamp\config.inc.php.install"; DestDir: "{app}\apps\phpmyadmin4.1.14"; Flags: ignoreversion;
 ;Source: "build\exe\doliwamp\httpd.conf.install"; DestDir: "{app}\bin\apache\apache2.4.9\conf"; Flags: ignoreversion;
 Source: "build\exe\doliwamp\httpd.conf.install"; DestDir: "{app}\bin\apache\apache2.4.41\conf"; Flags: ignoreversion;
 Source: "build\exe\doliwamp\my.ini.install"; DestDir: "{app}\bin\mysql\mysql5.0.45"; Flags: ignoreversion;
@@ -196,7 +193,6 @@ var destFileA: String;
 var srcContents: String;
 var browser: String;
 var mysqlVersion: String;
-var phpmyadminVersion: String;
 var phpDllCopy: String;
 var batFile: String;
 
@@ -246,7 +242,6 @@ begin
   phpVersion := '7.3.12' ;
   //mysqlVersion := '5.0.45';
   mysqlVersion := '10.4.10';
-  phpmyadminVersion := '4.1.14';
 
   smtpServer := 'localhost';
   apachePort := '80';
@@ -635,27 +630,6 @@ begin
       begin
 
 		
-		    //----------------------------------------------
-		    // Create file alias phpmyadmin (always)
-		    //----------------------------------------------
-		
-		    destFile := pathWithSlashes+'/alias/phpmyadmin.conf';
-		    srcFile := pathWithSlashes+'/alias/phpmyadmin.conf.install';
-		
-		    if FileExists(srcFile) then
-		    begin
-		      LoadStringFromFile (srcFile, srcContents);
-		
-		      //installDir et version de phpmyadmin
-		      StringChangeEx (srcContents, 'WAMPROOT', pathWithSlashes, True);
-		      StringChangeEx (srcContents, 'WAMPPHPMYADMINVERSION', phpmyadminVersion, True);
-		
-		      SaveStringToFile(destFile,srcContents, False);
-		    end;
-		    DeleteFile(srcFile);
-		
-		
-		
 		    //----------------------------------------------
 		    // Create file alias dolibarr (if not exists)
 		    //----------------------------------------------
@@ -691,35 +665,6 @@ begin
 		
 		
 		
-		    //----------------------------------------------
-		    // Create file configuration for phpmyadmin (if not exists)
-		    //----------------------------------------------
-		
-		    destFile := pathWithSlashes+'/apps/phpmyadmin'+phpmyadminVersion+'/config.inc.php';
-		    srcFile := pathWithSlashes+'/apps/phpmyadmin'+phpmyadminVersion+'/config.inc.php.install';
-		
-		    if FileExists(srcFile) then
-		    begin
-	  	      if not FileExists (destFile) then
-		      begin
-	            LoadStringFromFile (srcFile, srcContents);
-	            StringChangeEx (srcContents, 'WAMPMYSQLNEWPASSWORD', mypass, True);
-	            StringChangeEx (srcContents, 'WAMPMYSQLPORT', myport, True);
-	            SaveStringToFile(destFile,srcContents, False);
-		      end
-		      else
-		      begin
-		        // We must replace to use format 2.4 of apache
-	            DeleteFile(destFile);
-	            LoadStringFromFile (srcFile, srcContents);
-	            StringChangeEx (srcContents, 'WAMPMYSQLNEWPASSWORD', mypass, True);
-	            StringChangeEx (srcContents, 'WAMPMYSQLPORT', myport, True);
-	            SaveStringToFile(destFile,srcContents, False);
-		      end;
-		    end;
-		
-		
-		
 		    //----------------------------------------------
 		    // Create file httpd.conf (if not exists)
 		    //----------------------------------------------

+ 1 - 3
build/makepack-dolibarr.pl

@@ -582,9 +582,7 @@ if ($nboftargetok) {
 		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/teclib*`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/timesheet*`;
 		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/webmail*`;
-		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/themes/oblyon*`;
-		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/themes/allscreen*`;
-		$ret=`rm -f  $BUILDROOT/$PROJECT/htdocs/theme/common/octicons/LICENSE`;
+		$ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/theme/common/fontawesome-5/svgs`;
 		
 		# Removed other test files
 	    $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/public/test`;

+ 12 - 12
build/makepack-howto.txt

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

+ 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 → composer.json.disabled

@@ -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"
 	}
-}
+}

+ 0 - 2349
composer.lock

@@ -1,2349 +0,0 @@
-{
-    "_readme": [
-        "This file locks the dependencies of your project to a known state",
-        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
-        "This file is @generated automatically"
-    ],
-    "content-hash": "1dbd2d05cc0836acfca5988f29005cf2",
-    "packages": [
-        {
-            "name": "ckeditor/ckeditor",
-            "version": "4.12.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/ckeditor/ckeditor-releases.git",
-                "reference": "b1a25e93ae0b038f45dcba458f4c2c18bd7318e5"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/ckeditor/ckeditor-releases/zipball/b1a25e93ae0b038f45dcba458f4c2c18bd7318e5",
-                "reference": "b1a25e93ae0b038f45dcba458f4c2c18bd7318e5",
-                "shasum": ""
-            },
-            "type": "library",
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "GPL-2.0+",
-                "LGPL-2.1+",
-                "MPL-1.1+"
-            ],
-            "authors": [
-                {
-                    "name": "CKSource",
-                    "homepage": "http://cksource.com"
-                }
-            ],
-            "description": "JavaScript WYSIWYG web text editor.",
-            "homepage": "http://ckeditor.com",
-            "keywords": [
-                "CKEditor",
-                "editor",
-                "fckeditor",
-                "html",
-                "javascript",
-                "richtext",
-                "text",
-                "wysiwyg"
-            ],
-            "support": {
-                "forum": "http://ckeditor.com/forums",
-                "issues": "http://dev.ckeditor.com",
-                "source": "http://github.com/ckeditor/ckeditor-dev",
-                "wiki": "http://docs.ckeditor.com"
-            },
-            "time": "2019-06-28T10:41:23+00:00"
-        },
-        {
-            "name": "maximebf/debugbar",
-            "version": "v1.15.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/maximebf/php-debugbar.git",
-                "reference": "6c4277f6117e4864966c9cb58fb835cee8c74a1e"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/6c4277f6117e4864966c9cb58fb835cee8c74a1e",
-                "reference": "6c4277f6117e4864966c9cb58fb835cee8c74a1e",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.6",
-                "psr/log": "^1.0",
-                "symfony/var-dumper": "^2.6|^3|^4"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^5"
-            },
-            "suggest": {
-                "kriswallsmith/assetic": "The best way to manage assets",
-                "monolog/monolog": "Log using Monolog",
-                "predis/predis": "Redis storage"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.15-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "DebugBar\\": "src/DebugBar/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Maxime Bouroumeau-Fuseau",
-                    "email": "maxime.bouroumeau@gmail.com",
-                    "homepage": "http://maximebf.com"
-                },
-                {
-                    "name": "Barry vd. Heuvel",
-                    "email": "barryvdh@gmail.com"
-                }
-            ],
-            "description": "Debug bar in the browser for php application",
-            "homepage": "https://github.com/maximebf/php-debugbar",
-            "keywords": [
-                "debug",
-                "debugbar"
-            ],
-            "support": {
-                "issues": "https://github.com/maximebf/php-debugbar/issues",
-                "source": "https://github.com/maximebf/php-debugbar/tree/v1.15.1"
-            },
-            "time": "2019-09-24T14:55:42+00:00"
-        },
-        {
-            "name": "mike42/escpos-php",
-            "version": "v2.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/mike42/escpos-php.git",
-                "reference": "e5496cf819b048b11877117bd14a9cea4fb17c03"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/mike42/escpos-php/zipball/e5496cf819b048b11877117bd14a9cea4fb17c03",
-                "reference": "e5496cf819b048b11877117bd14a9cea4fb17c03",
-                "shasum": ""
-            },
-            "require": {
-                "ext-mbstring": "*",
-                "php": ">=5.4.0"
-            },
-            "require-dev": {
-                "guzzlehttp/guzzle": "^5.3",
-                "phpunit/phpunit": "^4.8",
-                "squizlabs/php_codesniffer": "^3.2"
-            },
-            "suggest": {
-                "ext-gd": "Used for image printing if present.",
-                "ext-imagick": "Will be used for image printing if present. Required for PDF printing or use of custom fonts.",
-                "guzzlehttp/guzzle": "Allows the use of the ApiConnector to send print jobs over HTTP."
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Mike42\\": "src/Mike42"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Michael Billington",
-                    "email": "michael.billington@gmail.com"
-                }
-            ],
-            "description": "PHP receipt printer library for use with ESC/POS-compatible thermal and impact printers",
-            "homepage": "https://github.com/mike42/escpos-php",
-            "keywords": [
-                "ESC-POS",
-                "driver",
-                "escpos",
-                "print",
-                "receipt"
-            ],
-            "support": {
-                "issues": "https://github.com/mike42/escpos-php/issues",
-                "source": "https://github.com/mike42/escpos-php/tree/v2.2"
-            },
-            "time": "2019-10-05T05:59:00+00:00"
-        },
-        {
-            "name": "mobiledetect/mobiledetectlib",
-            "version": "2.8.34",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/serbanghita/Mobile-Detect.git",
-                "reference": "6f8113f57a508494ca36acbcfa2dc2d923c7ed5b"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/6f8113f57a508494ca36acbcfa2dc2d923c7ed5b",
-                "reference": "6f8113f57a508494ca36acbcfa2dc2d923c7ed5b",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.0.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "~4.8.35||~5.7"
-            },
-            "type": "library",
-            "autoload": {
-                "classmap": [
-                    "Mobile_Detect.php"
-                ],
-                "psr-0": {
-                    "Detection": "namespaced/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Serban Ghita",
-                    "email": "serbanghita@gmail.com",
-                    "homepage": "http://mobiledetect.net",
-                    "role": "Developer"
-                }
-            ],
-            "description": "Mobile_Detect is a lightweight PHP class for detecting mobile devices. It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.",
-            "homepage": "https://github.com/serbanghita/Mobile-Detect",
-            "keywords": [
-                "detect mobile devices",
-                "mobile",
-                "mobile detect",
-                "mobile detector",
-                "php mobile detect"
-            ],
-            "support": {
-                "issues": "https://github.com/serbanghita/Mobile-Detect/issues",
-                "source": "https://github.com/serbanghita/Mobile-Detect/tree/2.8.34"
-            },
-            "time": "2019-09-18T18:44:20+00:00"
-        },
-        {
-            "name": "nnnick/chartjs",
-            "version": "v2.9.4",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/chartjs/Chart.js.git",
-                "reference": "9bd4cf82fda9f50a5fb50b72843e06ab88124278"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/chartjs/Chart.js/zipball/9bd4cf82fda9f50a5fb50b72843e06ab88124278",
-                "reference": "9bd4cf82fda9f50a5fb50b72843e06ab88124278",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.3"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "release/2.0": "v2.0-dev"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "NICK DOWNIE",
-                    "email": "hello@nickdownie.com"
-                }
-            ],
-            "description": "Simple HTML5 charts using the canvas element.",
-            "homepage": "https://www.chartjs.org/",
-            "keywords": [
-                "JS",
-                "chart"
-            ],
-            "support": {
-                "issues": "https://github.com/chartjs/Chart.js/issues",
-                "source": "https://github.com/chartjs/Chart.js/tree/v2.9.4"
-            },
-            "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": {
-                "type": "git",
-                "url": "https://github.com/PHPOffice/PHPExcel.git",
-                "reference": "1441011fb7ecdd8cc689878f54f8b58a6805f870"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/PHPOffice/PHPExcel/zipball/1441011fb7ecdd8cc689878f54f8b58a6805f870",
-                "reference": "1441011fb7ecdd8cc689878f54f8b58a6805f870",
-                "shasum": ""
-            },
-            "require": {
-                "ext-mbstring": "*",
-                "ext-xml": "*",
-                "ext-xmlwriter": "*",
-                "php": "^5.2|^7.0"
-            },
-            "require-dev": {
-                "squizlabs/php_codesniffer": "2.*"
-            },
-            "type": "library",
-            "autoload": {
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "LGPL-2.1"
-            ],
-            "authors": [
-                {
-                    "name": "Maarten Balliauw",
-                    "homepage": "http://blog.maartenballiauw.be"
-                },
-                {
-                    "name": "Erik Tilt"
-                },
-                {
-                    "name": "Franck Lefevre",
-                    "homepage": "http://rootslabs.net"
-                },
-                {
-                    "name": "Mark Baker",
-                    "homepage": "http://markbakeruk.net"
-                }
-            ],
-            "description": "PHPExcel - OpenXML - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
-            "homepage": "https://github.com/PHPOffice/PHPExcel",
-            "keywords": [
-                "OpenXML",
-                "excel",
-                "xlsx"
-            ],
-            "abandoned": "phpoffice/phpspreadsheet",
-            "time": "2018-11-22T23:07:24+00:00"
-        },        
-        {
-            "name": "restler/framework",
-            "version": "3.0.0-RC6",
-            "target-dir": "Luracast/Restler",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/Luracast/Restler-Framework.git",
-                "reference": "d52e61600d153bca60a287c35141c5c01863127b"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/Luracast/Restler-Framework/zipball/d52e61600d153bca60a287c35141c5c01863127b",
-                "reference": "d52e61600d153bca60a287c35141c5c01863127b",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.0"
-            },
-            "replace": {
-                "luracast/restler": "3.*"
-            },
-            "suggest": {
-                "bshaffer/oauth2-server-php": "If you want to use OAuth2 for authentication",
-                "illuminate/view": "If you want to use laravel blade templates with Html format",
-                "mustache/mustache": "If you want to use mustache/handlebar templates with Html format",
-                "rodneyrehm/plist": "If you need Apple plist binary/xml format",
-                "symfony/yaml": "If you need YAML format",
-                "twig/twig": "If you want to use twig templates with Html format",
-                "zendframework/zendamf": "If you need AMF format"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "master": "v3.0.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-0": {
-                    "Luracast\\Restler": ""
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "LGPL-2.1"
-            ],
-            "authors": [
-                {
-                    "name": "Luracast",
-                    "email": "arul@luracast.com"
-                }
-            ],
-            "description": "Just the Restler Framework without the tests and examples",
-            "homepage": "http://luracast.com/products/restler/",
-            "keywords": [
-                "api",
-                "framework",
-                "rest",
-                "server"
-            ],
-            "support": {
-                "source": "https://github.com/Luracast/Restler-Framework/tree/3.0.0-RC6"
-            },
-            "time": "2020-02-13T16:05:12+00:00"
-        },
-        {
-            "name": "stripe/stripe-php",
-            "version": "v6.43.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/stripe/stripe-php.git",
-                "reference": "42fcdaf99c44bb26937223f8eae1f263491d5ab8"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/stripe/stripe-php/zipball/42fcdaf99c44bb26937223f8eae1f263491d5ab8",
-                "reference": "42fcdaf99c44bb26937223f8eae1f263491d5ab8",
-                "shasum": ""
-            },
-            "require": {
-                "ext-curl": "*",
-                "ext-json": "*",
-                "ext-mbstring": "*",
-                "php": ">=5.4.0"
-            },
-            "require-dev": {
-                "php-coveralls/php-coveralls": "1.*",
-                "phpunit/phpunit": "~4.0",
-                "squizlabs/php_codesniffer": "~2.0",
-                "symfony/process": "~2.8"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "2.0-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Stripe\\": "lib/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Stripe and contributors",
-                    "homepage": "https://github.com/stripe/stripe-php/contributors"
-                }
-            ],
-            "description": "Stripe PHP Library",
-            "homepage": "https://stripe.com/",
-            "keywords": [
-                "api",
-                "payment processing",
-                "stripe"
-            ],
-            "support": {
-                "issues": "https://github.com/stripe/stripe-php/issues",
-                "source": "https://github.com/stripe/stripe-php/tree/master"
-            },
-            "time": "2019-08-29T16:56:12+00:00"
-        },
-        {
-            "name": "symfony/polyfill-mbstring",
-            "version": "v1.20.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "39d483bdf39be819deabf04ec872eb0b2410b531"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/39d483bdf39be819deabf04ec872eb0b2410b531",
-                "reference": "39d483bdf39be819deabf04ec872eb0b2410b531",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "suggest": {
-                "ext-mbstring": "For best performance"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.20-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Polyfill\\Mbstring\\": ""
-                },
-                "files": [
-                    "bootstrap.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill for the Mbstring extension",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "mbstring",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.20.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2020-10-23T14:02:19+00:00"
-        },
-        {
-            "name": "symfony/var-dumper",
-            "version": "v3.2.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/var-dumper.git",
-                "reference": "737e07704cca83f9dd0af926d45ce27eedc25657"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/737e07704cca83f9dd0af926d45ce27eedc25657",
-                "reference": "737e07704cca83f9dd0af926d45ce27eedc25657",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.5.9",
-                "symfony/polyfill-mbstring": "~1.0"
-            },
-            "require-dev": {
-                "twig/twig": "~1.20|~2.0"
-            },
-            "suggest": {
-                "ext-symfony_debug": ""
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "3.2-dev"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "Resources/functions/dump.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Component\\VarDumper\\": ""
-                },
-                "exclude-from-classmap": [
-                    "/Tests/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony mechanism for exploring and dumping PHP variables",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "debug",
-                "dump"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/var-dumper/tree/master"
-            },
-            "time": "2015-11-18T13:48:51+00:00"
-        },
-        {
-            "name": "tecnickcom/tcpdf",
-            "version": "6.3.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/tecnickcom/TCPDF.git",
-                "reference": "9fde7bb9b404b945e7ea88fb7eccd23d9a4e324b"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/9fde7bb9b404b945e7ea88fb7eccd23d9a4e324b",
-                "reference": "9fde7bb9b404b945e7ea88fb7eccd23d9a4e324b",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.0"
-            },
-            "type": "library",
-            "autoload": {
-                "classmap": [
-                    "config",
-                    "include",
-                    "tcpdf.php",
-                    "tcpdf_parser.php",
-                    "tcpdf_import.php",
-                    "tcpdf_barcodes_1d.php",
-                    "tcpdf_barcodes_2d.php",
-                    "include/tcpdf_colors.php",
-                    "include/tcpdf_filters.php",
-                    "include/tcpdf_font_data.php",
-                    "include/tcpdf_fonts.php",
-                    "include/tcpdf_images.php",
-                    "include/tcpdf_static.php",
-                    "include/barcodes/datamatrix.php",
-                    "include/barcodes/pdf417.php",
-                    "include/barcodes/qrcode.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "LGPL-3.0"
-            ],
-            "authors": [
-                {
-                    "name": "Nicola Asuni",
-                    "email": "info@tecnick.com",
-                    "role": "lead"
-                }
-            ],
-            "description": "TCPDF is a PHP class for generating PDF documents and barcodes.",
-            "homepage": "http://www.tcpdf.org/",
-            "keywords": [
-                "PDFD32000-2008",
-                "TCPDF",
-                "barcodes",
-                "datamatrix",
-                "pdf",
-                "pdf417",
-                "qrcode"
-            ],
-            "support": {
-                "source": "https://github.com/tecnickcom/TCPDF/tree/6.3.2"
-            },
-            "time": "2019-09-20T09:35:01+00:00"
-        }
-    ],
-    "packages-dev": [
-        {
-            "name": "doctrine/instantiator",
-            "version": "1.4.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/doctrine/instantiator.git",
-                "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b",
-                "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.1 || ^8.0"
-            },
-            "require-dev": {
-                "doctrine/coding-standard": "^8.0",
-                "ext-pdo": "*",
-                "ext-phar": "*",
-                "phpbench/phpbench": "^0.13 || 1.0.0-alpha2",
-                "phpstan/phpstan": "^0.12",
-                "phpstan/phpstan-phpunit": "^0.12",
-                "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Marco Pivetta",
-                    "email": "ocramius@gmail.com",
-                    "homepage": "https://ocramius.github.io/"
-                }
-            ],
-            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
-            "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
-            "keywords": [
-                "constructor",
-                "instantiate"
-            ],
-            "support": {
-                "issues": "https://github.com/doctrine/instantiator/issues",
-                "source": "https://github.com/doctrine/instantiator/tree/1.4.0"
-            },
-            "funding": [
-                {
-                    "url": "https://www.doctrine-project.org/sponsorship.html",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://www.patreon.com/phpdoctrine",
-                    "type": "patreon"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2020-11-10T18:47:58+00:00"
-        },
-        {
-            "name": "php-parallel-lint/php-console-color",
-            "version": "v0.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/php-parallel-lint/PHP-Console-Color.git",
-                "reference": "b6af326b2088f1ad3b264696c9fd590ec395b49e"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/php-parallel-lint/PHP-Console-Color/zipball/b6af326b2088f1ad3b264696c9fd590ec395b49e",
-                "reference": "b6af326b2088f1ad3b264696c9fd590ec395b49e",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.4.0"
-            },
-            "replace": {
-                "jakub-onderka/php-console-color": "*"
-            },
-            "require-dev": {
-                "php-parallel-lint/php-code-style": "1.0",
-                "php-parallel-lint/php-parallel-lint": "1.0",
-                "php-parallel-lint/php-var-dump-check": "0.*",
-                "phpunit/phpunit": "~4.3",
-                "squizlabs/php_codesniffer": "1.*"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "JakubOnderka\\PhpConsoleColor\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-2-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Jakub Onderka",
-                    "email": "jakub.onderka@gmail.com"
-                }
-            ],
-            "support": {
-                "issues": "https://github.com/php-parallel-lint/PHP-Console-Color/issues",
-                "source": "https://github.com/php-parallel-lint/PHP-Console-Color/tree/master"
-            },
-            "time": "2020-05-14T05:47:14+00:00"
-        },
-        {
-            "name": "php-parallel-lint/php-console-highlighter",
-            "version": "v0.5",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/php-parallel-lint/PHP-Console-Highlighter.git",
-                "reference": "21bf002f077b177f056d8cb455c5ed573adfdbb8"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/php-parallel-lint/PHP-Console-Highlighter/zipball/21bf002f077b177f056d8cb455c5ed573adfdbb8",
-                "reference": "21bf002f077b177f056d8cb455c5ed573adfdbb8",
-                "shasum": ""
-            },
-            "require": {
-                "ext-tokenizer": "*",
-                "php": ">=5.4.0",
-                "php-parallel-lint/php-console-color": "~0.2"
-            },
-            "replace": {
-                "jakub-onderka/php-console-highlighter": "*"
-            },
-            "require-dev": {
-                "php-parallel-lint/php-code-style": "~1.0",
-                "php-parallel-lint/php-parallel-lint": "~1.0",
-                "php-parallel-lint/php-var-dump-check": "~0.1",
-                "phpunit/phpunit": "~4.0",
-                "squizlabs/php_codesniffer": "~1.5"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "JakubOnderka\\PhpConsoleHighlighter\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Jakub Onderka",
-                    "email": "acci@acci.cz",
-                    "homepage": "http://www.acci.cz/"
-                }
-            ],
-            "description": "Highlight PHP code in terminal",
-            "support": {
-                "issues": "https://github.com/php-parallel-lint/PHP-Console-Highlighter/issues",
-                "source": "https://github.com/php-parallel-lint/PHP-Console-Highlighter/tree/master"
-            },
-            "time": "2020-05-13T07:37:49+00:00"
-        },
-        {
-            "name": "php-parallel-lint/php-parallel-lint",
-            "version": "v0.9.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/php-parallel-lint/PHP-Parallel-Lint.git",
-                "reference": "2ead2e4043ab125bee9554f356e0a86742c2d4fa"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/2ead2e4043ab125bee9554f356e0a86742c2d4fa",
-                "reference": "2ead2e4043ab125bee9554f356e0a86742c2d4fa",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.3"
-            },
-            "require-dev": {
-                "jakub-onderka/php-console-highlighter": "~0.3",
-                "nette/tester": "~1.3"
-            },
-            "suggest": {
-                "jakub-onderka/php-console-highlighter": "Highlight syntax in code snippet"
-            },
-            "bin": [
-                "parallel-lint"
-            ],
-            "type": "library",
-            "autoload": {
-                "classmap": [
-                    "./"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-2-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Jakub Onderka",
-                    "email": "jakub.onderka@gmail.com"
-                }
-            ],
-            "description": "This tool check syntax of PHP files about 20x faster than serial check.",
-            "homepage": "https://github.com/JakubOnderka/PHP-Parallel-Lint",
-            "support": {
-                "source": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/tree/v0.9.2"
-            },
-            "time": "2015-12-15T10:42:16+00:00"
-        },
-        {
-            "name": "phpdocumentor/reflection-common",
-            "version": "2.2.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
-                "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
-                "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.2 || ^8.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-2.x": "2.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "phpDocumentor\\Reflection\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Jaap van Otterdijk",
-                    "email": "opensource@ijaap.nl"
-                }
-            ],
-            "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
-            "homepage": "http://www.phpdoc.org",
-            "keywords": [
-                "FQSEN",
-                "phpDocumentor",
-                "phpdoc",
-                "reflection",
-                "static analysis"
-            ],
-            "support": {
-                "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
-                "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
-            },
-            "time": "2020-06-27T09:03:43+00:00"
-        },
-        {
-            "name": "phpdocumentor/reflection-docblock",
-            "version": "5.2.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
-                "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556",
-                "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556",
-                "shasum": ""
-            },
-            "require": {
-                "ext-filter": "*",
-                "php": "^7.2 || ^8.0",
-                "phpdocumentor/reflection-common": "^2.2",
-                "phpdocumentor/type-resolver": "^1.3",
-                "webmozart/assert": "^1.9.1"
-            },
-            "require-dev": {
-                "mockery/mockery": "~1.3.2"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "5.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "phpDocumentor\\Reflection\\": "src"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Mike van Riel",
-                    "email": "me@mikevanriel.com"
-                },
-                {
-                    "name": "Jaap van Otterdijk",
-                    "email": "account@ijaap.nl"
-                }
-            ],
-            "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
-            "support": {
-                "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
-                "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master"
-            },
-            "time": "2020-09-03T19:13:55+00:00"
-        },
-        {
-            "name": "phpdocumentor/type-resolver",
-            "version": "1.4.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/phpDocumentor/TypeResolver.git",
-                "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
-                "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.2 || ^8.0",
-                "phpdocumentor/reflection-common": "^2.0"
-            },
-            "require-dev": {
-                "ext-tokenizer": "*"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-1.x": "1.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "phpDocumentor\\Reflection\\": "src"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Mike van Riel",
-                    "email": "me@mikevanriel.com"
-                }
-            ],
-            "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
-            "support": {
-                "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
-                "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0"
-            },
-            "time": "2020-09-17T18:55:26+00:00"
-        },
-        {
-            "name": "phpspec/prophecy",
-            "version": "v1.10.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/phpspec/prophecy.git",
-                "reference": "451c3cd1418cf640de218914901e51b064abb093"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093",
-                "reference": "451c3cd1418cf640de218914901e51b064abb093",
-                "shasum": ""
-            },
-            "require": {
-                "doctrine/instantiator": "^1.0.2",
-                "php": "^5.3|^7.0",
-                "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
-                "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0",
-                "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0"
-            },
-            "require-dev": {
-                "phpspec/phpspec": "^2.5 || ^3.2",
-                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.10.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Prophecy\\": "src/Prophecy"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Konstantin Kudryashov",
-                    "email": "ever.zet@gmail.com",
-                    "homepage": "http://everzet.com"
-                },
-                {
-                    "name": "Marcello Duarte",
-                    "email": "marcello.duarte@gmail.com"
-                }
-            ],
-            "description": "Highly opinionated mocking framework for PHP 5.3+",
-            "homepage": "https://github.com/phpspec/prophecy",
-            "keywords": [
-                "Double",
-                "Dummy",
-                "fake",
-                "mock",
-                "spy",
-                "stub"
-            ],
-            "support": {
-                "issues": "https://github.com/phpspec/prophecy/issues",
-                "source": "https://github.com/phpspec/prophecy/tree/v1.10.3"
-            },
-            "time": "2020-03-05T15:02:03+00:00"
-        },
-        {
-            "name": "phpunit/php-code-coverage",
-            "version": "2.2.4",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-                "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
-                "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.3",
-                "phpunit/php-file-iterator": "~1.3",
-                "phpunit/php-text-template": "~1.2",
-                "phpunit/php-token-stream": "~1.3",
-                "sebastian/environment": "^1.3.2",
-                "sebastian/version": "~1.0"
-            },
-            "require-dev": {
-                "ext-xdebug": ">=2.1.4",
-                "phpunit/phpunit": "~4"
-            },
-            "suggest": {
-                "ext-dom": "*",
-                "ext-xdebug": ">=2.2.1",
-                "ext-xmlwriter": "*"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "2.2.x-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sb@sebastian-bergmann.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
-            "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
-            "keywords": [
-                "coverage",
-                "testing",
-                "xunit"
-            ],
-            "support": {
-                "irc": "irc://irc.freenode.net/phpunit",
-                "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
-                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/2.2"
-            },
-            "time": "2015-10-06T15:47:00+00:00"
-        },
-        {
-            "name": "phpunit/php-file-iterator",
-            "version": "1.4.5",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
-                "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4",
-                "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.3"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.4.x-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sb@sebastian-bergmann.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "FilterIterator implementation that filters files based on a list of suffixes.",
-            "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
-            "keywords": [
-                "filesystem",
-                "iterator"
-            ],
-            "support": {
-                "irc": "irc://irc.freenode.net/phpunit",
-                "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
-                "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/1.4.5"
-            },
-            "time": "2017-11-27T13:52:08+00:00"
-        },
-        {
-            "name": "phpunit/php-text-template",
-            "version": "1.2.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/php-text-template.git",
-                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
-                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.3"
-            },
-            "type": "library",
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "Simple template engine.",
-            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
-            "keywords": [
-                "template"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
-                "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1"
-            },
-            "time": "2015-06-21T13:50:34+00:00"
-        },
-        {
-            "name": "phpunit/php-timer",
-            "version": "1.0.9",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/php-timer.git",
-                "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
-                "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^5.3.3 || ^7.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sb@sebastian-bergmann.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "Utility class for timing",
-            "homepage": "https://github.com/sebastianbergmann/php-timer/",
-            "keywords": [
-                "timer"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/php-timer/issues",
-                "source": "https://github.com/sebastianbergmann/php-timer/tree/master"
-            },
-            "time": "2017-02-26T11:10:40+00:00"
-        },
-        {
-            "name": "phpunit/php-token-stream",
-            "version": "1.4.12",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/php-token-stream.git",
-                "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16",
-                "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16",
-                "shasum": ""
-            },
-            "require": {
-                "ext-tokenizer": "*",
-                "php": ">=5.3.3"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "~4.2"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.4-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                }
-            ],
-            "description": "Wrapper around PHP's tokenizer extension.",
-            "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
-            "keywords": [
-                "tokenizer"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/php-token-stream/issues",
-                "source": "https://github.com/sebastianbergmann/php-token-stream/tree/1.4"
-            },
-            "abandoned": true,
-            "time": "2017-12-04T08:55:13+00:00"
-        },
-        {
-            "name": "phpunit/phpunit",
-            "version": "4.8.36",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "46023de9a91eec7dfb06cc56cb4e260017298517"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46023de9a91eec7dfb06cc56cb4e260017298517",
-                "reference": "46023de9a91eec7dfb06cc56cb4e260017298517",
-                "shasum": ""
-            },
-            "require": {
-                "ext-dom": "*",
-                "ext-json": "*",
-                "ext-pcre": "*",
-                "ext-reflection": "*",
-                "ext-spl": "*",
-                "php": ">=5.3.3",
-                "phpspec/prophecy": "^1.3.1",
-                "phpunit/php-code-coverage": "~2.1",
-                "phpunit/php-file-iterator": "~1.4",
-                "phpunit/php-text-template": "~1.2",
-                "phpunit/php-timer": "^1.0.6",
-                "phpunit/phpunit-mock-objects": "~2.3",
-                "sebastian/comparator": "~1.2.2",
-                "sebastian/diff": "~1.2",
-                "sebastian/environment": "~1.3",
-                "sebastian/exporter": "~1.2",
-                "sebastian/global-state": "~1.0",
-                "sebastian/version": "~1.0",
-                "symfony/yaml": "~2.1|~3.0"
-            },
-            "suggest": {
-                "phpunit/php-invoker": "~1.1"
-            },
-            "bin": [
-                "phpunit"
-            ],
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "4.8.x-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "The PHP Unit Testing framework.",
-            "homepage": "https://phpunit.de/",
-            "keywords": [
-                "phpunit",
-                "testing",
-                "xunit"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/phpunit/issues",
-                "source": "https://github.com/sebastianbergmann/phpunit/tree/4.8.36"
-            },
-            "time": "2017-06-21T08:07:12+00:00"
-        },
-        {
-            "name": "phpunit/phpunit-mock-objects",
-            "version": "2.3.8",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
-                "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
-                "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
-                "shasum": ""
-            },
-            "require": {
-                "doctrine/instantiator": "^1.0.2",
-                "php": ">=5.3.3",
-                "phpunit/php-text-template": "~1.2",
-                "sebastian/exporter": "~1.2"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "~4.4"
-            },
-            "suggest": {
-                "ext-soap": "*"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "2.3.x-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sb@sebastian-bergmann.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "Mock Object library for PHPUnit",
-            "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
-            "keywords": [
-                "mock",
-                "xunit"
-            ],
-            "support": {
-                "irc": "irc://irc.freenode.net/phpunit",
-                "issues": "https://github.com/sebastianbergmann/phpunit-mock-objects/issues",
-                "source": "https://github.com/sebastianbergmann/phpunit-mock-objects/tree/2.3"
-            },
-            "abandoned": true,
-            "time": "2015-10-02T06:51:40+00:00"
-        },
-        {
-            "name": "phpunit/phpunit-selenium",
-            "version": "2.0.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/giorgiosironi/phpunit-selenium.git",
-                "reference": "013037eeea481657d236431634042648797e1da8"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/giorgiosironi/phpunit-selenium/zipball/013037eeea481657d236431634042648797e1da8",
-                "reference": "013037eeea481657d236431634042648797e1da8",
-                "shasum": ""
-            },
-            "require": {
-                "ext-curl": "*",
-                "ext-dom": "*",
-                "php": ">=5.3.3",
-                "phpunit/phpunit": "~4.8",
-                "sebastian/comparator": "~1.0"
-            },
-            "require-dev": {
-                "phing/phing": "2.*"
-            },
-            "type": "library",
-            "autoload": {
-                "classmap": [
-                    "PHPUnit/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "include-path": [
-                ""
-            ],
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Giorgio Sironi",
-                    "email": "info@giorgiosironi.com",
-                    "role": "developer"
-                },
-                {
-                    "name": "Ivan Kurnosov",
-                    "email": "zerkms@zerkms.com",
-                    "role": "developer"
-                },
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sb@sebastian-bergmann.de",
-                    "role": "original developer"
-                }
-            ],
-            "description": "Selenium Server integration for PHPUnit",
-            "homepage": "http://www.phpunit.de/",
-            "keywords": [
-                "phpunit",
-                "selenium",
-                "testing",
-                "xunit"
-            ],
-            "support": {
-                "irc": "irc://irc.freenode.net/phpunit",
-                "issues": "https://github.com/sebastianbergmann/phpunit-selenium/issues",
-                "source": "https://github.com/giorgiosironi/phpunit-selenium/tree/2.x"
-            },
-            "time": "2017-01-23T22:15:32+00:00"
-        },
-        {
-            "name": "sebastian/comparator",
-            "version": "1.2.4",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/comparator.git",
-                "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
-                "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.3",
-                "sebastian/diff": "~1.2",
-                "sebastian/exporter": "~1.2 || ~2.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "~4.4"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.2.x-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Jeff Welch",
-                    "email": "whatthejeff@gmail.com"
-                },
-                {
-                    "name": "Volker Dusch",
-                    "email": "github@wallbash.com"
-                },
-                {
-                    "name": "Bernhard Schussek",
-                    "email": "bschussek@2bepublished.at"
-                },
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                }
-            ],
-            "description": "Provides the functionality to compare PHP values for equality",
-            "homepage": "http://www.github.com/sebastianbergmann/comparator",
-            "keywords": [
-                "comparator",
-                "compare",
-                "equality"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/comparator/issues",
-                "source": "https://github.com/sebastianbergmann/comparator/tree/1.2"
-            },
-            "time": "2017-01-29T09:50:25+00:00"
-        },
-        {
-            "name": "sebastian/diff",
-            "version": "1.4.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/diff.git",
-                "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4",
-                "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^5.3.3 || ^7.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.4-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Kore Nordmann",
-                    "email": "mail@kore-nordmann.de"
-                },
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                }
-            ],
-            "description": "Diff implementation",
-            "homepage": "https://github.com/sebastianbergmann/diff",
-            "keywords": [
-                "diff"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/diff/issues",
-                "source": "https://github.com/sebastianbergmann/diff/tree/1.4"
-            },
-            "time": "2017-05-22T07:24:03+00:00"
-        },
-        {
-            "name": "sebastian/environment",
-            "version": "1.3.8",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/environment.git",
-                "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea",
-                "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^5.3.3 || ^7.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^4.8 || ^5.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.3.x-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                }
-            ],
-            "description": "Provides functionality to handle HHVM/PHP environments",
-            "homepage": "http://www.github.com/sebastianbergmann/environment",
-            "keywords": [
-                "Xdebug",
-                "environment",
-                "hhvm"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/environment/issues",
-                "source": "https://github.com/sebastianbergmann/environment/tree/1.3"
-            },
-            "time": "2016-08-18T05:49:44+00:00"
-        },
-        {
-            "name": "sebastian/exporter",
-            "version": "1.2.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/exporter.git",
-                "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4",
-                "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.3",
-                "sebastian/recursion-context": "~1.0"
-            },
-            "require-dev": {
-                "ext-mbstring": "*",
-                "phpunit/phpunit": "~4.4"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.3.x-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Jeff Welch",
-                    "email": "whatthejeff@gmail.com"
-                },
-                {
-                    "name": "Volker Dusch",
-                    "email": "github@wallbash.com"
-                },
-                {
-                    "name": "Bernhard Schussek",
-                    "email": "bschussek@2bepublished.at"
-                },
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                },
-                {
-                    "name": "Adam Harvey",
-                    "email": "aharvey@php.net"
-                }
-            ],
-            "description": "Provides the functionality to export PHP variables for visualization",
-            "homepage": "http://www.github.com/sebastianbergmann/exporter",
-            "keywords": [
-                "export",
-                "exporter"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/exporter/issues",
-                "source": "https://github.com/sebastianbergmann/exporter/tree/master"
-            },
-            "time": "2016-06-17T09:04:28+00:00"
-        },
-        {
-            "name": "sebastian/global-state",
-            "version": "1.1.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/global-state.git",
-                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
-                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.3"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "~4.2"
-            },
-            "suggest": {
-                "ext-uopz": "*"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                }
-            ],
-            "description": "Snapshotting of global state",
-            "homepage": "http://www.github.com/sebastianbergmann/global-state",
-            "keywords": [
-                "global state"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/global-state/issues",
-                "source": "https://github.com/sebastianbergmann/global-state/tree/1.1.1"
-            },
-            "time": "2015-10-12T03:26:01+00:00"
-        },
-        {
-            "name": "sebastian/recursion-context",
-            "version": "1.0.5",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/recursion-context.git",
-                "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
-                "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.3"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "~4.4"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.0.x-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Jeff Welch",
-                    "email": "whatthejeff@gmail.com"
-                },
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                },
-                {
-                    "name": "Adam Harvey",
-                    "email": "aharvey@php.net"
-                }
-            ],
-            "description": "Provides functionality to recursively process PHP variables",
-            "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
-                "source": "https://github.com/sebastianbergmann/recursion-context/tree/master"
-            },
-            "time": "2016-10-03T07:41:43+00:00"
-        },
-        {
-            "name": "sebastian/version",
-            "version": "1.0.6",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/version.git",
-                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
-                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
-                "shasum": ""
-            },
-            "type": "library",
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "Library that helps with managing the version number of Git-hosted PHP projects",
-            "homepage": "https://github.com/sebastianbergmann/version",
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/version/issues",
-                "source": "https://github.com/sebastianbergmann/version/tree/1.0.6"
-            },
-            "time": "2015-06-21T13:59:46+00:00"
-        },
-        {
-            "name": "squizlabs/php_codesniffer",
-            "version": "2.9.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
-                "reference": "2acf168de78487db620ab4bc524135a13cfe6745"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/2acf168de78487db620ab4bc524135a13cfe6745",
-                "reference": "2acf168de78487db620ab4bc524135a13cfe6745",
-                "shasum": ""
-            },
-            "require": {
-                "ext-simplexml": "*",
-                "ext-tokenizer": "*",
-                "ext-xmlwriter": "*",
-                "php": ">=5.1.2"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "~4.0"
-            },
-            "bin": [
-                "scripts/phpcs",
-                "scripts/phpcbf"
-            ],
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "2.x-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "CodeSniffer.php",
-                    "CodeSniffer/CLI.php",
-                    "CodeSniffer/Exception.php",
-                    "CodeSniffer/File.php",
-                    "CodeSniffer/Fixer.php",
-                    "CodeSniffer/Report.php",
-                    "CodeSniffer/Reporting.php",
-                    "CodeSniffer/Sniff.php",
-                    "CodeSniffer/Tokens.php",
-                    "CodeSniffer/Reports/",
-                    "CodeSniffer/Tokenizers/",
-                    "CodeSniffer/DocGenerators/",
-                    "CodeSniffer/Standards/AbstractPatternSniff.php",
-                    "CodeSniffer/Standards/AbstractScopeSniff.php",
-                    "CodeSniffer/Standards/AbstractVariableSniff.php",
-                    "CodeSniffer/Standards/IncorrectPatternException.php",
-                    "CodeSniffer/Standards/Generic/Sniffs/",
-                    "CodeSniffer/Standards/MySource/Sniffs/",
-                    "CodeSniffer/Standards/PEAR/Sniffs/",
-                    "CodeSniffer/Standards/PSR1/Sniffs/",
-                    "CodeSniffer/Standards/PSR2/Sniffs/",
-                    "CodeSniffer/Standards/Squiz/Sniffs/",
-                    "CodeSniffer/Standards/Zend/Sniffs/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Greg Sherwood",
-                    "role": "lead"
-                }
-            ],
-            "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
-            "homepage": "http://www.squizlabs.com/php-codesniffer",
-            "keywords": [
-                "phpcs",
-                "standards"
-            ],
-            "support": {
-                "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
-                "source": "https://github.com/squizlabs/PHP_CodeSniffer",
-                "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
-            },
-            "time": "2018-11-07T22:31:41+00:00"
-        },
-        {
-            "name": "symfony/polyfill-ctype",
-            "version": "v1.20.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "f4ba089a5b6366e453971d3aad5fe8e897b37f41"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f4ba089a5b6366e453971d3aad5fe8e897b37f41",
-                "reference": "f4ba089a5b6366e453971d3aad5fe8e897b37f41",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "suggest": {
-                "ext-ctype": "For best performance"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.20-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Polyfill\\Ctype\\": ""
-                },
-                "files": [
-                    "bootstrap.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Gert de Pagter",
-                    "email": "BackEndTea@gmail.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill for ctype functions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "ctype",
-                "polyfill",
-                "portable"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.20.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2020-10-23T14:02:19+00:00"
-        },
-        {
-            "name": "symfony/yaml",
-            "version": "v3.4.47",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/yaml.git",
-                "reference": "88289caa3c166321883f67fe5130188ebbb47094"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/yaml/zipball/88289caa3c166321883f67fe5130188ebbb47094",
-                "reference": "88289caa3c166321883f67fe5130188ebbb47094",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^5.5.9|>=7.0.8",
-                "symfony/polyfill-ctype": "~1.8"
-            },
-            "conflict": {
-                "symfony/console": "<3.4"
-            },
-            "require-dev": {
-                "symfony/console": "~3.4|~4.0"
-            },
-            "suggest": {
-                "symfony/console": "For validating YAML files using the lint command"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Component\\Yaml\\": ""
-                },
-                "exclude-from-classmap": [
-                    "/Tests/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony Yaml Component",
-            "homepage": "https://symfony.com",
-            "support": {
-                "source": "https://github.com/symfony/yaml/tree/v3.4.47"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2020-10-24T10:57:07+00:00"
-        },
-        {
-            "name": "webmozart/assert",
-            "version": "1.9.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/webmozart/assert.git",
-                "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389",
-                "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^5.3.3 || ^7.0 || ^8.0",
-                "symfony/polyfill-ctype": "^1.8"
-            },
-            "conflict": {
-                "phpstan/phpstan": "<0.12.20",
-                "vimeo/psalm": "<3.9.1"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^4.8.36 || ^7.5.13"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Webmozart\\Assert\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Bernhard Schussek",
-                    "email": "bschussek@gmail.com"
-                }
-            ],
-            "description": "Assertions to validate method input/output with nice error messages.",
-            "keywords": [
-                "assert",
-                "check",
-                "validate"
-            ],
-            "support": {
-                "issues": "https://github.com/webmozart/assert/issues",
-                "source": "https://github.com/webmozart/assert/tree/master"
-            },
-            "time": "2020-07-08T17:02:28+00:00"
-        }
-    ],
-    "aliases": [],
-    "minimum-stability": "stable",
-    "stability-flags": {
-        "restler/framework": 5
-    },
-    "prefer-stable": false,
-    "prefer-lowest": false,
-    "platform": {
-        "php": ">=5.6.0",
-        "ext-curl": "*"
-    },
-    "platform-dev": [],
-    "plugin-api-version": "2.0.0"
-}

+ 11 - 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
@@ -271,6 +275,13 @@ RESTLER:
 	with 
 	
 	$loaders = array_unique(static::$rogueLoaders, SORT_REGULAR);
+	
+* Replace CommentParser.php line 423
+	elseif (count($value) && is_numeric($value[0]))
+	
+	with
+	
+	elseif (count($value) && isset($value[0]) && is_numeric($value[0]))
 
 
 +With swagger 2 provided into /explorer:

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

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

@@ -1,13 +0,0 @@
-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)
------------------------------------------
-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

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

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

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


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


+ 0 - 129
dev/resources/iso-normes/barcode_EAN13.txt

@@ -1,129 +0,0 @@
-Barcode EAN 13
-
-FR
-==
-Signification des chiffres.
-
-- 2 chiffres pour le code pays ou code systeme
-- 5 chiffres pour l'identificateur de societe
-- 5 chiffres pour l'identificateur d'article
-- 1 chiffre pour la somme de controle
-
-Cette regle subit de nombreuses entorses pour ameliorer l'usage des chiffres disponibles.
-Voici la liste des codes pays ou systeme :
-
-
-
-EN
-==
-Meaning of the numbers.
-
-- 2 digits for the country code or system code
-- 5 digits for the company identifier
-- 5 digits for item identifier
-- 1 digit for checksum
-
-This rule has been twisted many times to improve the use of the available numbers.
-Here is the list of country codes or system:
-
-
-
-List
-====
-
-00 � 13 UCC (Etats-Unis et Canada)
-20 � 29 Codification interne en magasin
-30 � 37 GENCOD-EAN France
-380 BCCI (Bulgarie)
-383 SANA (Slovenie)
-385 CRO-EAN (Croatie)
-387 EAN-BIH (Bosnie-Herzegovine)
-400 � 440 CCG (Allemagne)
-45 + 49 Distribution Code Center � DCC (Japon)
-460 � 469 UNISCAN - EAN Russie (Federation de Russie)
-471 CAN (Taiwan)
-474 EAN Estonie
-475 EAN Lettonie
-476 EAN Azerba� djan
-477 EAN Lituanie
-478 EAN Ouzbekistan
-479 EAN Sri Lanka
-480 PANC (Philippines)
-481 EAN Bielorussie
-482 EAN Ukraine
-484 EAN Moldavie
-485 EAN Armenie
-486 EAN Georgie
-487 EAN Kazakhstan
-489 HKANA (Hong Kong)
-50 E Centre UK
-520 HELLCAN-EAN HELLAS (Grece)
-528 EAN Liban
-529 EAN Chypre
-531 EAN-MAC (FYR Mac�donie)
-535 EAN Malte
-539 EAN Irlande
-54 ICODIF/EAN Belgique. Luxembourg
-560 CODIPOR (Portugal)
-569 EAN Islande
-57 EAN Danemark
-590 EAN Pologne
-594 EAN Roumanie
-599 H.A.P.M.H. (Hongrie)
-600 - 601 EAN Afrique du Sud
-609 EAN Ile Maurice
-611 EAN Maroc
-613 EAN Algerie
-619 Tunicode (Tunisie)
-621 EAN Syrie
-622 EAN Egypte
-625 EAN Jordanie
-626 EAN Iran
-628 EAN Arabie Saoudite
-64 EAN Finlande
-690 - 693 Article Numbering Centre of China - ANCC (Chine)
-70 EAN Norge (Norvege)
-729 Israeli Bar Code Association � EAN Israel
-73 EAN Suede
-740 EAN Guatemala
-741 EAN El Salvador
-742 ICCC (Honduras)
-743 EAN Nicaragua
-744 EAN Costa Rica Panama
-746 746 EAN Republique Dominicaine
-750 AMECE (Mexique)
-759 EAN Venezuela
-76 EAN (Schweiz, Suisse, Svizzera)
-770 IAC (Colombie)
-773 EAN Uruguay
-775 APC - EAN Peru (Perou)
-777 EAN Bolivie
-779 CODIGO - EAN Argentine
-780 EAN Chili
-784 EAN Paraguay
-786 ECOP (Equateur)
-789 EAN Bresil
-80 � 83 INDICOD (Italie)
-84 AECOC (Espagne)
-850 Camera de Comercio de la Republica de Cuba (Cuba)
-858 EAN Slovaquie
-859 EAN Republique Tcheque
-860 EAN YU (Yougoslavie)
-867 EAN DPR Korea (Coree du Nord)
-869 Union of Chambers of Commerce of Turkey (Turquie)
-87 EAN Nederland (Hollande)
-880 EAN Korea (Coree du Sud)
-885 EAN Thailande
-888 SANC (Singapour)
-890 EAN Inde
-893 EAN Vietnam
-899 EAN Indonesie
-90 - 91 EAN Autriche
-93 EAN Australie
-94 EAN Nouvelle Zelande
-955 Malaysian Article Numbering Council (MANC) - Malaisie
-977 Publications sirielles (ISSN)
-978 - 979 Livres (ISBN)
-980 Refus de remboursement
-981 - 982 Coupons (monnaie courante)
-99 Coupons

+ 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

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

@@ -0,0 +1,29 @@
+QR-Code = Quick Response Code   -  is a two-dimensional / 2D- / Matrix-Barcode
+
+ISO/IEC 18004
+
+
+List of QR Code format we found on some invoices
+------------------------------------------------
+
+
+* 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 
+
+
+* FOR QR-Bill in switzerland
+----------------------------
+Syntax of QR Code https://www.swiss-qr-invoice.org/fr/
+Syntax of complentary field named "structured information of invoice S1": https://www.swiss-qr-invoice.org/downloads/qr-bill-s1-syntax-fr.pdf
+To test/validate: https://www.swiss-qr-invoice.org/validator/

+ 129 - 0
dev/resources/iso-normes/qr-bar-codes/barcode_EAN13.txt

@@ -0,0 +1,129 @@
+Barcode EAN 13
+
+FR
+==
+Signification des chiffres.
+
+- 2 chiffres pour le code pays ou code systeme
+- 5 chiffres pour l'identificateur de societe
+- 5 chiffres pour l'identificateur d'article
+- 1 chiffre pour la somme de controle
+
+Cette regle subit de nombreuses entorses pour ameliorer l'usage des chiffres disponibles.
+Voici la liste des codes pays ou systeme :
+
+
+
+EN
+==
+Meaning of the numbers:
+
+- first 2-3 digits for the country code or system code
+- 5 digits for the company identifier
+- 5 digits for item identifier
+- 1 digit for checksum
+
+This rule has been twisted many times to improve the use of the available numbers.
+
+Here is the list of country codes or system:
+
+
+List
+====
+
+00 - 13  UCC  (U.S.A / États-Unis & Canada)
+20 - 29  Flag for internal numbering / Codification interne en magasin
+30 - 37  GENCOD-EAN  France
+380      BCCI  (Bulgaria)
+383      SANA  (Slovenia)
+385      CRO-EAN  (Croatia)
+387      EAN-BIH  (Bosnia-Herzegovina)
+400-440  CCG  (DE/Germany/Allemagne)
+45 + 49  Distribution Code Center - DCC  (Japan)
+460-469  UNISCAN - EAN Russia  (Federation de Russie)
+471      CAN  Taiwan
+474      EAN  Estonia
+475      EAN  Latvia
+476      EAN  Azerbaijan
+477      EAN  Lithuania
+478      EAN  Uzbekistan
+479      EAN  Sri Lanka
+480      PANC Philippines
+481      EAN  Belarus
+482      EAN  Ukraine
+484      EAN  Moldova 
+485      EAN  Armenia
+486      EAN  Georgia
+487      EAN  Kazakhstan
+489      HKANA Hong Kong
+50       E Centre UK - United Kingdom 
+520      HELLCAN-EAN HELLAS - Greece
+528      EAN  Lebanon
+529      EAN  Cyprus
+531      EAN-MAC (FYR Macedonia)
+535      EAN  Malta
+539      EAN  Ireland
+54       ICODIF/EAN  Belgium & Luxembourg
+560      CODIPOR (Portugal)
+569      EAN  Iceland/Islande
+57       EAN  Denmark
+590      EAN  Poland
+594      EAN  Romania
+599      H.A.P.M.H. (Hungary)
+600-601  EAN  South Africa
+609      EAN  Mauritius Island
+611      EAN  Morocco
+613      EAN  Algeria
+619      Tunicode (Tunisia)
+621      EAN  Syria
+622      EAN  Egypt
+625      EAN  Jordan/Jordanie
+626      EAN  Iran
+628      EAN  Saudi Arabia
+64       EAN  Finland
+690-693  ANCC - Article Numbering Centre of China
+70  EAN Norge (Norvege)
+729 Israeli Bar Code Association - EAN Israel
+73  EAN Suede
+740 EAN Guatemala
+741 EAN El Salvador
+742 ICCC (Honduras)
+743 EAN Nicaragua
+744 EAN Costa Rica Panama
+746 746 EAN Republique Dominicaine
+750 AMECE (Mexique)
+759 EAN Venezuela
+76  EAN (Schweiz, Suisse, Svizzera)
+770 IAC (Colombie)
+773 EAN Uruguay
+775 APC - EAN Peru (Perou)
+777 EAN Bolivie
+779 CODIGO - EAN Argentine
+780 EAN Chili
+784 EAN Paraguay
+786 ECOP (Equateur)
+789 EAN Bresil
+80 - 83 INDICOD (Italy)
+84  AECOC (Espagne)
+850 Camera de Comercio de la Republica de Cuba (Cuba)
+858 EAN Slovaquie
+859 EAN Republique Tcheque
+860 EAN YU (Yougoslavie)
+867 EAN DPR Korea (Coree du Nord)
+869 Union of Chambers of Commerce of Turkey (Turquie)
+87  EAN Nederland (Hollande)
+880 EAN Korea (Coree du Sud)
+885 EAN Thailande
+888 SANC (Singapour)
+890 EAN Inde
+893 EAN Vietnam
+899 EAN Indonesie
+90 - 91 EAN Autriche
+93  EAN Australie
+94  EAN Nouvelle Zelande
+955 Malaysian Article Numbering Council (MANC) - Malaisie
+977 Publications sirielles (ISSN)
+978 - 979 Livres (ISBN)
+980 Refus de remboursement
+981 - 982 Coupons (monnaie courante)
+99  Coupons

+ 1 - 1
dev/setup/git/hooks/pre-commit

@@ -7,7 +7,7 @@
 # To run the fix manually: cd ~/git/dolibarr; phpcbf -s -p -d memory_limit=-1 --extensions=php --colors --tab-width=4 --standard=dev/setup/codesniffer/ruleset.xml --encoding=utf-8 --runtime-set ignore_warnings_on_exit true "fileordir"
 
 PROJECT=`php -r "echo dirname(dirname(dirname(realpath('$0'))));"`
-STAGED_FILES_CMD=`git diff --cached --name-only --diff-filter=ACMR HEAD | grep \\\\.php`
+STAGED_FILES_CMD=`git diff --cached --name-only --diff-filter=ACMR HEAD | grep -v '/includes/'| grep \\\\.php`
 DIRPHPCS=""
 AUTOFIX=1
 

+ 1 - 1
dev/tools/test/namespacemig/main.inc.php

@@ -1,7 +1,7 @@
 <?php
 
 /*spl_autoload_register(function ($class_name) {
-	var_dump('class='.$class_name);
+	//var_dump('class='.$class_name);
 	require $class_name;
 });
 */

+ 2 - 0
htdocs/accountancy/admin/account.php

@@ -350,6 +350,8 @@ if ($resql) {
 	    	</script>';
 	}
 
+	$newcardbutton = '';
+
 	print '<form method="POST" id="searchFormList" action="'.$_SERVER["PHP_SELF"].'">';
 	if ($optioncss != '') {
 		print '<input type="hidden" name="optioncss" value="'.$optioncss.'">';

+ 6 - 11
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++;
 		}

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

@@ -300,7 +300,7 @@ if ($action == 'create') {
 
 		// Edit mode
 		if ($action == 'update') {
-			print dol_get_fiche_head($head, 'card', $langs->trans('AccountAccounting'), 0, 'billr');
+			print dol_get_fiche_head($head, 'card', $langs->trans('AccountAccounting'), 0, 'accounting_account');
 
 			print '<form name="update" action="'.$_SERVER["PHP_SELF"].'" method="POST">'."\n";
 			print '<input type="hidden" name="token" value="'.newToken().'">';
@@ -368,7 +368,7 @@ if ($action == 'create') {
 			// View mode
 			$linkback = '<a href="'.DOL_URL_ROOT.'/accountancy/admin/account.php?restore_lastsearch_values=1">'.$langs->trans("BackToList").'</a>';
 
-			print dol_get_fiche_head($head, 'card', $langs->trans('AccountAccounting'), -1, 'billr');
+			print dol_get_fiche_head($head, 'card', $langs->trans('AccountAccounting'), -1, 'accounting_account');
 
 			dol_banner_tab($object, 'ref', $linkback, 1, 'account_number', 'ref');
 

+ 7 - 18
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++;
 		}

+ 61 - 11
htdocs/accountancy/admin/defaultaccounts.php

@@ -81,31 +81,31 @@ $list_account[] = '---Others---';
 $list_account[] = 'ACCOUNTING_VAT_BUY_ACCOUNT';
 $list_account[] = 'ACCOUNTING_VAT_SOLD_ACCOUNT';
 $list_account[] = 'ACCOUNTING_VAT_PAY_ACCOUNT';
-if ($conf->banque->enabled) {
+if (!empty($conf->banque->enabled)) {
 	$list_account[] = 'ACCOUNTING_ACCOUNT_TRANSFER_CASH';
 }
-if ($conf->don->enabled) {
+if (!empty($conf->don->enabled)) {
 	$list_account[] = 'DONATION_ACCOUNTINGACCOUNT';
 }
-if ($conf->adherent->enabled) {
+if (!empty($conf->adherent->enabled)) {
 	$list_account[] = 'ADHERENT_SUBSCRIPTION_ACCOUNTINGACCOUNT';
 }
-if ($conf->loan->enabled) {
+if (!empty($conf->loan->enabled)) {
 	$list_account[] = 'LOAN_ACCOUNTING_ACCOUNT_CAPITAL';
 	$list_account[] = 'LOAN_ACCOUNTING_ACCOUNT_INTEREST';
 	$list_account[] = 'LOAN_ACCOUNTING_ACCOUNT_INSURANCE';
 }
-if ($conf->societe->enabled) {
-	$list_account[] = 'ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT';
-}
 $list_account[] = 'ACCOUNTING_ACCOUNT_SUSPENSE';
+if (!empty($conf->societe->enabled)) {
+	$list_account[] = '---Deposits---';
+}
 
 /*
  * Actions
  */
 if ($action == 'update') {
 	$error = 0;
-
+	// Process $list_account_main
 	foreach ($list_account_main as $constname) {
 		$constvalue = GETPOST($constname, 'alpha');
 
@@ -113,7 +113,7 @@ if ($action == 'update') {
 			$error++;
 		}
 	}
-
+	// Process $list_account
 	foreach ($list_account as $constname) {
 		$reg = array();
 		if (preg_match('/---(.*)---/', $constname, $reg)) {	// This is a separator
@@ -127,6 +127,13 @@ if ($action == 'update') {
 		}
 	}
 
+	$constname = 'ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT';
+	$constvalue = GETPOST($constname, 'int');
+	if (!dolibarr_set_const($db, $constname, $constvalue, 'chaine', 0, '', $conf->entity)) {
+		$error++;
+	}
+
+
 	if (!$error) {
 		setEventMessages($langs->trans("SetupSaved"), null, 'mesgs');
 	} else {
@@ -134,6 +141,20 @@ if ($action == 'update') {
 	}
 }
 
+if ($action == 'setdisableauxiliaryaccountoncustomerdeposit') {
+	$setDisableAuxiliaryAccountOnCustomerDeposit = GETPOST('value', 'int');
+	$res = dolibarr_set_const($db, "ACCOUNTING_ACCOUNT_CUSTOMER_USE_AUXILIARY_ON_DEPOSIT", $setDisableAuxiliaryAccountOnCustomerDeposit, 'yesno', 0, '', $conf->entity);
+	if (!($res > 0)) {
+		$error++;
+	}
+
+	if (!$error) {
+		setEventMessages($langs->trans("SetupSaved"), null, 'mesgs');
+	} else {
+		setEventMessages($langs->trans("Error"), null, 'mesgs');
+	}
+}
+
 
 /*
  * View
@@ -180,7 +201,8 @@ foreach ($list_account_main as $key) {
 	print '</td>';
 	// Value
 	print '<td class="right">'; // Do not force class=right, or it align also the content of the select box
-	print $formaccounting->select_account($conf->global->$key, $key, 1, '', 1, 1, 'minwidth100 maxwidth300 maxwidthonsmartphone', 'accountsmain');
+	$key_value = getDolGlobalString($key);
+	print $formaccounting->select_account($key_value, $key, 1, '', 1, 1, 'minwidth100 maxwidth300 maxwidthonsmartphone', 'accountsmain');
 	print '</td>';
 	print '</tr>';
 }
@@ -232,10 +254,38 @@ foreach ($list_account as $key) {
 }
 
 
+// Customer deposit account
+print '<tr class="oddeven value">';
+// Param
+print '<td>';
+print img_picto('', 'bill', 'class="pictofixedwidth"') . $langs->trans('ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT');
+print '</td>';
+// Value
+print '<td class="right">'; // Do not force class=right, or it align also the content of the select box
+print $formaccounting->select_account(getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT'), 'ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT', 1, '', 1, 1, 'minwidth100 maxwidth300 maxwidthonsmartphone', 'accounts');
+print '</td>';
+print '</tr>';
+
+
+if (!empty($conf->societe->enabled)) {
+	print '<tr class="oddeven">';
+	print '<td>' . img_picto('', 'bill', 'class="pictofixedwidth"') . $langs->trans("UseAuxiliaryAccountOnCustomerDeposit") . '</td>';
+	if (getDolGlobalInt('ACCOUNTING_ACCOUNT_CUSTOMER_USE_AUXILIARY_ON_DEPOSIT')) {
+		print '<td class="right"><a class="reposition" href="' . $_SERVER['PHP_SELF'] . '?token=' . newToken() . '&action=setdisableauxiliaryaccountoncustomerdeposit&value=0">';
+		print img_picto($langs->trans("Activated"), 'switch_on', '', false, 0, 0, '', 'warning');
+		print '</a></td>';
+	} else {
+		print '<td class="right"><a class="reposition" href="' . $_SERVER['PHP_SELF'] . '?token=' . newToken() . '&action=setdisableauxiliaryaccountoncustomerdeposit&value=1">';
+		print img_picto($langs->trans("Disabled"), 'switch_off');
+		print '</a></td>';
+	}
+	print '</tr>';
+}
+
 print "</table>\n";
 print "</div>\n";
 
-print '<div class="center"><input type="submit" class="button button-edit" name="button" value="'.$langs->trans('Modify').'"></div>';
+print '<div class="center"><input type="submit" class="button button-edit" name="button" value="'.$langs->trans('Save').'"></div>';
 
 print '</form>';
 

+ 14 - 12
htdocs/accountancy/admin/export.php

@@ -1,11 +1,11 @@
 <?php
-/* Copyright (C) 2013-2014 Olivier Geffroy		<jeff@jeffinfo.com>
- * Copyright (C) 2013-2017 Alexandre Spangaro	<aspangaro@open-dsi.fr>
- * Copyright (C) 2014	   Florian Henry		<florian.henry@open-concept.pro>
- * Copyright (C) 2014      Marcos García        <marcosgdf@gmail.com>
- * Copyright (C) 2014	   Juanjo Menent		<jmenent@2byte.es>
- * Copyright (C) 2015      Jean-François Ferry	<jfefe@aternatik.fr>
- * Copyright (C) 2017-2018 Frédéric France      <frederic.france@netlogic.fr>
+/* Copyright (C) 2013-2014  Olivier Geffroy		<jeff@jeffinfo.com>
+ * Copyright (C) 2013-2022  Alexandre Spangaro	<aspangaro@open-dsi.fr>
+ * Copyright (C) 2014	    Florian Henry		<florian.henry@open-concept.pro>
+ * Copyright (C) 2014       Marcos García        <marcosgdf@gmail.com>
+ * Copyright (C) 2014	    Juanjo Menent		<jmenent@2byte.es>
+ * Copyright (C) 2015       Jean-François Ferry	<jfefe@aternatik.fr>
+ * Copyright (C) 2017-2018  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
@@ -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';
 
@@ -47,7 +47,8 @@ $main_option = array(
 	'ACCOUNTING_EXPORT_PREFIX_SPEC',
 );
 
-$configuration = AccountancyExport::getTypeConfig();
+$accountancyexport = new AccountancyExport($db);
+$configuration = $accountancyexport->getTypeConfig();
 
 $listparam = $configuration['param'];
 
@@ -117,7 +118,7 @@ if ($action == 'update') {
 
 	if (!$error) {
 		// reload
-		$configuration = AccountancyExport::getTypeConfig();
+		$configuration = $accountancyexport->getTypeConfig();
 		$listparam = $configuration['param'];
 		setEventMessages($langs->trans("SetupSaved"), null, 'mesgs');
 	} else {
@@ -210,7 +211,8 @@ if ($num) {
 
 		// Value
 		print '<td>';
-		print '<input type="text" size="20" id="'.$key.'" name="'.$key.'" value="'.$conf->global->$key.'">';
+		$key_value = getDolGlobalString($conf->global->$key, $conf->global->$key);
+		print '<input type="text" size="20" id="'.$key.'" name="'.$key.'" value="'.$key_value.'">';
 		print '</td></tr>';
 	}
 }
@@ -237,7 +239,7 @@ if (!$conf->use_javascript_ajax) {
 	print "</td>";
 } else {
 	print '<td>';
-	$listmodelcsv = AccountancyExport::getType();
+	$listmodelcsv = $accountancyexport->getType();
 	print $form->selectarray("ACCOUNTING_EXPORT_MODELCSV", $listmodelcsv, $conf->global->ACCOUNTING_EXPORT_MODELCSV, 0, 0, 0, '', 0, 0, 0, '', '', 1);
 
 	print '</td>';

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

@@ -409,7 +409,7 @@ foreach ($list_binding as $key) {
 	// Value
 	print '<td class="right">';
 	if ($key == 'ACCOUNTING_DATE_START_BINDING') {
-		print $form->selectDate(($conf->global->$key ? $db->idate($conf->global->$key) : -1), $key, 0, 0, 1);
+		print $form->selectDate((!empty($conf->global->$key) ? $db->idate($conf->global->$key) : -1), $key, 0, 0, 1);
 	} elseif ($key == 'ACCOUNTING_DEFAULT_PERIOD_ON_TRANSFER') {
 		$array = array(0=>$langs->trans("PreviousMonth"), 1=>$langs->trans("CurrentMonth"), 2=>$langs->trans("Fiscalyear"));
 		print $form->selectarray($key, $array, (isset($conf->global->ACCOUNTING_DEFAULT_PERIOD_ON_TRANSFER) ? $conf->global->ACCOUNTING_DEFAULT_PERIOD_ON_TRANSFER : 0));

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

@@ -1,5 +1,5 @@
 <?php
-/* Copyright (C) 2017		Alexandre Spangaro   <aspangaro@open-dsi.fr>
+/* Copyright (C) 2017-2022  Alexandre Spangaro   <aspangaro@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
@@ -76,6 +76,8 @@ if (empty($sortorder)) {
 
 $error = 0;
 
+$search_country_id = GETPOST('search_country_id', 'int');
+
 // Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context
 $hookmanager->initHooks(array('admin'));
 
@@ -165,45 +167,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 +211,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 +227,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 +254,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 +287,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 +374,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') {
@@ -510,7 +474,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 +599,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>';
 							}
 						}
 					}

+ 108 - 15
htdocs/accountancy/admin/productaccount.php

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

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

@@ -349,7 +349,7 @@ if ($action != 'export_csv') {
 		$sql .= " GROUP BY t.numero_compte";
 
 		$resql = $db->query($sql);
-		$nrows = $resql->num_rows;
+		$nrows = $db->num_rows($resql);
 		$opening_balances = array();
 		for ($i = 0; $i < $nrows; $i++) {
 			$arr = $resql->fetch_array();

+ 42 - 35
htdocs/accountancy/bookkeeping/card.php

@@ -267,7 +267,7 @@ if ($action == "confirm_update") {
 			if ($mode != '_tmp') {
 				setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
 			}
-			$action = 'update';
+			$action = '';
 			$id = $object->id;
 			$piece_num = $object->piece_num;
 		}
@@ -431,12 +431,12 @@ if ($action == 'create') {
 		// Account movement
 		print '<tr>';
 		print '<td class="titlefield">'.$langs->trans("NumMvts").'</td>';
-		print '<td>'.$object->piece_num.'</td>';
+		print '<td>'.($mode == '_tmp' ? '<span class="opacitymedium" title="Id tmp '.$object->piece_num.'">'.$langs->trans("Draft").'</span>' : $object->piece_num).'</td>';
 		print '</tr>';
 
 		// Date
 		print '<tr><td>';
-		print '<table class="nobordernopadding" width="100%"><tr><td>';
+		print '<table class="nobordernopadding centpercent"><tr><td>';
 		print $langs->trans('Docdate');
 		print '</td>';
 		if ($action != 'editdate') {
@@ -540,7 +540,7 @@ if ($action == 'create') {
 		print '</td>';
 		print '</tr>';
 
-		// Date document creation
+		// Date document export
 		print '<tr>';
 		print '<td class="titlefield">'.$langs->trans("DateExport").'</td>';
 		print '<td>';
@@ -548,7 +548,7 @@ if ($action == 'create') {
 		print '</td>';
 		print '</tr>';
 
-		// Date document creation
+		// Date document validation
 		print '<tr>';
 		print '<td class="titlefield">'.$langs->trans("DateValidation").'</td>';
 		print '<td>';
@@ -607,6 +607,7 @@ if ($action == 'create') {
 		print '<br>';
 
 		$result = $object->fetchAllPerMvt($piece_num, $mode);	// This load $object->linesmvt
+
 		if ($result < 0) {
 			setEventMessages($object->error, $object->errors, 'errors');
 		} else {
@@ -647,6 +648,15 @@ if ($action == 'create') {
 
 				print "</tr>\n";
 
+				// Add an empty line if there is not yet
+				if (!empty($object->linesmvt[0])) {
+					$tmpline = $object->linesmvt[0];
+					if (!empty($tmpline->numero_compte)) {
+						$line = new BookKeepingLine();
+						$object->linesmvt[] = $line;
+					}
+				}
+
 				foreach ($object->linesmvt as $line) {
 					print '<tr class="oddeven">';
 					$total_debit += $line->debit;
@@ -663,7 +673,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")).'">';
 						}
@@ -677,7 +687,33 @@ if ($action == 'create') {
 						print '<input type="hidden" name="id" value="'.$line->id.'">'."\n";
 						print '<input type="submit" class="button" name="update" value="'.$langs->trans("Update").'">';
 						print '</td>';
+					} elseif (empty($line->numero_compte) || (empty($line->debit) && empty($line->credit))) {
+						if ($action == "" || $action == 'add') {
+							print '<!-- td columns in add mode -->';
+							print '<td>';
+							print $formaccounting->select_account('', 'accountingaccount_number', 1, array(), 1, 1, '');
+							print '</td>';
+							print '<td>';
+							// TODO For the moment we keep a free input text instead of a combo. The select_auxaccount has problem because:
+							// It does not use the setup of "key pressed" to select a thirdparty and this hang browser on large databases.
+							// Also, it is not possible to use a value that is not in the list.
+							// Also, the label is not automatically filled when a value is selected.
+							if (!empty($conf->global->ACCOUNTANCY_COMBO_FOR_AUX)) {
+								print $formaccounting->select_auxaccount('', 'subledger_account', 1, 'maxwidth250', '', 'subledger_label');
+							} else {
+								print '<input type="text" class="maxwidth150" name="subledger_account" value="" placeholder="' . dol_escape_htmltag($langs->trans("SubledgerAccount")) . '">';
+							}
+							print '<br><input type="text" class="maxwidth150" name="subledger_label" value="" placeholder="' . dol_escape_htmltag($langs->trans("SubledgerAccountLabel")) . '">';
+							print '</td>';
+							print '<td><input type="text" class="minwidth200" name="label_operation" value="' . $label_operation . '"/></td>';
+							print '<td class="right"><input type="text" size="6" class="right" name="debit" value=""/></td>';
+							print '<td class="right"><input type="text" size="6" class="right" name="credit" value=""/></td>';
+							print '<td>';
+							print '<input type="submit" class="button" name="save" value="' . $langs->trans("Add") . '">';
+							print '</td>';
+						}
 					} else {
+						print '<!-- td columns in display mode -->';
 						$resultfetch = $accountingaccount->fetch(null, $line->numero_compte, true);
 						print '<td>';
 						if ($resultfetch > 0) {
@@ -733,35 +769,6 @@ if ($action == 'create') {
 					setEventMessages(null, array($langs->trans('MvtNotCorrectlyBalanced', $total_debit, $total_credit)), 'warnings');
 				}
 
-				if (empty($object->date_export) && empty($object->date_validation)) {
-					if ($action == "" || $action == 'add') {
-						print '<tr class="oddeven">';
-						print '<!-- td columns in add mode -->';
-						print '<td>';
-						print $formaccounting->select_account('', 'accountingaccount_number', 1, array(), 1, 1, '');
-						print '</td>';
-						print '<td>';
-						// TODO For the moment we keep a free input text instead of a combo. The select_auxaccount has problem because:
-						// It does not use the setup of "key pressed" to select a thirdparty and this hang browser on large databases.
-						// Also, it is not possible to use a value that is not in the list.
-						// Also, the label is not automatically filled when a value is selected.
-						if (!empty($conf->global->ACCOUNTANCY_COMBO_FOR_AUX)) {
-							print $formaccounting->select_auxaccount('', 'subledger_account', 1);
-						} else {
-							print '<input type="text" class="maxwidth150" name="subledger_account" value="" placeholder="' . dol_escape_htmltag($langs->trans("SubledgerAccount")) . '">';
-						}
-						print '<br><input type="text" class="maxwidth150" name="subledger_label" value="" placeholder="' . dol_escape_htmltag($langs->trans("SubledgerAccountLabel")) . '">';
-						print '</td>';
-						print '<td><input type="text" class="minwidth200" name="label_operation" value="' . $label_operation . '"/></td>';
-						print '<td class="right"><input type="text" size="6" class="right" name="debit" value=""/></td>';
-						print '<td class="right"><input type="text" size="6" class="right" name="credit" value=""/></td>';
-						print '<td>';
-						print '<input type="submit" class="button" name="save" value="' . $langs->trans("Add") . '">';
-						print '</td>';
-						print '</tr>';
-					}
-				}
-
 				print '</table>';
 				print '</div>';
 

+ 271 - 144
htdocs/accountancy/bookkeeping/list.php

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

+ 331 - 128
htdocs/accountancy/bookkeeping/listbyaccount.php

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

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

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

+ 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', 'aZ09comma');
-$sortorder = GETPOST('sortorder', 'aZ09comma');
-$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', 'aZ09comma');
-$sortorder = GETPOST('sortorder', 'aZ09comma');
-$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();

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

@@ -11,6 +11,7 @@
  * Copyright (C) 2017-2019  Frédéric France     <frederic.france@netlogic.fr>
  * Copyright (C) 2017       André Schild        <a.schild@aarboard.ch>
  * Copyright (C) 2020       Guillaume Alexandre <guillaume@tag-info.fr>
+ * Copyright (C) 2022		Joachim Kueter		<jkueter@gmx.de>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -34,6 +35,7 @@
 
 require_once DOL_DOCUMENT_ROOT.'/core/lib/functions.lib.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php';
 
 
 /**
@@ -64,6 +66,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)
@@ -89,11 +95,13 @@ class AccountancyExport
 	 */
 	public function __construct(DoliDB $db)
 	{
-		global $conf;
+		global $conf, $hookmanager;
 
 		$this->db = $db;
 		$this->separator = $conf->global->ACCOUNTING_EXPORT_SEPARATORCSV;
 		$this->end_line = empty($conf->global->ACCOUNTING_EXPORT_ENDLINE) ? "\n" : ($conf->global->ACCOUNTING_EXPORT_ENDLINE == 1 ? "\n" : "\r\n");
+
+		$hookmanager->initHooks(array('accountancyexport'));
 	}
 
 	/**
@@ -101,9 +109,9 @@ class AccountancyExport
 	 *
 	 * @return array of type
 	 */
-	public static function getType()
+	public function getType()
 	{
-		global $langs;
+		global $langs, $hookmanager;
 
 		$listofexporttypes = array(
 			self::$EXPORT_TYPE_CONFIGURABLE => $langs->trans('Modelcsv_configurable'),
@@ -128,6 +136,10 @@ class AccountancyExport
 			self::$EXPORT_TYPE_ISUITEEXPERT => 'Export iSuite Expert',
 		);
 
+		// allow modules to define export formats
+		$parameters = array();
+		$reshook = $hookmanager->executeHooks('getType', $parameters, $listofexporttypes);
+
 		ksort($listofexporttypes, SORT_NUMERIC);
 
 		return $listofexporttypes;
@@ -164,7 +176,12 @@ class AccountancyExport
 			self::$EXPORT_TYPE_ISUITEEXPERT => 'isuiteexpert',
 		);
 
-		return $formatcode[$type];
+		global $hookmanager;
+		$code = $formatcode[$type];
+		$parameters = array('type' => $type);
+		$reshook = $hookmanager->executeHooks('getFormatCode', $parameters, $code);
+
+		return $code;
 	}
 
 	/**
@@ -172,11 +189,11 @@ class AccountancyExport
 	 *
 	 * @return array of type
 	 */
-	public static function getTypeConfig()
+	public function getTypeConfig()
 	{
 		global $conf, $langs;
 
-		return array(
+		$exporttypes = array(
 			'param' => array(
 				self::$EXPORT_TYPE_CONFIGURABLE => array(
 					'label' => $langs->trans('Modelcsv_configurable'),
@@ -261,6 +278,11 @@ class AccountancyExport
 				'txt' => $langs->trans("txt")
 			),
 		);
+
+		global $hookmanager;
+		$parameters = array();
+		$reshook = $hookmanager->executeHooks('getTypeConfig', $parameters, $exporttypes);
+		return $exporttypes;
 	}
 
 
@@ -346,7 +368,13 @@ class AccountancyExport
 				$this->exportiSuiteExpert($TData);
 				break;
 			default:
-				$this->errors[] = $langs->trans('accountancy_error_modelnotfound');
+				global $hookmanager;
+				$parameters = array('format' => $formatexportset);
+				// file contents will be created in the hooked function via print
+				$reshook = $hookmanager->executeHooks('export', $parameters, $TData);
+				if ($reshook != 1) {
+					$this->errors[] = $langs->trans('accountancy_error_modelnotfound');
+				}
 				break;
 		}
 	}
@@ -976,6 +1004,8 @@ class AccountancyExport
 				print dol_string_unaccent($date_creation) . $separator;
 
 				// FEC:EcritureLib
+				// Clean label operation to prevent problem on export with tab separator & other character
+				$line->label_operation = str_replace(array("\t", "\n", "\r"), " ", $line->label_operation);
 				print dol_string_unaccent($line->label_operation) . $separator;
 
 				// FEC:Debit
@@ -1003,6 +1033,8 @@ class AccountancyExport
 				print $date_limit_payment . $separator;
 
 				// FEC_suppl:NumFacture
+				// Clean ref invoice to prevent problem on export with tab separator & other character
+				$refInvoice = str_replace(array("\t", "\n", "\r"), " ", $refInvoice);
 				print dol_trunc(self::toAnsi($refInvoice), 17, 'right', 'UTF-8', 1);
 
 				print $end_line;
@@ -1103,6 +1135,8 @@ class AccountancyExport
 				print $date_document . $separator;
 
 				// FEC:EcritureLib
+				// Clean label operation to prevent problem on export with tab separator & other character
+				$line->label_operation = str_replace(array("\t", "\n", "\r"), " ", $line->label_operation);
 				print dol_string_unaccent($line->label_operation) . $separator;
 
 				// FEC:Debit
@@ -1130,6 +1164,8 @@ class AccountancyExport
 				print $date_limit_payment . $separator;
 
 				// FEC_suppl:NumFacture
+				// Clean ref invoice to prevent problem on export with tab separator & other character
+				$refInvoice = str_replace(array("\t", "\n", "\r"), " ", $refInvoice);
 				print dol_trunc(self::toAnsi($refInvoice), 17, 'right', 'UTF-8', 1);
 
 
@@ -1708,6 +1744,8 @@ class AccountancyExport
 
 			print self::trunc($line->label_compte, 60).$separator; //Account label
 			print self::trunc($line->doc_ref, 20).$separator; //Piece
+			// Clean label operation to prevent problem on export with tab separator & other character
+			$line->label_operation = str_replace(array("\t", "\n", "\r"), " ", $line->label_operation);
 			print self::trunc($line->label_operation, 60).$separator; //Operation label
 			print price(abs($line->debit - $line->credit)).$separator; //Amount
 			print $line->sens.$separator; //Direction

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

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

@@ -586,11 +586,11 @@ class AccountingAccount extends CommonObject
 		$sql .= ' WHERE a.rowid = ' . ((int) $id);
 
 		dol_syslog(get_class($this) . '::info sql=' . $sql);
-		$result = $this->db->query($sql);
+		$resql = $this->db->query($sql);
 
-		if ($result) {
-			if ($this->db->num_rows($result)) {
-				$obj = $this->db->fetch_object($result);
+		if ($resql) {
+			if ($this->db->num_rows($resql)) {
+				$obj = $this->db->fetch_object($resql);
 				$this->id = $obj->rowid;
 				if ($obj->fk_user_author) {
 					$cuser = new User($this->db);
@@ -605,7 +605,7 @@ class AccountingAccount extends CommonObject
 				$this->date_creation = $this->db->jdate($obj->datec);
 				$this->date_modification = $this->db->jdate($obj->tms);
 			}
-			$this->db->free($result);
+			$this->db->free($resql);
 		} else {
 			dol_print_error($this->db);
 		}
@@ -741,13 +741,14 @@ class AccountingAccount extends CommonObject
 		global $hookmanager;
 
 		// Instantiate hooks for external modules
-		$hookmanager->initHooks(array('accoutancyBindingCalculation'));
+		$hookmanager->initHooks(array('accountancyBindingCalculation'));
 
-		// Execute hook accoutancyBindingCalculation
+		// Execute hook accountancyBindingCalculation
 		$parameters = array('buyer' => $buyer, 'seller' => $seller, 'product' => $product, 'facture' => $facture, 'factureDet' => $factureDet ,'accountingAccount'=>$accountingAccount, $type);
-		$reshook = $hookmanager->executeHooks('accoutancyBindingCalculation', $parameters); // Note that $action and $object may have been modified by some hooks
+		$reshook = $hookmanager->executeHooks('accountancyBindingCalculation', $parameters); // Note that $action and $object may have been modified by some hooks
 
 		if (empty($reshook)) {
+			$const_name = '';
 			if ($type == 'customer') {
 				$const_name = "SOLD";
 			} elseif ($type == 'supplier') {

+ 695 - 1
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
 	 *
@@ -345,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];
+	}
 }

+ 103 - 70
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) {
@@ -606,9 +606,13 @@ class BookKeeping extends CommonObject
 		if (empty($this->credit)) {
 			$this->credit = 0;
 		}
+		if (empty($this->montant)) {
+			$this->montant = 0;
+		}
 
 		$this->debit = price2num($this->debit, 'MT');
 		$this->credit = price2num($this->credit, 'MT');
+		$this->montant = price2num($this->montant, 'MT');
 
 		$now = dol_now();
 
@@ -745,7 +749,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 {
@@ -852,7 +856,8 @@ class BookKeeping extends CommonObject
 		$sql .= " t.piece_num,";
 		$sql .= " t.date_creation,";
 		$sql .= " t.date_export,";
-		$sql .= " t.date_validated as date_validation";
+		$sql .= " t.date_validated as date_validation,";
+		$sql .= " t.import_key";
 		// Manage filter
 		$sqlwhere = array();
 		if (count($filter) > 0) {
@@ -890,7 +895,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);
 		}
@@ -947,6 +952,7 @@ class BookKeeping extends CommonObject
 				$line->date_creation = $this->db->jdate($obj->date_creation);
 				$line->date_export = $this->db->jdate($obj->date_export);
 				$line->date_validation = $this->db->jdate($obj->date_validation);
+				$line->import_key = $obj->import_key;
 
 				$this->lines[] = $line;
 
@@ -1046,7 +1052,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";
 		}
@@ -1166,7 +1172,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);
 		}
@@ -1464,7 +1470,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';
@@ -1485,7 +1491,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";
 
@@ -1524,7 +1530,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);
 
@@ -1646,7 +1652,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);
@@ -1659,11 +1665,10 @@ class BookKeeping extends CommonObject
 			$this->doc_date = $this->db->jdate($obj->doc_date);
 			$this->doc_ref = $obj->doc_ref;
 			$this->doc_type = $obj->doc_type;
-			$this->date_creation = $obj->date_creation;
-			$this->date_modification = $obj->date_modification;
-			$this->date_export = $obj->date_export;
-			$this->date_validation = $obj->date_validated;
-			$this->date_validation = $obj->date_validation;
+			$this->date_creation = $this->db->jdate($obj->date_creation);
+			$this->date_modification = $this->db->jdate($obj->date_modification);
+			$this->date_export = $this->db->jdate($obj->date_export);
+			$this->date_validation = $this->db->jdate($obj->date_validation);
 		} else {
 			$this->error = "Error ".$this->db->lasterror();
 			dol_syslog(__METHOD__.$this->error, LOG_ERR);
@@ -1684,9 +1689,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) {
@@ -1727,7 +1733,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);
@@ -1790,7 +1796,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);
 
@@ -1840,12 +1846,14 @@ class BookKeeping extends CommonObject
 	/**
 	 * Transform transaction
 	 *
-	 * @param  number   $direction      If 0 tmp => real, if 1 real => tmp
-	 * @param  string   $piece_num      Piece num
+	 * @param  number   $direction      If 0: tmp => real, if 1: real => tmp
+	 * @param  string   $piece_num      Piece num = Transaction ref
 	 * @return int                      int <0 if KO, >0 if OK
 	 */
 	public function transformTransaction($direction = 0, $piece_num = '')
 	{
+		global $conf;
+
 		$error = 0;
 
 		$this->db->begin();
@@ -1857,57 +1865,82 @@ class BookKeeping extends CommonObject
 			if ($next_piecenum < 0) {
 				$error++;
 			}
-			$sql = 'INSERT INTO '.MAIN_DB_PREFIX.$this->table_element.' (doc_date, doc_type,';
-			$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, piece_num, date_creation)';
-			$sql .= ' SELECT doc_date, doc_type,';
-			$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);
-			$resql = $this->db->query($sql);
-			if (!$resql) {
-				$error++;
-				$this->errors[] = 'Error '.$this->db->lasterror();
-				dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
+
+			if (!$error) {
+				// Delete if there is an empty line
+				$sql = 'DELETE FROM '.MAIN_DB_PREFIX.$this->table_element.'_tmp WHERE piece_num = '.((int) $piece_num).' AND entity = ' .((int) $conf->entity)." AND numero_compte IS NULL AND debit = 0 AND credit = 0";
+				$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);
-			$resql = $this->db->query($sql);
-			if (!$resql) {
-				$error++;
-				$this->errors[] = 'Error '.$this->db->lasterror();
-				dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
+
+			if (!$error) {
+				$sql = 'INSERT INTO '.MAIN_DB_PREFIX.$this->table_element.' (doc_date, doc_type,';
+				$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, piece_num, date_creation)';
+				$sql .= ' SELECT doc_date, doc_type,';
+				$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).' AND numero_compte IS NOT NULL 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);
+				}
+			}
+
+			if (!$error) {
+				$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++;
+					$this->errors[] = 'Error '.$this->db->lasterror();
+					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);
-			$resql = $this->db->query($sql);
-			if (!$resql) {
-				$error++;
-				$this->errors[] = 'Error '.$this->db->lasterror();
-				dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
+			if (!$error) {
+				$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++;
+					$this->errors[] = 'Error '.$this->db->lasterror();
+					dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
+				}
 			}
-			$sql = 'INSERT INTO '.MAIN_DB_PREFIX.$this->table_element.'_tmp (doc_date, doc_type,';
-			$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 .= ' SELECT doc_date, doc_type,';
-			$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);
-			$resql = $this->db->query($sql);
-			if (!$resql) {
-				$error++;
-				$this->errors[] = 'Error '.$this->db->lasterror();
-				dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
+
+			if (!$error) {
+				$sql = 'INSERT INTO '.MAIN_DB_PREFIX.$this->table_element.'_tmp (doc_date, doc_type,';
+				$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 .= ' SELECT doc_date, doc_type,';
+				$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).' 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);
-			$resql = $this->db->query($sql);
-			if (!$resql) {
-				$error++;
-				$this->errors[] = 'Error '.$this->db->lasterror();
-				dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
+
+			if (!$error) {
+				$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++;
+					$this->errors[] = 'Error '.$this->db->lasterror();
+					dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
+				}
 			}
 		}
 		if (!$error) {
@@ -1957,7 +1990,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);
@@ -2021,7 +2054,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);
@@ -2061,7 +2094,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);

+ 461 - 16
htdocs/accountancy/class/lettering.class.php

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

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

+ 8 - 6
htdocs/accountancy/customer/index.php

@@ -1,9 +1,9 @@
 <?php
-/* Copyright (C) 2013       Olivier Geffroy		<jeff@jeffinfo.com>
- * Copyright (C) 2013-2014  Florian Henry		<florian.henry@open-concept.pro>
- * Copyright (C) 2013-2021  Alexandre Spangaro	<aspangaro@open-dsi.fr>
- * Copyright (C) 2014       Juanjo Menent		<jmenent@2byte.es>
- * Copyright (C) 2015       Jean-François Ferry	<jfefe@aternatik.fr>
+/* Copyright (C) 2013       Olivier Geffroy     <jeff@jeffinfo.com>
+ * Copyright (C) 2013-2014  Florian Henry       <florian.henry@open-concept.pro>
+ * Copyright (C) 2013-2022  Alexandre Spangaro  <aspangaro@open-dsi.fr>
+ * Copyright (C) 2014       Juanjo Menent       <jmenent@2byte.es>
+ * Copyright (C) 2015       Jean-François Ferry <jfefe@aternatik.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
@@ -174,7 +174,9 @@ 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 AND l.product_type <= 2 AND f.entity = ".((int) $conf->entity);
+	$sql .= " WHERE f.fk_statut > 0 AND l.fk_code_ventilation <= 0";
+	$sql .= " AND l.product_type <= 2";
+	$sql .= " AND f.entity IN (".getEntity('invoice', 0).")"; // We don't share object for accountancy
 	if (!empty($conf->global->ACCOUNTING_DATE_START_BINDING)) {
 		$sql .= " AND f.datef >= '".$db->idate($conf->global->ACCOUNTING_DATE_START_BINDING)."'";
 	}

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

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

@@ -5,6 +5,7 @@
  * Copyright (C) 2013-2021	Florian Henry		<florian.henry@open-concept.pro>
  * Copyright (C) 2014	  	Juanjo Menent		<jmenent@2byte.es>
  * Copyright (C) 2016	  	Laurent Destailleur <eldy@users.sourceforge.net>
+ * 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
@@ -46,6 +47,7 @@ $show_files = GETPOST('show_files', 'int');
 $confirm = GETPOST('confirm', 'alpha');
 $toselect = GETPOST('toselect', 'array');
 $optioncss = GETPOST('optioncss', 'alpha');
+$default_account = GETPOST('default_account', 'int');
 
 // Select Box
 $mesCasesCochees = GETPOST('toselect', 'array');
@@ -156,8 +158,8 @@ if (empty($reshook)) {
 
 	// Mass actions
 	$objectclass = 'AccountingAccount';
-	$permissiontoread = $user->rights->accounting->read;
-	$permissiontodelete = $user->rights->accounting->delete;
+	$permissiontoread = $user->hasRight('accounting', 'read');
+	$permissiontodelete = $user->hasRight('accounting', 'delete');
 	$uploaddir = $conf->accounting->dir_output;
 	include DOL_DOCUMENT_ROOT.'/core/actions_massactions.inc.php';
 }
@@ -434,12 +436,15 @@ if ($result) {
 
 	$arrayofmassactions = array(
 		'ventil'=>img_picto('', 'check', 'class="pictofixedwidth"').$langs->trans("Ventilate")
+		,'set_default_account'=>img_picto('', 'check', 'class="pictofixedwidth"').$langs->trans("ConfirmPreselectAccount")
 		//'presend'=>img_picto('', 'email', 'class="pictofixedwidth"').$langs->trans("SendByMail"),
 		//'builddoc'=>img_picto('', 'pdf', 'class="pictofixedwidth"').$langs->trans("PDFMerge"),
 	);
 	//if ($user->rights->mymodule->supprimer) $arrayofmassactions['predelete'] = img_picto('', 'delete', 'class="pictofixedwidth"').$langs->trans("Delete");
 	//if (in_array($massaction, array('presend','predelete'))) $arrayofmassactions=array();
-	$massactionbutton = $form->selectMassAction('ventil', $arrayofmassactions, 1);
+	if ($massaction !== 'set_default_account') {
+		$massactionbutton = $form->selectMassAction('ventil', $arrayofmassactions, 1);
+	}
 
 	print '<form action="'.$_SERVER["PHP_SELF"].'" method="post">'."\n";
 	print '<input type="hidden" name="action" value="ventil">';
@@ -454,9 +459,17 @@ if ($result) {
 
 	print_barre_liste($langs->trans("InvoiceLines"), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num_lines, $nbtotalofrecords, 'title_accountancy', 0, '', '', $limit);
 
+	if ($massaction == 'set_default_account') {
+		$formquestion[]=array('type' => 'other',
+			'name' => 'set_default_account',
+			'label' => $langs->trans("AccountancyCode"),
+			'value' => $formaccounting->select_account('', 'default_account', 1, array(), 0, 0, 'maxwidth200 maxwidthonsmartphone', 'cachewithshowemptyone'));
+		print $form->formconfirm($_SERVER["PHP_SELF"], $langs->trans("ConfirmPreselectAccount"), $langs->trans("ConfirmPreselectAccountQuestion", count($toselect)), "confirm_set_default_account", $formquestion, 1, 0, 200, 500, 1);
+	}
+
 	print '<span class="opacitymedium">'.$langs->trans("DescVentilTodoCustomer").'</span></br><br>';
 
-	if ($msg) {
+	if (!empty($msg)) {
 		print $msg.'<br>';
 	}
 
@@ -637,7 +650,7 @@ if ($result) {
 
 		// 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>';
@@ -712,7 +725,7 @@ if ($result) {
 
 		// Suggested accounting account
 		print '<td>';
-		print $formaccounting->select_account($suggestedid, 'codeventil'.$facture_static_det->id, 1, array(), 0, 0, 'codeventil maxwidth200 maxwidthonsmartphone', 'cachewithshowemptyone');
+		print $formaccounting->select_account(($default_account > 0 && $confirm === 'yes' && in_array($objp->rowid."_".$i, $toselect)) ? $default_account : $suggestedid, 'codeventil'.$facture_static_det->id, 1, array(), 0, 0, 'codeventil maxwidth200 maxwidthonsmartphone', 'cachewithshowemptyone');
 		print '</td>';
 
 		// Column with checkbox
@@ -721,6 +734,14 @@ if ($result) {
 		if (!empty($suggestedid) && $suggestedaccountingaccountfor != '' && $suggestedaccountingaccountfor != 'eecwithoutvatnumber') {
 			$ischecked = 1;
 		}
+
+		if (!empty($toselect)) {
+			$ischecked = 0;
+			if (in_array($objp->rowid."_".$i, $toselect)) {
+				$ischecked=1;
+			}
+		}
+
 		print '<input type="checkbox" class="flat checkforselect checkforselect'.$facture_static_det->id.'" name="toselect[]" value="'.$facture_static_det->id."_".$i.'"'.($ischecked ? " checked" : "").'/>';
 		print '</td>';
 

+ 51 - 29
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_month) {
-		$param .= '&search_month='.urlencode($search_month);
+	if ($search_date_startmonth) {
+		$param .= '&search_date_startmonth='.urlencode($search_date_startmonth);
 	}
-	if ($search_year) {
-		$param .= '&search_year='.urlencode($search_year);
+	if ($search_date_startyear) {
+		$param .= '&search_date_startyear='.urlencode($search_date_startyear);
+	}
+	if ($search_date_endday) {
+		$param .= '&search_date_endday='.urlencode($search_date_endday);
+	}
+	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>';
@@ -394,12 +417,11 @@ if ($result) {
 		print '<td class="center">'.vatrate($objp->tva_tx.($objp->vat_src_code ? ' ('.$objp->vat_src_code.')' : '')).'</td>';
 
 		// Accounting account affected
-		print '<td>';
+		print '<td class="center">';
 		print $accountingaccountstatic->getNomUrl(0, 1, 1, '', 1);
 		print ' <a class="editfielda reposition marginleftonly marginrightonly" href="./card.php?id='.$objp->rowid.'&backtopage='.urlencode($_SERVER["PHP_SELF"].($param ? '?'.$param : '')).'">';
 		print img_edit();
 		print '</a></td>';
-
 		print '<td class="center"><input type="checkbox" class="checkforaction" name="changeaccount[]" value="'.$objp->rowid.'"/></td>';
 
 		print "</tr>";

+ 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->hasRight('accounting', 'read');
+	$permissiontodelete = $user->hasRight('accounting', '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 != '') {
@@ -318,7 +370,7 @@ if ($result) {
 
 	print '<span class="opacitymedium">'.$langs->trans("DescVentilTodoExpenseReport").'</span></br><br>';
 
-	if ($msg) {
+	if (!empty($msg)) {
 		print $msg.'<br>';
 	}
 
@@ -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,6 +521,9 @@ 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">

+ 59 - 57
htdocs/accountancy/index.php

@@ -86,7 +86,7 @@ if (!empty($conf->global->INVOICE_USE_SITUATION) && $conf->global->INVOICE_USE_S
 
 	print '<span class="opacitymedium">'.$langs->trans("SorryThisModuleIsNotCompatibleWithTheExperimentalFeatureOfSituationInvoices")."</span>\n";
 	print "<br>";
-} elseif ($conf->accounting->enabled) {
+} elseif (!empty($conf->accounting->enabled)) {
 	$step = 0;
 
 	$resultboxes = FormOther::getBoxesArea($user, "27"); // Load $resultboxes (selectboxlist + boxactivated + boxlista + boxlistb)
@@ -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)) {
+
 		$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
 

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

@@ -117,7 +117,7 @@ if (!GETPOSTISSET('date_startmonth') && (empty($date_start) || empty($date_end))
 	$date_end = dol_get_last_day($pastmonthyear, $pastmonth, false);
 }
 
-$sql  = "SELECT b.rowid, b.dateo as do, b.datev as dv, b.amount, b.label, b.rappro, b.num_releve, b.num_chq, b.fk_type, b.fk_account,";
+$sql  = "SELECT b.rowid, b.dateo as do, b.datev as dv, b.amount, b.amount_main_currency, b.label, b.rappro, b.num_releve, b.num_chq, b.fk_type, b.fk_account,";
 $sql .= " ba.courant, ba.ref as baref, ba.account_number, ba.fk_accountancy_journal,";
 $sql .= " soc.rowid as socid, soc.nom as name, soc.email as email, bu1.type as typeop_company,";
 if (!empty($conf->global->MAIN_COMPANY_PERENTITY_SHARED)) {
@@ -142,7 +142,7 @@ if (!empty($conf->global->MAIN_COMPANY_PERENTITY_SHARED)) {
 }
 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user as u on bu2.url_id=u.rowid";
 $sql .= " WHERE ba.fk_accountancy_journal=".((int) $id_journal);
-$sql .= ' AND b.amount != 0 AND ba.entity IN ('.getEntity('bank_account', 0).')'; // We don't share object for accountancy
+$sql .= ' AND b.amount <> 0 AND ba.entity IN ('.getEntity('bank_account', 0).')'; // We don't share object for accountancy
 if ($date_start && $date_end) {
 	$sql .= " AND b.dateo >= '".$db->idate($date_start)."' AND b.dateo <= '".$db->idate($date_end)."'";
 }
@@ -282,6 +282,7 @@ if ($result) {
 		$tabpay[$obj->rowid]["fk_bank"] = $obj->rowid;
 		$tabpay[$obj->rowid]["bank_account_ref"] = $obj->baref;
 		$tabpay[$obj->rowid]["fk_bank_account"] = $obj->fk_account;
+		$reg = array();
 		if (preg_match('/^\((.*)\)$/i', $obj->label, $reg)) {
 			$tabpay[$obj->rowid]["lib"] = $langs->trans($reg[1]);
 		} else {
@@ -296,10 +297,24 @@ if ($result) {
 		$tabtype[$obj->rowid] = 'unknown';
 		$tabmoreinfo[$obj->rowid] = array();
 
+		$amounttouse = $obj->amount;
+		if (!empty($obj->amount_main_currency)) {
+			// If $obj->amount_main_currency is set, it means that $obj->amount is not in same currency, we must use $obj->amount_main_currency
+			$amounttouse = $obj->amount_main_currency;
+		}
+
 		// get_url may return -1 which is not traversable
 		if (is_array($links) && count($links) > 0) {
+			$is_sc = false;
+			foreach ($links as $v) {
+				if ($v['type'] == 'sc') {
+					$is_sc = true;
+					break;
+				}
+			}
 			// Now loop on each link of record in bank (code similar to bankentries_list.php)
 			foreach ($links as $key => $val) {
+				if ($links[$key]['type'] == 'user' && !$is_sc) continue;
 				if (in_array($links[$key]['type'], array('sc', 'payment_sc', 'payment', 'payment_supplier', 'payment_vat', 'payment_expensereport', 'banktransfert', 'payment_donation', 'member', 'payment_loan', 'payment_salary', 'payment_various'))) {
 					// So we excluded 'company' and 'user' here. We want only payment lines
 
@@ -334,7 +349,7 @@ if ($result) {
 					$societestatic->email = $tabcompany[$obj->rowid]['email'];
 					$tabpay[$obj->rowid]["soclib"] = $societestatic->getNomUrl(1, '', 30);
 					if ($compta_soc) {
-						$tabtp[$obj->rowid][$compta_soc] += $obj->amount;
+						$tabtp[$obj->rowid][$compta_soc] += $amounttouse;
 					}
 				} elseif ($links[$key]['type'] == 'user') {
 					$userstatic->id = $links[$key]['url_id'];
@@ -350,7 +365,7 @@ if ($result) {
 						$tabpay[$obj->rowid]["soclib"] = '???'; // Should not happen, but happens with old data when id of user was not saved on expense report payment.
 					}
 					if ($compta_user) {
-						$tabtp[$obj->rowid][$compta_user] += $obj->amount;
+						$tabtp[$obj->rowid][$compta_user] += $amounttouse;
 					}
 				} elseif ($links[$key]['type'] == 'sc') {
 					$chargestatic->id = $links[$key]['url_id'];
@@ -383,7 +398,7 @@ if ($result) {
 					$resultmid = $db->query($sqlmid);
 					if ($resultmid) {
 						$objmid = $db->fetch_object($resultmid);
-						$tabtp[$obj->rowid][$objmid->accountancy_code] += $obj->amount;
+						$tabtp[$obj->rowid][$objmid->accountancy_code] += $amounttouse;
 					}
 				} elseif ($links[$key]['type'] == 'payment_donation') {
 					$paymentdonstatic->id = $links[$key]['url_id'];
@@ -391,7 +406,7 @@ if ($result) {
 					$paymentdonstatic->fk_donation = $links[$key]['url_id'];
 					$tabpay[$obj->rowid]["lib"] .= ' '.$paymentdonstatic->getNomUrl(2);
 					$tabpay[$obj->rowid]["paymentdonationid"] = $paymentdonstatic->id;
-					$tabtp[$obj->rowid][$account_pay_donation] += $obj->amount;
+					$tabtp[$obj->rowid][$account_pay_donation] += $amounttouse;
 				} elseif ($links[$key]['type'] == 'member') {
 					$paymentsubscriptionstatic->id = $links[$key]['url_id'];
 					$paymentsubscriptionstatic->ref = $links[$key]['url_id'];
@@ -399,14 +414,14 @@ if ($result) {
 					$tabpay[$obj->rowid]["lib"] .= ' '.$paymentsubscriptionstatic->getNomUrl(2);
 					$tabpay[$obj->rowid]["paymentsubscriptionid"] = $paymentsubscriptionstatic->id;
 					$paymentsubscriptionstatic->fetch($paymentsubscriptionstatic->id);
-					$tabtp[$obj->rowid][$account_pay_subscription] += $obj->amount;
+					$tabtp[$obj->rowid][$account_pay_subscription] += $amounttouse;
 				} elseif ($links[$key]['type'] == 'payment_vat') {				// Payment VAT
 					$paymentvatstatic->id = $links[$key]['url_id'];
 					$paymentvatstatic->ref = $links[$key]['url_id'];
 					$paymentvatstatic->label = $links[$key]['label'];
 					$tabpay[$obj->rowid]["lib"] .= ' '.$paymentvatstatic->getNomUrl(2);
 					$tabpay[$obj->rowid]["paymentvatid"] = $paymentvatstatic->id;
-					$tabtp[$obj->rowid][$account_pay_vat] += $obj->amount;
+					$tabtp[$obj->rowid][$account_pay_vat] += $amounttouse;
 				} elseif ($links[$key]['type'] == 'payment_salary') {
 					$paymentsalstatic->id = $links[$key]['url_id'];
 					$paymentsalstatic->ref = $links[$key]['url_id'];
@@ -438,7 +453,7 @@ if ($result) {
 						if (empty($obj->typeop_user)) {	// Add test to avoid to add amount twice if a link already exists also on user.
 							$compta_user = $userstatic->accountancy_code;
 							if ($compta_user) {
-								$tabtp[$obj->rowid][$compta_user] += $obj->amount;
+								$tabtp[$obj->rowid][$compta_user] += $amounttouse;
 								$tabuser[$obj->rowid] = array(
 								'id' => $userstatic->id,
 								'name' => dolGetFirstLastname($userstatic->firstname, $userstatic->lastname),
@@ -465,14 +480,14 @@ if ($result) {
 					$account_various = (!empty($paymentvariousstatic->accountancy_code) ? $paymentvariousstatic->accountancy_code : 'NotDefined'); // NotDefined is a reserved word
 					$account_subledger = (!empty($paymentvariousstatic->subledger_account) ? $paymentvariousstatic->subledger_account : ''); // NotDefined is a reserved word
 					$tabpay[$obj->rowid]["account_various"] = $account_various;
-					$tabtp[$obj->rowid][$account_subledger] += $obj->amount;
+					$tabtp[$obj->rowid][$account_subledger] += $amounttouse;
 				} elseif ($links[$key]['type'] == 'payment_loan') {
 					$paymentloanstatic->id = $links[$key]['url_id'];
 					$paymentloanstatic->ref = $links[$key]['url_id'];
 					$paymentloanstatic->fk_loan = $links[$key]['url_id'];
 					$tabpay[$obj->rowid]["lib"] .= ' '.$paymentloanstatic->getNomUrl(2);
 					$tabpay[$obj->rowid]["paymentloanid"] = $paymentloanstatic->id;
-					//$tabtp[$obj->rowid][$account_pay_loan] += $obj->amount;
+					//$tabtp[$obj->rowid][$account_pay_loan] += $amounttouse;
 					$sqlmid = 'SELECT pl.amount_capital, pl.amount_insurance, pl.amount_interest, l.accountancy_account_capital, l.accountancy_account_insurance, l.accountancy_account_interest';
 					$sqlmid .= ' FROM '.MAIN_DB_PREFIX.'payment_loan as pl, '.MAIN_DB_PREFIX.'loan as l';
 					$sqlmid .= ' WHERE l.rowid = pl.fk_loan AND pl.fk_bank = '.((int) $obj->rowid);
@@ -488,14 +503,14 @@ if ($result) {
 				} elseif ($links[$key]['type'] == 'banktransfert') {
 					$accountLinestatic->fetch($links[$key]['url_id']);
 					$tabpay[$obj->rowid]["lib"] .= ' '.$langs->trans("BankTransfer").'- '.$accountLinestatic ->getNomUrl(1);
-					$tabtp[$obj->rowid][$account_transfer] += $obj->amount;
+					$tabtp[$obj->rowid][$account_transfer] += $amounttouse;
 					$bankaccountstatic->fetch($tabpay[$obj->rowid]['fk_bank_account']);
 					$tabpay[$obj->rowid]["soclib"] = $bankaccountstatic->getNomUrl(2);
 				}
 			}
 		}
 
-		$tabbq[$obj->rowid][$compta_bank] += $obj->amount;
+		$tabbq[$obj->rowid][$compta_bank] += $amounttouse;
 
 		// If no links were found to know the amount on thirdparty, we try to guess it.
 		// This may happens on bank entries without the links lines to 'company'.
@@ -542,7 +557,7 @@ if ($result) {
 			}
 		}*/
 
-		// if($obj->socid)$tabtp[$obj->rowid][$compta_soc] += $obj->amount;
+		// if($obj->socid)$tabtp[$obj->rowid][$compta_soc] += $amounttouse;
 
 		$i++;
 	}
@@ -551,11 +566,11 @@ if ($result) {
 }
 
 
-/*var_dump($tabpay);
-var_dump($tabcompany);
-var_dump($tabbq);
-var_dump($tabtp);
-var_dump($tabtype);*/
+//var_dump($tabpay);
+//var_dump($tabcompany);
+//var_dump($tabbq);
+//var_dump($tabtp);
+//var_dump($tabtype);
 
 // Write bookkeeping
 if (!$error && $action == 'writebookkeeping') {
@@ -587,9 +602,9 @@ if (!$error && $action == 'writebookkeeping') {
 		$db->begin();
 
 		// Introduce a protection. Total of tabtp must be total of tabbq
-		/*var_dump($tabpay);
-		var_dump($tabtp);
-		var_dump($tabbq);exit;*/
+		//var_dump($tabpay);
+		//var_dump($tabtp);
+		//var_dump($tabbq);exit;
 
 		// Bank
 		if (!$errorforline && is_array($tabbq[$key])) {
@@ -658,6 +673,8 @@ if (!$error && $action == 'writebookkeeping') {
 				// Line into thirdparty account
 				foreach ($tabtp[$key] as $k => $mt) {
 					if ($mt) {
+						$lettering = false;
+
 						$reflabel = '';
 						if (!empty($val['lib'])) {
 							$reflabel .= dol_string_nohtmltag($val['lib']).($val['soclib'] ? " - " : "");
@@ -686,11 +703,13 @@ if (!$error && $action == 'writebookkeeping') {
 						$bookkeeping->date_creation = $now;
 
 						if ($tabtype[$key] == 'payment') {	// If payment is payment of customer invoice, we get ref of invoice
+							$lettering = true;
 							$bookkeeping->subledger_account = $k; // For payment, the subledger account is stored as $key of $tabtp
 							$bookkeeping->subledger_label = $tabcompany[$key]['name']; // $tabcompany is defined only if we are sure there is 1 thirdparty for the bank transaction
 							$bookkeeping->numero_compte = $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER;
 							$bookkeeping->label_compte = $accountingaccountcustomer->label;
 						} elseif ($tabtype[$key] == 'payment_supplier') {	// If payment is payment of supplier invoice, we get ref of invoice
+							$lettering = true;
 							$bookkeeping->subledger_account = $k; // For payment, the subledger account is stored as $key of $tabtp
 							$bookkeeping->subledger_label = $tabcompany[$key]['name']; // $tabcompany is defined only if we are sure there is 1 thirdparty for the bank transaction
 							$bookkeeping->numero_compte = $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER;
@@ -773,6 +792,12 @@ if (!$error && $action == 'writebookkeeping') {
 								$errorforline++;
 								setEventMessages($bookkeeping->error, $bookkeeping->errors, 'errors');
 							}
+						} else {
+							if ($lettering && getDolGlobalInt('ACCOUNTING_ENABLE_LETTERING')) {
+								require_once DOL_DOCUMENT_ROOT . '/accountancy/class/lettering.class.php';
+								$lettering_static = new Lettering($db);
+								$nb_lettering = $lettering_static->bookkeepingLetteringAll(array($bookkeeping->id));
+							}
 						}
 					}
 				}
@@ -1250,9 +1275,9 @@ if (empty($action) || $action == 'view') {
 						$accounttoshowsubledger = length_accounta($k);
 						if ($accounttoshow != $accounttoshowsubledger) {
 							if (empty($accounttoshowsubledger) || $accounttoshowsubledger == 'NotDefined') {
-								/*var_dump($tabpay[$key]);
-								var_dump($tabtype[$key]);
-								var_dump($tabbq[$key]);*/
+								//var_dump($tabpay[$key]);
+								//var_dump($tabtype[$key]);
+								//var_dump($tabbq[$key]);
 								//print '<span class="error">'.$langs->trans("ThirdpartyAccountNotDefined").'</span>';
 								if (!empty($tabcompany[$key]['code_compta'])) {
 									if (in_array($tabtype[$key], array('payment_various', 'payment_salary'))) {

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

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

+ 25 - 2
htdocs/accountancy/journal/sellsjournal.php

@@ -390,6 +390,12 @@ if ($action == 'writebookkeeping') {
 						$errorforinvoice[$key] = 'other';
 						setEventMessages($bookkeeping->error, $bookkeeping->errors, 'errors');
 					}
+				} else {
+					if (getDolGlobalInt('ACCOUNTING_ENABLE_LETTERING')) {
+						require_once DOL_DOCUMENT_ROOT . '/accountancy/class/lettering.class.php';
+						$lettering_static = new Lettering($db);
+						$nb_lettering = $lettering_static->bookkeepingLettering(array($bookkeeping->id), 'customer_invoice');
+					}
 				}
 			}
 		}
@@ -412,8 +418,18 @@ 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 (!empty($conf->global->ACCOUNTING_ACCOUNT_CUSTOMER_USE_AUXILIARY_ON_DEPOSIT)) {
+						if ($k == getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT')) {
+							$bookkeeping->subledger_account = $tabcompany[$key]['code_compta'];
+							$bookkeeping->subledger_label = $tabcompany[$key]['name'];
+						} else {
+							$bookkeeping->subledger_account = '';
+							$bookkeeping->subledger_label = '';
+						}
+					} else {
+						$bookkeeping->subledger_account = '';
+						$bookkeeping->subledger_label = '';
+					}
 
 					$bookkeeping->numero_compte = $k;
 					$bookkeeping->label_compte = $label_account;
@@ -886,6 +902,13 @@ if (empty($action) || $action == 'view') {
 			print "</td>";
 			// Subledger account
 			print "<td>";
+			if (!empty($conf->global->ACCOUNTING_ACCOUNT_CUSTOMER_USE_AUXILIARY_ON_DEPOSIT)) {
+				if ($k == getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT')) {
+					print length_accounta($tabcompany[$key]['code_compta']);
+				}
+			} elseif (($accountoshow == "") || $accountoshow == 'NotDefined') {
+				print '<span class="error">' . $langs->trans("ThirdpartyAccountNotDefined") . '</span>';
+			}
 			print '</td>';
 			$companystatic->id = $tabcompany[$key]['id'];
 			$companystatic->name = $tabcompany[$key]['name'];

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

@@ -0,0 +1,314 @@
+<?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';
+}
+
+// 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.'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');
+}
+
+// Security check
+if (empty($conf->accounting->enabled)) {
+	accessforbidden();
+}
+if ($user->socid > 0) {
+	accessforbidden();
+}
+if (empty($user->rights->accounting->mouvements->lire)) {
+	accessforbidden();
+}
+
+
+/*
+ * 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();

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

@@ -1,7 +1,7 @@
 <?php
 /* Copyright (C) 2013-2014 Olivier Geffroy		<jeff@jeffinfo.com>
  * Copyright (C) 2013-2021 Florian Henry		<florian.henry@open-concept.pro>
- * Copyright (C) 2013-2021 Alexandre Spangaro	<aspangaro@open-dsi.fr>
+ * Copyright (C) 2013-2022 Alexandre Spangaro	<aspangaro@open-dsi.fr>
  * Copyright (C) 2014	   Juanjo Menent		<jmenent@2byte.es>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -38,7 +38,7 @@ $validatemonth = GETPOST('validatemonth', 'int');
 $validateyear = GETPOST('validateyear', 'int');
 
 // Security check
-if (empty($conf->accounting->enabled)) {
+if (!isModEnabled('accounting')) {
 	accessforbidden();
 }
 if ($user->socid > 0) {
@@ -75,7 +75,7 @@ $action = GETPOST('action', 'aZ09');
 $chartaccountcode = dol_getIdFromCode($db, $conf->global->CHARTOFACCOUNTS, 'accounting_system', 'rowid', 'pcg_version');
 
 // Security check
-if (empty($conf->accounting->enabled)) {
+if (!isModEnabled('accounting')) {
 	accessforbidden();
 }
 if ($user->socid > 0) {
@@ -171,8 +171,10 @@ if ($action == 'validatehistory') {
 	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa  ON " . $alias_product_perentity . ".accountancy_code_buy = aa.account_number         AND aa.active = 1  AND aa.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa.entity = ".$conf->entity;
 	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa2 ON " . $alias_product_perentity . ".accountancy_code_buy_intra = aa2.account_number  AND aa2.active = 1 AND aa2.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa2.entity = ".$conf->entity;
 	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa3 ON " . $alias_product_perentity . ".accountancy_code_buy_export = aa3.account_number AND aa3.active = 1 AND aa3.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa3.entity = ".$conf->entity;
-	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa4 ON " . $alias_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 AND l.product_type <= 2 AND f.entity = ".((int) $conf->entity);
+	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."accounting_account as aa4 ON " . $alias_product_perentity . ".accountancy_code_buy = aa4.account_number        AND aa4.active = 1 AND aa4.fk_pcg_version = '".$db->escape($chartaccountcode)."' AND aa4.entity = ".$conf->entity;
+	$sql .= " WHERE f.fk_statut > 0 AND l.fk_code_ventilation <= 0";
+	$sql .= " AND l.product_type <= 2";
+	$sql .= " AND f.entity IN (".getEntity('facture_fourn', 0).")"; // We don't share object for accountancy
 	if (!empty($conf->global->ACCOUNTING_DATE_START_BINDING)) {
 		$sql .= " AND f.datef >= '".$db->idate($conf->global->ACCOUNTING_DATE_START_BINDING)."'";
 	}
@@ -373,10 +375,14 @@ $sql .= "  AND ff.fk_statut > 0";
 $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";
+if (!empty($conf->global->FACTURE_SUPPLIER_DEPOSITS_ARE_JUST_PAYMENTS)) {
+	$sql .= " AND ff.type IN (".FactureFournisseur::TYPE_STANDARD.",".FactureFournisseur::TYPE_REPLACEMENT.",".FactureFournisseur::TYPE_CREDIT_NOTE.")";
+} else {
+	$sql .= " AND ff.type IN (".FactureFournisseur::TYPE_STANDARD.",".FactureFournisseur::TYPE_REPLACEMENT.",".FactureFournisseur::TYPE_CREDIT_NOTE.",".FactureFournisseur::TYPE_DEPOSIT.")";
+}
 $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');
+dol_syslog('htdocs/accountancy/supplier/index.php', LOG_DEBUG);
 $resql = $db->query($sql);
 if ($resql) {
 	$num = $db->num_rows($resql);
@@ -474,11 +480,17 @@ $sql .= "  AND ff.datef <= '".$db->idate($search_date_end)."'";
 if (!empty($conf->global->ACCOUNTING_DATE_START_BINDING)) {
 	$sql .= " AND ff.datef >= '".$db->idate($conf->global->ACCOUNTING_DATE_START_BINDING)."'";
 }
+$sql .= " AND ff.entity IN (".getEntity('facture_fourn', 0).")"; // We don't share object for accountancy
 $sql .= "  AND ff.fk_statut > 0";
 $sql .= "  AND ffd.product_type <= 2";
-$sql .= " AND ff.entity IN (".getEntity('facture_fourn', 0).")"; // We don't share object for accountancy
+if (!empty($conf->global->FACTURE_SUPPLIER_DEPOSITS_ARE_JUST_PAYMENTS)) {
+	$sql .= " AND ff.type IN (".FactureFournisseur::TYPE_STANDARD.", ".FactureFournisseur::TYPE_REPLACEMENT.", ".FactureFournisseur::TYPE_CREDIT_NOTE.")";
+} else {
+	$sql .= " AND ff.type IN (".FactureFournisseur::TYPE_STANDARD.", ".FactureFournisseur::TYPE_REPLACEMENT.", ".FactureFournisseur::TYPE_CREDIT_NOTE.", ".FactureFournisseur::TYPE_DEPOSIT.")";
+}
 $sql .= " AND aa.account_number IS NOT 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);
@@ -494,13 +506,15 @@ if ($resql) {
 			print length_accountg($row[0]);
 		}
 		print '</td>';
-		print '<td>';
+
+		print '<td class="left">';
 		if ($row[0] == 'tobind') {
 			print $langs->trans("UseMenuToSetBindindManualy", DOL_URL_ROOT.'/accountancy/supplier/list.php?search_year='.((int) $y), $langs->transnoentitiesnoconv("ToBind"));
 		} else {
 			print $row[1];
 		}
 		print '</td>';
+
 		for ($i = 2; $i <= 13; $i++) {
 			print '<td class="right nowraponall amount">';
 			print price($row[$i]);
@@ -523,7 +537,6 @@ print "</table>\n";
 print '</div>';
 
 
-
 if ($conf->global->MAIN_FEATURES_LEVEL > 0) { // This part of code looks strange. Why showing a report that should rely on result of this step ?
 	print '<br>';
 	print '<br>';
@@ -560,9 +573,14 @@ if ($conf->global->MAIN_FEATURES_LEVEL > 0) { // This part of code looks strange
 	if (!empty($conf->global->ACCOUNTING_DATE_START_BINDING)) {
 		$sql .= " AND ff.datef >= '".$db->idate($conf->global->ACCOUNTING_DATE_START_BINDING)."'";
 	}
+	$sql .= " AND ff.entity IN (".getEntity('facture_fourn', 0).")"; // We don't share object for accountancy
 	$sql .= "  AND ff.fk_statut > 0";
 	$sql .= "  AND ffd.product_type <= 2";
-	$sql .= " AND ff.entity IN (".getEntity('facture_fourn', 0).")"; // We don't share object for accountancy
+	if (!empty($conf->global->FACTURE_SUPPLIER_DEPOSITS_ARE_JUST_PAYMENTS)) {
+		$sql .= " AND ff.type IN (".FactureFournisseur::TYPE_STANDARD.", ".FactureFournisseur::TYPE_REPLACEMENT.", ".FactureFournisseur::TYPE_CREDIT_NOTE.")";
+	} else {
+		$sql .= " AND ff.type IN (".FactureFournisseur::TYPE_STANDARD.", ".FactureFournisseur::TYPE_REPLACEMENT.", ".FactureFournisseur::TYPE_CREDIT_NOTE.", ".FactureFournisseur::TYPE_DEPOSIT.")";
+	}
 
 	dol_syslog('htdocs/accountancy/supplier/index.php');
 	$resql = $db->query($sql);

+ 33 - 14
htdocs/accountancy/supplier/lines.php

@@ -1,9 +1,9 @@
 <?php
-/* Copyright (C) 2013-2016 Olivier Geffroy		<jeff@jeffinfo.com>
- * Copyright (C) 2013-2021 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>
+/* Copyright (C) 2013-2016  Olivier Geffroy     <jeff@jeffinfo.com>
+ * 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>
  *
  * 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
@@ -49,6 +49,7 @@ $search_societe = GETPOST('search_societe', 'alpha');
 $search_lineid = GETPOST('search_lineid', 'int');
 $search_ref = GETPOST('search_ref', 'alpha');
 $search_invoice = GETPOST('search_invoice', 'alpha');
+$search_ref_supplier = GETPOST('search_ref_supplier', 'alpha');
 $search_label = GETPOST('search_label', 'alpha');
 $search_desc = GETPOST('search_desc', 'alpha');
 $search_amount = GETPOST('search_amount', 'alpha');
@@ -112,6 +113,7 @@ if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x'
 	$search_lineid = '';
 	$search_ref = '';
 	$search_invoice = '';
+	$search_ref_supplier = '';
 	$search_label = '';
 	$search_desc = '';
 	$search_amount = '';
@@ -140,9 +142,9 @@ if (is_array($changeaccount) && count($changeaccount) > 0 && $user->rights->acco
 	if (!$error) {
 		$db->begin();
 
-		$sql1 = "UPDATE ".MAIN_DB_PREFIX."facture_fourn_det 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."facture_fourn_det";
+		$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/supplier/lines.php::changeaccount sql= '.$sql1);
 		$resql1 = $db->query($sql1);
@@ -236,6 +238,9 @@ if ($search_lineid) {
 if (strlen(trim($search_invoice))) {
 	$sql .= natural_search("f.ref", $search_invoice);
 }
+if (strlen(trim($search_ref_supplier))) {
+	$sql .= natural_search("f.ref_supplier", $search_ref_supplier);
+}
 if (strlen(trim($search_label))) {
 	$sql .= natural_search("f.libelle", $search_label);
 }
@@ -328,6 +333,9 @@ if ($result) {
 	if ($search_ref) {
 		$param .= "&search_ref=".urlencode($search_ref);
 	}
+	if ($search_ref_supplier) {
+		$param .= '&search_ref_supplier='.urlencode($search_ref_supplier);
+	}
 	if ($search_label) {
 		$param .= "&search_label=".urlencode($search_label);
 	}
@@ -376,7 +384,7 @@ if ($result) {
 	print '<input type="hidden" name="sortorder" value="'.$sortorder.'">';
 	print '<input type="hidden" name="page" value="'.$page.'">';
 
-	print_barre_liste($langs->trans("InvoiceLinesDone"), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num_lines, $nbtotalofrecords, 'title_accountancy', 0, '', '', $limit);
+	print_barre_liste($langs->trans("InvoiceLinesDone"), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num_lines, $nbtotalofrecords, 'title_accountancy', 0, '', '', $limit);
 	print '<span class="opacitymedium">'.$langs->trans("DescVentilDoneSupplier").'</span><br>';
 
 	print '<br><div class="inline-block divButAction paddingbottom">'.$langs->trans("ChangeAccount").' ';
@@ -392,6 +400,7 @@ if ($result) {
 	print '<tr class="liste_titre_filter">';
 	print '<td class="liste_titre"><input type="text" class="flat maxwidth25" name="search_lineid" value="'.dol_escape_htmltag($search_lineid).'"></td>';
 	print '<td class="liste_titre"><input type="text" class="flat maxwidth50" name="search_invoice" value="'.dol_escape_htmltag($search_invoice).'"></td>';
+	print '<td class="liste_titre"><input type="text" class="flat maxwidth50" name="search_ref_supplier" value="'.dol_escape_htmltag($search_ref_supplier).'"></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 center">';
 	print '<div class="nowrap">';
@@ -420,6 +429,7 @@ if ($result) {
 	print '<tr class="liste_titre">';
 	print_liste_field_titre("LineId", $_SERVER["PHP_SELF"], "l.rowid", "", $param, '', $sortfield, $sortorder);
 	print_liste_field_titre("Invoice", $_SERVER["PHP_SELF"], "f.ref", "", $param, '', $sortfield, $sortorder);
+	print_liste_field_titre("RefSupplier", $_SERVER["PHP_SELF"], "f.ref_supplier", "", $param, '', $sortfield, $sortorder);
 	print_liste_field_titre("InvoiceLabel", $_SERVER["PHP_SELF"], "f.libelle", "", $param, '', $sortfield, $sortorder);
 	print_liste_field_titre("Date", $_SERVER["PHP_SELF"], "f.datef, f.ref, l.rowid", "", $param, '', $sortfield, $sortorder, 'center ');
 	print_liste_field_titre("ProductRef", $_SERVER["PHP_SELF"], "p.ref", "", $param, '', $sortfield, $sortorder);
@@ -446,6 +456,9 @@ if ($result) {
 
 		$facturefournisseur_static->ref = $objp->ref;
 		$facturefournisseur_static->id = $objp->facid;
+		$facturefournisseur_static->type = $objp->ftype;
+		$facturefournisseur_static->ref_supplier = $objp->ref_supplier;
+		$facturefournisseur_static->label = $objp->invoice_label;
 
 		$thirdpartystatic->id = $objp->socid;
 		$thirdpartystatic->name = $objp->name;
@@ -465,8 +478,8 @@ if ($result) {
 		$productstatic->status = $objp->tosell;
 		$productstatic->status_buy = $objp->tobuy;
 		$productstatic->accountancy_code_buy = $objp->accountancy_code_buy;
-		$productstatic->accountancy_code_buy_intra = $objp->accountancy_code_sell_buy;
-		$productstatic->accountancy_code_buy_export = $objp->accountancy_code_sell_buy;
+		$productstatic->accountancy_code_buy_intra = $objp->accountancy_code_sell_buy_intra;
+		$productstatic->accountancy_code_buy_export = $objp->accountancy_code_sell_buy_export;
 
 		$accountingaccountstatic->rowid = $objp->fk_compte;
 		$accountingaccountstatic->label = $objp->label_account;
@@ -481,7 +494,13 @@ if ($result) {
 		// Ref Invoice
 		print '<td class="nowraponall">'.$facturefournisseur_static->getNomUrl(1).'</td>';
 
-		print '<td class="tdoverflowonsmartphone">';
+		// Ref supplier invoice
+		print '<td class="tdoverflowmax100" title="'.dol_escape_htmltag($objp->ref_supplier).'">';
+		print $objp->ref_supplier;
+		print '</td>';
+
+		// Supplier invoice label
+		print '<td class="tdoverflowonsmartphone small" title="'.dol_escape_htmltag($objp->invoice_label).'">';
 		print $objp->invoice_label;
 		print '</td>';
 
@@ -501,8 +520,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>';

+ 52 - 14
htdocs/accountancy/supplier/list.php

@@ -1,10 +1,11 @@
 <?php
 /* Copyright (C) 2013-2014	Olivier Geffroy			<jeff@jeffinfo.com>
- * Copyright (C) 2013-2021	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-2021	Florian Henry			<florian.henry@open-concept.pro>
- * Copyright (C) 2014		Juanjo Menent			<jmenent@2byte.es>s
- * Copyright (C) 2016		Laurent Destailleur		<eldy@users.sourceforge.net>
+ * Copyright (C) 2021      	Gauthier VERDOL     <gauthier.verdol@atm-consulting.fr>
+ * Copyright (C) 2014       Juanjo Menent           <jmenent@2byte.es>s
+ * Copyright (C) 2016       Laurent Destailleur     <eldy@users.sourceforge.net>
  *
  * 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,6 +48,7 @@ $show_files = GETPOST('show_files', 'int');
 $confirm = GETPOST('confirm', 'alpha');
 $toselect = GETPOST('toselect', 'array');
 $optioncss = GETPOST('optioncss', 'alpha');
+$default_account = GETPOST('default_account', 'int');
 
 // Select Box
 $mesCasesCochees = GETPOST('toselect', 'array');
@@ -55,6 +57,7 @@ $mesCasesCochees = GETPOST('toselect', 'array');
 $search_societe = GETPOST('search_societe', 'alpha');
 $search_lineid = GETPOST('search_lineid', 'int');
 $search_ref = GETPOST('search_ref', 'alpha');
+$search_ref_supplier = GETPOST('search_ref_supplier', 'alpha');
 $search_invoice = GETPOST('search_invoice', 'alpha');
 $search_label = GETPOST('search_label', 'alpha');
 $search_desc = GETPOST('search_desc', 'alpha');
@@ -137,6 +140,7 @@ if (empty($reshook)) {
 		$search_societe = '';
 		$search_lineid = '';
 		$search_ref = '';
+		$search_ref_supplier = '';
 		$search_invoice = '';
 		$search_label = '';
 		$search_desc = '';
@@ -157,8 +161,8 @@ if (empty($reshook)) {
 
 	// Mass actions
 	$objectclass = 'AccountingAccount';
-	$permissiontoread = $user->rights->accounting->read;
-	$permissiontodelete = $user->rights->accounting->delete;
+	$permissiontoread = $user->hasRight('accounting', 'read');
+	$permissiontodelete = $user->hasRight('accounting', 'delete');
 	$uploaddir = $conf->accounting->dir_output;
 	include DOL_DOCUMENT_ROOT.'/core/actions_massactions.inc.php';
 }
@@ -290,6 +294,9 @@ if (strlen(trim($search_invoice))) {
 if (strlen(trim($search_ref))) {
 	$sql .= natural_search("p.ref", $search_ref);
 }
+if (strlen(trim($search_ref_supplier))) {
+	$sql .= natural_search("f.ref_supplier", $search_ref_supplier);
+}
 if (strlen(trim($search_label))) {
 	$sql .= natural_search(array("p.label", "f.libelle"), $search_label);
 }
@@ -413,6 +420,9 @@ if ($result) {
 	if ($search_ref) {
 		$param .= '&search_ref='.urlencode($search_ref);
 	}
+	if ($search_ref_supplier) {
+		$param .= '&search_ref_supplier='.urlencode($search_ref_supplier);
+	}
 	if ($search_label) {
 		$param .= '&search_label='.urlencode($search_label);
 	}
@@ -434,12 +444,15 @@ if ($result) {
 
 	$arrayofmassactions = array(
 		'ventil'=>img_picto('', 'check', 'class="pictofixedwidth"').$langs->trans("Ventilate")
+		,'set_default_account'=>img_picto('', 'check', 'class="pictofixedwidth"').$langs->trans("ConfirmPreselectAccount")
 		//'presend'=>img_picto('', 'email', 'class="pictofixedwidth"').$langs->trans("SendByMail"),
 		//'builddoc'=>img_picto('', 'pdf', 'class="pictofixedwidth"').$langs->trans("PDFMerge"),
 	);
 	//if ($user->rights->mymodule->supprimer) $arrayofmassactions['predelete'] = img_picto('', 'delete', 'class="pictofixedwidth"').$langs->trans("Delete");
 	//if (in_array($massaction, array('presend','predelete'))) $arrayofmassactions=array();
-	$massactionbutton = $form->selectMassAction('ventil', $arrayofmassactions, 1);
+	if ($massaction !== 'set_default_account') {
+		$massactionbutton = $form->selectMassAction('ventil', $arrayofmassactions, 1);
+	}
 
 	print '<form action="'.$_SERVER["PHP_SELF"].'" method="post">'."\n";
 	print '<input type="hidden" name="action" value="ventil">';
@@ -454,9 +467,17 @@ if ($result) {
 
 	print_barre_liste($langs->trans("InvoiceLines"), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num_lines, $nbtotalofrecords, 'title_accountancy', 0, '', '', $limit);
 
+	if ($massaction == 'set_default_account') {
+		$formquestion[]=array('type' => 'other',
+				'name' => 'set_default_account',
+				'label' => $langs->trans("AccountancyCode"),
+				'value' => $formaccounting->select_account('', 'default_account', 1, array(), 0, 0, 'maxwidth200 maxwidthonsmartphone', 'cachewithshowemptyone'));
+		print $form->formconfirm($_SERVER["PHP_SELF"], $langs->trans("ConfirmPreselectAccount"), $langs->trans("ConfirmPreselectAccountQuestion", count($toselect)), "confirm_set_default_account", $formquestion, 1, 0, 200, 500, 1);
+	}
+
 	print '<span class="opacitymedium">'.$langs->trans("DescVentilTodoCustomer").'</span></br><br>';
 
-	if ($msg) {
+	if (!empty($msg)) {
 		print $msg.'<br>';
 	}
 
@@ -469,7 +490,8 @@ if ($result) {
 	print '<tr class="liste_titre_filter">';
 	print '<td class="liste_titre"><input type="text" class="flat maxwidth25" name="search_lineid" value="'.dol_escape_htmltag($search_lineid).'"></td>';
 	print '<td class="liste_titre"><input type="text" class="flat maxwidth50" name="search_invoice" value="'.dol_escape_htmltag($search_invoice).'"></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_ref_supplier" value="'.dol_escape_htmltag($search_ref_supplier).'"></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 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'));
@@ -499,7 +521,8 @@ if ($result) {
 	print '<tr class="liste_titre">';
 	print_liste_field_titre("LineId", $_SERVER["PHP_SELF"], "l.rowid", "", $param, '', $sortfield, $sortorder);
 	print_liste_field_titre("Invoice", $_SERVER["PHP_SELF"], "f.ref", "", $param, '', $sortfield, $sortorder);
-	//print_liste_field_titre("InvoiceLabel", $_SERVER["PHP_SELF"], "f.libelle", "", $param, '', $sortfield, $sortorder);
+	print_liste_field_titre("RefSupplier", $_SERVER["PHP_SELF"], "f.ref_supplier", "", $param, '', $sortfield, $sortorder);
+	print_liste_field_titre("InvoiceLabel", $_SERVER["PHP_SELF"], "f.libelle", "", $param, '', $sortfield, $sortorder);
 	print_liste_field_titre("Date", $_SERVER["PHP_SELF"], "f.datef, f.ref, l.rowid", "", $param, '', $sortfield, $sortorder, 'center ');
 	print_liste_field_titre("ProductRef", $_SERVER["PHP_SELF"], "p.ref", "", $param, '', $sortfield, $sortorder);
 	//print_liste_field_titre("ProductLabel", $_SERVER["PHP_SELF"], "p.label", "", $param, '', $sortfield, $sortorder);
@@ -566,6 +589,7 @@ if ($result) {
 		$facturefourn_static->ref = $objp->ref;
 		$facturefourn_static->id = $objp->facid;
 		$facturefourn_static->type = $objp->ftype;
+		$facturefourn_static->ref_supplier = $objp->ref_supplier;
 		$facturefourn_static->label = $objp->invoice_label;
 		$facturefourn_static->date = $db->jdate($objp->datef);
 
@@ -625,15 +649,21 @@ if ($result) {
 		// Ref Invoice
 		print '<td class="nowraponall">'.$facturefourn_static->getNomUrl(1).'</td>';
 
-		/*print '<td class="tdoverflowonsmartphone">';
+		// Ref supplier invoice
+		print '<td class="tdoverflowmax100" title="'.dol_escape_htmltag($objp->ref_supplier).'">';
+		print $objp->ref_supplier;
+		print '</td>';
+
+		// Supplier invoice label
+		print '<td class="tdoverflowmax125 small" title="'.dol_escape_htmltag($objp->invoice_label).'">';
 		print $objp->invoice_label;
 		print '</td>';
-		*/
 
+		// Date
 		print '<td class="center">'.dol_print_date($facturefourn_static->date, 'day').'</td>';
 
 		// Ref Product
-		print '<td class="tdoverflowmax150">';
+		print '<td class="tdoverflowmax100">';
 		if ($product_static->id > 0) {
 			print $product_static->getNomUrl(1);
 		}
@@ -644,7 +674,7 @@ if ($result) {
 
 		// Description
 		print '<td class="tdoverflowonsmartphone small">';
-		$text = dolGetFirstLineOfText(dol_string_nohtmltag($facturefourn_static_det->desc));
+		$text = dolGetFirstLineOfText(dol_string_nohtmltag($facturefourn_static_det->desc, 1));
 		$trunclength = empty($conf->global->ACCOUNTING_LENGTH_DESCRIPTION) ? 32 : $conf->global->ACCOUNTING_LENGTH_DESCRIPTION;
 		print $form->textwithtooltip(dol_trunc($text, $trunclength), $facturefourn_static_det->desc);
 		print '</td>';
@@ -719,7 +749,7 @@ if ($result) {
 
 		// Suggested accounting account
 		print '<td>';
-		print $formaccounting->select_account($suggestedid, 'codeventil'.$facturefourn_static_det->id, 1, array(), 0, 0, 'codeventil maxwidth200 maxwidthonsmartphone', 'cachewithshowemptyone');
+		print $formaccounting->select_account(($default_account > 0 && $confirm === 'yes' && in_array($objp->rowid."_".$i, $toselect)) ? $default_account : $suggestedid, 'codeventil'.$facturefourn_static_det->id, 1, array(), 0, 0, 'codeventil maxwidth200 maxwidthonsmartphone', 'cachewithshowemptyone');
 		print '</td>';
 
 		// Column with checkbox
@@ -728,6 +758,14 @@ if ($result) {
 		if (!empty($suggestedid) && $suggestedaccountingaccountfor != '' && $suggestedaccountingaccountfor != 'eecwithoutvatnumber') {
 			$ischecked = 1;
 		}
+
+		if (!empty($toselect)) {
+			$ischecked = 0;
+			if (in_array($objp->rowid."_".$i, $toselect)) {
+				$ischecked=1;
+			}
+		}
+
 		print '<input type="checkbox" class="flat checkforselect checkforselect'.$facturefourn_static_det->id.'" name="toselect[]" value="'.$facturefourn_static_det->id."_".$i.'"'.($ischecked ? " checked" : "").'/>';
 		print '</td>';
 

+ 68 - 51
htdocs/adherents/admin/member.php

@@ -64,7 +64,7 @@ if ($action == 'set_default') {
 } elseif ($action == 'del_default') {
 	$ret = delDocumentModel($value, $type);
 	if ($ret > 0) {
-		if ($conf->global->MEMBER_ADDON_PDF_ODT == "$value") {
+		if (getDolGlobalString('MEMBER_ADDON_PDF_ODT') == "$value") {
 			dolibarr_del_const($db, 'MEMBER_ADDON_PDF_ODT', $conf->entity);
 		}
 	}
@@ -108,7 +108,7 @@ if ($action == 'set_default') {
 	$res3 = dolibarr_set_const($db, 'ADHERENT_CREATE_EXTERNAL_USER_LOGIN', GETPOST('ADHERENT_CREATE_EXTERNAL_USER_LOGIN', 'alpha'), 'chaine', 0, '', $conf->entity);
 	$res4 = dolibarr_set_const($db, 'ADHERENT_BANK_USE', GETPOST('ADHERENT_BANK_USE', 'alpha'), 'chaine', 0, '', $conf->entity);
 	// Use vat for invoice creation
-	if ($conf->facture->enabled) {
+	if (isModEnabled('facture')) {
 		$res4 = dolibarr_set_const($db, 'ADHERENT_VAT_FOR_SUBSCRIPTIONS', GETPOST('ADHERENT_VAT_FOR_SUBSCRIPTIONS', 'alpha'), 'chaine', 0, '', $conf->entity);
 		$res5 = dolibarr_set_const($db, 'ADHERENT_PRODUCT_ID_FOR_SUBSCRIPTIONS', GETPOST('ADHERENT_PRODUCT_ID_FOR_SUBSCRIPTIONS', 'alpha'), 'chaine', 0, '', $conf->entity);
 		if (!empty($conf->product->enabled) || !empty($conf->service->enabled)) {
@@ -196,6 +196,9 @@ print '<form action="'.$_SERVER["PHP_SELF"].'" method="POST">';
 print '<input type="hidden" name="token" value="'.newToken().'">';
 print '<input type="hidden" name="action" value="updateall">';
 
+
+// Mains options
+
 print load_fiche_titre($langs->trans("MemberMainOptions"), '', '');
 
 print '<div class="div-table-responsive-no-min">';
@@ -238,22 +241,22 @@ $arraychoices = array('0'=>$langs->trans("None"));
 if (!empty($conf->banque->enabled)) {
 	$arraychoices['bankdirect'] = $langs->trans("MoreActionBankDirect");
 }
-if (!empty($conf->banque->enabled) && !empty($conf->societe->enabled) && !empty($conf->facture->enabled)) {
+if (!empty($conf->banque->enabled) && !empty($conf->societe->enabled) && isModEnabled('facture')) {
 	$arraychoices['invoiceonly'] = $langs->trans("MoreActionInvoiceOnly");
 }
-if (!empty($conf->banque->enabled) && !empty($conf->societe->enabled) && !empty($conf->facture->enabled)) {
+if (!empty($conf->banque->enabled) && !empty($conf->societe->enabled) && isModEnabled('facture')) {
 	$arraychoices['bankviainvoice'] = $langs->trans("MoreActionBankViaInvoice");
 }
 print '<td>';
-print $form->selectarray('ADHERENT_BANK_USE', $arraychoices, $conf->global->ADHERENT_BANK_USE, 0);
-if ($conf->global->ADHERENT_BANK_USE == 'bankdirect' || $conf->global->ADHERENT_BANK_USE == 'bankviainvoice') {
+print $form->selectarray('ADHERENT_BANK_USE', $arraychoices, getDolGlobalString('ADHERENT_BANK_USE'), 0);
+if (getDolGlobalString('ADHERENT_BANK_USE') == 'bankdirect' || getDolGlobalString('ADHERENT_BANK_USE') == 'bankviainvoice') {
 	print '<br><div style="padding-top: 5px;"><span class="opacitymedium">'.$langs->trans("ABankAccountMustBeDefinedOnPaymentModeSetup").'</span></div>';
 }
 print '</td>';
 print "</tr>\n";
 
 // Use vat for invoice creation
-if ($conf->facture->enabled) {
+if (isModEnabled('facture')) {
 	print '<tr class="oddeven"><td>'.$langs->trans("VATToUseForSubscriptions").'</td>';
 	if (!empty($conf->banque->enabled)) {
 		print '<td>';
@@ -286,51 +289,15 @@ print '</div>';
 
 print '</form>';
 
-print '<br>';
-
-
-/*
- * Edit info of model document
- */
-$constantes = array(
-		'ADHERENT_CARD_TYPE',
-		//'ADHERENT_CARD_BACKGROUND',
-		'ADHERENT_CARD_HEADER_TEXT',
-		'ADHERENT_CARD_TEXT',
-		'ADHERENT_CARD_TEXT_RIGHT',
-		'ADHERENT_CARD_FOOTER_TEXT'
-);
-
-print load_fiche_titre($langs->trans("MembersCards"), '', '');
-
-$helptext = '*'.$langs->trans("FollowingConstantsWillBeSubstituted").'<br>';
-$helptext .= '__DOL_MAIN_URL_ROOT__, __ID__, __FIRSTNAME__, __LASTNAME__, __FULLNAME__, __LOGIN__, __PASSWORD__, ';
-$helptext .= '__COMPANY__, __ADDRESS__, __ZIP__, __TOWN__, __COUNTRY__, __EMAIL__, __BIRTH__, __PHOTO__, __TYPE__, ';
-$helptext .= '__YEAR__, __MONTH__, __DAY__';
-
-form_constantes($constantes, 0, $helptext);
 
 print '<br>';
 
 
-/*
- * Edit info of model document
- */
-$constantes = array('ADHERENT_ETIQUETTE_TYPE', 'ADHERENT_ETIQUETTE_TEXT');
-
-print load_fiche_titre($langs->trans("MembersTickets"), '', '');
-
-$helptext = '*'.$langs->trans("FollowingConstantsWillBeSubstituted").'<br>';
-$helptext .= '__DOL_MAIN_URL_ROOT__, __ID__, __FIRSTNAME__, __LASTNAME__, __FULLNAME__, __LOGIN__, __PASSWORD__, ';
-$helptext .= '__COMPANY__, __ADDRESS__, __ZIP__, __TOWN__, __COUNTRY__, __EMAIL__, __BIRTH__, __PHOTO__, __TYPE__, ';
-$helptext .= '__YEAR__, __MONTH__, __DAY__';
-
-form_constantes($constantes, 0, $helptext);
 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
 
 // Defined model definition table
 $def = array();
-$sql = "SELECT nom";
+$sql = "SELECT nom as name";
 $sql .= " FROM ".MAIN_DB_PREFIX."document_model";
 $sql .= " WHERE type = '".$db->escape($type)."'";
 $sql .= " AND entity = ".$conf->entity;
@@ -339,14 +306,15 @@ if ($resql) {
 	$i = 0;
 	$num_rows = $db->num_rows($resql);
 	while ($i < $num_rows) {
-		$array = $db->fetch_array($resql);
-		array_push($def, $array[0]);
+		$obj = $db->fetch_object($resql);
+		array_push($def, $obj->name);
 		$i++;
 	}
 } else {
 	dol_print_error($db);
 }
 
+
 print load_fiche_titre($langs->trans("MembersDocModules"), '', '');
 
 print '<div class="div-table-responsive-no-min">';
@@ -410,16 +378,16 @@ foreach ($dirmodels as $reldir) {
 									print '</td>';
 								} else {
 									print '<td class="center">'."\n";
-									print '<a href="'.$_SERVER["PHP_SELF"].'?action=set_default&token='.newToken().'&value='.$name.'&scandir='.$module->scandir.'&label='.urlencode($module->name).'">'.img_picto($langs->trans("Disabled"), 'switch_off').'</a>';
+									print '<a href="'.$_SERVER["PHP_SELF"].'?action=set_default&token='.newToken().'&value='.$name.'&scandir='.(!empty($module->scandir) ? $module->scandir : '').'&label='.urlencode($module->name).'">'.img_picto($langs->trans("Disabled"), 'switch_off').'</a>';
 									print "</td>";
 								}
 
 								// Defaut
 								print '<td class="center">';
-								if ($conf->global->MEMBER_ADDON_PDF == $name) {
+								if (getDolGlobalString('MEMBER_ADDON_PDF') == $name) {
 									print img_picto($langs->trans("Default"), 'on');
 								} else {
-									print '<a href="'.$_SERVER["PHP_SELF"].'?action=setdoc&token='.newToken().'&value='.$name.'&scandir='.$module->scandir.'&label='.urlencode($module->name).'" alt="'.$langs->trans("Default").'">'.img_picto($langs->trans("Disabled"), 'off').'</a>';
+									print '<a href="'.$_SERVER["PHP_SELF"].'?action=setdoc&token='.newToken().'&value='.$name.'&scandir='.(!empty($module->scandir) ? $module->scandir : '').'&label='.urlencode($module->name).'" alt="'.$langs->trans("Default").'">'.img_picto($langs->trans("Disabled"), 'off').'</a>';
 								}
 								print '</td>';
 
@@ -430,8 +398,8 @@ foreach ($dirmodels as $reldir) {
 									$htmltooltip .= '<br>'.$langs->trans("Width").'/'.$langs->trans("Height").': '.$module->page_largeur.'/'.$module->page_hauteur;
 								}
 								$htmltooltip .= '<br><br><u>'.$langs->trans("FeaturesSupported").':</u>';
-								$htmltooltip .= '<br>'.$langs->trans("Logo").': '.yn($module->option_logo, 1, 1);
-								$htmltooltip .= '<br>'.$langs->trans("MultiLanguage").': '.yn($module->option_multilang, 1, 1);
+								$htmltooltip .= '<br>'.$langs->trans("Logo").': '.yn(!empty($module->option_logo) ? $module->option_logo : 0, 1, 1);
+								$htmltooltip .= '<br>'.$langs->trans("MultiLanguage").': '.yn(!empty($module->option_multilang) ? $module->option_multilang : 0, 1, 1);
 
 
 								print '<td class="center">';
@@ -460,6 +428,55 @@ foreach ($dirmodels as $reldir) {
 print '</table>';
 print '</div>';
 
+
+
+/*
+TODO Use a global form instead of embeded form into table
+print '<form action="'.$_SERVER["PHP_SELF"].'" method="POST">';
+print '<input type="hidden" name="token" value="'.newToken().'">';
+print '<input type="hidden" name="action" value="updateall">';
+*/
+
+/*
+ * Edit info of model document
+ */
+$constantes = array(
+		'ADHERENT_CARD_TYPE',
+		//'ADHERENT_CARD_BACKGROUND',
+		'ADHERENT_CARD_HEADER_TEXT',
+		'ADHERENT_CARD_TEXT',
+		'ADHERENT_CARD_TEXT_RIGHT',
+		'ADHERENT_CARD_FOOTER_TEXT'
+);
+
+print load_fiche_titre($langs->trans("MembersCards"), '', '');
+
+$helptext = '*'.$langs->trans("FollowingConstantsWillBeSubstituted").'<br>';
+$helptext .= '__DOL_MAIN_URL_ROOT__, __ID__, __FIRSTNAME__, __LASTNAME__, __FULLNAME__, __LOGIN__, __PASSWORD__, ';
+$helptext .= '__COMPANY__, __ADDRESS__, __ZIP__, __TOWN__, __COUNTRY__, __EMAIL__, __BIRTH__, __PHOTO__, __TYPE__, ';
+$helptext .= '__YEAR__, __MONTH__, __DAY__';
+
+form_constantes($constantes, 0, $helptext);
+
+print '<br>';
+
+
+/*
+ * Edit info of model document
+ */
+$constantes = array('ADHERENT_ETIQUETTE_TYPE', 'ADHERENT_ETIQUETTE_TEXT');
+
+print load_fiche_titre($langs->trans("MembersTickets"), '', '');
+
+$helptext = '*'.$langs->trans("FollowingConstantsWillBeSubstituted").'<br>';
+$helptext .= '__DOL_MAIN_URL_ROOT__, __ID__, __FIRSTNAME__, __LASTNAME__, __FULLNAME__, __LOGIN__, __PASSWORD__, ';
+$helptext .= '__COMPANY__, __ADDRESS__, __ZIP__, __TOWN__, __COUNTRY__, __EMAIL__, __BIRTH__, __PHOTO__, __TYPE__, ';
+$helptext .= '__YEAR__, __MONTH__, __DAY__';
+
+form_constantes($constantes, 0, $helptext);
+
+//print '</form>';
+
 print "<br>";
 
 print dol_get_fiche_end();

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

@@ -84,7 +84,7 @@ print dol_get_fiche_end();
 // Buttons
 if ($action != 'create' && $action != 'edit') {
 	print '<div class="tabsAction">';
-	print '<div class="inline-block divButAction"><a class="butAction" href="'.$_SERVER["PHP_SELF"]."?action=create\">".$langs->trans("NewAttribute").'</a></div>';
+	print '<a class="butAction reposition" href="'.$_SERVER["PHP_SELF"].'?action=create">'.$langs->trans("NewAttribute").'</a>';
 	print "</div>";
 }
 

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

@@ -87,7 +87,7 @@ print dol_get_fiche_end();
 // Buttons
 if ($action != 'create' && $action != 'edit') {
 	print '<div class="tabsAction">';
-	print '<div class="inline-block divButAction"><a class="butAction" href="'.$_SERVER["PHP_SELF"]."?action=create\">".$langs->trans("NewAttribute").'</a></div>';
+	print '<a class="butAction reposition" href="'.$_SERVER["PHP_SELF"].'?action=create">'.$langs->trans("NewAttribute").'</a>';
 	print "</div>";
 }
 

+ 3 - 2
htdocs/adherents/admin/website.php

@@ -95,12 +95,13 @@ if ($action == 'update') {
 
 $form = new Form($db);
 
+$title = $langs->trans("MembersSetup");
 $help_url = 'EN:Module_Foundations|FR:Module_Adh&eacute;rents|ES:M&oacute;dulo_Miembros';
-llxHeader('', $langs->trans("MembersSetup"), $help_url);
+llxHeader('', $title, $help_url);
 
 
 $linkback = '<a href="'.DOL_URL_ROOT.'/admin/modules.php?restore_lastsearch_values=1">'.$langs->trans("BackToModuleList").'</a>';
-print load_fiche_titre($langs->trans("MembersSetup"), $linkback, 'title_setup');
+print load_fiche_titre($title, $linkback, 'title_setup');
 
 $head = member_admin_prepare_head();
 

+ 3 - 3
htdocs/adherents/agenda.php

@@ -162,11 +162,11 @@ if ($object->id > 0) {
 
 
 	$newcardbutton = '';
-	if (!empty($conf->agenda->enabled)) {
-		$newcardbutton .= dolGetButtonTitle($langs->trans('AddAction'), '', 'fa fa-plus-circle', DOL_URL_ROOT.'/comm/action/card.php?action=create&backtopage=1&origin=member&originid='.$id);
+	if (isModEnabled('agenda')) {
+		$newcardbutton .= dolGetButtonTitle($langs->trans('AddAction'), '', 'fa fa-plus-circle', DOL_URL_ROOT.'/comm/action/card.php?action=create&backtopage='.urlencode($_SERVER['PHP_SELF']).($object->id > 0 ? '?id='.$object->id : '').'&origin=member&originid='.$id);
 	}
 
-	if (!empty($conf->agenda->enabled) && (!empty($user->rights->agenda->myactions->read) || !empty($user->rights->agenda->allactions->read))) {
+	if (isModEnabled('agenda') && (!empty($user->rights->agenda->myactions->read) || !empty($user->rights->agenda->allactions->read))) {
 		print '<br>';
 
 		$param = '&id='.$id;

+ 18 - 25
htdocs/adherents/card.php

@@ -285,7 +285,7 @@ if (empty($reshook)) {
 			$object->lastname    = trim(GETPOST("lastname", 'alphanohtml'));
 			$object->gender      = trim(GETPOST("gender", 'alphanohtml'));
 			$object->login       = trim(GETPOST("login", 'alphanohtml'));
-			$object->pass        = trim(GETPOST("pass", 'alpha'));
+			$object->pass        = trim(GETPOST("pass", 'none'));	// For password, we must use 'none'
 
 			$object->societe     = trim(GETPOST("societe", 'alphanohtml')); // deprecated
 			$object->company     = trim(GETPOST("societe", 'alphanohtml'));
@@ -450,8 +450,8 @@ if (empty($reshook)) {
 		$email = preg_replace('/\s+/', '', GETPOST("member_email", 'alpha'));
 		$url   = trim(GETPOST('url', 'custom', 0, FILTER_SANITIZE_URL));
 		$login = GETPOST("member_login", 'alphanohtml');
-		$pass = GETPOST("password", 'alpha');
-		$photo = GETPOST("photo", 'alpha');
+		$pass = GETPOST("password", 'none');	// For password, we use 'none'
+		$photo = GETPOST("photo", 'alphanohtml');
 		$morphy = GETPOST("morphy", 'alphanohtml');
 		$public = GETPOST("public", 'alphanohtml');
 
@@ -999,7 +999,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 			require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
 			$generated_password = getRandomPassword(false);
 			print '<tr><td><span class="fieldrequired">'.$langs->trans("Password").'</span></td><td>';
-			print '<input type="text" class="minwidth300" maxlength="50" name="password" value="'.$generated_password.'">';
+			print '<input type="text" class="minwidth300" maxlength="50" name="password" value="'.dol_escape_htmltag($generated_password).'">';
 			print '</td></tr>';
 		}
 
@@ -1224,7 +1224,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 
 		// Password
 		if (empty($conf->global->ADHERENT_LOGIN_NOT_REQUIRED)) {
-			print '<tr><td class="fieldrequired">'.$langs->trans("Password").'</td><td><input type="password" name="pass" class="minwidth300" maxlength="50" value="'.(GETPOSTISSET("pass") ? GETPOST("pass", '', 2) : $object->pass).'"></td></tr>';
+			print '<tr><td class="fieldrequired">'.$langs->trans("Password").'</td><td><input type="password" name="pass" class="minwidth300" maxlength="50" value="'.dol_escape_htmltag(GETPOSTISSET("pass") ? GETPOST("pass", 'none', 2) : $object->pass).'"></td></tr>';
 		}
 		// Morphy
 		$morphys["phy"] = $langs->trans("Physical");
@@ -1280,13 +1280,20 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 				print '<tr><td><input type="checkbox" class="flat photodelete" name="deletephoto" id="photodelete"> '.$langs->trans("Delete").'<br><br></td></tr>';
 			}
 			print '<tr><td>'.$langs->trans("PhotoFile").'</td></tr>';
-			print '<tr><td><input type="file" class="flat" name="photo" id="photoinput"></td></tr>';
+			print '<tr><td>';
+			$maxfilesizearray = getMaxFileSizeArray();
+			$maxmin = $maxfilesizearray['maxmin'];
+			if ($maxmin > 0) {
+				print '<input type="hidden" name="MAX_FILE_SIZE" value="'.($maxmin * 1024).'">';	// MAX_FILE_SIZE must precede the field type=file
+			}
+			print '<input type="file" class="flat" name="photo" id="photoinput">';
+			print '</td></tr>';
 			print '</table>';
 		}
 		print '</td></tr>';
 
 		// EMail
-		print '<tr><td>'.($conf->global->ADHERENT_MAIL_REQUIRED ? '<span class="fieldrequired">' : '').$langs->trans("EMail").($conf->global->ADHERENT_MAIL_REQUIRED ? '</span>' : '').'</td>';
+		print '<tr><td>'.(getDolGlobalString("ADHERENT_MAIL_REQUIRED") ? '<span class="fieldrequired">' : '').$langs->trans("EMail").(getDolGlobalString("ADHERENT_MAIL_REQUIRED") ? '</span>' : '').'</td>';
 		print '<td>'.img_picto('', 'object_email', 'class="pictofixedwidth"').'<input type="text" name="member_email" class="minwidth300" maxlength="255" value="'.(GETPOSTISSET("member_email") ? GETPOST("member_email", '', 2) : $object->email).'"></td></tr>';
 
 		// Website
@@ -1719,8 +1726,6 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 		print '<tr><td>'.$langs->trans("MemberNature").'</td><td class="valeur" >'.$object->getmorphylib().'</td>';
 		print '</tr>';
 
-		print '</td></tr>';
-
 		// Company
 		print '<tr><td>'.$langs->trans("Company").'</td><td class="valeur">'.dol_escape_htmltag($object->company).'</td></tr>';
 
@@ -1737,9 +1742,9 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 				if ($user->admin) {
 					print '<!-- '.$langs->trans("Crypted").': '.$object->pass_indatabase_crypted.' -->';
 				}
-				print '<span class="opacitymedium"'.$langs->trans("Hidden").'</span>';
+				print '<span class="opacitymedium">'.$langs->trans("Hidden").'</span>';
 			}
-			if ((!empty($object->pass) || !empty($object->pass_crypted)) && empty($object->user_id)) {
+			if (!empty($object->pass_indatabase) && empty($object->user_id)) {	// Show warning only for old password still in clear (does not happen anymore)
 				$langs->load("errors");
 				$htmltext = $langs->trans("WarningPasswordSetWithNoAccount");
 				print ' '.$form->textwithpicto('', $htmltext, 1, 'warning');
@@ -1778,7 +1783,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 		print '<div class="fichehalfright">';
 		print '<div class="underbanner clearboth"></div>';
 
-		print '<table class="border tableforfield tableforfield centpercent">';
+		print '<table class="border tableforfield centpercent">';
 
 		// Tags / Categories
 		if (!empty($conf->categorie->enabled) && !empty($user->rights->categorie->lire)) {
@@ -1853,18 +1858,6 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 		}
 		print '</td></tr>';
 
-		// VCard
-		/*
-		print '<tr><td>';
-		print $langs->trans("VCard").'</td><td colspan="3">';
-		print '<a href="'.DOL_URL_ROOT.'/adherents/vcard.php?id='.$object->id.'">';
-		print img_picto($langs->trans("Download").' vcard', 'vcard.png', 'class="paddingrightonly"');
-		print $langs->trans("Download");
-		print img_picto($langs->trans("Download").' vcard', 'download', 'class="paddingleft"');
-		print '</a>';
-		print '</td></tr>';
-		*/
-
 		print "</table>\n";
 
 		print "</div></div>\n";
@@ -2044,7 +2037,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 
 			$MAX = 10;
 
-			$morehtmlcenter = dolGetButtonTitle($langs->trans('SeeAll'), '', 'fa fa-list-alt imgforviewmode', DOL_URL_ROOT.'/adherents/agenda.php?id='.$object->id);
+			$morehtmlcenter = dolGetButtonTitle($langs->trans('SeeAll'), '', 'fa fa-bars imgforviewmode', DOL_URL_ROOT.'/adherents/agenda.php?id='.$object->id);
 
 			// List of actions on element
 			include_once DOL_DOCUMENT_ROOT.'/core/class/html.formactions.class.php';

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

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

+ 8 - 9
htdocs/adherents/class/adherent.class.php

@@ -659,8 +659,7 @@ class Adherent extends CommonObject
 		$nbrowsaffected = 0;
 		$error = 0;
 
-		dol_syslog(get_class($this)."::update notrigger=".$notrigger.", nosyncuser=".$nosyncuser.", nosyncuserpass=".$nosyncuserpass." nosyncthirdparty=".$nosyncthirdparty.", email=".
-			$this->email);
+		dol_syslog(get_class($this)."::update notrigger=".$notrigger.", nosyncuser=".$nosyncuser.", nosyncuserpass=".$nosyncuserpass." nosyncthirdparty=".$nosyncthirdparty.", email=".$this->email);
 
 		// Clean parameters
 		$this->lastname = trim($this->lastname) ? trim($this->lastname) : trim($this->lastname);
@@ -693,7 +692,9 @@ class Adherent extends CommonObject
 		$sql .= ", gender = ".($this->gender != -1 ? "'".$this->db->escape($this->gender)."'" : "null"); // 'man' or 'woman'
 		$sql .= ", login = ".($this->login ? "'".$this->db->escape($this->login)."'" : "null");
 		$sql .= ", societe = ".($this->company ? "'".$this->db->escape($this->company)."'" : ($this->societe ? "'".$this->db->escape($this->societe)."'" : "null"));
-		$sql .= ", fk_soc = ".($this->socid > 0 ? $this->db->escape($this->socid) : "null");
+		if ($this->socid) {
+			$sql .= ", fk_soc = ".($this->socid > 0 ? $this->db->escape($this->socid) : "null");	 // Must be modified only when creating from a third-party
+		}
 		$sql .= ", address = ".($this->address ? "'".$this->db->escape($this->address)."'" : "null");
 		$sql .= ", zip = ".($this->zip ? "'".$this->db->escape($this->zip)."'" : "null");
 		$sql .= ", town = ".($this->town ? "'".$this->db->escape($this->town)."'" : "null");
@@ -713,9 +714,6 @@ class Adherent extends CommonObject
 		$sql .= ", fk_adherent_type = ".$this->db->escape($this->typeid);
 		$sql .= ", morphy = '".$this->db->escape($this->morphy)."'";
 		$sql .= ", birth = ".($this->birth ? "'".$this->db->idate($this->birth)."'" : "null");
-		if ($this->socid) {
-			$sql .= ", fk_soc = '".$this->db->escape($this->socid)."'"; // Must be modified only when creating from a third-party
-		}
 		if ($this->datefin) {
 			$sql .= ", datefin = '".$this->db->idate($this->datefin)."'"; // Must be modified only when deleting a subscription
 		}
@@ -1536,9 +1534,9 @@ class Adherent extends CommonObject
 	 *
 	 *	@param	int	        $date        		Date of effect of subscription
 	 *	@param	double		$amount     		Amount of subscription (0 accepted for some members)
-	 *	@param	int			$accountid			Id bank account
-	 *	@param	string		$operation			Type of payment (if Id bank account provided). Example: 'CB', ...
-	 *	@param	string		$label				Label operation (if Id bank account provided)
+	 *	@param	int			$accountid			Id bank account. NOT USED.
+	 *	@param	string		$operation			Code of payment mode (if Id bank account provided). Example: 'CB', ... NOT USED.
+	 *	@param	string		$label				Label operation (if Id bank account provided).
 	 *	@param	string		$num_chq			Numero cheque (if Id bank account provided)
 	 *	@param	string		$emetteur_nom		Name of cheque writer
 	 *	@param	string		$emetteur_banque	Name of bank of cheque
@@ -1805,6 +1803,7 @@ class Adherent extends CommonObject
 				$paiement = new Paiement($this->db);
 				$paiement->datepaye = $paymentdate;
 				$paiement->amounts = $amounts;
+				$paiement->paiementcode = $operation;
 				$paiement->paiementid = dol_getIdFromCode($this->db, $operation, 'c_paiement', 'code', 'id', 1);
 				$paiement->num_payment = $num_chq;
 				$paiement->note_public = $label;

+ 4 - 1
htdocs/adherents/class/adherent_type.class.php

@@ -121,6 +121,9 @@ class AdherentType extends CommonObject
 	/** @var array Array of members */
 	public $members = array();
 
+	/** @var string string other */
+	public $other = array();
+
 	public $multilangs = array();
 
 
@@ -271,7 +274,7 @@ class AdherentType extends CommonObject
 		$result = $this->db->query($sql);
 		if ($result) {
 			// Call trigger
-			$result = $this->call_trigger('ADHERENT_TYPE_DEL_MULTILANGS', $user);
+			$result = $this->call_trigger('MEMBER_TYPE_DEL_MULTILANGS', $user);
 			if ($result < 0) {
 				$this->error = $this->db->lasterror();
 				dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);

+ 1 - 1
htdocs/adherents/class/api_members.class.php

@@ -358,7 +358,7 @@ class Members extends DolibarrApi
 		if ($member->update(DolibarrApiAccess::$user) >= 0) {
 			return $this->get($id);
 		} else {
-			throw new RestException(500, $member->error);
+			throw new RestException(500, 'Error when updating member: '.$member->error);
 		}
 	}
 

+ 1 - 1
htdocs/adherents/class/api_memberstypes.class.php

@@ -204,7 +204,7 @@ class MembersTypes extends DolibarrApi
 		if ($membertype->update(DolibarrApiAccess::$user) >= 0) {
 			return $this->get($id);
 		} else {
-			throw new RestException(500, $membertype->error);
+			throw new RestException(500, 'Error when updating member type: '.$membertype->error);
 		}
 	}
 

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

@@ -159,7 +159,7 @@ class Subscriptions extends DolibarrApi
 			$subscription->$field = $value;
 		}
 		if ($subscription->create(DolibarrApiAccess::$user) < 0) {
-			throw new RestException(500, 'Error when creating subscription', array_merge(array($subscription->error), $subscription->errors));
+			throw new RestException(500, 'Error when creating contribution', array_merge(array($subscription->error), $subscription->errors));
 		}
 		return $subscription->id;
 	}
@@ -193,7 +193,7 @@ class Subscriptions extends DolibarrApi
 		if ($subscription->update(DolibarrApiAccess::$user) > 0) {
 			return $this->get($id);
 		} else {
-			throw new RestException(500, $subscription->error);
+			throw new RestException(500, 'Error when updating contribution: '.$subscription->error);
 		}
 	}
 

+ 6 - 6
htdocs/adherents/class/subscription.class.php

@@ -358,7 +358,7 @@ class Subscription extends CommonObject
 					$result = $member->update_end_date($user);
 
 					if ($this->fk_bank > 0 && is_object($accountline) && $accountline->id > 0) {	// If we found bank account line (this means this->fk_bank defined)
-						$result = $accountline->delete($user); // Return false if refused because line is conciliated
+						$result = $accountline->delete($user); // Return false if refused because line is reconciled
 						if ($result > 0) {
 							$this->db->commit();
 							return 1;
@@ -490,17 +490,17 @@ class Subscription extends CommonObject
 		$sql .= ' FROM '.MAIN_DB_PREFIX.'subscription as c';
 		$sql .= ' WHERE c.rowid = '.((int) $id);
 
-		$result = $this->db->query($sql);
-		if ($result) {
-			if ($this->db->num_rows($result)) {
-				$obj = $this->db->fetch_object($result);
+		$resql = $this->db->query($sql);
+		if ($resql) {
+			if ($this->db->num_rows($resql)) {
+				$obj = $this->db->fetch_object($resql);
 				$this->id = $obj->rowid;
 
 				$this->date_creation = $this->db->jdate($obj->datec);
 				$this->date_modification = $this->db->jdate($obj->datem);
 			}
 
-			$this->db->free($result);
+			$this->db->free($resql);
 		} else {
 			dol_print_error($this->db);
 		}

+ 14 - 14
htdocs/adherents/index.php

@@ -99,12 +99,12 @@ $sql .= " WHERE t.entity IN (".getEntity('member_type').")";
 $sql .= " GROUP BY t.rowid, t.libelle, t.subscription, d.statut";
 
 dol_syslog("index.php::select nb of members per type", LOG_DEBUG);
-$result = $db->query($sql);
-if ($result) {
-	$num = $db->num_rows($result);
+$resql = $db->query($sql);
+if ($resql) {
+	$num = $db->num_rows($resql);
 	$i = 0;
 	while ($i < $num) {
-		$objp = $db->fetch_object($result);
+		$objp = $db->fetch_object($resql);
 
 		$adhtype = new AdherentType($db);
 		$adhtype->id = $objp->rowid;
@@ -127,7 +127,7 @@ if ($result) {
 
 		$i++;
 	}
-	$db->free($result);
+	$db->free($resql);
 }
 
 $now = dol_now();
@@ -143,16 +143,16 @@ $sql .= " AND t.rowid = d.fk_adherent_type";
 $sql .= " GROUP BY d.fk_adherent_type";
 
 dol_syslog("index.php::select nb of uptodate members by type", LOG_DEBUG);
-$result = $db->query($sql);
-if ($result) {
-	$num = $db->num_rows($result);
+$resql = $db->query($sql);
+if ($resql) {
+	$num = $db->num_rows($resql);
 	$i = 0;
 	while ($i < $num) {
-		$objp = $db->fetch_object($result);
+		$objp = $db->fetch_object($resql);
 		$MembersUpToDate[$objp->fk_adherent_type] = $objp->somme;
 		$i++;
 	}
-	$db->free();
+	$db->free($resql);
 }
 
 /*
@@ -177,8 +177,8 @@ if ($conf->use_javascript_ajax) {
 	$i = 0;
 	foreach ($AdherentType as $key => $adhtype) {
 		$dataval['draft'][] = array($i, isset($MembersToValidate[$key]) ? $MembersToValidate[$key] : 0);
-		$dataval['notuptodate'][] = array($i, isset($MembersValidated[$key]) ? $MembersValidated[$key] - (isset($MembersUpToDate[$key]) ? $MembersUpToDate[$key] : 0) : 0);
 		$dataval['uptodate'][] = array($i, isset($MembersUpToDate[$key]) ? $MembersUpToDate[$key] : 0);
+		$dataval['notuptodate'][] = array($i, isset($MembersValidated[$key]) ? $MembersValidated[$key] - (isset($MembersUpToDate[$key]) ? $MembersUpToDate[$key] : 0) : 0);
 		$dataval['excluded'][] = array($i, isset($MembersExcluded[$key]) ? $MembersExcluded[$key] : 0);
 		$dataval['resiliated'][] = array($i, isset($MembersResiliated[$key]) ? $MembersResiliated[$key] : 0);
 
@@ -191,18 +191,18 @@ if ($conf->use_javascript_ajax) {
 	}
 	$total = $SumToValidate + $SumValidated + $SumUpToDate + $SumExcluded + $SumResiliated;
 	$dataseries = array();
-	$dataseries[] = array($langs->transnoentitiesnoconv("OutOfDate"), round($SumValidated));
+	$dataseries[] = array($langs->transnoentitiesnoconv("MembersStatusToValid"), round($SumToValidate));			// Draft, not yet validated
 	$dataseries[] = array($langs->transnoentitiesnoconv("UpToDate"), round($SumUpToDate));
+	$dataseries[] = array($langs->transnoentitiesnoconv("OutOfDate"), round($SumValidated));
 	$dataseries[] = array($langs->transnoentitiesnoconv("MembersStatusExcluded"), round($SumExcluded));
 	$dataseries[] = array($langs->transnoentitiesnoconv("MembersStatusResiliated"), round($SumResiliated));
-	$dataseries[] = array($langs->transnoentitiesnoconv("MembersStatusToValid"), round($SumToValidate));
 
 	include DOL_DOCUMENT_ROOT.'/theme/'.$conf->theme.'/theme_vars.inc.php';
 
 	include_once DOL_DOCUMENT_ROOT.'/core/class/dolgraph.class.php';
 	$dolgraph = new DolGraph();
 	$dolgraph->SetData($dataseries);
-	$dolgraph->SetDataColor(array($badgeStatus1, $badgeStatus4, '-'.$badgeStatus8, $badgeStatus6, '-'.$badgeStatus0));
+	$dolgraph->SetDataColor(array('-'.$badgeStatus0, $badgeStatus4, '-'.$badgeStatus1, '-'.$badgeStatus8, $badgeStatus6));
 	$dolgraph->setShowLegend(2);
 	$dolgraph->setShowPercent(1);
 	$dolgraph->SetType(array('pie'));

+ 90 - 54
htdocs/adherents/list.php

@@ -1,7 +1,7 @@
 <?php
 /* Copyright (C) 2001-2003  Rodolphe Quiedeville    <rodolphe@quiedeville.org>
  * Copyright (C) 2002-2003  Jean-Louis Bergamo      <jlb@j1b.org>
- * Copyright (C) 2004-2016  Laurent Destailleur     <eldy@users.sourceforge.net>
+ * Copyright (C) 2004-2022  Laurent Destailleur     <eldy@users.sourceforge.net>
  * Copyright (C) 2013-2015  Raphaël Doursenaud      <rdoursenaud@gpcsolutions.fr>
  * Copyright (C) 2014-2016  Juanjo Menent           <jmenent@2byte.es>
  * Copyright (C) 2018       Alexandre Spangaro      <aspangaro@open-dsi.fr>
@@ -63,6 +63,7 @@ $search_email = GETPOST("search_email", 'alpha');
 $search_categ = GETPOST("search_categ", 'int');
 $search_filter = GETPOST("search_filter", 'alpha');
 $search_status = GETPOST("search_status", 'intcomma');
+$search_import_key  = trim(GETPOST("search_import_key", "alpha"));
 $catid        = GETPOST("catid", 'int');
 $optioncss = GETPOST('optioncss', 'alpha');
 $socid = GETPOST('socid', 'int');
@@ -156,7 +157,8 @@ $arrayfields = array(
 	'd.datec'=>array('label'=>$langs->trans("DateCreation"), 'checked'=>0, 'position'=>500),
 	'd.birth'=>array('label'=>$langs->trans("Birthday"), 'checked'=>0, 'position'=>500),
 	'd.tms'=>array('label'=>$langs->trans("DateModificationShort"), 'checked'=>0, 'position'=>500),
-	'd.statut'=>array('label'=>$langs->trans("Status"), 'checked'=>1, 'position'=>1000)
+	'd.statut'=>array('label'=>$langs->trans("Status"), 'checked'=>1, 'position'=>1000),
+	'd.import_key'=>array('label'=>"ImportId", 'checked'=>0, 'position'=>1100),
 );
 // Extra fields
 include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_array_fields.tpl.php';
@@ -172,7 +174,7 @@ $result = restrictedArea($user, 'adherent');
 if (GETPOST('cancel', 'alpha')) {
 	$action = 'list'; $massaction = '';
 }
-if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend' && $massaction != 'confirm_createbills') {
+if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend') {
 	$massaction = '';
 }
 
@@ -213,9 +215,10 @@ if (empty($reshook)) {
 		$search_categ = "";
 		$search_filter = "";
 		$search_status = "";
+		$search_import_key = '';
 		$catid = "";
 		$sall = "";
-		$toselect = '';
+		$toselect = array();
 		$search_array_options = array();
 	}
 
@@ -305,9 +308,11 @@ $formother = new FormOther($db);
 $membertypestatic = new AdherentType($db);
 $memberstatic = new Adherent($db);
 
+$title = $langs->trans("Members");
+
 $now = dol_now();
 
-if (!empty($search_categ) || !empty($catid)) {
+if ((!empty($search_categ) && $search_categ > 0) || !empty($catid)) {
 	$sql = "SELECT DISTINCT";
 } else {
 	$sql = "SELECT";
@@ -316,7 +321,7 @@ $sql .= " d.rowid, d.ref, d.login, d.lastname, d.firstname, d.gender, d.societe
 $sql .= " d.civility, d.datefin, d.address, d.zip, d.town, d.state_id, d.country,";
 $sql .= " d.email, d.phone, d.phone_perso, d.phone_mobile, d.birth, d.public, d.photo,";
 $sql .= " d.fk_adherent_type as type_id, d.morphy, d.statut, d.datec as date_creation, d.tms as date_update,";
-$sql .= " d.note_private, d.note_public,";
+$sql .= " d.note_private, d.note_public, d.import_key,";
 $sql .= " s.nom,";
 $sql .= " ".$db->ifsql("d.societe IS NULL", "s.nom", "d.societe")." as companyname,";
 $sql .= " t.libelle as type, t.subscription,";
@@ -336,7 +341,7 @@ $sql .= " FROM ".MAIN_DB_PREFIX."adherent as d";
 if (!empty($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label'])) {
 	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$object->table_element."_extrafields as ef on (d.rowid = ef.fk_object)";
 }
-if (!empty($search_categ) || !empty($catid)) {
+if ((!empty($search_categ) && $search_categ > 0) || !empty($catid)) {
 	// We need this table joined to the select in order to filter by categ
 	$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX."categorie_member as cm ON d.rowid = cm.fk_member";
 }
@@ -425,6 +430,9 @@ if ($search_phone_mobile) {
 if ($search_country) {
 	$sql .= " AND d.country IN (".$db->sanitize($search_country).')';
 }
+if ($search_import_key) {
+	$sql .= natural_search("d.import_key", $search_import_key);
+}
 
 // Add where from extra fields
 include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php';
@@ -434,8 +442,6 @@ $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 with no order and no limits
 $nbtotalofrecords = '';
 if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
@@ -445,15 +451,20 @@ if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
 	} else {
 		dol_print_error($db);
 	}
+
 	if (($page * $limit) > $nbtotalofrecords) {	// if total resultset is smaller then paging size (filtering), goto and load page 0
 		$page = 0;
 		$offset = 0;
 	}
+	$db->free($resql);
+}
+
+// Complete request and execute it with limit
+$sql .= $db->order($sortfield, $sortorder);
+if ($limit) {
+	$sql .= $db->plimit($limit + 1, $offset);
 }
-// Add limit
-$sql .= $db->plimit($limit + 1, $offset);
 
-dol_syslog("get list", LOG_DEBUG);
 $resql = $db->query($sql);
 if (!$resql) {
 	dol_print_error($db);
@@ -462,6 +473,7 @@ if (!$resql) {
 
 $num = $db->num_rows($resql);
 
+
 $arrayofselected = is_array($toselect) ? $toselect : array();
 
 if ($num == 1 && !empty($conf->global->MAIN_SEARCH_DIRECT_OPEN_IF_ONLY_ONE) && $sall) {
@@ -471,42 +483,42 @@ if ($num == 1 && !empty($conf->global->MAIN_SEARCH_DIRECT_OPEN_IF_ONLY_ONE) && $
 	exit;
 }
 
-llxHeader('', $langs->trans("Member"), 'EN:Module_Foundations|FR:Module_Adh&eacute;rents|ES:M&oacute;dulo_Miembros');
+$help_url = 'EN:Module_Foundations|FR:Module_Adh&eacute;rents|ES:M&oacute;dulo_Miembros';
+llxHeader('', $title, $help_url);
 
-$titre = $langs->trans("MembersList");
 if (GETPOSTISSET("search_status")) {
 	if ($search_status == '-1,1') { // TODO : check this test as -1 == Adherent::STATUS_DRAFT and -2 == Adherent::STATUS_EXLCUDED
-		$titre = $langs->trans("MembersListQualified");
+		$title = $langs->trans("MembersListQualified");
 	}
 	if ($search_status == Adherent::STATUS_DRAFT) {
-		$titre = $langs->trans("MembersListToValid");
+		$title = $langs->trans("MembersListToValid");
 	}
 	if ($search_status == Adherent::STATUS_VALIDATED && $filter == '') {
-		$titre = $langs->trans("MembersValidated");
+		$title = $langs->trans("MenuMembersValidated");
 	}
 	if ($search_status == Adherent::STATUS_VALIDATED && $filter == 'withoutsubscription') {
-		$titre = $langs->trans("MembersWithSubscriptionToReceive");
+		$title = $langs->trans("MembersWithSubscriptionToReceive");
 	}
 	if ($search_status == Adherent::STATUS_VALIDATED && $filter == 'uptodate') {
-		$titre = $langs->trans("MembersListUpToDate");
+		$title = $langs->trans("MembersListUpToDate");
 	}
 	if ($search_status == Adherent::STATUS_VALIDATED && $filter == 'outofdate') {
-		$titre = $langs->trans("MembersListNotUpToDate");
+		$title = $langs->trans("MembersListNotUpToDate");
 	}
 	if ((string) $search_status == (string) Adherent::STATUS_RESILIATED) {	// The cast to string is required to have test false when search_status is ''
-		$titre = $langs->trans("MembersListResiliated");
+		$title = $langs->trans("MembersListResiliated");
 	}
 	if ($search_status == Adherent::STATUS_EXCLUDED) {
-		$titre = $langs->trans("MembersListExcluded");
+		$title = $langs->trans("MembersListExcluded");
 	}
 } elseif ($action == 'search') {
-	$titre = $langs->trans("MembersListQualified");
+	$title = $langs->trans("MembersListQualified");
 }
 
 if ($search_type > 0) {
 	$membertype = new AdherentType($db);
-	$result = $membertype->fetch(GETPOST("type", 'int'));
-	$titre .= " (".$membertype->label.")";
+	$result = $membertype->fetch($search_type);
+	$title .= " (".$membertype->label.")";
 }
 
 $param = '';
@@ -540,7 +552,7 @@ if ($search_login) {
 if ($search_email) {
 	$param .= "&search_email=".urlencode($search_email);
 }
-if ($search_categ) {
+if ($search_categ > 0 || $search_categ == -2) {
 	$param .= "&search_categ=".urlencode($search_categ);
 }
 if ($search_company) {
@@ -573,9 +585,12 @@ if ($search_phone_mobile != '') {
 if ($search_filter && $search_filter != '-1') {
 	$param .= "&search_filter=".urlencode($search_filter);
 }
-if ($search_status != "" && $search_status != Adherent::STATUS_DRAFT) {
+if ($search_status != "" && $search_status != -3) {
 	$param .= "&search_status=".urlencode($search_status);
 }
+if ($search_import_key != '') {
+	$param .= '&search_import_key='.urlencode($search_import_key);
+}
 if ($search_type > 0) {
 	$param .= "&search_type=".urlencode($search_type);
 }
@@ -587,7 +602,7 @@ include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php';
 
 // List of mass actions available
 $arrayofmassactions = array(
-	//'presend'=>img_picto('', 'email', 'class="pictofixedwidth"').'&ensp;'.$langs->trans("SendByMail"),
+	//'presend'=>img_picto('', 'email', 'class="pictofixedwidth"').$langs->trans("SendByMail"),
 	//'builddoc'=>img_picto('', 'pdf', 'class="pictofixedwidth"').$langs->trans("PDFMerge"),
 );
 if ($user->rights->adherent->creer) {
@@ -602,7 +617,7 @@ if ($user->rights->societe->creer) {
 if ($user->rights->adherent->creer && $user->rights->user->user->creer) {
 	$arrayofmassactions['createexternaluser'] = img_picto('', 'user', 'class="pictofixedwidth"').$langs->trans("CreateExternalUser");
 }
-if (in_array($massaction, array('presend', 'predelete', 'preaffecttag'))) {
+if (GETPOST('nomassaction', 'int') || in_array($massaction, array('presend', 'predelete', 'preaffecttag'))) {
 	$arrayofmassactions = array();
 }
 $massactionbutton = $form->selectMassAction('', $arrayofmassactions);
@@ -623,7 +638,7 @@ print '<input type="hidden" name="sortfield" value="'.$sortfield.'">';
 print '<input type="hidden" name="sortorder" value="'.$sortorder.'">';
 print '<input type="hidden" name="contextpage" value="'.$contextpage.'">';
 
-print_barre_liste($titre, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, $object->picto, 0, $newcardbutton, '', $limit, 0, 0, 1);
+print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, $object->picto, 0, $newcardbutton, '', $limit, 0, 0, 1);
 
 $topicmail = "Information";
 $modelmail = "member";
@@ -668,7 +683,6 @@ if ($massactionbutton) {
 print '<div class="div-table-responsive">';
 print '<table class="tagtable liste'.($moreforfilter ? " listwithfilterbefore" : "").'">'."\n";
 
-
 // Line for filters fields
 print '<tr class="liste_titre_filter">';
 
@@ -767,7 +781,7 @@ if (!empty($arrayfields['d.email']['checked'])) {
 }
 // End of subscription date
 if (!empty($arrayfields['d.datefin']['checked'])) {
-	print '<td class="liste_titre left">';
+	print '<td class="liste_titre center">';
 	$selectarray = array('-1'=>'', 'withoutsubscription'=>$langs->trans("WithoutSubscription"), 'uptodate'=>$langs->trans("UpToDate"), 'outofdate'=>$langs->trans("OutOfDate"));
 	print $form->selectarray('search_filter', $selectarray, $search_filter);
 	print '</td>';
@@ -806,15 +820,24 @@ if (!empty($arrayfields['d.statut']['checked'])) {
 	print $form->selectarray('search_status', $liststatus, $search_status, -3);
 	print '</td>';
 }
-// Action column
-print '<td class="liste_titre middle">';
-$searchpicto = $form->showFilterButtons();
-print $searchpicto;
-print '</td>';
-
+if (!empty($arrayfields['d.import_key']['checked'])) {
+	print '<td class="liste_titre center">';
+	print '<input class="flat searchstring maxwidth50" type="text" name="search_import_key" value="'.dol_escape_htmltag($search_import_key).'">';
+	print '</td>';
+}
+if (empty($conf->global->MAIN_CHECKBOX_LEFT_COLUMN)) {
+	// Action column
+	print '<td class="liste_titre middle">';
+	$searchpicto = $form->showFilterButtons();
+	print $searchpicto;
+	print '</td>';
+}
 print "</tr>\n";
 
 print '<tr class="liste_titre">';
+if (!empty($conf->global->MAIN_CHECKBOX_LEFT_COLUMN)) {
+	print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', '', $sortfield, $sortorder, 'center maxwidthsearch actioncolumn ');
+}
 if (!empty($conf->global->MAIN_SHOW_TECHNICAL_ID)) {
 	print_liste_field_titre("ID", $_SERVER["PHP_SELF"], '', '', $param, 'align="center"', $sortfield, $sortorder);
 }
@@ -858,7 +881,7 @@ if (!empty($arrayfields['state.nom']['checked'])) {
 	print_liste_field_titre($arrayfields['state.nom']['label'], $_SERVER["PHP_SELF"], "state.nom", "", $param, '', $sortfield, $sortorder);
 }
 if (!empty($arrayfields['country.code_iso']['checked'])) {
-	print_liste_field_titre($arrayfields['country.code_iso']['label'], $_SERVER["PHP_SELF"], "country.code_iso", "", $param, 'align="center"', $sortfield, $sortorder);
+	print_liste_field_titre($arrayfields['country.code_iso']['label'], $_SERVER["PHP_SELF"], "country.code_iso", "", $param, '', $sortfield, $sortorder, 'center ');
 }
 if (!empty($arrayfields['d.phone']['checked'])) {
 	print_liste_field_titre($arrayfields['d.phone']['label'], $_SERVER["PHP_SELF"], 'd.phone', '', $param, '', $sortfield, $sortorder);
@@ -873,7 +896,7 @@ if (!empty($arrayfields['d.email']['checked'])) {
 	print_liste_field_titre($arrayfields['d.email']['label'], $_SERVER["PHP_SELF"], 'd.email', '', $param, '', $sortfield, $sortorder);
 }
 if (!empty($arrayfields['d.datefin']['checked'])) {
-	print_liste_field_titre($arrayfields['d.datefin']['label'], $_SERVER["PHP_SELF"], 'd.datefin', '', $param, 'align="center"', $sortfield, $sortorder);
+	print_liste_field_titre($arrayfields['d.datefin']['label'], $_SERVER["PHP_SELF"], 'd.datefin', '', $param, '', $sortfield, $sortorder, 'center ');
 }
 // Extra fields
 include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_title.tpl.php';
@@ -894,7 +917,12 @@ if (!empty($arrayfields['d.tms']['checked'])) {
 if (!empty($arrayfields['d.statut']['checked'])) {
 	print_liste_field_titre($arrayfields['d.statut']['label'], $_SERVER["PHP_SELF"], "d.statut", "", $param, 'class="right"', $sortfield, $sortorder);
 }
-print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', 'align="center"', $sortfield, $sortorder, 'maxwidthsearch ');
+if (!empty($arrayfields['d.import_key']['checked'])) {
+	print_liste_field_titre($arrayfields['d.import_key']['label'], $_SERVER["PHP_SELF"], "d.import_key", "", $param, '', $sortfield, $sortorder, 'center ');
+}
+if (empty($conf->global->MAIN_CHECKBOX_LEFT_COLUMN)) {
+	print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', 'align="center"', $sortfield, $sortorder, 'maxwidthsearch ');
+}
 print "</tr>\n";
 
 $i = 0;
@@ -935,7 +963,7 @@ while ($i < min($num, $limit)) {
 	print '<tr class="oddeven">';
 
 	if (!empty($conf->global->MAIN_SHOW_TECHNICAL_ID)) {
-		print '<td class="center">'.$obj->rowid.'</td>';
+		print '<td class="center" data-key="id">'.$obj->rowid.'</td>';
 		if (!$i) {
 			$totalarray['nbfield']++;
 		}
@@ -1110,16 +1138,14 @@ while ($i < min($num, $limit)) {
 	// End of subscription date
 	$datefin = $db->jdate($obj->datefin);
 	if (!empty($arrayfields['d.datefin']['checked'])) {
+		print '<td class="nowrap center">';
 		if ($datefin) {
-			print '<td class="nowrap center">';
 			print dol_print_date($datefin, 'day');
 			if ($memberstatic->hasDelay()) {
 				$textlate = ' ('.$langs->trans("DateReference").' > '.$langs->trans("DateToday").' '.(ceil($conf->adherent->subscription->warning_delay / 60 / 60 / 24) >= 0 ? '+' : '').ceil($conf->adherent->subscription->warning_delay / 60 / 60 / 24).' '.$langs->trans("days").')';
 				print " ".img_warning($langs->trans("SubscriptionLate").$textlate);
 			}
-			print '</td>';
 		} else {
-			print '<td class="nowrap left">';
 			if (!empty($obj->subscription)) {
 				print $langs->trans("SubscriptionNotReceived");
 				if ($obj->statut > 0) {
@@ -1128,8 +1154,8 @@ while ($i < min($num, $limit)) {
 			} else {
 				print '&nbsp;';
 			}
-			print '</td>';
 		}
+		print '</td>';
 	}
 	// Extra fields
 	include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_print_fields.tpl.php';
@@ -1173,21 +1199,31 @@ while ($i < min($num, $limit)) {
 			$totalarray['nbfield']++;
 		}
 	}
+	if (!empty($arrayfields['d.import_key']['checked'])) {
+		print '<td class="tdoverflowmax100 center" title="'.dol_escape_htmltag($obj->import_key).'">';
+		print dol_escape_htmltag($obj->import_key);
+		print "</td>\n";
+		if (!$i) {
+			$totalarray['nbfield']++;
+		}
+	}
 	// Action column
-	print '<td class="center">';
-	if ($massactionbutton || $massaction) {   // If we are in select mode (massactionbutton defined) or if we have already selected and sent an action ($massaction) defined
-		$selected = 0;
-		if (in_array($obj->rowid, $arrayofselected)) {
-			$selected = 1;
+	if (empty($conf->global->MAIN_CHECKBOX_LEFT_COLUMN)) {
+		print '<td class="center">';
+		if ($massactionbutton || $massaction) {   // If we are in select mode (massactionbutton defined) or if we have already selected and sent an action ($massaction) defined
+			$selected = 0;
+			if (in_array($obj->rowid, $arrayofselected)) {
+				$selected = 1;
+			}
+			print '<input id="cb'.$obj->rowid.'" class="flat checkforselect" type="checkbox" name="toselect[]" value="'.$obj->rowid.'"'.($selected ? ' checked="checked"' : '').'>';
 		}
-		print '<input id="cb'.$obj->rowid.'" class="flat checkforselect" type="checkbox" name="toselect[]" value="'.$obj->rowid.'"'.($selected ? ' checked="checked"' : '').'>';
+		print '</td>';
 	}
-	print '</td>';
 	if (!$i) {
 		$totalarray['nbfield']++;
 	}
 
-	print "</tr>\n";
+	print '</tr>'."\n";
 	$i++;
 }
 

+ 12 - 5
htdocs/adherents/partnership.php

@@ -85,11 +85,18 @@ $usercanclose 			= $user->rights->partnership->write; // Used by the include of
 $upload_dir 			= $conf->partnership->multidir_output[isset($object->entity) ? $object->entity : 1];
 
 
-if ($conf->global->PARTNERSHIP_IS_MANAGED_FOR != 'member') accessforbidden();
-if (empty($conf->partnership->enabled)) accessforbidden();
-if (empty($permissiontoread)) accessforbidden();
-if ($action == 'edit' && empty($permissiontoadd)) accessforbidden();
-
+if (getDolGlobalString('PARTNERSHIP_IS_MANAGED_FOR') != 'member') {
+	accessforbidden('Partnership module is not activated for members');
+}
+if (empty($conf->partnership->enabled)) {
+	accessforbidden();
+}
+if (empty($permissiontoread)) {
+	accessforbidden();
+}
+if ($action == 'edit' && empty($permissiontoadd)) {
+	accessforbidden();
+}
 if (($action == 'update' || $action == 'edit') && $object->status != $object::STATUS_DRAFT) {
 	accessforbidden();
 }

+ 30 - 30
htdocs/adherents/subscription.php

@@ -501,7 +501,7 @@ if ($rowid > 0) {
 
 	// Login
 	if (empty($conf->global->ADHERENT_LOGIN_NOT_REQUIRED)) {
-		print '<tr><td class="titlefield">'.$langs->trans("Login").' / '.$langs->trans("Id").'</td><td class="valeur">'.$object->login.'&nbsp;</td></tr>';
+		print '<tr><td class="titlefield">'.$langs->trans("Login").' / '.$langs->trans("Id").'</td><td class="valeur">'.dol_escape_htmltag($object->login).'</td></tr>';
 	}
 
 	// Type
@@ -512,25 +512,24 @@ if ($rowid > 0) {
 	print '</tr>';
 
 	// Company
-	print '<tr><td>'.$langs->trans("Company").'</td><td class="valeur">'.$object->company.'</td></tr>';
+	print '<tr><td>'.$langs->trans("Company").'</td><td class="valeur">'.dol_escape_htmltag($object->company).'</td></tr>';
 
 	// Civility
-	print '<tr><td>'.$langs->trans("UserTitle").'</td><td class="valeur">'.$object->getCivilityLabel().'&nbsp;</td>';
+	print '<tr><td>'.$langs->trans("UserTitle").'</td><td class="valeur">'.$object->getCivilityLabel().'</td>';
 	print '</tr>';
 
 	// Password
 	if (empty($conf->global->ADHERENT_LOGIN_NOT_REQUIRED)) {
-		print '<tr><td>'.$langs->trans("Password").'</td><td>'.preg_replace('/./i', '*', $object->pass);
+		print '<tr><td>'.$langs->trans("Password").'</td><td>';
 		if ($object->pass) {
 			print preg_replace('/./i', '*', $object->pass);
 		} else {
 			if ($user->admin) {
-				print $langs->trans("Crypted").': '.$object->pass_indatabase_crypted;
-			} else {
-				print $langs->trans("Hidden");
+				print '<!-- '.$langs->trans("Crypted").': '.$object->pass_indatabase_crypted.' -->';
 			}
+			print '<span class="opacitymedium">'.$langs->trans("Hidden").'</span>';
 		}
-		if ((!empty($object->pass) || !empty($object->pass_crypted)) && empty($object->user_id)) {
+		if (!empty($object->pass_indatabase) && empty($object->user_id)) {	// Show warning only for old password still in clear (does not happen anymore)
 			$langs->load("errors");
 			$htmltext = $langs->trans("WarningPasswordSetWithNoAccount");
 			print ' '.$form->textwithpicto('', $htmltext, 1, 'warning');
@@ -546,15 +545,17 @@ if ($rowid > 0) {
 			print " ".img_warning($langs->trans("Late"));
 		}
 	} else {
-		if (!$adht->subscription) {
+		if ($object->need_subscription == 0) {
+			print $langs->trans("SubscriptionNotNeeded");
+		} elseif (!$adht->subscription) {
 			print $langs->trans("SubscriptionNotRecorded");
-			if ($object->statut > 0) {
-				print " ".img_warning($langs->trans("Late")); // Display a delay picto only if it is not a draft and is not canceled
+			if (Adherent::STATUS_VALIDATED == $object->statut) {
+				print " ".img_warning($langs->trans("Late")); // displays delay Pictogram only if not a draft, not excluded and not resiliated
 			}
 		} else {
 			print $langs->trans("SubscriptionNotReceived");
-			if ($object->statut > 0) {
-				print " ".img_warning($langs->trans("Late")); // Display a delay picto only if it is not a draft and is not canceled
+			if (Adherent::STATUS_VALIDATED == $object->statut) {
+				print " ".img_warning($langs->trans("Late")); // displays delay Pictogram only if not a draft, not excluded and not resiliated
 			}
 		}
 	}
@@ -563,18 +564,13 @@ if ($rowid > 0) {
 	print '</table>';
 
 	print '</div>';
-	print '<div class="fichehalfright">';
 
+	print '<div class="fichehalfright">';
 	print '<div class="underbanner clearboth"></div>';
-	print '<table class="border tableforfield centpercent">';
-
-	// Birthday
-	print '<tr><td class="titlefield">'.$langs->trans("DateOfBirth").'</td><td class="valeur">'.dol_print_date($object->birth, 'day').'</td></tr>';
 
-	// Public
-	print '<tr><td>'.$langs->trans("Public").'</td><td class="valeur">'.yn($object->public).'</td></tr>';
+	print '<table class="border tableforfield centpercent">';
 
-	// Categories
+	// Tags / Categories
 	if (!empty($conf->categorie->enabled) && !empty($user->rights->categorie->lire)) {
 		print '<tr><td>'.$langs->trans("Categories").'</td>';
 		print '<td colspan="2">';
@@ -582,6 +578,12 @@ if ($rowid > 0) {
 		print '</td></tr>';
 	}
 
+	// Birth Date
+	print '<tr><td class="titlefield">'.$langs->trans("DateOfBirth").'</td><td class="valeur">'.dol_print_date($object->birth, 'day').'</td></tr>';
+
+	// Public
+	print '<tr><td>'.$langs->trans("Public").'</td><td class="valeur">'.yn($object->public).'</td></tr>';
+
 	// Other attributes
 	$cols = 2;
 	include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_view.tpl.php';
@@ -663,8 +665,6 @@ if ($rowid > 0) {
 
 	print dol_get_fiche_end();
 
-	print '</form>';
-
 
 	/*
 	 * Action bar
@@ -831,11 +831,11 @@ if ($rowid > 0) {
 				$bankviainvoice = 1;
 			}
 		} else {
-			if (!empty($conf->global->ADHERENT_BANK_USE) && $conf->global->ADHERENT_BANK_USE == 'bankviainvoice' && !empty($conf->banque->enabled) && !empty($conf->societe->enabled) && !empty($conf->facture->enabled)) {
+			if (!empty($conf->global->ADHERENT_BANK_USE) && $conf->global->ADHERENT_BANK_USE == 'bankviainvoice' && !empty($conf->banque->enabled) && !empty($conf->societe->enabled) && isModEnabled('facture')) {
 				$bankviainvoice = 1;
 			} elseif (!empty($conf->global->ADHERENT_BANK_USE) && $conf->global->ADHERENT_BANK_USE == 'bankdirect' && !empty($conf->banque->enabled)) {
 				$bankdirect = 1;
-			} elseif (!empty($conf->global->ADHERENT_BANK_USE) && $conf->global->ADHERENT_BANK_USE == 'invoiceonly' && !empty($conf->banque->enabled) && !empty($conf->societe->enabled) && !empty($conf->facture->enabled)) {
+			} elseif (!empty($conf->global->ADHERENT_BANK_USE) && $conf->global->ADHERENT_BANK_USE == 'invoiceonly' && !empty($conf->banque->enabled) && !empty($conf->societe->enabled) && isModEnabled('facture')) {
 				$invoiceonly = 1;
 			}
 		}
@@ -982,7 +982,7 @@ if ($rowid > 0) {
 			print '"></td></tr>';
 
 			// Complementary action
-			if ((!empty($conf->banque->enabled) || !empty($conf->facture->enabled)) && empty($conf->global->ADHERENT_SUBSCRIPTION_HIDECOMPLEMENTARYACTIONS)) {
+			if ((!empty($conf->banque->enabled) || isModEnabled('facture')) && empty($conf->global->ADHERENT_SUBSCRIPTION_HIDECOMPLEMENTARYACTIONS)) {
 				$company = new Societe($db);
 				if ($object->fk_soc) {
 					$result = $company->fetch($object->fk_soc);
@@ -1003,7 +1003,7 @@ if ($rowid > 0) {
 					print '><label for="bankdirect">  '.$langs->trans("MoreActionBankDirect").'</label><br>';
 				}
 				// Add invoice with no payments
-				if (!empty($conf->societe->enabled) && !empty($conf->facture->enabled)) {
+				if (!empty($conf->societe->enabled) && isModEnabled('facture')) {
 					print '<input type="radio" class="moreaction" id="invoiceonly" name="paymentsave" value="invoiceonly"'.(!empty($invoiceonly) ? ' checked' : '');
 					//if (empty($object->fk_soc)) print ' disabled';
 					print '><label for="invoiceonly"> '.$langs->trans("MoreActionInvoiceOnly");
@@ -1033,7 +1033,7 @@ if ($rowid > 0) {
 					print '</label><br>';
 				}
 				// Add invoice with payments
-				if (!empty($conf->banque->enabled) && !empty($conf->societe->enabled) && !empty($conf->facture->enabled)) {
+				if (!empty($conf->banque->enabled) && !empty($conf->societe->enabled) && isModEnabled('facture')) {
 					print '<input type="radio" class="moreaction" id="bankviainvoice" name="paymentsave" value="bankviainvoice"'.(!empty($bankviainvoice) ? ' checked' : '');
 					//if (empty($object->fk_soc)) print ' disabled';
 					print '><label for="bankviainvoice">  '.$langs->trans("MoreActionBankViaInvoice");
@@ -1067,12 +1067,12 @@ if ($rowid > 0) {
 				// Bank account
 				print '<tr class="bankswitchclass"><td class="fieldrequired">'.$langs->trans("FinancialAccount").'</td><td>';
 				print img_picto('', 'bank_account');
-				$form->select_comptes(GETPOST('accountid'), 'accountid', 0, '', 2);
+				$form->select_comptes(GETPOST('accountid'), 'accountid', 0, '', 2, '', 0, 'minwidth200');
 				print "</td></tr>\n";
 
 				// Payment mode
 				print '<tr class="bankswitchclass"><td class="fieldrequired">'.$langs->trans("PaymentMode").'</td><td>';
-				$form->select_types_paiements(GETPOST('operation'), 'operation', '', 2);
+				$form->select_types_paiements(GETPOST('operation'), 'operation', '', 2, 1, 0, 0, 1, 'minwidth200');
 				print "</td></tr>\n";
 
 				// Date of payment

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

@@ -93,8 +93,8 @@ if ($user->rights->adherent->cotisation->creer && $action == 'update' && !$cance
 			if ($accountline->rappro) {
 				$errmsg = $langs->trans("SubscriptionLinkedToConciliatedTransaction");
 			} else {
-				$accountline->datev = dol_mktime($_POST['datesubhour'], $_POST['datesubmin'], 0, $_POST['datesubmonth'], $_POST['datesubday'], $_POST['datesubyear']);
-				$accountline->dateo = dol_mktime($_POST['datesubhour'], $_POST['datesubmin'], 0, $_POST['datesubmonth'], $_POST['datesubday'], $_POST['datesubyear']);
+				$accountline->datev = dol_mktime(GETPOST('datesubhour', 'int'), GETPOST('datesubmin', 'int'), 0, GETPOST('datesubmonth', 'int'), GETPOST('datesubday', 'int'), GETPOST('datesubyear', 'int'));
+				$accountline->dateo = dol_mktime(GETPOST('datesubhour', 'int'), GETPOST('datesubmin', 'int'), 0, GETPOST('datesubmonth', 'int'), GETPOST('datesubday', 'int'), GETPOST('datesubyear', 'int'));
 				$accountline->amount = $amount;
 				$result = $accountline->update($user);
 				if ($result < 0) {
@@ -105,12 +105,12 @@ if ($user->rights->adherent->cotisation->creer && $action == 'update' && !$cance
 
 		if (!$errmsg) {
 			// Modify values
-			$object->dateh = dol_mktime($_POST['datesubhour'], $_POST['datesubmin'], 0, $_POST['datesubmonth'], $_POST['datesubday'], $_POST['datesubyear']);
-			$object->datef = dol_mktime($_POST['datesubendhour'], $_POST['datesubendmin'], 0, $_POST['datesubendmonth'], $_POST['datesubendday'], $_POST['datesubendyear']);
+			$object->dateh = dol_mktime(GETPOST('datesubhour', 'int'), GETPOST('datesubmin', 'int'), 0, GETPOST('datesubmonth', 'int'), GETPOST('datesubday', 'int'), GETPOST('datesubyear', 'int'));
+			$object->datef = dol_mktime(GETPOST('datesubendhour', 'int'), GETPOST('datesubendmin', 'int'), 0, GETPOST('datesubendmonth', 'int'), GETPOST('datesubendday', 'int'), GETPOST('datesubendyear', 'int'));
 			$object->fk_type = $typeid;
 			$object->note = $note;
+			$object->note_private = $note;
 			$object->amount = $amount;
-			//print 'datef='.$object->datef.' '.$_POST['datesubendday'];
 
 			$result = $object->update($user);
 			if ($result >= 0 && !count($object->errors)) {

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

@@ -111,7 +111,7 @@ $result = restrictedArea($user, 'adherent', '', '', 'cotisation');
 if (GETPOST('cancel', 'alpha')) {
 	$action = 'list'; $massaction = '';
 }
-if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend' && $massaction != 'confirm_createbills') {
+if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend') {
 	$massaction = '';
 }
 
@@ -135,7 +135,7 @@ if (empty($reshook)) {
 		$search_note = "";
 		$search_amount = "";
 		$search_account = "";
-		$toselect = '';
+		$toselect = array();
 		$search_array_options = array();
 	}
 }
@@ -628,10 +628,10 @@ while ($i < min($num, $limit)) {
 	print '<td class="center">';
 	if ($massactionbutton || $massaction) {   // If we are in select mode (massactionbutton defined) or if we have already selected and sent an action ($massaction) defined
 		$selected = 0;
-		if (in_array($obj->rowid, $arrayofselected)) {
+		if (in_array($obj->crowid, $arrayofselected)) {
 			$selected = 1;
 		}
-		print '<input id="cb'.$obj->rowid.'" class="flat checkforselect" type="checkbox" name="toselect[]" value="'.$obj->rowid.'"'.($selected ? ' checked="checked"' : '').'>';
+		print '<input id="cb'.$obj->crowid.'" class="flat checkforselect" type="checkbox" name="toselect[]" value="'.$obj->crowid.'"'.($selected ? ' checked="checked"' : '').'>';
 	}
 	print '</td>';
 	if (!$i) {

+ 1 - 1
htdocs/adherents/tpl/linkedobjectblock.tpl.php

@@ -23,7 +23,7 @@ if (empty($conf) || !is_object($conf)) {
 	exit;
 }
 
-echo "<!-- BEGIN PHP TEMPLATE adherents/tpl/linkedopjectblock.tpl.php -->\n";
+echo "<!-- BEGIN PHP TEMPLATE adherents/tpl/linkedobjectblock.tpl.php -->\n";
 
 global $user;
 

+ 7 - 5
htdocs/adherents/type.php

@@ -125,6 +125,7 @@ if ($action == 'add' && $user->rights->adherent->configurer) {
 	$object->duration_value = $duration_value;
 	$object->duration_unit = $duration_unit;
 	$object->note = trim($comment);
+	$object->note_public = trim($comment);
 	$object->mail_valid = trim($mail_valid);
 	$object->vote = (int) $vote;
 
@@ -176,6 +177,7 @@ if ($action == 'update' && $user->rights->adherent->configurer) {
 	$object->duration_value = $duration_value;
 	$object->duration_unit = $duration_unit;
 	$object->note = trim($comment);
+	$object->note_public = trim($comment);
 	$object->mail_valid = trim($mail_valid);
 	$object->vote = (boolean) trim($vote);
 
@@ -358,7 +360,7 @@ if ($action == 'create') {
 	print '<tr><td class="titlefieldcreate fieldrequired">'.$langs->trans("Label").'</td><td><input type="text" class="minwidth200" name="label" autofocus="autofocus"></td></tr>';
 
 	print '<tr><td>'.$langs->trans("Status").'</td><td>';
-	print $form->selectarray('status', array('0'=>$langs->trans('ActivityCeased'), '1'=>$langs->trans('InActivity')), 1);
+	print $form->selectarray('status', array('0'=>$langs->trans('ActivityCeased'), '1'=>$langs->trans('InActivity')), 1, 0, 0, 0, '', 0, 0, 0, '', 'minwidth100');
 	print '</td></tr>';
 
 	// Morphy
@@ -375,7 +377,7 @@ if ($action == 'create') {
 	print '</td></tr>';
 
 	print '<tr><td>'.$langs->trans("Amount").'</td><td>';
-	print '<input name="amount" size="5" value="'.price($amount).'">';
+	print '<input name="amount" size="5" value="'.(GETPOSTISSET('amount') ? GETPOST('amount') : price($amount)).'">';
 	print '</td></tr>';
 
 	print '<tr><td>'.$langs->trans("VoteAllowed").'</td><td>';
@@ -389,12 +391,12 @@ if ($action == 'create') {
 
 	print '<tr><td class="tdtop">'.$langs->trans("Description").'</td><td>';
 	require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
-	$doleditor = new DolEditor('comment', $object->note, '', 200, 'dolibarr_notes', '', false, true, empty($conf->fckeditor->enabled) ? false : $conf->fckeditor->enabled, 15, '90%');
+	$doleditor = new DolEditor('comment', (GETPOSTISSET('comment') ? GETPOST('comment', 'restricthtml') : $object->note_public), '', 200, 'dolibarr_notes', '', false, true, empty($conf->fckeditor->enabled) ? false : $conf->fckeditor->enabled, 15, '90%');
 	$doleditor->Create();
 
 	print '<tr><td class="tdtop">'.$langs->trans("WelcomeEMail").'</td><td>';
 	require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
-	$doleditor = new DolEditor('mail_valid', $object->mail_valid, '', 250, 'dolibarr_notes', '', false, true, empty($conf->fckeditor->enabled) ? false : $conf->fckeditor->enabled, 15, '90%');
+	$doleditor = new DolEditor('mail_valid', GETPOSTISSET('mail_valid') ? GETPOST('mail_valid') : $object->mail_valid, '', 250, 'dolibarr_notes', '', false, true, empty($conf->fckeditor->enabled) ? false : $conf->fckeditor->enabled, 15, '90%');
 	$doleditor->Create();
 	print '</td></tr>';
 
@@ -783,7 +785,7 @@ if ($rowid > 0) {
 		print '<tr><td class="fieldrequired">'.$langs->trans("Label").'</td><td><input type="text" class="minwidth300" name="label" value="'.dol_escape_htmltag($object->label).'"></td></tr>';
 
 		print '<tr><td>'.$langs->trans("Status").'</td><td>';
-		print $form->selectarray('status', array('0'=>$langs->trans('ActivityCeased'), '1'=>$langs->trans('InActivity')), $object->status);
+		print $form->selectarray('status', array('0'=>$langs->trans('ActivityCeased'), '1'=>$langs->trans('InActivity')), $object->status, 0, 0, 0, '', 0, 0, 0, '', 'minwidth100');
 		print '</td></tr>';
 
 		// Morphy

+ 21 - 21
htdocs/admin/accountant.php

@@ -53,18 +53,18 @@ if ($reshook < 0) {
 
 if (($action == 'update' && !GETPOST("cancel", 'alpha'))
 || ($action == 'updateedit')) {
-	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_NAME", GETPOST("nom", 'nohtml'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_ADDRESS", GETPOST("address", 'nohtml'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_TOWN", GETPOST("town", 'nohtml'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_ZIP", GETPOST("zipcode", 'alpha'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_STATE", GETPOST("state_id", 'alpha'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_REGION", GETPOST("region_code", 'alpha'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_NAME", GETPOST("nom", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_ADDRESS", GETPOST("address", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_TOWN", GETPOST("town", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_ZIP", GETPOST("zipcode", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_STATE", GETPOST("state_id", 'int'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_REGION", GETPOST("region_code", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
 	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_COUNTRY", GETPOST('country_id', 'int'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_PHONE", GETPOST("tel", 'alpha'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_FAX", GETPOST("fax", 'alpha'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_MAIL", GETPOST("mail", 'alpha'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_WEB", GETPOST("web", 'alpha'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_CODE", GETPOST("code", 'nohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_PHONE", GETPOST("tel", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_FAX", GETPOST("fax", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_MAIL", GETPOST("mail", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_WEB", GETPOST("web", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_CODE", GETPOST("code", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
 	dolibarr_set_const($db, "MAIN_INFO_ACCOUNTANT_NOTE", GETPOST("note", 'restricthtml'), 'chaine', 0, '', $conf->entity);
 
 	if ($action != 'updateedit' && !$error) {
@@ -119,17 +119,17 @@ print '<tr class="liste_titre"><th class="titlefieldcreate wordbreak">'.$langs->
 
 // Name
 print '<tr class="oddeven"><td><label for="name">'.$langs->trans("CompanyName").'</label></td><td>';
-print '<input name="nom" id="name" class="minwidth200" value="'.(GETPOSTISSET('nom') ? GETPOST('nom', 'nohtml') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_NAME) ? $conf->global->MAIN_INFO_ACCOUNTANT_NAME : '')).'"'.(empty($conf->global->MAIN_INFO_ACCOUNTANT_NAME) ? ' autofocus="autofocus"' : '').'></td></tr>'."\n";
+print '<input name="nom" id="name" class="minwidth200" value="'.dol_escape_htmltag(GETPOSTISSET('nom') ? GETPOST('nom', 'alphanohtml') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_NAME) ? $conf->global->MAIN_INFO_ACCOUNTANT_NAME : '')).'"'.(empty($conf->global->MAIN_INFO_ACCOUNTANT_NAME) ? ' autofocus="autofocus"' : '').'></td></tr>'."\n";
 
 // Address
 print '<tr class="oddeven"><td><label for="address">'.$langs->trans("CompanyAddress").'</label></td><td>';
-print '<textarea name="address" id="address" class="quatrevingtpercent" rows="'.ROWS_3.'">'.(GETPOSTISSET('address') ? GETPOST('address', 'nohtml') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_ADDRESS) ? $conf->global->MAIN_INFO_ACCOUNTANT_ADDRESS : '')).'</textarea></td></tr>'."\n";
+print '<textarea name="address" id="address" class="quatrevingtpercent" rows="'.ROWS_3.'">'.dol_escape_htmltag(GETPOSTISSET('address') ? GETPOST('address', 'alphanohtml') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_ADDRESS) ? $conf->global->MAIN_INFO_ACCOUNTANT_ADDRESS : '')).'</textarea></td></tr>'."\n";
 
 print '<tr class="oddeven"><td><label for="zipcode">'.$langs->trans("CompanyZip").'</label></td><td>';
-print '<input class="minwidth100" name="zipcode" id="zipcode" value="'.(GETPOSTISSET('zipcode') ? GETPOST('zipcode', 'alpha') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_ZIP) ? $conf->global->MAIN_INFO_ACCOUNTANT_ZIP : '')).'"></td></tr>'."\n";
+print '<input class="minwidth100" name="zipcode" id="zipcode" value="'.dol_escape_htmltag(GETPOSTISSET('zipcode') ? GETPOST('zipcode', 'alphanohtml') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_ZIP) ? $conf->global->MAIN_INFO_ACCOUNTANT_ZIP : '')).'"></td></tr>'."\n";
 
 print '<tr class="oddeven"><td><label for="town">'.$langs->trans("CompanyTown").'</label></td><td>';
-print '<input name="town" class="minwidth100" id="town" value="'.(GETPOSTISSET('town') ? GETPOST('town', 'nohtml') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_TOWN) ? $conf->global->MAIN_INFO_ACCOUNTANT_TOWN : '')).'"></td></tr>'."\n";
+print '<input name="town" class="minwidth100" id="town" value="'.dol_escape_htmltag(GETPOSTISSET('town') ? GETPOST('town', 'alphanohtml') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_TOWN) ? $conf->global->MAIN_INFO_ACCOUNTANT_TOWN : '')).'"></td></tr>'."\n";
 
 // Country
 print '<tr class="oddeven"><td><label for="selectcountry_id">'.$langs->trans("Country").'</label></td><td class="maxwidthonsmartphone">';
@@ -142,33 +142,33 @@ print '</td></tr>'."\n";
 
 print '<tr class="oddeven"><td><label for="state_id">'.$langs->trans("State").'</label></td><td class="maxwidthonsmartphone">';
 print img_picto('', 'state', 'class="pictofixedwidth"');
-print $formcompany->select_state((GETPOSTISSET('state_id') ? GETPOST('state_id', 'alpha') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_STATE) ? $conf->global->MAIN_INFO_ACCOUNTANT_STATE : '')), (GETPOSTISSET('country_id') ? GETPOST('country_id', 'int') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_COUNTRY) ? $conf->global->MAIN_INFO_ACCOUNTANT_COUNTRY : '')), 'state_id');
+print $formcompany->select_state((GETPOSTISSET('state_id') ? GETPOST('state_id', 'int') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_STATE) ? $conf->global->MAIN_INFO_ACCOUNTANT_STATE : '')), (GETPOSTISSET('country_id') ? GETPOST('country_id', 'int') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_COUNTRY) ? $conf->global->MAIN_INFO_ACCOUNTANT_COUNTRY : '')), 'state_id');
 print '</td></tr>'."\n";
 
 print '<tr class="oddeven"><td><label for="phone">'.$langs->trans("Phone").'</label></td><td>';
 print img_picto('', 'object_phoning', '', false, 0, 0, '', 'pictofixedwidth');
-print '<input name="tel" id="phone" class="maxwidth150 widthcentpercentminusx" value="'.(GETPOSTISSET('tel') ? GETPOST('tel', 'alpha') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_PHONE) ? $conf->global->MAIN_INFO_ACCOUNTANT_PHONE : '')).'"></td></tr>';
+print '<input name="tel" id="phone" class="maxwidth150 widthcentpercentminusx" value="'.dol_escape_htmltag(GETPOSTISSET('tel') ? GETPOST('tel', 'alphanohtml') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_PHONE) ? $conf->global->MAIN_INFO_ACCOUNTANT_PHONE : '')).'"></td></tr>';
 print '</td></tr>'."\n";
 
 print '<tr class="oddeven"><td><label for="fax">'.$langs->trans("Fax").'</label></td><td>';
 print img_picto('', 'object_phoning_fax', '', false, 0, 0, '', 'pictofixedwidth');
-print '<input name="fax" id="fax" class="maxwidth150 widthcentpercentminusx" value="'.(GETPOSTISSET('fax') ? GETPOST('fax', 'alpha') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_FAX) ? $conf->global->MAIN_INFO_ACCOUNTANT_FAX : '')).'"></td></tr>';
+print '<input name="fax" id="fax" class="maxwidth150 widthcentpercentminusx" value="'.dol_escape_htmltag(GETPOSTISSET('fax') ? GETPOST('fax', 'alphanohtml') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_FAX) ? $conf->global->MAIN_INFO_ACCOUNTANT_FAX : '')).'"></td></tr>';
 print '</td></tr>'."\n";
 
 print '<tr class="oddeven"><td><label for="email">'.$langs->trans("EMail").'</label></td><td>';
 print img_picto('', 'object_email', '', false, 0, 0, '', 'pictofixedwidth');
-print '<input name="mail" id="email" class="maxwidth300 widthcentpercentminusx" value="'.(GETPOSTISSET('mail') ? GETPOST('mail', 'alpha') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_MAIL) ? $conf->global->MAIN_INFO_ACCOUNTANT_MAIL : '')).'"></td></tr>';
+print '<input name="mail" id="email" class="maxwidth300 widthcentpercentminusx" value="'.dol_escape_htmltag(GETPOSTISSET('mail') ? GETPOST('mail', 'alphanohtml') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_MAIL) ? $conf->global->MAIN_INFO_ACCOUNTANT_MAIL : '')).'"></td></tr>';
 print '</td></tr>'."\n";
 
 // Web
 print '<tr class="oddeven"><td><label for="web">'.$langs->trans("Web").'</label></td><td>';
 print img_picto('', 'globe', '', false, 0, 0, '', 'pictofixedwidth');
-print '<input name="web" id="web" class="maxwidth300 widthcentpercentminusx" value="'.(GETPOSTISSET('web') ? GETPOST('web', 'alpha') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_WEB) ? $conf->global->MAIN_INFO_ACCOUNTANT_WEB : '')).'"></td></tr>';
+print '<input name="web" id="web" class="maxwidth300 widthcentpercentminusx" value="'.dol_escape_htmltag(GETPOSTISSET('web') ? GETPOST('web', 'alphanohtml') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_WEB) ? $conf->global->MAIN_INFO_ACCOUNTANT_WEB : '')).'"></td></tr>';
 print '</td></tr>'."\n";
 
 // Code
 print '<tr class="oddeven"><td><label for="code">'.$langs->trans("AccountantFileNumber").'</label></td><td>';
-print '<input name="code" id="code" class="minwidth100" value="'.(GETPOSTISSET('code') ? GETPOST('code', 'nohtml') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_CODE) ? $conf->global->MAIN_INFO_ACCOUNTANT_CODE : '')).'"></td></tr>'."\n";
+print '<input name="code" id="code" class="minwidth100" value="'.dol_escape_htmltag(GETPOSTISSET('code') ? GETPOST('code', 'alphanohtml') : (!empty($conf->global->MAIN_INFO_ACCOUNTANT_CODE) ? $conf->global->MAIN_INFO_ACCOUNTANT_CODE : '')).'"></td></tr>'."\n";
 
 // Note
 print '<tr class="oddeven"><td class="tdtop"><label for="note">'.$langs->trans("Note").'</label></td><td>';

+ 3 - 1
htdocs/admin/agenda.php

@@ -88,7 +88,6 @@ if ($action == "save" && empty($cancel)) {
 
 	foreach ($triggers as $trigger) {
 		$keyparam = 'MAIN_AGENDA_ACTIONAUTO_'.$trigger['code'];
-		//print "param=".$param." - ".$_POST[$param];
 		if ($search_event === '' || preg_match('/'.preg_quote($search_event, '/').'/i', $keyparam)) {
 			$res = dolibarr_set_const($db, $keyparam, (GETPOST($keyparam, 'alpha') ?GETPOST($keyparam, 'alpha') : ''), 'chaine', 0, '', $conf->entity);
 			if (!($res > 0)) {
@@ -174,6 +173,9 @@ if (!empty($triggers)) {
 		if ($module == 'contact') {
 			$module = 'societe';
 		}
+		if ($module == 'facturerec') {
+			$module = 'facture';
+		}
 
 		// If 'element' value is myobject@mymodule instead of mymodule
 		$tmparray = explode('@', $module);

+ 1 - 1
htdocs/admin/agenda_extrafields.php

@@ -89,7 +89,7 @@ print dol_get_fiche_end();
 // Buttons
 if ($action != 'create' && $action != 'edit') {
 	print '<div class="tabsAction">';
-	print "<a class=\"butAction\" href=\"".$_SERVER["PHP_SELF"]."?action=create#newattrib\">".$langs->trans("NewAttribute")."</a>";
+	print '<a class="butAction reposition" href="'.$_SERVER["PHP_SELF"].'?action=create">'.$langs->trans("NewAttribute").'</a>';
 	print "</div>";
 }
 

+ 4 - 14
htdocs/admin/bank.php

@@ -53,8 +53,6 @@ $type = 'bankaccount';
 // Order display of bank account
 if ($action == 'setbankorder') {
 	if (dolibarr_set_const($db, "BANK_SHOW_ORDER_OPTION", GETPOST('value', 'alpha'), 'chaine', 0, '', $conf->entity) > 0) {
-		header("Location: ".$_SERVER["PHP_SELF"]);
-		exit;
 	} else {
 		dol_print_error($db);
 	}
@@ -63,15 +61,11 @@ if ($action == 'setbankorder') {
 // Auto report last num releve on conciliate
 if ($action == 'setreportlastnumreleve') {
 	if (dolibarr_set_const($db, "BANK_REPORT_LAST_NUM_RELEVE", 1, 'chaine', 0, '', $conf->entity) > 0) {
-		header("Location: ".$_SERVER["PHP_SELF"]);
-		exit;
 	} else {
 		dol_print_error($db);
 	}
 } elseif ($action == 'unsetreportlastnumreleve') {
 	if (dolibarr_set_const($db, "BANK_REPORT_LAST_NUM_RELEVE", 0, 'chaine', 0, '', $conf->entity) > 0) {
-		header("Location: ".$_SERVER["PHP_SELF"]);
-		exit;
 	} else {
 		dol_print_error($db);
 	}
@@ -80,15 +74,11 @@ if ($action == 'setreportlastnumreleve') {
 // Colorize movements
 if ($action == 'setbankcolorizemovement') {
 	if (dolibarr_set_const($db, "BANK_COLORIZE_MOVEMENT", 1, 'chaine', 0, '', $conf->entity) > 0) {
-		header("Location: ".$_SERVER["PHP_SELF"]);
-		exit;
 	} else {
 		dol_print_error($db);
 	}
 } elseif ($action == 'unsetbankcolorizemovement') {
 	if (dolibarr_set_const($db, "BANK_COLORIZE_MOVEMENT", 0, 'chaine', 0, '', $conf->entity) > 0) {
-		header("Location: ".$_SERVER["PHP_SELF"]);
-		exit;
 	} else {
 		dol_print_error($db);
 	}
@@ -427,13 +417,13 @@ print "</td>";
 // Active
 if ($conf->global->BANK_COLORIZE_MOVEMENT) {
 	print '<td class="center">'."\n";
-	print '<a href="'.$_SERVER["PHP_SELF"].'?action=unsetbankcolorizemovement&token='.newToken().'">';
+	print '<a class="reposition" href="'.$_SERVER["PHP_SELF"].'?action=unsetbankcolorizemovement&token='.newToken().'">';
 	print img_picto($langs->trans("Enabled"), 'switch_on');
 	print '</a>';
 	print '</td>';
 } else {
 	print '<td class="center">'."\n";
-	print '<a href="'.$_SERVER["PHP_SELF"].'?action=setbankcolorizemovement&token='.newToken().'">'.img_picto($langs->trans("Disabled"), 'switch_off').'</a>';
+	print '<a class="reposition" href="'.$_SERVER["PHP_SELF"].'?action=setbankcolorizemovement&token='.newToken().'">'.img_picto($langs->trans("Disabled"), 'switch_off').'</a>';
 	print "</td>";
 }
 
@@ -483,13 +473,13 @@ print '</td>';
 // Active
 if ($conf->global->BANK_REPORT_LAST_NUM_RELEVE) {
 	print '<td class="center">'."\n";
-	print '<a href="'.$_SERVER["PHP_SELF"].'?action=unsetreportlastnumreleve&amp;token='.newToken().'">';
+	print '<a class="reposition" href="'.$_SERVER["PHP_SELF"].'?action=unsetreportlastnumreleve&token='.newToken().'">';
 	print img_picto($langs->trans("Enabled"), 'switch_on');
 	print '</a>';
 	print '</td>';
 } else {
 	print '<td class="center">'."\n";
-	print '<a href="'.$_SERVER["PHP_SELF"].'?action=setreportlastnumreleve">'.img_picto($langs->trans("Disabled"), 'switch_off').'</a>';
+	print '<a class="reposition" href="'.$_SERVER["PHP_SELF"].'?action=setreportlastnumreleve&token='.newToken().'">'.img_picto($langs->trans("Disabled"), 'switch_off').'</a>';
 	print "</td>";
 }
 

+ 1 - 1
htdocs/admin/bank_extrafields.php

@@ -85,7 +85,7 @@ print dol_get_fiche_end();
 // Buttons
 if ($action != 'create' && $action != 'edit') {
 	print '<div class="tabsAction">';
-	print "<a class=\"butAction\" href=\"".$_SERVER["PHP_SELF"]."?action=create#newattrib\">".$langs->trans("NewAttribute")."</a>";
+	print '<a class="butAction reposition" href="'.$_SERVER["PHP_SELF"].'?action=create">'.$langs->trans("NewAttribute").'</a>';
 	print "</div>";
 }
 

+ 12 - 11
htdocs/admin/bom.php

@@ -93,7 +93,7 @@ if ($action == 'updateMask') {
 			header("Location: ".DOL_URL_ROOT."/document.php?modulepart=bom&file=SPECIMEN.pdf");
 			return;
 		} else {
-			setEventMessages($module->error, null, 'errors');
+			setEventMessages($module->error, $module->errors, 'errors');
 			dol_syslog($module->error, LOG_ERR);
 		}
 	} else {
@@ -176,7 +176,7 @@ $head = bomAdminPrepareHead();
 print dol_get_fiche_head($head, 'settings', $langs->trans("BOMs"), -1, 'bom');
 
 /*
- * BOMs Numbering model
+ * Numbering module
  */
 
 print load_fiche_titre($langs->trans("BOMsNumberingModules"), '', '');
@@ -202,10 +202,11 @@ foreach ($dirmodels as $reldir) {
 			while (($file = readdir($handle)) !== false) {
 				if (substr($file, 0, 8) == 'mod_bom_' && substr($file, dol_strlen($file) - 3, 3) == 'php') {
 					$file = substr($file, 0, dol_strlen($file) - 4);
+					$classname = $file;
 
 					require_once $dir.$file.'.php';
 
-					$module = new $file($db);
+					$module = new $classname($db);
 
 					// Show modules according to features level
 					if ($module->version == 'development' && $conf->global->MAIN_FEATURES_LEVEL < 2) {
@@ -220,7 +221,7 @@ foreach ($dirmodels as $reldir) {
 						print $module->info();
 						print '</td>';
 
-						// Show example of numbering model
+						// Show example of numbering module
 						print '<td class="nowrap">';
 						$tmp = $module->getExample();
 						if (preg_match('/^Error/', $tmp)) {
@@ -277,13 +278,13 @@ foreach ($dirmodels as $reldir) {
 }
 print "</table>";
 print "</div>";
-print "<br>\n";
 
 
 /*
  * Document templates generators
  */
 
+print "<br>\n";
 print load_fiche_titre($langs->trans("BOMsModelModule"), '', '');
 
 // Load array def with activated templates
@@ -307,8 +308,8 @@ if ($resql) {
 
 
 print '<div class="div-table-responsive-no-min">';
-print "<table class=\"noborder\" width=\"100%\">\n";
-print "<tr class=\"liste_titre\">\n";
+print '<table class="noborder centpercent">';
+print '<tr class="liste_titre">';
 print '<td>'.$langs->trans("Name").'</td>';
 print '<td>'.$langs->trans("Description").'</td>';
 print '<td class="center" width="60">'.$langs->trans("Status")."</td>\n";
@@ -364,13 +365,13 @@ foreach ($dirmodels as $reldir) {
 								// Active
 								if (in_array($name, $def)) {
 									print '<td class="center">'."\n";
-									print '<a class="reposition" href="'.$_SERVER["PHP_SELF"].'?action=del&token='.newToken().'&value='.$name.'">';
+									print '<a class="reposition" href="'.$_SERVER["PHP_SELF"].'?action=del&token='.newToken().'&value='.urlencode($name).'">';
 									print img_picto($langs->trans("Enabled"), 'switch_on');
 									print '</a>';
 									print '</td>';
 								} else {
 									print '<td class="center">'."\n";
-									print '<a class="reposition" href="'.$_SERVER["PHP_SELF"].'?action=set&token='.newToken().'&value='.$name.'&scan_dir='.$module->scandir.'&label='.urlencode($module->name).'">'.img_picto($langs->trans("Disabled"), 'switch_off').'</a>';
+									print '<a class="reposition" href="'.$_SERVER["PHP_SELF"].'?action=set&token='.newToken().'&value='.urlencode($name).'&scan_dir='.urlencode($module->scandir).'&label='.urlencode($module->name).'">'.img_picto($langs->trans("Disabled"), 'switch_off').'</a>';
 									print "</td>";
 								}
 
@@ -379,7 +380,7 @@ foreach ($dirmodels as $reldir) {
 								if ($conf->global->BOM_ADDON_PDF == $name) {
 									print img_picto($langs->trans("Default"), 'on');
 								} else {
-									print '<a class="reposition" href="'.$_SERVER["PHP_SELF"].'?action=setdoc&token='.newToken().'&value='.$name.'&scan_dir='.$module->scandir.'&amp;label='.urlencode($module->name).'" alt="'.$langs->trans("Default").'">'.img_picto($langs->trans("Disabled"), 'off').'</a>';
+									print '<a class="reposition" href="'.$_SERVER["PHP_SELF"].'?action=setdoc&token='.newToken().'&value='.urlencode($name).'&scan_dir='.urlencode($module->scandir).'&amp;label='.urlencode($module->name).'" alt="'.$langs->trans("Default").'">'.img_picto($langs->trans("Disabled"), 'off').'</a>';
 								}
 								print '</td>';
 
@@ -421,12 +422,12 @@ foreach ($dirmodels as $reldir) {
 
 print '</table>';
 print '</div>';
-print "<br>";
 
 /*
  * Other options
  */
 
+print "<br>";
 print load_fiche_titre($langs->trans("OtherOptions"), '', '');
 
 print '<div class="div-table-responsive-no-min">';

+ 1 - 1
htdocs/admin/bom_extrafields.php

@@ -84,7 +84,7 @@ print dol_get_fiche_end();
 // Buttons
 if ($action != 'create' && $action != 'edit') {
 	print '<div class="tabsAction">';
-	print "<a class=\"butAction\" href=\"".$_SERVER["PHP_SELF"]."?action=create#newattrib\">".$langs->trans("NewAttribute")."</a>";
+	print '<a class="butAction reposition" href="'.$_SERVER["PHP_SELF"].'?action=create">'.$langs->trans("NewAttribute").'</a>';
 	print "</div>";
 }
 

+ 1 - 0
htdocs/admin/boxes.php

@@ -229,6 +229,7 @@ $sql .= " WHERE b.box_id = bd.rowid";
 $sql .= " AND b.entity IN (0,".$conf->entity.")";
 $sql .= " AND b.fk_user=0";
 $sql .= " ORDER by b.position, b.box_order";
+//print $sql;
 
 dol_syslog("Search available boxes", LOG_DEBUG);
 $resql = $db->query($sql);

+ 3 - 2
htdocs/admin/commande.php

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

+ 1 - 1
htdocs/admin/commande_fournisseur_dispatch_extrafields.php

@@ -94,7 +94,7 @@ print dol_get_fiche_end();
 // Buttons
 if ($action != 'create' && $action != 'edit') {
 	print '<div class="tabsAction">';
-	print "<a class=\"butAction\" href=\"".$_SERVER["PHP_SELF"]."?action=create\">".$langs->trans("NewAttribute")."</a>";
+	print '<a class="butAction reposition" href="'.$_SERVER["PHP_SELF"].'?action=create">'.$langs->trans("NewAttribute").'</a>';
 	print "</div>";
 }
 

+ 35 - 25
htdocs/admin/company.php

@@ -91,9 +91,9 @@ if (($action == 'update' && !GETPOST("cancel", 'alpha'))
 
 	$db->begin();
 
-	dolibarr_set_const($db, "MAIN_INFO_SOCIETE_NOM", GETPOST("nom", 'nohtml'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_SOCIETE_ADDRESS", GETPOST("MAIN_INFO_SOCIETE_ADDRESS", 'nohtml'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_SOCIETE_TOWN", GETPOST("MAIN_INFO_SOCIETE_TOWN", 'nohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_SOCIETE_NOM", GETPOST("nom", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_SOCIETE_ADDRESS", GETPOST("MAIN_INFO_SOCIETE_ADDRESS", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_SOCIETE_TOWN", GETPOST("MAIN_INFO_SOCIETE_TOWN", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
 	dolibarr_set_const($db, "MAIN_INFO_SOCIETE_ZIP", GETPOST("MAIN_INFO_SOCIETE_ZIP", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
 	dolibarr_set_const($db, "MAIN_INFO_SOCIETE_REGION", GETPOST("region_code", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
 	dolibarr_set_const($db, "MAIN_MONNAIE", GETPOST("currency", 'aZ09'), 'chaine', 0, '', $conf->entity);
@@ -178,19 +178,19 @@ if (($action == 'update' && !GETPOST("cancel", 'alpha'))
 		}
 	}
 
-	dolibarr_set_const($db, "MAIN_INFO_SOCIETE_MANAGERS", GETPOST("MAIN_INFO_SOCIETE_MANAGERS", 'nohtml'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_GDPR", GETPOST("MAIN_INFO_GDPR", 'nohtml'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_CAPITAL", GETPOST("capital", 'nohtml'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_SOCIETE_FORME_JURIDIQUE", GETPOST("forme_juridique_code", 'nohtml'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_SIREN", GETPOST("siren", 'nohtml'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_SIRET", GETPOST("siret", 'nohtml'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_APE", GETPOST("ape", 'nohtml'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_RCS", GETPOST("rcs", 'nohtml'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_PROFID5", GETPOST("MAIN_INFO_PROFID5", 'nohtml'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_PROFID6", GETPOST("MAIN_INFO_PROFID6", 'nohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_SOCIETE_MANAGERS", GETPOST("MAIN_INFO_SOCIETE_MANAGERS", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_GDPR", GETPOST("MAIN_INFO_GDPR", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_CAPITAL", GETPOST("capital", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_SOCIETE_FORME_JURIDIQUE", GETPOST("forme_juridique_code", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_SIREN", GETPOST("siren", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_SIRET", GETPOST("siret", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_APE", GETPOST("ape", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_RCS", GETPOST("rcs", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_PROFID5", GETPOST("MAIN_INFO_PROFID5", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_PROFID6", GETPOST("MAIN_INFO_PROFID6", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
 
-	dolibarr_set_const($db, "MAIN_INFO_TVAINTRA", GETPOST("tva", 'nohtml'), 'chaine', 0, '', $conf->entity);
-	dolibarr_set_const($db, "MAIN_INFO_SOCIETE_OBJECT", GETPOST("object", 'nohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_TVAINTRA", GETPOST("tva", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
+	dolibarr_set_const($db, "MAIN_INFO_SOCIETE_OBJECT", GETPOST("object", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
 
 	dolibarr_set_const($db, "SOCIETE_FISCAL_MONTH_START", GETPOST("SOCIETE_FISCAL_MONTH_START", 'int'), 'chaine', 0, '', $conf->entity);
 
@@ -369,7 +369,7 @@ $form = new Form($db);
 $formother = new FormOther($db);
 $formcompany = new FormCompany($db);
 
-$countrynotdefined = '<span class="error">'.$langs->trans("ErrorSetACountryFirst").' ('.$langs->trans("SeeAbove").')</span>';
+$countrynotdefined = '<span class="error">'.$langs->trans("ErrorSetACountryFirst").' <a href="#trzipbeforecountry">('.$langs->trans("SeeAbove").')</a></span>';
 
 print load_fiche_titre($langs->trans("CompanyFoundation"), '', 'title_setup');
 
@@ -403,18 +403,18 @@ print '<tr class="liste_titre"><th class="titlefieldcreate wordbreak">'.$langs->
 
 // Name
 print '<tr class="oddeven"><td class="fieldrequired wordbreak"><label for="name">'.$langs->trans("CompanyName").'</label></td><td>';
-print '<input name="nom" id="name" class="minwidth200" value="'.dol_escape_htmltag((GETPOSTISSET('nom') ? GETPOST('nom', 'nohtml') : (!empty($conf->global->MAIN_INFO_SOCIETE_NOM) ? $conf->global->MAIN_INFO_SOCIETE_NOM : ''))).'"'.(empty($conf->global->MAIN_INFO_SOCIETE_NOM) ? ' autofocus="autofocus"' : '').'></td></tr>'."\n";
+print '<input name="nom" id="name" class="minwidth200" value="'.dol_escape_htmltag((GETPOSTISSET('nom') ? GETPOST('nom', 'alphanohtml') : (!empty($conf->global->MAIN_INFO_SOCIETE_NOM) ? $conf->global->MAIN_INFO_SOCIETE_NOM : ''))).'"'.(empty($conf->global->MAIN_INFO_SOCIETE_NOM) ? ' autofocus="autofocus"' : '').'></td></tr>'."\n";
 
 // Address
 print '<tr class="oddeven"><td><label for="MAIN_INFO_SOCIETE_ADDRESS">'.$langs->trans("CompanyAddress").'</label></td><td>';
-print '<textarea name="MAIN_INFO_SOCIETE_ADDRESS" id="MAIN_INFO_SOCIETE_ADDRESS" class="quatrevingtpercent" rows="'.ROWS_3.'">'.(GETPOSTISSET('MAIN_INFO_SOCIETE_ADDRESS') ? GETPOST('MAIN_INFO_SOCIETE_ADDRESS', 'nohtml') : (!empty($conf->global->MAIN_INFO_SOCIETE_ADDRESS) ? $conf->global->MAIN_INFO_SOCIETE_ADDRESS : '')).'</textarea></td></tr>'."\n";
+print '<textarea name="MAIN_INFO_SOCIETE_ADDRESS" id="MAIN_INFO_SOCIETE_ADDRESS" class="quatrevingtpercent" rows="'.ROWS_3.'">'.(GETPOSTISSET('MAIN_INFO_SOCIETE_ADDRESS') ? GETPOST('MAIN_INFO_SOCIETE_ADDRESS', 'alphanohtml') : (!empty($conf->global->MAIN_INFO_SOCIETE_ADDRESS) ? $conf->global->MAIN_INFO_SOCIETE_ADDRESS : '')).'</textarea></td></tr>'."\n";
 
 // Zip
-print '<tr class="oddeven"><td><label for="MAIN_INFO_SOCIETE_ZIP">'.$langs->trans("CompanyZip").'</label></td><td>';
-print '<input class="width100" name="MAIN_INFO_SOCIETE_ZIP" id="MAIN_INFO_SOCIETE_ZIP" value="'.dol_escape_htmltag((GETPOSTISSET('MAIN_INFO_SOCIETE_ZIP') ? GETPOST('MAIN_INFO_SOCIETE_ZIP', 'alpha') : (!empty($conf->global->MAIN_INFO_SOCIETE_ZIP) ? $conf->global->MAIN_INFO_SOCIETE_ZIP : ''))).'"></td></tr>'."\n";
+print '<tr class="oddeven" id="trzipbeforecountry"><td><label for="MAIN_INFO_SOCIETE_ZIP">'.$langs->trans("CompanyZip").'</label></td><td>';
+print '<input class="width100" name="MAIN_INFO_SOCIETE_ZIP" id="MAIN_INFO_SOCIETE_ZIP" value="'.dol_escape_htmltag((GETPOSTISSET('MAIN_INFO_SOCIETE_ZIP') ? GETPOST('MAIN_INFO_SOCIETE_ZIP', 'alphanohtml') : (!empty($conf->global->MAIN_INFO_SOCIETE_ZIP) ? $conf->global->MAIN_INFO_SOCIETE_ZIP : ''))).'"></td></tr>'."\n";
 
-print '<tr class="oddeven"><td><label for="MAIN_INFO_SOCIETE_TOWN">'.$langs->trans("CompanyTown").'</label></td><td>';
-print '<input name="MAIN_INFO_SOCIETE_TOWN" class="minwidth200" id="MAIN_INFO_SOCIETE_TOWN" value="'.dol_escape_htmltag((GETPOSTISSET('MAIN_INFO_SOCIETE_TOWN') ? GETPOST('MAIN_INFO_SOCIETE_TOWN', 'nohtml') : (!empty($conf->global->MAIN_INFO_SOCIETE_TOWN) ? $conf->global->MAIN_INFO_SOCIETE_TOWN : ''))).'"></td></tr>'."\n";
+print '<tr class="oddeven" id="trtownbeforecountry"><td><label for="MAIN_INFO_SOCIETE_TOWN">'.$langs->trans("CompanyTown").'</label></td><td>';
+print '<input name="MAIN_INFO_SOCIETE_TOWN" class="minwidth200" id="MAIN_INFO_SOCIETE_TOWN" value="'.dol_escape_htmltag((GETPOSTISSET('MAIN_INFO_SOCIETE_TOWN') ? GETPOST('MAIN_INFO_SOCIETE_TOWN', 'alphanohtml') : (!empty($conf->global->MAIN_INFO_SOCIETE_TOWN) ? $conf->global->MAIN_INFO_SOCIETE_TOWN : ''))).'"></td></tr>'."\n";
 
 // Country
 print '<tr class="oddeven"><td class="fieldrequired"><label for="selectcountry_id">'.$langs->trans("Country").'</label></td><td>';
@@ -477,6 +477,11 @@ if (!empty($conf->barcode->enabled)) {
 // Logo
 print '<tr class="oddeven"><td><label for="logo">'.$form->textwithpicto($langs->trans("Logo"), 'png, jpg').'</label></td><td>';
 print '<div class="centpercent nobordernopadding valignmiddle "><div class="inline-block marginrightonly">';
+$maxfilesizearray = getMaxFileSizeArray();
+$maxmin = $maxfilesizearray['maxmin'];
+if ($maxmin > 0) {
+	print '<input type="hidden" name="MAX_FILE_SIZE" value="'.($maxmin * 1024).'">';	// MAX_FILE_SIZE must precede the field type=file
+}
 print '<input type="file" class="flat minwidth100 maxwidthinputfileonsmartphone" name="logo" id="logo" accept="image/*">';
 print '</div>';
 if (!empty($mysoc->logo_small)) {
@@ -514,6 +519,11 @@ print '</td></tr>';
 // Logo (squarred)
 print '<tr class="oddeven"><td><label for="logo_squarred">'.$form->textwithpicto($langs->trans("LogoSquarred"), 'png, jpg').'</label></td><td>';
 print '<div class="centpercent nobordernopadding valignmiddle"><div class="inline-block marginrightonly">';
+$maxfilesizearray = getMaxFileSizeArray();
+$maxmin = $maxfilesizearray['maxmin'];
+if ($maxmin > 0) {
+	print '<input type="hidden" name="MAX_FILE_SIZE" value="'.($maxmin * 1024).'">';	// MAX_FILE_SIZE must precede the field type=file
+}
 print '<input type="file" class="flat minwidth100 maxwidthinputfileonsmartphone" name="logo_squarred" id="logo_squarred" accept="image/*">';
 print '</div>';
 if (!empty($mysoc->logo_squarred_small)) {
@@ -564,17 +574,17 @@ $langs->load("companies");
 
 // Managing Director(s)
 print '<tr class="oddeven"><td><label for="director">'.$langs->trans("ManagingDirectors").'</label></td><td>';
-print '<input name="MAIN_INFO_SOCIETE_MANAGERS" id="directors" class="minwidth300" value="'.dol_escape_htmltag((GETPOSTISSET('MAIN_INFO_SOCIETE_MANAGERS') ? GETPOST('MAIN_INFO_SOCIETE_MANAGERS', 'nohtml') : (!empty($conf->global->MAIN_INFO_SOCIETE_MANAGERS) ? $conf->global->MAIN_INFO_SOCIETE_MANAGERS : ''))).'"></td></tr>';
+print '<input name="MAIN_INFO_SOCIETE_MANAGERS" id="directors" class="minwidth300" value="'.dol_escape_htmltag((GETPOSTISSET('MAIN_INFO_SOCIETE_MANAGERS') ? GETPOST('MAIN_INFO_SOCIETE_MANAGERS', 'alphanohtml') : (!empty($conf->global->MAIN_INFO_SOCIETE_MANAGERS) ? $conf->global->MAIN_INFO_SOCIETE_MANAGERS : ''))).'"></td></tr>';
 
 // GDPR contact
 print '<tr class="oddeven"><td>';
 print $form->textwithpicto($langs->trans("GDPRContact"), $langs->trans("GDPRContactDesc"));
 print '</td><td>';
-print '<input name="MAIN_INFO_GDPR" id="infodirector" class="minwidth300" value="'.dol_escape_htmltag((GETPOSTISSET("MAIN_INFO_GDPR") ? GETPOST("MAIN_INFO_GDPR", 'nohtml') : (!empty($conf->global->MAIN_INFO_GDPR) ? $conf->global->MAIN_INFO_GDPR : ''))).'"></td></tr>';
+print '<input name="MAIN_INFO_GDPR" id="infodirector" class="minwidth300" value="'.dol_escape_htmltag((GETPOSTISSET("MAIN_INFO_GDPR") ? GETPOST("MAIN_INFO_GDPR", 'alphanohtml') : (!empty($conf->global->MAIN_INFO_GDPR) ? $conf->global->MAIN_INFO_GDPR : ''))).'"></td></tr>';
 
 // Capital
 print '<tr class="oddeven"><td><label for="capital">'.$langs->trans("Capital").'</label></td><td>';
-print '<input name="capital" id="capital" class="maxwidth100" value="'.dol_escape_htmltag((GETPOSTISSET('capital') ? GETPOST('capital', 'nohtml') : (!empty($conf->global->MAIN_INFO_CAPITAL) ? $conf->global->MAIN_INFO_CAPITAL : ''))).'"></td></tr>';
+print '<input name="capital" id="capital" class="maxwidth100" value="'.dol_escape_htmltag((GETPOSTISSET('capital') ? GETPOST('capital', 'alphanohtml') : (!empty($conf->global->MAIN_INFO_CAPITAL) ? $conf->global->MAIN_INFO_CAPITAL : ''))).'"></td></tr>';
 
 // Juridical Status
 print '<tr class="oddeven"><td><label for="forme_juridique_code">'.$langs->trans("JuridicalStatus").'</label></td><td>';

+ 9 - 7
htdocs/admin/debugbar.php

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

+ 1 - 1
htdocs/admin/defaultvalues.php

@@ -101,7 +101,7 @@ if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x'
 	$defaulturl = '';
 	$defaultkey = '';
 	$defaultvalue = '';
-	$toselect = '';
+	$toselect = array();
 	$search_array_options = array();
 }
 

部分文件因为文件数量过多而无法显示