Przeglądaj źródła

Merge branch '19.0' into 19.0-mmi

Mathieu Moulin 6 miesięcy temu
rodzic
commit
90c07dde67
100 zmienionych plików z 1158 dodań i 390 usunięć
  1. 20 0
      .github/workflows/pr-18.yaml
  2. 6 0
      .pre-commit-config.yaml
  3. 260 5
      ChangeLog
  4. 2 1
      dev/resources/iso-normes/qr-bar-codes/QR code for invoices.txt
  5. 3 1
      dev/tools/codespell/codespell-ignore.txt
  6. 19 4
      htdocs/accountancy/admin/fiscalyear_card.php
  7. 2 5
      htdocs/accountancy/bookkeeping/export.php
  8. 1 1
      htdocs/accountancy/class/bookkeeping.class.php
  9. 51 16
      htdocs/accountancy/closure/index.php
  10. 4 1
      htdocs/accountancy/journal/bankjournal.php
  11. 11 11
      htdocs/accountancy/journal/expensereportsjournal.php
  12. 17 8
      htdocs/accountancy/journal/sellsjournal.php
  13. 1 1
      htdocs/adherents/card.php
  14. 1 1
      htdocs/adherents/stats/index.php
  15. 1 1
      htdocs/admin/dict.php
  16. 4 0
      htdocs/admin/mails_templates.php
  17. 36 4
      htdocs/admin/modules.php
  18. 1 1
      htdocs/admin/multicurrency.php
  19. 13 0
      htdocs/admin/pdf_other.php
  20. 1 1
      htdocs/api/class/api_documents.class.php
  21. 2 2
      htdocs/categories/class/api_categories.class.php
  22. 1 1
      htdocs/categories/traduction.php
  23. 26 4
      htdocs/comm/action/card.php
  24. 1 3
      htdocs/comm/action/class/actioncomm.class.php
  25. 12 12
      htdocs/comm/card.php
  26. 1 1
      htdocs/comm/mailing/card.php
  27. 14 2
      htdocs/comm/propal/card.php
  28. 1 1
      htdocs/comm/propal/class/propal.class.php
  29. 2 1
      htdocs/comm/propal/contact.php
  30. 1 0
      htdocs/comm/propal/document.php
  31. 49 33
      htdocs/comm/propal/list.php
  32. 8 8
      htdocs/commande/card.php
  33. 6 1
      htdocs/commande/class/commande.class.php
  34. 31 11
      htdocs/commande/list.php
  35. 1 1
      htdocs/commande/list_det.php
  36. 30 14
      htdocs/compta/accounting-files.php
  37. 19 13
      htdocs/compta/bank/bankentries_list.php
  38. 3 2
      htdocs/compta/bank/card.php
  39. 34 2
      htdocs/compta/bank/class/account.class.php
  40. 3 0
      htdocs/compta/bank/class/paymentvarious.class.php
  41. 2 1
      htdocs/compta/bank/line.php
  42. 7 3
      htdocs/compta/facture/card-rec.php
  43. 5 1
      htdocs/compta/facture/card.php
  44. 8 0
      htdocs/compta/facture/class/facture-rec.class.php
  45. 3 3
      htdocs/compta/facture/class/facture.class.php
  46. 2 2
      htdocs/compta/facture/contact.php
  47. 1 0
      htdocs/compta/facture/document.php
  48. 13 10
      htdocs/compta/facture/list.php
  49. 13 3
      htdocs/compta/facture/tpl/linkedobjectblock.tpl.php
  50. 6 1
      htdocs/compta/index.php
  51. 1 1
      htdocs/compta/paiement/class/paiement.class.php
  52. 7 4
      htdocs/compta/prelevement/index.php
  53. 4 2
      htdocs/compta/recap-compta.php
  54. 1 1
      htdocs/compta/stats/cabyprodserv.php
  55. 2 1
      htdocs/compta/tva/list.php
  56. 4 2
      htdocs/contrat/card.php
  57. 4 4
      htdocs/contrat/services_list.php
  58. 5 0
      htdocs/core/actions_massactions.inc.php
  59. 1 1
      htdocs/core/actions_sendmails.inc.php
  60. 1 1
      htdocs/core/boxes/box_actions.php
  61. 6 5
      htdocs/core/boxes/box_factures_imp.php
  62. 4 4
      htdocs/core/class/CMailFile.class.php
  63. 8 1
      htdocs/core/class/commondocgenerator.class.php
  64. 8 7
      htdocs/core/class/commonobject.class.php
  65. 2 0
      htdocs/core/class/conf.class.php
  66. 15 0
      htdocs/core/class/extrafields.class.php
  67. 5 3
      htdocs/core/class/hookmanager.class.php
  68. 40 27
      htdocs/core/class/html.form.class.php
  69. 1 1
      htdocs/core/class/html.formfile.class.php
  70. 6 5
      htdocs/core/class/html.formmail.class.php
  71. 4 4
      htdocs/core/class/html.formticket.class.php
  72. 28 6
      htdocs/core/class/ldap.class.php
  73. 10 4
      htdocs/core/class/utils.class.php
  74. 8 0
      htdocs/core/lib/ajax.lib.php
  75. 2 1
      htdocs/core/lib/company.lib.php
  76. 26 25
      htdocs/core/lib/date.lib.php
  77. 40 10
      htdocs/core/lib/functions.lib.php
  78. 1 1
      htdocs/core/lib/functions2.lib.php
  79. 7 7
      htdocs/core/lib/product.lib.php
  80. 10 5
      htdocs/core/login/functions_ldap.php
  81. 56 0
      htdocs/core/modules/DolibarrModules.class.php
  82. 4 4
      htdocs/core/modules/asset/doc/doc_generic_asset_odt.modules.php
  83. 1 1
      htdocs/core/modules/asset/doc/pdf_standard_asset.modules.php
  84. 4 4
      htdocs/core/modules/bom/doc/doc_generic_bom_odt.modules.php
  85. 4 4
      htdocs/core/modules/commande/doc/doc_generic_order_odt.modules.php
  86. 1 1
      htdocs/core/modules/commande/doc/pdf_einstein.modules.php
  87. 1 1
      htdocs/core/modules/commande/doc/pdf_eratosthene.modules.php
  88. 4 4
      htdocs/core/modules/contract/doc/doc_generic_contract_odt.modules.php
  89. 16 2
      htdocs/core/modules/contract/doc/pdf_strato.modules.php
  90. 4 4
      htdocs/core/modules/expedition/doc/doc_generic_shipment_odt.modules.php
  91. 6 6
      htdocs/core/modules/facture/doc/doc_generic_invoice_odt.modules.php
  92. 1 1
      htdocs/core/modules/facture/doc/pdf_crabe.modules.php
  93. 3 3
      htdocs/core/modules/facture/doc/pdf_sponge.modules.php
  94. 1 1
      htdocs/core/modules/member/doc/doc_generic_member_odt.class.php
  95. 24 16
      htdocs/core/modules/modSociete.class.php
  96. 1 0
      htdocs/core/modules/modTakePos.class.php
  97. 1 1
      htdocs/core/modules/modTicket.class.php
  98. 1 1
      htdocs/core/modules/mrp/doc/doc_generic_mo_odt.modules.php
  99. 1 1
      htdocs/core/modules/product/doc/doc_generic_product_odt.modules.php
  100. 4 4
      htdocs/core/modules/project/doc/doc_generic_project_odt.modules.php

+ 20 - 0
.github/workflows/pr-18.yaml

@@ -0,0 +1,20 @@
+on:
+  pull_request:
+    types:
+      - opened
+    branches:
+      - "18.0"
+
+jobs:
+  run:
+    runs-on: ubuntu-22.04
+    permissions:
+      pull-requests: write
+    steps:
+      - name: Create PR review request
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          url: ${{ github.event.pull_request.html_url }}
+        run: |
+          gh pr edit "$url" --add-assignee lvessiller-opendsi,rycks --add-reviewer lvessiller-opendsi,rycks
+          gh pr merge "$url" --merge --auto

+ 6 - 0
.pre-commit-config.yaml

@@ -26,6 +26,12 @@ repos:
       - id: fix-byte-order-marker
       - id: check-case-conflict
 
+  # Beautify shell scripts
+  - repo: https://github.com/gitleaks/gitleaks.git
+    rev: v8.16.1
+    hooks:
+      - id: gitleaks
+
   # Beautify shell scripts
   - repo: https://github.com/lovesegfault/beautysh.git
     rev: v6.2.1

+ 260 - 5
ChangeLog

@@ -2,6 +2,129 @@
 English Dolibarr ChangeLog
 --------------------------------------------------------------
 
+***** ChangeLog for 19.0.4 compared to 19.0.3 *****
+FIX: fatal when updating recurring supplier invoice line with php8 ($remise_percent is '' instead of 0) (#31713)
+FIX: supplier invoice template card: buyer and seller swapped in VAT-related function calls (probably a copy-paste from customer invoice templates) (#31446)
+FIX: #25853 Thirdparty Massaction (#25868)
+FIX: #28505 Blank page from thirparty to projects (#31637)
+FIX: #30757
+FIX: #30762
+FIX: #30768 allocate the correct invoice_line_id to the element timespent (#30769)
+FIX: #30772 Accountancy document export - The project filter on expenses report don't work (#30824)
+FIX: #30836
+FIX: #30960 show and search extrafields (#31026)
+FIX: Add same security test when uploading files from API than from GUI (#31114)
+FIX: ajaxik URL in ExpenseReport to load coef calculation (#30918)
+FIX: attached file on first page load (#30694)
+FIX: autoselect the fiscal period by default
+FIX: avoid from re-initializing array result on nested hook getEntity (#30626)
+FIX: avoid odt errors (#31126)
+FIX: avoid pgsql error (#30678)
+FIX: avoid the return "AND (())" (#30829)
+FIX: Backport fix fatal error on price with some truncating setup
+FIX: box_actions.php still uses fk_user_done which no longer exists (#31190)
+FIX: can validate shipment without stock movement (#31780)
+FIX: column position on PDF of payments
+FIX: const  WORKFLOW_RECEPTION_CLASSIFY_NEWD_INVOICE (#31601)
+FIX: Debug extrafields for bank lines
+FIX: email templates for expense report not visible
+FIX: Error message overwritten when a error occurs during update of product multilangs (#30841)
+FIX: error return missing in mo creation when qty to consume is <= 0 (#31134)
+FIX: Extrafield following between rec invoice and classic invoice (#31445)
+FIX: Fatal error on create loan schedule (#30656)
+FIX: Fix bug select columns and access to the public ticket list from the public ticket card (case when we have connected to another client before, the track id stocked in session overwrite the new track id from the public ticket card) (#31000)
+FIX: fixed search filter for projects resetting when set to empty (#30902)
+FIX: if you call fetchLines several times, your $object->lines contains duplicates (#31167)
+FIX: If you have no stock of your product, an error is displayed when you delete the reception. (#31504)
+FIX: Invoice unpaid widget - SQL error on group by with constant MAIN_COMPANY_PERENTITY_SHARED (#30866)
+FIX: issue on action set condition in particular when you set a deposi… (#31518)
+FIX: late order search option (v18+) (#30692)
+FIX: late propal search option (v18+) (#30687)
+FIX: missing fk_parent_line parameter (#30806)
+FIX: mysql error during dump for enable sandbox M999999 (#31116)
+FIX: PHP-warning-multiprice-in-liste-product-if-level-is-not-defined-on-product (#31507)
+FIX: Protection to avoid an extrafield to be mandatory if computed
+FIX: purge files cron: php warnings when rest module enabled (#30919)
+FIX: removed unreachable code (#31141)
+FIX: show preview pdf list expensereport (#31694)
+FIX: Substitution error in ticket emails in the subject (#30778)
+FIX: substitutions THIRDPARTY_XXX are not available for actioncomm reminders (#31385)
+FIX: use price() to display qty on a product's stats tab to avoid showing too many decimals when rounding errors are possible (#31165)
+FIX: Vat code is lost when updating lines in POS
+FIX: when qty is not an integer, apply price() (#31138)
+FIX: Wrong default PDF model when creating the second situation invoice (#30843)
+FIX: wrong subprice if price base type is TTC (#30887)
+
+***** ChangeLog for 19.0.3 compared to 19.0.2 *****
+FIX: #29403 HRM - Unable to delete a skill in a job Profile (#29779)
+FIX: #29439 incomplete API return (#29796)
+FIX: #29756 Sql error on comment search (#29761)
+FIX: #29780 Restore filtes when using "back to list"
+FIX: #30010 : Use conf TICKET_MESSAGE_MAIL_INTRO instead of translation key (#30081)
+FIX: #30274 : Add the include before executing dolibarr_set_const (#30320)
+FIX: #30467
+FIX: #30576 - Salary payment - Date of the payment is not displayed (#30592)
+FIX: Accountancy - Avoid space on FEC name file (#29716)
+FIX: Accountancy - Generate entries of expense report with localtax (#30411)
+FIX: ASSET: annual depreciation starting year (Again ;-)) #26084 (#30040)
+FIX: Backport page inventory.php from v18 to fix pagination bugs causing data loss (#29688)
+FIX: back to page on error in contact card (#29627)
+FIX: Bad calculation of $nbtotalofrecord (#30183)
+FIX: Bad count of total of supplie rinvoice into the list
+FIX: Better compatibility when objectdesc is not valid, and warnings
+FIX: broken pdf preview when multicompany sharing (#30188)
+FIX: compatibility with MULTICOMPANY_TRANSVERSE_MODE (#30599)
+FIX: Conflict with autoload (#30399)
+FIX: Display the real_PMP on inventory when its value is equal to 0 (#22291)
+FIX: Error mesg show untranslated extrafield name (#30227)
+FIX: executeHooks $object default value (#29647)
+FIX: expedition PDF models using units labels (#30358)
+FIX: Extrafield intshowzero in list (#29789)
+FIX: Extrafields always been delete and re insert for categories (#29781)
+FIX: extrafields on Organized events was broken
+FIX: fatal error on loading pictures in attached documents of an event (#30553)
+FIX: for country type (#29745)
+FIX: group by qty in product margin tab (#29853)
+FIX: init total amounts in margin module (#29854)
+FIX: issue to get the right files exported in Quadratrus export.php (#30004)
+FIX: lang output for sales representative on PDF (#30469)
+FIX: langs in common docgenerator (#29774)
+FIX: langs overwrite (#29630)
+FIX: lettering (auto) for invoice deposit with company discount (#29633)
+FIX: missing $object and $action for hook parameters (#30484)
+FIX: Missing $param in hook call for list
+FIX: Missing expense report picto in list (#29917)
+FIX: Missing expense report picto in menu (#29908)
+FIX: Missing the description in tooltip when option show in tooltip on
+FIX: mo cloning (#29686)
+FIX: modification date from label in accounting bookkeeping list (#30038)
+FIX: move porpale ref pdf cornas (#29989)
+FIX: Not qualified lines for reception (#29473)
+FIX: on change ref for bank account attachment are lost (#30529)
+FIX: orders to bill menu (#30179)
+FIX: Page expands when ticket messages are too long (#29785)
+FIX: parameter name (#29666)
+FIX: PHP8 warning if $conf->reception is checked the old fashion way (#29697)
+FIX: PHP 8 warning on output of successful cronjob (#29922)
+FIX: PHP exception on getSpecialCode (#29646)
+FIX: php warning if cookie doesn’t exist (#29723)
+FIX: pos: invoice date incorrectly set because of timezome mismatches (reverts #36e91da) (#30184)
+FIX: public project form return an error if SOCIETE_EMAIL_UNIQUE (#29942)
+FIX: REPLENISH MANY FOURN WHEN ORDER ALREADY CREATE (#29710)
+FIX: Supplier Order search on date valid (#30448)
+FIX: Ternary operator condition is always true/false (#29649)
+FIX: to avoid error during upgrade with pgsql (#30443)
+FIX: transfer in accountancy for expense reports.
+FIX: uninitialised var (#29728)
+FIX: - Unknown Character on HTML (#30257)
+FIX: Unsigned propal having signing date (#29825)
+FIX: Update asset.class.php
+FIX: update date_echeance of supplier invoices when we update invoice date in the past (#29886)
+FIX: User List - Function is show in wrong column when module HRM enabled (#30186)
+FIX: var name error and remove useless code (#30601)
+FIX: Warning: Undefined property: PropaleLigne::$situation_percent in /home/httpd/vhosts/aflac.fr/domains/dol190.aflac.fr/httpdocs/core/lib/pdf.lib.php on line 2442 (#30033)
+FIX: wrong value for duration unit (#30261)
+FIX: The ZAR currency must show the R before the amount
 
 ***** ChangeLog for 19.0.2 compared to 19.0.1 *****
 FIX: $object->oldcopy may be a stdClass and not original object
@@ -481,6 +604,138 @@ The following changes may create regressions for some external modules, but were
 * The load of hook context productdao has been removed before calling loadvirtualstock. Modules must use the context of main parent page or 'all' for all cases.
 
 
+***** ChangeLog for 18.0.6 compared to 18.0.5 *****
+FIX: 16.0 - parent company gets emptied when updating a third party from the card in edit mode (#28269)
+FIX: 16.0 - the e-mail templates configured in the notification module are not used if the recipient is a fixed e-mail address (#29407)
+FIX: 17.0: $num doesn't take trigger-modified newref into account, leading to inconsistencies if BILL_SUPPLIER_VALIDATE changes the invoice's ref (#28684)
+FIX: 17.0: fatal when updating recurring supplier invoice line with php8 ($remise_percent is '' instead of 0) (#31713)
+FIX: 17.0: supplier invoice template card: buyer and seller swapped in VAT-related function calls (probably a copy-paste from customer invoice templates) (#31446)
+FIX: #24265 regression cannot see all product on takepos (#28753)
+FIX: #25853 Thirdparty Massaction (#25868)
+FIX: #28205
+FIX: #28251 Fixing subpermission name on api_multicurrencies.class.php (#28252)
+FIX: #28369
+FIX: #28518 (#28520)
+FIX: #28978 FIX: #28976
+FIX: #29029 Impossible to delete an order line
+FIX: #29114 Missing contact term in intervention
+FIX: #29114 Translate contact term in intervention
+FIX: #29439 incomplete API return (#29796)
+FIX: #29496 filtering a record should not hide its child not filtered
+FIX: #30010 Use conf TICKET_MESSAGE_MAIL_INTRO instead of translation key (#30081)
+FIX: #30274 Add the include before executing dolibarr_set_const (#30320)
+FIX: #30467
+FIX: #30768 allocate the correct invoice_line_id to the element timespent (#30769)
+FIX: Accountancy export with file or not
+FIX: Accountancy - Generate entries of expense report with localtax (#30411)
+FIX: Accountancy - Not trunc id_import
+FIX: accounting FEC import (Issue #28306) (#29414)
+FIX: Add new hidden conf "DISABLE_QTY_OVERWRITTEN" (#28523)
+FIX: Add same security test when uploading files from API than from GUI (#31114)
+FIX: Amount of localtaxes in foreign currency was wrong on screen and PDF
+FIX: an error in a complex else condition
+FIX: ASSET: annual depreciation starting year (Again ;-)) #26084 (#30040)
+FIX: avoid error "Column 'entity' in where clause is ambiguous" (#28270)
+FIX: avoid from re-initializing array result on nested hook getEntity (#30626)
+FIX: avoid php warnings (#29247)
+FIX: avoid to delete "lock" and "unlock" files
+FIX: avoid Unknown column 'pfp.ref_fourn' (#28145)
+FIX: background color for enabled modules (#29378)
+FIX: Backport fix fatal error on price with some truncating setup
+FIX: Backport page inventory.php from v18 to fix pagination bugs causing data loss (#29688)
+FIX: back to page on error in contact card (#29627)
+FIX: Bad calculation of $nbtotalofrecord (#30183)
+FIX: box_actions.php still uses fk_user_done which no longer exists (#31190)
+FIX: can validate shipment without stock movement (#31780)
+FIX: Condition on newDateLimReglement
+FIX: Conflict with autoload (#30399)
+FIX: const  WORKFLOW_RECEPTION_CLASSIFY_NEWD_INVOICE (#31601)
+FIX: contact/address title is always "New Contact/Address" even if the contact/address already exists (#29581)
+FIX: Display the date according to user language on substitutions (#29510)
+FIX: Display the real_PMP on inventory when its value is equal to 0 (#22291)
+FIX: Don't display column when it's out of date (#28271)
+FIX: email templates for expense report not visible
+FIX: Error mesg show untranslated extrafield name (#30227)
+FIX: Error message overwrote when a error occurs during update of product multilangs (#30841)
+FIX: Error When cloning fourn price no default value for tva_tx (#28368)
+FIX: executeHooks $object default value (#29647)
+FIX: expedition PDF models using units labels (#30358)
+FIX: Extrafield following between rec invoice and classic invoice (#31445)
+FIX: fatal error on loading pictures in attached documents of an event (#30553)
+FIX: fatal error Unsupported operand types when recording load payment
+FIX: Fix bug select columns and access to the public ticket list from the public ticket card (case when we have connected to another client before, the track id stocked in session overwrite the new track id from the public ticket card) (#31000)
+FIX: Fix create shipping with product who have negative stocks on warehouse but the negative stock transfer is allowed (#26217)
+FIX: Fix save directory for invoice ODT and multientities
+FIX: group by qty in product margin tab (#29853)
+FIX: Hierarchy Employee view do not filter on employee = 1 (#29496)
+FIX: if you call fetchLines several times, your $object->lines contains duplicates (#31167)
+FIX: If you have no stock of your product, an error is displayed when you delete the reception. (#31504)
+FIX: incorrect page numbering in PDF #29458 (#29476)
+FIX: inventoryDeletePermission id define twice
+FIX: issue on action set condition in particular when you set a deposi… (#31518)
+FIX: issue to get the right files exported in Quadratrus export.php (#30004)
+FIX: lang output for sales representative on PDF (#30469)
+FIX: late order search option (#30692) and propal (#30687)
+FIX: lettering (auto) for invoice deposit with company discount (#29633)
+FIX: made invalid code shape error more user friendly (#29498)
+FIX: Merge of thirdparties must also move uploaded files
+FIX: missing entity parameter for ajax_constantonoff
+FIX: missing hide "new" button where "product" or "service" module are disable
+FIX: mo cloning (#29686)
+FIX: modification date from label in accounting bookkeeping list (#30038)
+FIX: Move the trigger for delete order line before the SQL request
+FIX: multiple problems with multicompany
+FIX: mysql error during dump for enable sandbox M999999 (#31116)
+FIX: notification: error 500 in fixed emails due to a bad copy/paste (#29580)
+FIX: notification module: for supplier orders (any of the 3 triggers), user can choose an e-mail template in conf, but the conf is not used when sending the notification (#28216)
+FIX: Not qualified lines for reception (#29473)
+FIX: not redirect when error occurs on updating card (#29388)
+FIX: Not trancate the multicurrency rate shown on cards (even if the global MAIN_MAX_DECIMALS_SHOWN is set to 0) (#28211)
+FIX: on change ref for bank account attachment are lost (#30529)
+FIX: Option MAIN_DOC_USE_TIMING can be a string with date format
+FIX: orders to bill menu (#30179)
+FIX: Payment on customer invoice - Remove accountid in url if empty for apply default value (#28156)
+FIX: PDF Fatal error : Backport fix from  #23972
+FIX: PDF Translations Extrafields
+FIX: permission on payment file of a tax
+FIX: php8: Fatal when empty $tmpvat is an empty string (no silent conversion to '0' when used in arithmetic operations) (#29451)
+FIX: PHP 8 warning on output of successful cronjob (#29922)
+FIX: PHP exception on getSpecialCode (#29646)
+FIX: php warning global conf (#29478)
+FIX: pos: invoice date incorrectly set because of timezome mismatches (reverts #36e91da) (#30184)
+FIX: public project form return an error if SOCIETE_EMAIL_UNIQUE (#29942)
+FIX: purge files cron: php warnings when rest module enabled (#30919)
+FIX: PUT /thirdparties/{id} and PUT /contacts/{id} should throw exception if update returns < 0 (#29596)
+FIX: Regression #29340
+FIX: Repair the replenishment list (#29336)
+FIX: REPLENISH MANY FOURN WHEN ORDER ALREADY CREATE (#29710)
+FIX: round capital societe (#29211)
+FIX: search and add extrafields to tasks when conf disabled (#29542)
+FIX: show preview pdf list expensereport (#31694)
+FIX: sometimes a string type instead integer is return, why ?
+FIX: Special code is now transmitted by args only in order supplier (#28546) (#28619)
+FIX: SQL syntax error in DDLUpdateField
+FIX: subscription must be editable when accounting isn't reconciled (#28469)
+FIX: substitutions THIRDPARTY_XXX are not available for actioncomm reminders (#31385)
+FIX: Supplier Order search on date valid (#30448)
+FIX: supplier price duplicate entry on update supplier product ref (#29290)
+FIX: syntax error
+FIX: TakePOS | Add product / Delete line of existing invoice
+FIX: Ticket new message notification sent twice
+FIX: transfer in accountancy for expense reports.
+FIX: Unsigned propal having signing date (#29825)
+FIX: Update asset.class.php
+FIX: update date_echeance of supplier invoices when we update invoice date in the past (#29886)
+FIX: use $conf->browser->os instead
+FIX: use price() to display qty on a product's stats tab to avoid showing too many decimals when rounding errors are possible (#31165)
+FIX: User List - Function is show in wrong column when module HRM enabled (#30186)
+fix: when invoice is created by WORKFLOW_ORDER_AUTOCREATE_INVOICE on ORDER_NEW, the invoice must have the default bank account of the thirdparty is it's empty on order (#29462)
+FIX: when qty is not an integer, apply price() (#31138)
+FIX: Wrong currency shown in TakePOS payment page
+FIX: wrong shortcut key for macintosh
+FIX: wrong sql request with product category filter
+FIX: wrong stock permission number
+
 ***** ChangeLog for 18.0.5 compared to 18.0.4 *****
 FIX: 17.0: deprecated field should only be a fallback
 FIX: 17.0 - php8 warnings: test for $field existence before checking if it is null or empty
@@ -836,7 +1091,7 @@ NEW: No overwrite of optionals during put() contact
 NEW: Notifications: add Customer Order delivered (ORDER_NEW) in module Notification
 NEW: Notifications: for Sign or Refused Propal from Online Page
 NEW: Now we can edit amount on VAT and salaries clone action
-NEW: only get opened contact from liste_contact function, to not have access to closed contact as mail receiver
+NEW: only get open contact from liste_contact function, to not have access to closed contact as mail receiver
 NEW: Option: MAIN_SECURITY_MAXFILESIZE_DOWNLOADED #yogosha10660
 NEW: Option to manage deposit slips for more payment modes (not only
 NEW: Option to show column for field and line selection on the left
@@ -3091,7 +3346,7 @@ NEW: add quick dropdown menu in top right menu (experimental with MAIN_USE_TOP_M
 NEW: add region in export companies and contacts
 NEW: add rights on margin info on invoice list
 NEW: add search param for close date on order list
-NEW: add show preview for mail attachement on form mail
+NEW: add show preview for mail attachment on form mail
 NEW: add State/Province origin for products
 NEW: add the workflow interaction close intervention on closing ticket
 NEW: add tracking number in list and search_all items
@@ -4476,7 +4731,7 @@ FIX: access to public interface when origin email has an alias.
 FIX: Alias name is not into the email recipient label.
 FIX: allow standalone credit note even if no invoice
 FIX: an admin can not access his own permissions after enabling advanced permissions
-FIX: Attachement of linked files on ticket when sending a message
+FIX: Attachment of linked files on ticket when sending a message
 FIX: avoid non numeric warning
 FIX: Bad currency var used in stripe for connect
 FIX: Bad list of ticket on public interface for ticket emailcollector
@@ -5272,7 +5527,7 @@ NEW: hidden option to define an invoice template for each invoice type
 NEW: Highlight lines on lists when they are checked
 NEW: Notification module support expense report+holiday validation and approval
 NEW: On customer/supplier card, add simple tooltip to amount boxes
-NEW: Page to check if the operations/items created between two dates have attached item(s) and possibility to download all attachements
+NEW: Page to check if the operations/items created between two dates have attached item(s) and possibility to download all attachments
 NEW: possibility to add all rights of all modules in one time
 NEW: redirect if only one result on global search on card
 NEW: Permission to ignore price min
@@ -6716,7 +6971,7 @@ NEW: No external check of version without explicit click in about page.
 NEW: ODT docs for USER USERGROUP CONTRACT and PRODUCT class
 NEW: odt usergroup
 NEW: On invoices generated by template, we save if invoice come from a source template.
-NEW: option to copy into attachement files of events, files send by mail (with auto event creation)
+NEW: option to copy into attachment files of events, files send by mail (with auto event creation)
 NEW: PDF with numbertoword
 NEW: Permit multiple file upload in linked documents
 NEW: PHP 7.1 compatibility

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

@@ -23,6 +23,7 @@ Method to encode/decode ZATCA string is available in test/phpunit/BarcodeTest.ph
 
 
 * FOR QR-Bill in switzerland - Facture-QR
+-----------------------------------------
 Syntax of QR Code - See file ig-qr-bill-v2.2-fr.pdf  (more doc on https://www.swiss-qr-invoice.org/downloads/)
-Syntax of complentary field named "structured information of invoice S1": https://www.swiss-qr-invoice.org/downloads/qr-bill-s1-syntax-fr.pdf
+Syntax of complementary 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/

+ 3 - 1
dev/tools/codespell/codespell-ignore.txt

@@ -1,9 +1,11 @@
 # List of words codespell will ignore
-# one per line, case-sensitive (when not lowercase)
+# one per line, must be in lower case.
 # PROVid
 provid
 # PostgreSQL
 postgresql
+# ZAR currency
+zar
 
 alltime
 ba

+ 19 - 4
htdocs/accountancy/admin/fiscalyear_card.php

@@ -94,7 +94,7 @@ if ($reshook < 0) {
 	setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
 }
 
-if ($action == 'confirm_delete' && $confirm == "yes") {
+if ($action == 'confirm_delete' && $confirm == "yes" && $permissiontoadd) {
 	$result = $object->delete($id);
 	if ($result >= 0) {
 		header("Location: fiscalyear.php");
@@ -102,7 +102,7 @@ if ($action == 'confirm_delete' && $confirm == "yes") {
 	} else {
 		setEventMessages($object->error, $object->errors, 'errors');
 	}
-} elseif ($action == 'add') {
+} elseif ($action == 'add' && $permissiontoadd) {
 	if (!GETPOST('cancel', 'alpha')) {
 		$error = 0;
 
@@ -144,7 +144,7 @@ if ($action == 'confirm_delete' && $confirm == "yes") {
 		header("Location: ./fiscalyear.php");
 		exit();
 	}
-} elseif ($action == 'update') {
+} elseif ($action == 'update' && $permissiontoadd) {
 	// Update record
 	if (!GETPOST('cancel', 'alpha')) {
 		$result = $object->fetch($id);
@@ -166,8 +166,19 @@ if ($action == 'confirm_delete' && $confirm == "yes") {
 		header("Location: ".$_SERVER["PHP_SELF"]."?id=".$id);
 		exit();
 	}
-}
+} elseif ($action == 'reopen' && $permissiontoadd && getDolGlobalString('ACCOUNTING_CAN_REOPEN_CLOSED_PERIOD')) {
+	$result = $object->fetch($id);
+
+	$object->status = GETPOST('status', 'int');
+	$result = $object->update($user);
 
+	if ($result > 0) {
+		header("Location: ".$_SERVER["PHP_SELF"]."?id=".$id);
+		exit();
+	} else {
+		setEventMessages($object->error, $object->errors, 'errors');
+	}
+}
 
 
 /*
@@ -345,6 +356,10 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 	if ($user->hasRight('accounting', 'fiscalyear', 'write')) {
 		print '<div class="tabsAction">';
 
+		if (getDolGlobalString('ACCOUNTING_CAN_REOPEN_CLOSED_PERIOD') && $object->status == $object::STATUS_CLOSED) {
+			print dolGetButtonAction($langs->trans("ReOpen"), '', 'reopen', $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=reopen&token='.newToken(), 'reopen', $permissiontoadd);
+		}
+
 		print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?action=edit&token='.newToken().'&id='.$id.'">'.$langs->trans('Modify').'</a>';
 
 		//print dolGetButtonAction($langs->trans("Delete"), '', 'delete', $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=delete&token='.newToken(), 'delete', $permissiontodelete);

+ 2 - 5
htdocs/accountancy/bookkeeping/export.php

@@ -1196,7 +1196,6 @@ while ($i < min($num, $limit)) {
 			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);
@@ -1208,11 +1207,10 @@ while ($i < min($num, $limit)) {
 			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);
+			$filedir = $conf->fournisseur->facture->dir_output.'/'.get_exdir($line->fk_doc, 2, 0, 0, $objectstatic, $objectstatic->element).dol_sanitizeFileName($line->doc_ref);
+			$subdir = get_exdir($objectstatic->id, 2, 0, 0, $objectstatic, $objectstatic->element).dol_sanitizeFileName($line->doc_ref);
 			$documentlink = $formfile->getDocumentsLink($objectstatic->element, $subdir, $filedir);
 		} elseif ($line->doc_type == 'expense_report') {
 			$langs->loadLangs(array('trips'));
@@ -1220,7 +1218,6 @@ while ($i < min($num, $limit)) {
 			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);

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

@@ -2462,7 +2462,7 @@ class BookKeeping extends CommonObject
 	}
 
 	/**
-	 * Get list of fiscal period
+	 * Get list of fiscal period ordered by start date.
 	 *
 	 * @param 	string	$filter		Filter
 	 * @return 	array|int			Return integer <0 if KO, Fiscal periods : [[id, date_start, date_end, label], ...]

+ 51 - 16
htdocs/accountancy/closure/index.php

@@ -60,33 +60,57 @@ if (!is_array($fiscal_periods)) {
 	setEventMessages($object->error, $object->errors, 'errors');
 }
 
+// Define the arrays of fiscal periods
 $active_fiscal_periods = array();
+$first_active_fiscal_period = null;
 $last_fiscal_period = null;
 $current_fiscal_period = null;
 $next_fiscal_period = null;
 $next_active_fiscal_period = null;
 if (is_array($fiscal_periods)) {
-	foreach ($fiscal_periods as $fiscal_period) {
-		if (empty($fiscal_period['status'])) {
+	foreach ($fiscal_periods as $fiscal_period) {		// List of fiscal periods sorted by date start
+		if (empty($first_active_fiscal_period) && empty($fiscal_period['status'])) {
+			$first_active_fiscal_period = $fiscal_period;
+		}
+		if (empty($fiscal_period['status'])) {	// if not closed
 			$active_fiscal_periods[] = $fiscal_period;
 		}
-		if (isset($current_fiscal_period)) {
+		if (isset($current_fiscal_period)) {	// If we already reach then current fiscal period, then this one is the next one just after
 			if (!isset($next_fiscal_period)) {
 				$next_fiscal_period = $fiscal_period;
 			}
 			if (!isset($next_active_fiscal_period) && empty($fiscal_period['status'])) {
 				$next_active_fiscal_period = $fiscal_period;
 			}
-		} else {
+		} else {								// If we did not found the current fiscal period
 			if ($fiscal_period_id == $fiscal_period['id'] || (empty($fiscal_period_id) && $fiscal_period['date_start'] <= $now && $now <= $fiscal_period['date_end'])) {
 				$current_fiscal_period = $fiscal_period;
 			} else {
-				$last_fiscal_period = $fiscal_period;
+				$last_fiscal_period = $fiscal_period;	// $last_fiscal_period is in fact $previous_fiscal_period
 			}
 		}
 	}
 }
 
+// If a current fiscal period open with an end and start date was not found, we autoselect the first one that is open and has a start and end date defined
+if (empty($current_fiscal_period) && !empty($first_active_fiscal_period)) {
+	$current_fiscal_period = $first_active_fiscal_period;
+	$last_fiscal_period = null;
+	$foundcurrent = false;
+	foreach ($fiscal_periods as $fiscal_period) {		// List of fiscal periods sorted by date start
+		if ($foundcurrent) {
+			$next_fiscal_period = $fiscal_period;
+			break;
+		}
+		if ($fiscal_period['id'] == $current_fiscal_period['id']) {
+			$foundcurrent = true;
+		}
+		if (!$foundcurrent) {
+			$last_fiscal_period = $fiscal_period;
+		}
+	}
+}
+
 $accounting_groups_used_for_balance_sheet_account = array_filter(array_map('trim', explode(',', getDolGlobalString('ACCOUNTING_CLOSURE_ACCOUNTING_GROUPS_USED_FOR_BALANCE_SHEET_ACCOUNT'))), 'strlen');
 $accounting_groups_used_for_income_statement = array_filter(array_map('trim', explode(',', getDolGlobalString('ACCOUNTING_CLOSURE_ACCOUNTING_GROUPS_USED_FOR_INCOME_STATEMENT'))), 'strlen');
 
@@ -123,14 +147,27 @@ if (empty($reshook)) {
 			$separate_auxiliary_account = GETPOST('separate_auxiliary_account', 'aZ09');
 			$generate_bookkeeping_records = GETPOST('generate_bookkeeping_records', 'aZ09');
 
-			$result = $object->closeFiscalPeriod($current_fiscal_period['id'], $new_fiscal_period_id, $separate_auxiliary_account, $generate_bookkeeping_records);
-			if ($result < 0) {
-				setEventMessages($object->error, $object->errors, 'errors');
-			} else {
-				setEventMessages($langs->trans("AccountancyClosureCloseSuccessfully"), null, 'mesgs');
+			$error = 0;
+			if ($generate_bookkeeping_records) {
+				if (!getDolGlobalString('ACCOUNTING_CLOSURE_ACCOUNTING_GROUPS_USED_FOR_BALANCE_SHEET_ACCOUNT')) {
+					$error++;
+					setEventMessages($langs->trans("ErrorAccountingClosureSetupNotComplete"), null, 'errors');
+				} elseif (!getDolGlobalString('ACCOUNTING_CLOSURE_ACCOUNTING_GROUPS_USED_FOR_INCOME_STATEMENT')) {
+					$error++;
+					setEventMessages($langs->trans("ErrorAccountingClosureSetupNotComplete"), null, 'errors');
+				}
+			}
 
-				header("Location: " . $_SERVER['PHP_SELF'] . (isset($current_fiscal_period) ? '?fiscal_period_id=' . $current_fiscal_period['id'] : ''));
-				exit;
+			if (!$error) {
+				$result = $object->closeFiscalPeriod($current_fiscal_period['id'], $new_fiscal_period_id, $separate_auxiliary_account, $generate_bookkeeping_records);
+				if ($result < 0) {
+					setEventMessages($object->error, $object->errors, 'errors');
+				} else {
+					setEventMessages($langs->trans("AccountancyClosureCloseSuccessfully"), null, 'mesgs');
+
+					header("Location: " . $_SERVER['PHP_SELF'] . (isset($current_fiscal_period) ? '?fiscal_period_id=' . $current_fiscal_period['id'] : ''));
+					exit;
+				}
 			}
 		} elseif ($action == 'confirm_step_3' && $confirm == "yes") {
 			$inventory_journal_id = GETPOST('inventory_journal_id', 'int');
@@ -301,9 +338,7 @@ print load_fiche_titre($langs->trans("Closure") . " - " . $fiscal_period_nav_tex
 
 if (empty($current_fiscal_period)) {
 	print $langs->trans('ErrorNoFiscalPeriodActiveFound');
-}
-
-if (isset($current_fiscal_period)) {
+} else {
 	// Step 1
 	$head = array();
 	$head[0][0] = DOL_URL_ROOT . '/accountancy/closure/index.php?fiscal_period_id=' . $current_fiscal_period['id'];
@@ -367,7 +402,7 @@ if (isset($current_fiscal_period)) {
 	if (empty($count_by_month['total']) && empty($current_fiscal_period['status'])) {
 		$button = '<a class="butAction" href="' . $_SERVER["PHP_SELF"] . '?action=step_2&fiscal_period_id=' . $current_fiscal_period['id'] . '">' . $langs->trans("AccountancyClosureClose") . '</a>';
 	} else {
-		$button = '<a class="butActionRefused classfortooltip" href="#">' . $langs->trans("AccountancyClosureClose") . '</a>';
+		$button = '<a class="butActionRefused classfortooltip" href="#" title="This fiscal period already has the status Closed. Feature disabled.">' . $langs->trans("AccountancyClosureClose") . '</a>';
 	}
 	print_barre_liste('', '', '', '', '', '', '', -1, '', '', 0, $button, '', 0, 1, 0);
 

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

@@ -218,7 +218,7 @@ if ($result) {
 	$tabtype = array();
 	$tabmoreinfo = array();
 
-	// Loop on each line into llx_bank table. For each line, we should get:
+	// Loop on each line into the llx_bank table. For each line, we should get:
 	// one line tabpay = line into bank
 	// one line for bank record = tabbq
 	// one line for thirdparty record = tabtp
@@ -333,9 +333,12 @@ if ($result) {
 					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 && !$is_salary && !$is_expensereport) {
+					// We must avoid as much as possible this "continue". If we want to jump to next loop, it means we don't want to process
+					// the case the link is user (often because managed by hard coded code into another link), and we must avoid this.
 					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'))) {

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

@@ -1,13 +1,13 @@
 <?php
-/* Copyright (C) 2007-2010  Laurent Destailleur     <eldy@users.sourceforge.net>
- * Copyright (C) 2007-2010  Jean Heimburger         <jean@tiaris.info>
- * Copyright (C) 2011       Juanjo Menent           <jmenent@2byte.es>
- * Copyright (C) 2012       Regis Houssin           <regis.houssin@inodbox.com>
- * Copyright (C) 2013-2023  Alexandre Spangaro      <aspangaro@easya.solutions>
- * Copyright (C) 2013-2016  Olivier Geffroy         <jeff@jeffinfo.com>
- * Copyright (C) 2013-2016  Florian Henry           <florian.henry@open-concept.pro>
- * Copyright (C) 2018       Frédéric France         <frederic.france@netlogic.fr>
- * Copyright (C) 2018		Eric Seigne             <eric.seigne@cap-rel.fr>
+/* Copyright (C) 2007-2010	Laurent Destailleur			<eldy@users.sourceforge.net>
+ * Copyright (C) 2007-2010	Jean Heimburger				<jean@tiaris.info>
+ * Copyright (C) 2011		Juanjo Menent				<jmenent@2byte.es>
+ * Copyright (C) 2012		Regis Houssin				<regis.houssin@inodbox.com>
+ * Copyright (C) 2013-2024	Alexandre Spangaro			<alexandre@inovea-conseil.com>
+ * Copyright (C) 2013-2016	Olivier Geffroy				<jeff@jeffinfo.com>
+ * Copyright (C) 2013-2016	Florian Henry				<florian.henry@open-concept.pro>
+ * Copyright (C) 2018		Frédéric France				<frederic.france@free.fr>
+ * Copyright (C) 2018		Eric Seigne					<eric.seigne@cap-rel.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
@@ -175,8 +175,8 @@ if ($result) {
 
 		$vatdata = getTaxesFromId($obj->tva_tx.($obj->vat_src_code ? ' ('.$obj->vat_src_code.')' : ''), $mysoc, $mysoc, 0);
 		$compta_tva = (!empty($vatdata['accountancy_code_buy']) ? $vatdata['accountancy_code_buy'] : $account_vat);
-		$compta_localtax1 = (!empty($vatdata['accountancy_code_buy']) ? $vatdata['accountancy_code_buy'] : $cpttva);
-		$compta_localtax2 = (!empty($vatdata['accountancy_code_buy']) ? $vatdata['accountancy_code_buy'] : $cpttva);
+		$compta_localtax1 = (!empty($vatdata['accountancy_code_buy']) ? $vatdata['accountancy_code_buy'] : $account_vat);
+		$compta_localtax2 = (!empty($vatdata['accountancy_code_buy']) ? $vatdata['accountancy_code_buy'] : $account_vat);
 
 		// Define array to display all VAT rates that use this accounting account $compta_tva
 		if (price2num($obj->tva_tx) || !empty($obj->vat_src_code)) {

+ 17 - 8
htdocs/accountancy/journal/sellsjournal.php

@@ -225,6 +225,14 @@ if ($result) {
 			$vatdata = $vatdata_cache[$tax_id];
 		} else {
 			$vatdata = getTaxesFromId($tax_id, $mysoc, $mysoc, 0);
+			if (getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')) {
+				$buyer = new Societe($db);
+				$buyer->fetch($obj->socid);
+			} else {
+				$buyer = null;	// We don't need the buyer in this case
+			}
+			$seller = $mysoc;
+			$vatdata = getTaxesFromId($tax_id, $buyer, $seller, 0);
 			$vatdata_cache[$tax_id] = $vatdata;
 		}
 		$compta_tva = (!empty($vatdata['accountancy_code_sell']) ? $vatdata['accountancy_code_sell'] : $cpttva);
@@ -396,15 +404,16 @@ WHERE
 GROUP BY fk_facture
 ";
 $resql = $db->query($sql);
-
-$num = $db->num_rows($resql);
-$i = 0;
-while ($i < $num) {
-	$obj = $db->fetch_object($resql);
-	if ($obj->nb > 0) {
-		$errorforinvoice[$obj->fk_facture_fourn] = 'somelinesarenotbound';
+if ($resql) {
+	$num = $db->num_rows($resql);
+	$i = 0;
+	while ($i < $num) {
+		$obj = $db->fetch_object($resql);
+		if ($obj->nb > 0) {
+			$errorforinvoice[$obj->fk_facture_fourn] = 'somelinesarenotbound';
+		}
+		$i++;
 	}
-	$i++;
 }
 //var_dump($errorforinvoice);exit;
 

+ 1 - 1
htdocs/adherents/card.php

@@ -1423,7 +1423,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 		print '</td></tr>';
 
 		// Other attributes. Fields from hook formObjectOptions and Extrafields.
-		include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_add.tpl.php';
+		include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_edit.tpl.php';
 
 		print '</table>';
 		print dol_get_fiche_end();

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

@@ -188,7 +188,7 @@ foreach ($data as $val) {
 		$oldyear--;
 		print '<tr class="oddeven" height="24">';
 		print '<td class="center">';
-		//print '<a href="month.php?year='.$oldyear.'&amp;mode='.$mode.'">';
+		//print '<a href="month.php?year='.$oldyear.'&mode='.$mode.'">';
 		print $oldyear;
 		//print '</a>';
 		print '</td>';

+ 1 - 1
htdocs/admin/dict.php

@@ -239,7 +239,7 @@ $tabsql[31] = "SELECT t.rowid, t.code, t.label, c.label as country, c.code as co
 $tabsql[32] = "SELECT a.id    as rowid, a.entity, a.code, a.fk_country as country_id, c.code as country_code, c.label as country, a.dayrule, a.day, a.month, a.year, a.active FROM ".MAIN_DB_PREFIX."c_hrm_public_holiday as a LEFT JOIN ".MAIN_DB_PREFIX."c_country as c ON a.fk_country=c.rowid AND c.active=1";
 $tabsql[33] = "SELECT t.rowid, t.pos, t.code, t.label, t.active FROM ".MAIN_DB_PREFIX."c_hrm_department as t";
 $tabsql[34] = "SELECT t.rowid, t.pos, t.code, t.label, t.c_level, t.active FROM ".MAIN_DB_PREFIX."c_hrm_function as t";
-$tabsql[35] = "SELECT c.rowid, c.label, c.active, c.entity FROM ".MAIN_DB_PREFIX."c_exp_tax_cat c as c";
+$tabsql[35] = "SELECT c.rowid, c.label, c.active, c.entity FROM ".MAIN_DB_PREFIX."c_exp_tax_cat as c";
 $tabsql[36] = "SELECT r.rowid, r.fk_c_exp_tax_cat, r.range_ik, r.active, r.entity FROM ".MAIN_DB_PREFIX."c_exp_tax_range r";
 $tabsql[37] = "SELECT r.rowid, r.code, r.sortorder, r.label, r.short_label, r.unit_type, r.scale, r.active FROM ".MAIN_DB_PREFIX."c_units r";
 $tabsql[38] = "SELECT s.rowid, s.entity, s.code, s.label, s.url, s.icon, s.active FROM ".MAIN_DB_PREFIX."c_socialnetworks as s WHERE s.entity IN (".getEntity($tabname[38]).")";

+ 4 - 0
htdocs/admin/mails_templates.php

@@ -1107,6 +1107,10 @@ if ($num) {
 				if (getDolGlobalString('MAIN_EMAIL_TEMPLATES_FOR_OBJECT_LINES')) {
 					$fieldsforcontent[] = 'content_lines';
 				}
+
+				$parameters = array('fieldsforcontent' => &$fieldsforcontent, 'tabname' => $tabname[$id]);
+				$hookmanager->executeHooks('editEmailTemplateFieldsForContent', $parameters, $obj, $tmpaction); // Note that $action and $object may have been modified by some hooks
+
 				foreach ($fieldsforcontent as $tmpfieldlist) {
 					$showfield = 1;
 					$css = "left";

+ 36 - 4
htdocs/admin/modules.php

@@ -1,7 +1,7 @@
 <?php
 /* Copyright (C) 2003-2007	Rodolphe Quiedeville	<rodolphe@quiedeville.org>
  * Copyright (C) 2003		Jean-Louis Bergamo		<jlb@j1b.org>
- * Copyright (C) 2004-2017	Laurent Destailleur		<eldy@users.sourceforge.net>
+ * Copyright (C) 2004-2024	Laurent Destailleur		<eldy@users.sourceforge.net>
  * Copyright (C) 2004		Eric Seigne				<eric.seigne@ryxeo.com>
  * Copyright (C) 2005-2017	Regis Houssin			<regis.houssin@inodbox.com>
  * Copyright (C) 2011-2023	Juanjo Menent			<jmenent@2byte.es>
@@ -267,6 +267,30 @@ if ($action == 'install' && $allowonlineinstall) {
 		}
 	}
 
+	/*
+	if (!$error) {
+		if (GETPOST('checkforcompliance')) {
+			$dir = $dirins;
+			$file = $modulenameval;
+			// $installedmodule
+			try {
+				$res = include_once $dir.$file; // A class already exists in a different file will send a non catchable fatal error.
+				$modName = substr($file, 0, dol_strlen($file) - 10);
+				if ($modName) {
+					if (class_exists($modName)) {
+						$objMod = new $modName($db);
+						'@phan-var-force DolibarrModules $objMod';
+
+						//var_dump($objMod);
+					}
+				}
+			} catch(Exception $e) {
+				// Nothing done
+			}
+		}
+	}
+	*/
+
 	if (!$error) {
 		setEventMessages($langs->trans("SetupIsReadyForUse", DOL_URL_ROOT.'/admin/modules.php?mainmenu=home', $langs->transnoentitiesnoconv("Home").' - '.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("Modules")), null, 'warnings');
 	}
@@ -837,11 +861,19 @@ if ($mode == 'common' || $mode == 'commonkanban') {
 				|| getDolGlobalString('CHECKLASTVERSION_EXTERNALMODULE')
 			)
 		) {
-			$checkRes = $objMod->checkForUpdate();
+			$checkRes = $objMod->checkForUpdate();	// Check for update version
 			if ($checkRes > 0) {
 				setEventMessage($objMod->getName().' : '.$versiontrans.' -> '.$objMod->lastVersion);
 			} elseif ($checkRes < 0) {
-				setEventMessage($objMod->getName().' '.$langs->trans('CheckVersionFail'), 'warnings');
+				setEventMessage($objMod->getName().': '.$langs->trans('CheckVersionFail'), 'warnings');
+			}
+		}
+
+		if ($objMod->isCoreOrExternalModule() == 'external' && $action == 'checklastversion' && !getDolGlobalString('DISABLE_CHECK_ON_MALWARE_MODULES')) {
+			$checkRes = $objMod->checkForCompliance();	// Check if module is reported as non compliant with Dolibarr rules and law
+			if (!is_numeric($checkRes) && $checkRes != '') {
+				$langs->load("errors");
+				setEventMessages($objMod->getName().' : '.$langs->trans($checkRes), null, 'errors');
 			}
 		}
 
@@ -1305,7 +1337,7 @@ if ($mode == 'deploy') {
 				$(document).ready(function() {
 					jQuery("#fileinstall").on("change", function() {
 						if(this.files[0].size > '.($maxmin * 1024).') {
-							alert("'.dol_escape_js($langs->trans("ErrorFileSizeTooLarge")).'");
+							alert("'.dol_escape_js($langs->transnoentitiesnoconv("ErrorFileSizeTooLarge")).'");
 							this.value = "";
 						}
 					});

+ 1 - 1
htdocs/admin/multicurrency.php

@@ -121,7 +121,7 @@ if ($action == 'add_currency') {
 		$currency = new MultiCurrency($db);
 
 		if ($currency->fetch($fk_multicurrency) > 0) {
-			if ($currency->delete() > 0) {
+			if ($currency->delete($user) > 0) {
 				setEventMessages($langs->trans('RecordDeleted'), array());
 			} else {
 				setEventMessages($langs->trans('ErrorDeleteCurrencyFail'), array(), 'errors');

+ 13 - 0
htdocs/admin/pdf_other.php

@@ -289,6 +289,19 @@ if (isModEnabled('facture')) {
 	}
 	print '</td></tr>';
 
+	/* Keep this option hidden for the moment to avoid options inflation. We'll see later if it is used enough...
+	print '<tr class="oddeven"><td>';
+	print $form->textwithpicto($langs->trans("SUPPLIER_PROPOSAL_ADD_BILLING_CONTACT"), $langs->trans("SUPPLIER_PROPOSAL_ADD_BILLING_CONTACTMore"));
+	print '</td><td>';
+	if ($conf->use_javascript_ajax) {
+		print ajax_constantonoff('SUPPLIER_PROPOSAL_ADD_BILLING_CONTACT');
+	} else {
+		$arrval = array('0' => $langs->trans("No"), '1' => $langs->trans("Yes"));
+		print $form->selectarray("SUPPLIER_PROPOSAL_ADD_BILLING_CONTACT", $arrval, $conf->global->SUPPLIER_PROPOSAL_ADD_BILLING_CONTACT);
+	}
+	print '</td></tr>';
+	*/
+
 	print '</table>';
 	print '</div>';
 }

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

@@ -917,7 +917,7 @@ class Documents extends DolibarrApi
 		// Move the temporary file at its final emplacement
 		$result = dol_move($destfiletmp, $dest_file, 0, $overwriteifexists, 1, 1, $moreinfo);
 		if (!$result) {
-			throw new RestException(500, "Failed to move file into '".$destfile."'");
+			throw new RestException(500, "Failed to move file into '".$dest_file."'");
 		}
 
 		return dol_basename($destfile);

+ 2 - 2
htdocs/categories/class/api_categories.class.php

@@ -278,8 +278,8 @@ class Categories extends DolibarrApi
 			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
 		}
 
-		if (!$this->category->delete(DolibarrApiAccess::$user)) {
-			throw new RestException(401, 'error when delete category');
+		if ($this->category->delete(DolibarrApiAccess::$user) <= 0) {
+			throw new RestException(500, 'Error when delete category : ' . $this->category->error);
 		}
 
 		return array(

+ 1 - 1
htdocs/categories/traduction.php

@@ -132,7 +132,7 @@ $cancel != $langs->trans("Cancel") &&
 
 	foreach ($object->multilangs as $key => $value) {     // recording of new values in the object
 		$libelle = GETPOST('libelle-'.$key, 'alpha');
-		$desc = GETPOST('desc-'.$key);
+		$desc = GETPOST('desc-'.$key, 'restricthtml');
 
 		if (empty($libelle)) {
 			$error++;

+ 26 - 4
htdocs/comm/action/card.php

@@ -2067,7 +2067,27 @@ if ($id > 0) {
 			// related contact
 			print '<tr><td>'.$langs->trans("ActionOnContact").'</td><td>';
 			print '<div class="maxwidth200onsmartphone">';
-			print img_picto('', 'contact', 'class="paddingrightonly"').$form->selectcontacts(!getDolGlobalString('MAIN_ACTIONCOM_CAN_ADD_ANY_CONTACT') ? $object->socid : 0, array_keys($object->socpeopleassigned), 'socpeopleassigned[]', 1, '', '', 1, 'minwidth300 widthcentpercentminusx', false, 0, 0, array(), 'multiple', 'contactid');
+
+			$searchSocid = ($object->socid > 0) ? $object->socid : (getDolGlobalString('MAIN_ACTIONCOM_CAN_ADD_ANY_CONTACT') ? 0 : -1);
+
+			print img_picto('', 'contact', 'class="paddingrightonly"');
+			print $form->selectcontacts(
+				$searchSocid,
+				array_keys($object->socpeopleassigned),
+				'socpeopleassigned[]',
+				1,
+				'',
+				'',
+				1,
+				'minwidth300 widthcentpercentminusx',
+				false,
+				0,
+				0,
+				array(),
+				'multiple',
+				'contactid'
+			);
+
 			print '</div>';
 			print '</td>';
 			print '</tr>';
@@ -2511,9 +2531,11 @@ if ($id > 0) {
 		}
 
 		// Priority
-		print '<tr><td class="nowrap" class="titlefield">'.$langs->trans("Priority").'</td><td>';
-		print($object->priority ? $object->priority : '');
-		print '</td></tr>';
+		if (getDolGlobalString('AGENDA_SUPPORT_PRIORITY_IN_EVENTS')) {
+			print '<tr><td class="nowrap" class="titlefield">' . $langs->trans("Priority") . '</td><td>';
+			print($object->priority ? $object->priority : '');
+			print '</td></tr>';
+		}
 
 		// Object linked (if link is for thirdparty, contact, project it is a recording error. We should not have links in link table
 		// for such objects because there is already a dedicated field into table llx_actioncomm.

+ 1 - 3
htdocs/comm/action/class/actioncomm.class.php

@@ -851,9 +851,6 @@ class ActionComm extends CommonObject
 				$this->type_color = $obj->type_color;
 				$this->type_picto = $obj->type_picto;
 				$this->type       = $obj->type_type;
-				/*$transcode = $langs->trans("Action".$obj->type_code);
-				$this->type       = (($transcode != "Action".$obj->type_code) ? $transcode : $obj->type_label); */
-				$transcode = $langs->trans("Action".$obj->type_code.'Short');
 
 				$this->code = $obj->code;
 				$this->label = $obj->label;
@@ -2549,6 +2546,7 @@ class ActionComm extends CommonObject
 
 					// Load event
 					$res = $this->fetch($actionCommReminder->fk_actioncomm);
+					if ($res > 0) $res = $this->fetch_thirdparty();
 					if ($res > 0) {
 						// PREPARE EMAIL
 						$errormesg = '';

+ 12 - 12
htdocs/comm/card.php

@@ -871,9 +871,9 @@ if ($object->id > 0) {
 				$filedir = $conf->propal->multidir_output[$objp->entity].'/'.dol_sanitizeFileName($objp->ref);
 				$file_list = null;
 				if (!empty($filedir)) {
-					$file_list = dol_dir_list($filedir, 'files', 0, '', '(\.meta|_preview.*.*\.png)$', 'date', SORT_DESC);
+					$file_list = dol_dir_list($filedir, 'files', 0, dol_sanitizeFileName($objp->ref).'.pdf', '(\.meta|_preview.*.*\.png)$', 'date', SORT_DESC);
 				}
-				if (is_array($file_list)) {
+				if (is_array($file_list) && !empty($file_list)) {
 					// Defined relative dir to DOL_DATA_ROOT
 					$relativedir = '';
 					if ($filedir) {
@@ -983,9 +983,9 @@ if ($object->id > 0) {
 				$filedir = $conf->commande->multidir_output[$objp->entity].'/'.dol_sanitizeFileName($objp->ref);
 				$file_list = null;
 				if (!empty($filedir)) {
-					$file_list = dol_dir_list($filedir, 'files', 0, '', '(\.meta|_preview.*.*\.png)$', 'date', SORT_DESC);
+					$file_list = dol_dir_list($filedir, 'files', 0, dol_sanitizeFileName($objp->ref).'.pdf', '(\.meta|_preview.*.*\.png)$', 'date', SORT_DESC);
 				}
-				if (is_array($file_list)) {
+				if (is_array($file_list) && !empty($file_list)) {
 					// Defined relative dir to DOL_DATA_ROOT
 					$relativedir = '';
 					if ($filedir) {
@@ -1077,9 +1077,9 @@ if ($object->id > 0) {
 				$filedir = $conf->expedition->multidir_output[$objp->entity].'/'.dol_sanitizeFileName($objp->ref);
 				$file_list = null;
 				if (!empty($filedir)) {
-					$file_list = dol_dir_list($filedir, 'files', 0, '', '(\.meta|_preview.*.*\.png)$', 'date', SORT_DESC);
+					$file_list = dol_dir_list($filedir, 'files', 0, dol_sanitizeFileName($objp->ref).'.pdf', '(\.meta|_preview.*.*\.png)$', 'date', SORT_DESC);
 				}
-				if (is_array($file_list)) {
+				if (is_array($file_list) && !empty($file_list)) {
 					// Defined relative dir to DOL_DATA_ROOT
 					$relativedir = '';
 					if ($filedir) {
@@ -1183,9 +1183,9 @@ if ($object->id > 0) {
 					$filedir = $conf->contrat->multidir_output[$objp->entity].'/'.dol_sanitizeFileName($objp->ref);
 					$file_list = null;
 					if (!empty($filedir)) {
-						$file_list = dol_dir_list($filedir, 'files', 0, '', '(\.meta|_preview.*.*\.png)$', 'date', SORT_DESC);
+						$file_list = dol_dir_list($filedir, 'files', 0, dol_sanitizeFileName($objp->ref).'.pdf', '(\.meta|_preview.*.*\.png)$', 'date', SORT_DESC);
 					}
-					if (is_array($file_list)) {
+					if (is_array($file_list) && !empty($file_list)) {
 						// Defined relative dir to DOL_DATA_ROOT
 						$relativedir = '';
 						if ($filedir) {
@@ -1274,9 +1274,9 @@ if ($object->id > 0) {
 				$filedir = $conf->ficheinter->multidir_output[$objp->entity].'/'.dol_sanitizeFileName($objp->ref);
 				$file_list = null;
 				if (!empty($filedir)) {
-					$file_list = dol_dir_list($filedir, 'files', 0, '', '(\.meta|_preview.*.*\.png)$', 'date', SORT_DESC);
+					$file_list = dol_dir_list($filedir, 'files', 0, dol_sanitizeFileName($objp->ref).'.pdf', '(\.meta|_preview.*.*\.png)$', 'date', SORT_DESC);
 				}
-				if (is_array($file_list)) {
+				if (is_array($file_list) && !empty($file_list)) {
 					// Defined relative dir to DOL_DATA_ROOT
 					$relativedir = '';
 					if ($filedir) {
@@ -1486,9 +1486,9 @@ if ($object->id > 0) {
 				$filedir = $conf->facture->multidir_output[$objp->entity].'/'.dol_sanitizeFileName($objp->ref);
 				$file_list = null;
 				if (!empty($filedir)) {
-					$file_list = dol_dir_list($filedir, 'files', 0, '', '(\.meta|_preview.*.*\.png)$', 'date', SORT_DESC);
+					$file_list = dol_dir_list($filedir, 'files', 0, dol_sanitizeFileName($objp->ref).'.pdf', '(\.meta|_preview.*.*\.png)$', 'date', SORT_DESC);
 				}
-				if (is_array($file_list)) {
+				if (is_array($file_list) && !empty($file_list)) {
 					// Defined relative dir to DOL_DATA_ROOT
 					$relativedir = '';
 					if ($filedir) {

+ 1 - 1
htdocs/comm/mailing/card.php

@@ -421,7 +421,7 @@ if (empty($reshook)) {
 							dol_syslog("comm/mailing/card.php: error for #".$iforemailloop.($mail->error ? ' - '.$mail->error : ''), LOG_WARNING);
 
 							$sql = "UPDATE ".MAIN_DB_PREFIX."mailing_cibles";
-							$sql .= " SET statut=-1, error_text='".$db->escape($mail->error)."', date_envoi='".$db->idate($now)."' WHERE rowid=".((int) $obj->rowid);
+							$sql .= " SET statut=-1, error_text='".$db->escape(dol_trunc($mail->error, 250))."', date_envoi='".$db->idate($now)."' WHERE rowid=".((int) $obj->rowid);
 							$resql2 = $db->query($sql);
 							if (!$resql2) {
 								dol_print_error($db);

+ 14 - 2
htdocs/comm/propal/card.php

@@ -149,7 +149,6 @@ $reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action
 if ($reshook < 0) {
 	setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
 }
-
 if (empty($reshook)) {
 	$backurlforlist = DOL_URL_ROOT.'/comm/propal/list.php';
 
@@ -1622,7 +1621,20 @@ if (empty($reshook)) {
 		$result = $object->set_demand_reason($user, GETPOST('demand_reason_id', 'int'));
 	} elseif ($action == 'setconditions' && $usercancreate) {
 		// Terms of payment
-		$result = $object->setPaymentTerms(GETPOST('cond_reglement_id', 'int'), GETPOST('cond_reglement_id_deposit_percent', 'alpha'));
+		$sql = "SELECT code ";
+		$sql .= "FROM " . $db->prefix() . "c_payment_term";
+		$sql .= " WHERE rowid = " . ((int) GETPOST('cond_reglement_id', 'int'));
+		$result = $db->query($sql);
+		if ($result) {
+			$obj = $db->fetch_object($result);
+			if ($obj->code == 'DEP30PCTDEL') {
+				$result = $object->setPaymentTerms(GETPOST('cond_reglement_id', 'int'), GETPOST('cond_reglement_id_deposit_percent', 'alpha'));
+			} else {
+				$object->deposit_percent = 0;
+				$object->update($user);
+				$result = $object->setPaymentTerms(GETPOST('cond_reglement_id', 'int'), $object->deposit_percent);
+			}
+		}
 		//} elseif ($action == 'setremisepercent' && $usercancreate) {
 		//	$result = $object->set_remise_percent($user, price2num(GETPOST('remise_percent'), '', 2));
 		//} elseif ($action == 'setremiseabsolue' && $usercancreate) {

+ 1 - 1
htdocs/comm/propal/class/propal.class.php

@@ -3499,7 +3499,7 @@ class Propal extends CommonObject
 			$response->label = $label;
 			$response->labelShort = $labelShort;
 			$response->url = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals';
-			$response->url_late = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
+			$response->url_late = DOL_URL_ROOT.'/comm/propal/list.php?search_option=late&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
 			$response->img = img_object('', "propal");
 
 			// This assignment in condition is not a bug. It allows walking the results.

+ 2 - 1
htdocs/comm/propal/contact.php

@@ -68,9 +68,10 @@ $socid = '';
 if (!empty($user->socid)) {
 	$socid = $user->socid;
 }
-$result = restrictedArea($user, 'propal', $object->id);
 $hookmanager->initHooks(array('proposalcontactcard', 'globalcard'));
 
+restrictedArea($user, 'propal', $object->id);
+
 $usercancreate = $user->hasRight("propal", "creer");
 
 /*

+ 1 - 0
htdocs/comm/propal/document.php

@@ -81,6 +81,7 @@ $socid = '';
 if (!empty($user->socid)) {
 	$socid = $user->socid;
 }
+$hookmanager->initHooks(array('propaldocument', 'globalcard'));
 restrictedArea($user, 'propal', $object->id);
 
 $usercancreate = $user->hasRight("propal", "creer");

+ 49 - 33
htdocs/comm/propal/list.php

@@ -17,6 +17,7 @@
  * Copyright (C) 2021	   Anthony Berton			<anthony.berton@bb2a.fr>
  * Copyright (C) 2021      Frédéric France			<frederic.france@netlogic.fr>
  * Copyright (C) 2022      Josep Lluís Amador		<joseplluis@lliuretic.cat>
+ * Copyright (C) 2024		William Mead			<william.mead@manchenumerique.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
@@ -72,6 +73,7 @@ $confirm 	= GETPOST('confirm', 'alpha');
 $cancel     = GETPOST('cancel', 'alpha');
 $toselect 	= GETPOST('toselect', 'array');
 $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'proposallist';
+$optioncss  = GETPOST('optioncss', 'alpha');
 $mode 		= GETPOST('mode', 'alpha');
 
 // Search Fields
@@ -145,6 +147,12 @@ $search_status = GETPOST('search_status', 'alpha');
 $optioncss = GETPOST('optioncss', 'alpha');
 $object_statut = GETPOST('search_statut', 'alpha');
 
+$search_option = GETPOST('search_option', 'alpha');
+if ($search_option == 'late') {
+	$search_status = '1';
+	$object_statut = '1';
+}
+
 // Pagination
 $limit = GETPOST('limit', 'int') ? GETPOST('limit', 'int') : $conf->liste_limit;
 $sortfield = GETPOST('sortfield', 'aZ09comma');
@@ -260,27 +268,27 @@ $arrayfields = array(
 
 // List of fields to search into when doing a "search in all"
 /*$fieldstosearchall = array();
-foreach ($object->fields as $key => $val) {
-	if (!empty($val['searchall'])) {
-		$fieldstosearchall['t.'.$key] = $val['label'];
-	}
-}*/
+ foreach ($object->fields as $key => $val) {
+ if (!empty($val['searchall'])) {
+ $fieldstosearchall['t.'.$key] = $val['label'];
+ }
+ }*/
 
 // Definition of array of fields for columns
 /*$arrayfields = array();
-foreach ($object->fields as $key => $val) {
-	// If $val['visible']==0, then we never show the field
-	if (!empty($val['visible'])) {
-		$visible = (int) dol_eval($val['visible'], 1);
-		$arrayfields['t.'.$key] = array(
-			'label'=>$val['label'],
-			'checked'=>(($visible < 0) ? 0 : 1),
-			'enabled'=>(abs($visible) != 3 && dol_eval($val['enabled'], 1)),
-			'position'=>$val['position'],
-			'help'=> isset($val['help']) ? $val['help'] : ''
-		);
-	}
-}*/
+ foreach ($object->fields as $key => $val) {
+ // If $val['visible']==0, then we never show the field
+ if (!empty($val['visible'])) {
+ $visible = (int) dol_eval($val['visible'], 1);
+ $arrayfields['t.'.$key] = array(
+ 'label'=>$val['label'],
+ 'checked'=>(($visible < 0) ? 0 : 1),
+ 'enabled'=>(abs($visible) != 3 && dol_eval($val['enabled'], 1)),
+ 'position'=>$val['position'],
+ 'help'=> isset($val['help']) ? $val['help'] : ''
+ );
+ }
+ }*/
 
 if (!$user->hasRight('societe', 'client', 'voir')) {
 	$search_sale = $user->id;
@@ -380,6 +388,7 @@ if (empty($reshook)) {
 		$search_date_delivery_start = '';
 		$search_date_delivery_end = '';
 		$search_availability = '';
+		$search_option = '';
 		$search_status = '';
 		$object_statut = '';
 		$search_categ_cus = 0;
@@ -825,6 +834,9 @@ if (!empty($searchCategoryProductList)) {
 		}
 	}
 }
+if ($search_option == 'late') {
+	$sql .= " AND p.fin_validite < '".$db->idate(dol_now() - $conf->propal->cloture->warning_delay)."'";
+}
 // Add where from extra fields
 include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php';
 //print $sql;
@@ -873,8 +885,8 @@ if (!$resql) {
 	exit;
 }
 
-	$objectstatic = new Propal($db);
-	$userstatic = new User($db);
+$objectstatic = new Propal($db);
+$userstatic = new User($db);
 
 if ($socid > 0) {
 	$soc = new Societe($db);
@@ -887,9 +899,9 @@ if ($socid > 0) {
 	$title = $langs->trans('Proposals');
 }
 
-	$num = $db->num_rows($resql);
+$num = $db->num_rows($resql);
 
-	$arrayofselected = is_array($toselect) ? $toselect : array();
+$arrayofselected = is_array($toselect) ? $toselect : array();
 
 if ($num == 1 && getDolGlobalString('MAIN_SEARCH_DIRECT_OPEN_IF_ONLY_ONE') && $search_all) {
 	$obj = $db->fetch_object($resql);
@@ -900,10 +912,10 @@ if ($num == 1 && getDolGlobalString('MAIN_SEARCH_DIRECT_OPEN_IF_ONLY_ONE') && $s
 	exit;
 }
 
-	$help_url = 'EN:Commercial_Proposals|FR:Proposition_commerciale|ES:Presupuestos';
-	llxHeader('', $title, $help_url);
+$help_url = 'EN:Commercial_Proposals|FR:Proposition_commerciale|ES:Presupuestos';
+llxHeader('', $title, $help_url);
 
-	$param = '&search_status='.urlencode($search_status);
+$param = '&search_status='.urlencode($search_status);
 if (!empty($mode)) {
 	$param .= '&mode='.urlencode($mode);
 }
@@ -1570,7 +1582,7 @@ if (!empty($arrayfields['state.nom']['checked'])) {
 	$totalarray['nbfield']++;
 }
 if (!empty($arrayfields['country.code_iso']['checked'])) {
-	print_liste_field_titre($arrayfields['country.code_iso']['label'], $_SERVER["PHP_SELF"], "country.code_iso", "", $param, 'class="center"', $sortfield, $sortorder);
+	print_liste_field_titre($arrayfields['country.code_iso']['label'], $_SERVER["PHP_SELF"], "country.code_iso", "", $param, 'class="center"', $sortfield, $sortorder, 'center ');
 	$totalarray['nbfield']++;
 }
 if (!empty($arrayfields['s.email']['checked'])) {
@@ -1586,19 +1598,19 @@ if (!empty($arrayfields['typent.code']['checked'])) {
 	$totalarray['nbfield']++;
 }
 if (!empty($arrayfields['p.date']['checked'])) {
-	print_liste_field_titre($arrayfields['p.date']['label'], $_SERVER["PHP_SELF"], 'p.datep', '', $param, 'class="center"', $sortfield, $sortorder);
+	print_liste_field_titre($arrayfields['p.date']['label'], $_SERVER["PHP_SELF"], 'p.datep', '', $param, 'class="center"', $sortfield, $sortorder, 'center ');
 	$totalarray['nbfield']++;
 }
 if (!empty($arrayfields['p.fin_validite']['checked'])) {
-	print_liste_field_titre($arrayfields['p.fin_validite']['label'], $_SERVER["PHP_SELF"], 'dfv', '', $param, 'class="center"', $sortfield, $sortorder);
+	print_liste_field_titre($arrayfields['p.fin_validite']['label'], $_SERVER["PHP_SELF"], 'dfv', '', $param, 'class="center"', $sortfield, $sortorder, 'center ');
 	$totalarray['nbfield']++;
 }
 if (!empty($arrayfields['p.date_livraison']['checked'])) {
-	print_liste_field_titre($arrayfields['p.date_livraison']['label'], $_SERVER["PHP_SELF"], 'p.date_livraison', '', $param, 'class="center"', $sortfield, $sortorder);
+	print_liste_field_titre($arrayfields['p.date_livraison']['label'], $_SERVER["PHP_SELF"], 'p.date_livraison', '', $param, 'class="center"', $sortfield, $sortorder, 'center ');
 	$totalarray['nbfield']++;
 }
 if (!empty($arrayfields['p.date_signature']['checked'])) {
-	print_liste_field_titre($arrayfields['p.date_signature']['label'], $_SERVER["PHP_SELF"], 'p.date_signature', '', $param, 'class="center"', $sortfield, $sortorder);
+	print_liste_field_titre($arrayfields['p.date_signature']['label'], $_SERVER["PHP_SELF"], 'p.date_signature', '', $param, 'class="center"', $sortfield, $sortorder, 'center ');
 	$totalarray['nbfield']++;
 }
 if (!empty($arrayfields['ava.rowid']['checked'])) {
@@ -1748,10 +1760,11 @@ if (isModEnabled('margin') && (
 	|| !empty($arrayfields['total_margin']['checked'])
 	|| !empty($arrayfields['total_margin_rate']['checked'])
 	|| !empty($arrayfields['total_mark_rate']['checked'])
-)
-) {
-	$with_margin_info = true;
+	)
+	) {
+		$with_margin_info = true;
 }
+
 $total_ht = 0;
 $total_margin = 0;
 
@@ -1765,6 +1778,9 @@ while ($i < $imaxinloop) {
 	if (empty($obj)) {
 		break; // Should not happen
 	}
+	if ($search_option) {
+		$param .= "&search_option=".urlencode($search_option);
+	}
 
 	$objectstatic->id = $obj->rowid;
 	$objectstatic->ref = $obj->ref;

+ 8 - 8
htdocs/commande/card.php

@@ -880,17 +880,17 @@ if (empty($reshook)) {
 
 				// Set unit price to use
 				if (!empty($price_ht) || $price_ht === '0') {
-					$pu_ht = price2num($price_ht, 'MU');
-					$pu_ttc = price2num($pu_ht * (1 + ($tmpvat / 100)), 'MU');
+					$pu_ht = (float) price2num($price_ht, 'MU');
+					$pu_ttc = (float) price2num($pu_ht * (1 + ($tmpvat / 100)), 'MU');
 				} elseif (!empty($price_ttc) || $price_ttc === '0') {
-					$pu_ttc = price2num($price_ttc, 'MU');
-					$pu_ht = price2num($pu_ttc / (1 + ($tmpvat / 100)), 'MU');
+					$pu_ttc = (float) price2num($price_ttc, 'MU');
+					$pu_ht = (float) price2num($pu_ttc / (1 + ($tmpvat / 100)), 'MU');
 				} elseif ($tmpvat != $tmpprodvat) {
 					// Is this still used ?
 					if ($price_base_type != 'HT') {
-						$pu_ht = price2num($pu_ttc / (1 + ($tmpvat / 100)), 'MU');
+						$pu_ht = (float) price2num($pu_ttc / (1 + ($tmpvat / 100)), 'MU');
 					} else {
-						$pu_ttc = price2num($pu_ht * (1 + ($tmpvat / 100)), 'MU');
+						$pu_ttc = (float) price2num($pu_ht * (1 + ($tmpvat / 100)), 'MU');
 					}
 				}
 
@@ -1954,7 +1954,7 @@ if ($action == 'create' && $usercancreate) {
 		}
 
 		// Source / Channel - What trigger creation
-		print '<tr><td>'.$langs->trans('Channel').'</td><td>';
+		print '<tr><td>'.$langs->trans('Source').'</td><td>';
 		print img_picto('', 'question', 'class="pictofixedwidth"');
 		$form->selectInputReason((GETPOSTISSET('demand_reason_id') ? GETPOST('demand_reason_id') : $demand_reason_id), 'demand_reason_id', '', 1, 'maxwidth200 widthcentpercentminusx');
 		print '</td></tr>';
@@ -2967,7 +2967,7 @@ if ($action == 'create' && $usercancreate) {
 				$arrayforbutaction = array();
 				// Create a purchase order
 				if (getDolGlobalInt('COMMANDE_DISABLE_ADD_PURCHASE_ORDER') == 0) {
-					$arrayforbutaction[] = array('lang'=>'orders', 'enabled'=>(isModEnabled("supplier_order") && $object->statut > Commande::STATUS_DRAFT && $object->getNbLinesProductOrServiceOnBuy(getDolGlobalInt('COMMANDE_ADD_PURCHASE_ORDER_IGNORE_FREE_PRODUCTS')) > 0), 'perm'=>$usercancreatepurchaseorder, 'label'=>'AddPurchaseOrder', 'url'=>'/fourn/commande/card.php?action=create&amp;origin='.$object->element.'&amp;originid='.$object->id);
+					$arrayforbutaction[] = array('lang'=>'orders', 'enabled'=>(isModEnabled("supplier_order") && $object->statut > Commande::STATUS_DRAFT ), 'perm'=>$usercancreatepurchaseorder, 'label'=>'AddPurchaseOrder', 'url'=>'/fourn/commande/card.php?action=create&amp;origin='.$object->element.'&amp;originid='.$object->id);
 				}
 				/*if (isModEnabled("supplier_order") && $object->statut > Commande::STATUS_DRAFT && $object->getNbOfServicesLines() > 0) {
 					if ($usercancreatepurchaseorder) { isModEnabled("supplier_order") && $object->statut > Commande::STATUS_DRAFT && $object->getNbOfServicesLines() > 0

+ 6 - 1
htdocs/commande/class/commande.class.php

@@ -13,6 +13,7 @@
  * Copyright (C) 2016-2022 Ferran Marcet        <fmarcet@2byte.es>
  * Copyright (C) 2021-2023 Frédéric France      <frederic.france@netlogic.fr>
  * Copyright (C) 2022      Gauthier VERDOL      <gauthier.verdol@atm-consulting.fr>
+ * Copyright (C) 2024		William Mead		<william.mead@manchenumerique.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
@@ -3242,7 +3243,7 @@ class Commande extends CommonOrder
 			$this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
 			$this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
 			$this->line->remise_percent = $remise_percent;
-			$this->line->subprice       = $subprice;
+			$this->line->subprice       = $pu_ht;
 			$this->line->info_bits      = $info_bits;
 			$this->line->special_code   = $special_code;
 			$this->line->total_ht       = $total_ht;
@@ -3605,10 +3606,12 @@ class Commande extends CommonOrder
 			}
 
 			$response = new WorkboardResponse();
+
 			$response->warning_delay = $delay_warning;
 			$response->label = $label;
 			$response->labelShort = $labelShort;
 			$response->url = $url;
+			$response->url_late = DOL_URL_ROOT.'/commande/list.php?search_option=late&mainmenu=commercial&leftmenu=orders';
 			$response->img = img_object('', "order");
 
 			$generic_commande = new Commande($this->db);
@@ -4360,6 +4363,8 @@ class OrderLine extends CommonOrderLine
 			$this->multicurrency_total_tva	= $objp->multicurrency_total_tva;
 			$this->multicurrency_total_ttc	= $objp->multicurrency_total_ttc;
 
+			$this->fetch_optionals();
+
 			$this->db->free($result);
 
 			return 1;

+ 31 - 11
htdocs/commande/list.php

@@ -12,6 +12,7 @@
  * Copyright (C) 2016-2023  Ferran Marcet           <fmarcet@2byte.es>
  * Copyright (C) 2018-2023  Charlene Benke	        <charlene@patas-monkey.com>
  * Copyright (C) 2021	   	Anthony Berton			<anthony.berton@bb2a.fr>
+ * Copyright (C) 2024		William Mead			<william.mead@manchenumerique.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
@@ -116,6 +117,10 @@ $search_fk_cond_reglement = GETPOST('search_fk_cond_reglement', 'int');
 $search_fk_shipping_method = GETPOST('search_fk_shipping_method', 'int');
 $search_fk_mode_reglement = GETPOST('search_fk_mode_reglement', 'int');
 $search_fk_input_reason = GETPOST('search_fk_input_reason', 'int');
+$search_option = GETPOST('search_option', 'alpha');
+if ($search_option == 'late') {
+	$search_status = '-2';
+}
 
 $diroutputmassaction = $conf->commande->multidir_output[$conf->entity].'/temp/massgeneration/'.$user->id;
 
@@ -186,7 +191,7 @@ $arrayfields = array(
 	'c.fk_shipping_method'=>array('label'=>"SendingMethod", 'checked'=>-1, 'position'=>66 , 'enabled'=>isModEnabled("expedition")),
 	'c.fk_cond_reglement'=>array('label'=>"PaymentConditionsShort", 'checked'=>-1, 'position'=>67),
 	'c.fk_mode_reglement'=>array('label'=>"PaymentMode", 'checked'=>-1, 'position'=>68),
-	'c.fk_input_reason'=>array('label'=>"Channel", 'checked'=>-1, 'position'=>69),
+	'c.fk_input_reason'=>array('label'=>"Origin", 'checked'=>-1, 'position'=>69),
 	'c.total_ht'=>array('label'=>"AmountHT", 'checked'=>1, 'position'=>75),
 	'c.total_vat'=>array('label'=>"AmountVAT", 'checked'=>0, 'position'=>80),
 	'c.total_ttc'=>array('label'=>"AmountTTC", 'checked'=>0, 'position'=>85),
@@ -299,6 +304,7 @@ if (empty($reshook)) {
 		$search_fk_shipping_method = '';
 		$search_fk_mode_reglement = '';
 		$search_fk_input_reason = '';
+		$search_option = '';
 	}
 	if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')
 		|| GETPOST('button_search_x', 'alpha') || GETPOST('button_search.x', 'alpha') || GETPOST('button_search', 'alpha')) {
@@ -614,6 +620,9 @@ if (empty($reshook)) {
 			if ($search_status != '') {
 				$param .= '&search_status='.urlencode($search_status);
 			}
+			if ($search_option) {
+				$param .= "&search_option=".urlencode($search_option);
+			}
 			if ($search_orderday) {
 				$param .= '&search_orderday='.urlencode($search_orderday);
 			}
@@ -916,7 +925,9 @@ if ($search_status != '') {
 		$sql .= ' AND (c.fk_statut IN (1,2,3))'; // validated, in process or closed
 	}
 }
-
+if ($search_option == 'late') {
+	$sql .= " AND c.date_commande < '".$db->idate(dol_now() - $conf->commande->client->warning_delay)."'";
+}
 if ($search_datecloture_start) {
 	$sql .= " AND c.date_cloture >= '".$db->idate($search_datecloture_start)."'";
 }
@@ -1203,6 +1214,9 @@ if ($socid > 0) {
 if ($search_status != '') {
 	$param .= '&search_status='.urlencode($search_status);
 }
+if ($search_option) {
+	$param .= "&search_option=".urlencode($search_option);
+}
 if ($search_datecloture_start) {
 	$param .= '&search_datecloture_startday='.dol_print_date($search_datecloture_start, '%d').'&search_datecloture_startmonth='.dol_print_date($search_datecloture_start, '%m').'&search_datecloture_startyear='.dol_print_date($search_datecloture_start, '%Y');
 }
@@ -1504,6 +1518,7 @@ if (isModEnabled('stock') && getDolGlobalString('WAREHOUSE_ASK_WAREHOUSE_DURING_
 	$moreforfilter .= img_picto($tmptitle, 'stock', 'class="pictofixedwidth"').$formproduct->selectWarehouses($search_warehouse, 'search_warehouse', '', 1, 0, 0, $tmptitle, 0, 0, array(), 'maxwidth250 widthcentpercentminusx');
 	$moreforfilter .= '</div>';
 }
+
 $parameters = array();
 $reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 if (empty($reshook)) {
@@ -2222,8 +2237,8 @@ while ($i < $imaxinloop) {
 
 		// Alias name
 		if (!empty($arrayfields['s.name_alias']['checked'])) {
-			print '<td class="nocellnopadd">';
-			print $obj->alias;
+			print '<td class="tdoverflowmax100" title="'.dol_escape_htmltag($obj->alias).'">';
+			print dol_escape_htmltag($obj->alias);
 			print '</td>';
 			if (!$i) {
 				$totalarray['nbfield']++;
@@ -2253,8 +2268,8 @@ while ($i < $imaxinloop) {
 
 		// Town
 		if (!empty($arrayfields['s.town']['checked'])) {
-			print '<td class="nocellnopadd">';
-			print $obj->town;
+			print '<td class="tdoverflowmax100" title="'.dol_escape_htmltag($obj->town).'">';
+			print dol_escape_htmltag($obj->town);
 			print '</td>';
 			if (!$i) {
 				$totalarray['nbfield']++;
@@ -2263,8 +2278,8 @@ while ($i < $imaxinloop) {
 
 		// Zip
 		if (!empty($arrayfields['s.zip']['checked'])) {
-			print '<td class="nocellnopadd">';
-			print $obj->zip;
+			print '<td class="tdoverflowmax100" title="'.dol_escape_htmltag($obj->zip).'">';
+			print dol_escape_htmltag($obj->zip);
 			print '</td>';
 			if (!$i) {
 				$totalarray['nbfield']++;
@@ -2273,7 +2288,7 @@ while ($i < $imaxinloop) {
 
 		// State
 		if (!empty($arrayfields['state.nom']['checked'])) {
-			print "<td>".$obj->state_name."</td>\n";
+			print '<td class="tdoverflowmax100" title="'.dol_escape_htmltag($obj->state_name).'">'.dol_escape_htmltag($obj->state_name)."</td>\n";
 			if (!$i) {
 				$totalarray['nbfield']++;
 			}
@@ -2306,7 +2321,7 @@ while ($i < $imaxinloop) {
 			if (empty($typenArray)) {
 				$typenArray = $formcompany->typent_array(1);
 			}
-			print $typenArray[$obj->typent_code];
+			print $typenArray[$obj->typent_code]??'';
 			print '</td>';
 			if (!$i) {
 				$totalarray['nbfield']++;
@@ -2557,6 +2572,11 @@ while ($i < $imaxinloop) {
 			if (!$i) {
 				$totalarray['pos'][$totalarray['nbfield']] = 'total_margin';
 			}
+
+			if (!isset($totalarray['val']['total_margin'])) {
+				$totalarray['val']['total_margin'] = 0;
+			}
+
 			$totalarray['val']['total_margin'] += $marginInfo['total_margin'];
 		}
 
@@ -2669,7 +2689,7 @@ while ($i < $imaxinloop) {
 								$productstat_cachevirtual[$generic_commande->lines[$lig]->fk_product]['stock_reel'] = $generic_product->stock_theorique;
 							} else {
 								$generic_product->stock_reel = $productstat_cache[$generic_commande->lines[$lig]->fk_product]['stock_reel'];
-								$generic_product->stock_theorique = $productstat_cachevirtual[$generic_commande->lines[$lig]->fk_product]['stock_reel'] = $generic_product->stock_theorique;
+								$generic_product->stock_theorique = $productstat_cachevirtual[$generic_commande->lines[$lig]->fk_product]['stock_reel'];
 							}
 
 							if ($reliquat > $generic_product->stock_reel) {

+ 1 - 1
htdocs/commande/list_det.php

@@ -2104,7 +2104,7 @@ if ($resql) {
 							$productstat_cachevirtual[$obj->fk_product]['stock_reel'] = $generic_product->stock_theorique;
 						} else {
 							$generic_product->stock_reel = $productstat_cache[$obj->fk_product]['stock_reel'];
-							$generic_product->stock_theorique = $productstat_cachevirtual[$obj->fk_product]['stock_reel'] = $generic_product->stock_theorique;
+							$generic_product->stock_theorique = $productstat_cachevirtual[$obj->fk_product]['stock_reel'];
 						}
 
 						if ($reliquat > $generic_product->stock_reel) {

+ 30 - 14
htdocs/compta/accounting-files.php

@@ -1,10 +1,10 @@
 <?php
-/* Copyright (C) 2001-2006  Rodolphe Quiedeville <rodolphe@quiedeville.org>
- * Copyright (C) 2004-2019  Laurent Destailleur  <eldy@users.sourceforge.net>
- * Copyright (C) 2017       Pierre-Henry Favre   <support@atm-consulting.fr>
- * Copyright (C) 2020       Maxime DEMAREST      <maxime@indelog.fr>
- * Copyright (C) 2021       Gauthier VERDOL      <gauthier.verdol@atm-consulting.fr>
- * Copyright (C) 2022-2024  Alexandre Spangaro   <aspangaro@easya.solutions>
+/* Copyright (C) 2001-2006  Rodolphe Quiedeville        <rodolphe@quiedeville.org>
+ * Copyright (C) 2004-2019  Laurent Destailleur         <eldy@users.sourceforge.net>
+ * Copyright (C) 2017       Pierre-Henry Favre          <support@atm-consulting.fr>
+ * Copyright (C) 2020       Maxime DEMAREST             <maxime@indelog.fr>
+ * Copyright (C) 2021       Gauthier VERDOL             <gauthier.verdol@atm-consulting.fr>
+ * Copyright (C) 2022-2024  Alexandre Spangaro          <alexandre@inovea-conseil.com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -206,17 +206,33 @@ if (($action == 'searchfiles' || $action == 'dl')) {
 			}
 		}
 		// Expense reports
-		if (GETPOST('selectexpensereports') && !empty($listofchoices['selectexpensereports']['perms']) && empty($projectid)) {
+		if (GETPOST('selectexpensereports') && !empty($listofchoices['selectexpensereports']['perms'])) {
 			if (!empty($sql)) {
 				$sql .= " UNION ALL";
 			}
-			$sql .= " SELECT t.rowid as id, t.entity, t.ref, t.paid, t.total_ht, t.total_ttc, t.total_tva as total_vat,";
-			$sql .= " 0 as localtax1, 0 as localtax2, 0 as revenuestamp,";
-			$sql .= " t.multicurrency_code as currency, t.fk_user_author as fk_soc, t.date_fin as date, t.date_fin as date_due, 'ExpenseReport' as item, CONCAT(CONCAT(u.lastname, ' '), u.firstname) as thirdparty_name, '' as thirdparty_code, c.code as country_code, '' as vatnum, ".PAY_DEBIT." as sens";
-			$sql .= " FROM ".MAIN_DB_PREFIX."expensereport as t LEFT JOIN ".MAIN_DB_PREFIX."user as u ON u.rowid = t.fk_user_author LEFT JOIN ".MAIN_DB_PREFIX."c_country as c ON c.rowid = u.fk_country";
-			$sql .= " WHERE date_fin between  ".$wheretail;
-			$sql .= " AND t.entity IN (".$db->sanitize($entity == 1 ? '0,1' : $entity).')';
-			$sql .= " AND t.fk_statut <> ".ExpenseReport::STATUS_DRAFT;
+			// if project filter is used on an expense report,
+			// show only total_ht/total_tva/total_ttc of the line considered by the project
+			if (!empty($projectid)) {
+				$sql .= " SELECT t.rowid as id, t.entity, t.ref, t.paid, SUM(td.total_ht) as total_ht, SUM(td.total_ttc) as total_ttc, SUM(td.total_tva) as total_vat,";
+				$sql .= " 0 as localtax1, 0 as localtax2, 0 as revenuestamp,";
+				$sql .= " td.multicurrency_code as currency, t.fk_user_author as fk_soc, t.date_fin as date, t.date_fin as date_due, 'ExpenseReport' as item, CONCAT(CONCAT(u.lastname, ' '), u.firstname) as thirdparty_name, '' as thirdparty_code, c.code as country_code, '' as vatnum, " . PAY_DEBIT . " as sens";
+				$sql .= " FROM " . MAIN_DB_PREFIX . "expensereport as t";
+				$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "expensereport_det as td ON t.rowid = td.fk_expensereport";
+				$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "user as u ON u.rowid = t.fk_user_author";
+				$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_country as c ON c.rowid = u.fk_country";
+				$sql .= " WHERE date_fin between  " . $wheretail;
+				$sql .= " AND t.entity IN (" . $db->sanitize($entity == 1 ? '0,1' : $entity) . ')';
+				$sql .= " AND t.fk_statut <> " . ExpenseReport::STATUS_DRAFT;
+				$sql .= " AND fk_projet = ".((int) $projectid);
+			} else {
+				$sql .= " SELECT t.rowid as id, t.entity, t.ref, t.paid, t.total_ht, t.total_ttc, t.total_tva as total_vat,";
+				$sql .= " 0 as localtax1, 0 as localtax2, 0 as revenuestamp,";
+				$sql .= " t.multicurrency_code as currency, t.fk_user_author as fk_soc, t.date_fin as date, t.date_fin as date_due, 'ExpenseReport' as item, CONCAT(CONCAT(u.lastname, ' '), u.firstname) as thirdparty_name, '' as thirdparty_code, c.code as country_code, '' as vatnum, " . PAY_DEBIT . " as sens";
+				$sql .= " FROM " . MAIN_DB_PREFIX . "expensereport as t LEFT JOIN " . MAIN_DB_PREFIX . "user as u ON u.rowid = t.fk_user_author LEFT JOIN " . MAIN_DB_PREFIX . "c_country as c ON c.rowid = u.fk_country";
+				$sql .= " WHERE date_fin between  " . $wheretail;
+				$sql .= " AND t.entity IN (" . $db->sanitize($entity == 1 ? '0,1' : $entity) . ')';
+				$sql .= " AND t.fk_statut <> " . ExpenseReport::STATUS_DRAFT;
+			}
 		}
 		// Donations
 		if (GETPOST('selectdonations') && !empty($listofchoices['selectdonations']['perms'])) {

+ 19 - 13
htdocs/compta/bank/bankentries_list.php

@@ -145,9 +145,11 @@ if (($sortfield == 'b.datev' || $sortfield == 'b.datev,b.dateo,b.rowid')) {
 $hookmanager->initHooks(array('banktransactionlist', $contextpage));
 $extrafields = new ExtraFields($db);
 
+$extrafieldsobjectkey = 'bank';	// Used by extrafields_..._tpl.php
+
 // fetch optionals attributes and labels
-$extrafields->fetch_name_optionals_label('banktransaction');
-$search_array_options = $extrafields->getOptionalsFromPost('banktransaction', '', 'search_');
+$extrafields->fetch_name_optionals_label($extrafieldsobjectkey);
+$search_array_options = $extrafields->getOptionalsFromPost($extrafieldsobjectkey, '', 'search_');
 
 $arrayfields = array(
 	'b.rowid'=>array('label'=>$langs->trans("Ref"), 'checked'=>1,'position'=>10),
@@ -235,7 +237,7 @@ if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x'
 if (empty($reshook)) {
 	$objectclass = 'Account';
 	$objectlabel = 'BankTransaction';
-	$permissiontoread = !empty($user->rights->banque->lire);
+	$permissiontoread = $user->hasRight('banque', 'lire');
 	$permissiontodelete = $user->hasRight('banque', 'modifier');
 	$uploaddir = $conf->bank->dir_output;
 	include DOL_DOCUMENT_ROOT.'/core/actions_massactions.inc.php';
@@ -602,7 +604,7 @@ if (!empty($extrafields->attributes[$object->table_element]['label'])) {
 }
 // Add fields from hooks
 $parameters = array();
-$reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters); // Note that $action and $object may have been modified by hook
+$reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 $sql .= $hookmanager->resPrint;
 $sql .= " FROM ";
 if ($search_bid > 0) {
@@ -616,7 +618,7 @@ if (!empty($extrafields->attributes[$object->table_element]['label']) && is_arra
 
 // Add fields from hooks
 $parameters = array();
-$reshook = $hookmanager->executeHooks('printFieldListJoin', $parameters); // Note that $action and $object may have been modified by hook
+$reshook = $hookmanager->executeHooks('printFieldListJoin', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 $sql .= $hookmanager->resPrint;
 
 $sql .= " WHERE b.fk_account = ba.rowid";
@@ -708,7 +710,7 @@ include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php';
 
 // Add where from hooks
 $parameters = array();
-$reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters); // Note that $action and $object may have been modified by hook
+$reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 $sql .= $hookmanager->resPrint;
 
 $sql .= $db->order($sortfield, $sortorder);
@@ -1065,7 +1067,7 @@ if ($resql) {
 	}
 
 	$parameters = array();
-	$reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters); // Note that $action and $object may have been modified by hook
+	$reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 	if (empty($reshook)) {
 		$moreforfilter .= $hookmanager->resPrint;
 	} else {
@@ -1173,8 +1175,10 @@ if ($resql) {
 	}
 	// Bordereau
 	if (!empty($arrayfields['b.fk_bordereau']['checked'])) {
-		print '<td class="liste_titre" align="center"><input type="text" class="flat" name="search_fk_bordereau" value="'.dol_escape_htmltag($search_fk_bordereau).'" size="3"></td>';
+		print '<td class="liste_titre center"><input type="text" class="flat" name="search_fk_bordereau" value="'.dol_escape_htmltag($search_fk_bordereau).'" size="3"></td>';
 	}
+	// Extra fields
+	include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_input.tpl.php';
 	// Action edit/delete and select
 	print '<td class="nowraponall" align="center"></td>';
 
@@ -1264,7 +1268,7 @@ if ($resql) {
 	include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_title.tpl.php';
 	// Hook fields
 	$parameters = array('arrayfields'=>$arrayfields, 'param'=>$param, 'sortfield'=>$sortfield, 'sortorder'=>$sortorder, 'totalarray'=>&$totalarray);
-	$reshook = $hookmanager->executeHooks('printFieldListTitle', $parameters); // Note that $action and $object may have been modified by hook
+	$reshook = $hookmanager->executeHooks('printFieldListTitle', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 	print $hookmanager->resPrint;
 	// Action edit/delete and select
 	print '<td class="nowraponall" align="center"></td>';
@@ -1353,8 +1357,8 @@ if ($resql) {
 						}
 					}
 				}
-				// Extra fields
-				$element = 'banktransaction';
+				// Extra
+				$element = $extrafieldsobjectkey;
 				if (!empty($extrafields->attributes[$element]['label']) && is_array($extrafields->attributes[$element]['label']) && count($extrafields->attributes[$element]['label'])) {
 					foreach ($extrafields->attributes[$element]['label'] as $key => $val) {
 						if (!empty($arrayfields["ef.".$key]['checked'])) {
@@ -1826,9 +1830,11 @@ if ($resql) {
 			}
 		}
 
+		// Extra fields
+		include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_print_fields.tpl.php';
 		// Fields from hook
-		$parameters=array('arrayfields'=>$arrayfields, 'obj'=>$objp, 'i'=>$i, 'totalarray'=>&$totalarray);
-		$reshook=$hookmanager->executeHooks('printFieldListValue', $parameters, $objecttmp);    // Note that $action and $objecttmpect may have been modified by hook
+		$parameters=array('arrayfields'=>$arrayfields, 'object'=>$object, 'obj'=>$objp, 'i'=>$i, 'totalarray'=>&$totalarray);
+		$reshook=$hookmanager->executeHooks('printFieldListValue', $parameters, $object, $action);    // Note that $action and $objecttmpect may have been modified by hook
 		print $hookmanager->resPrint;
 
 		// Action edit/delete and select

+ 3 - 2
htdocs/compta/bank/card.php

@@ -82,7 +82,6 @@ if (GETPOST("id", 'int') || GETPOST("ref")) {
 
 $result = restrictedArea($user, 'banque', $id, 'bank_account&bank_account', '', '', $fieldid);
 
-
 /*
  * Actions
  */
@@ -142,7 +141,8 @@ if (empty($reshook)) {
 		$object->pti_in_ctti = empty(GETPOST("pti_in_ctti")) ? 0 : 1;
 
 		$object->proprio = trim(GETPOST("proprio", 'alphanohtml'));
-		$object->domiciliation = trim(GETPOST("domiciliation", "alphanohtml"));
+		$object->address = trim(GETPOST("account_address", "alphanohtml"));
+		$object->domiciliation = $object->address;	// deprecated
 		$object->owner_address = trim(GETPOST("owner_address", 'alphanohtml'));
 		$object->owner_zip = trim(GETPOST("owner_zip", 'alphanohtml'));
 		$object->owner_town = trim(GETPOST("owner_town", 'alphanohtml'));
@@ -238,6 +238,7 @@ if (empty($reshook)) {
 		$object = new Account($db);
 		$object->fetch(GETPOST("id", 'int'));
 
+		$object->oldref = $object->ref;
 		$object->ref = dol_string_nospecial(trim(GETPOST('ref', 'alpha')));
 		$object->label = trim(GETPOST("label", 'alphanohtml'));
 		$object->courant = GETPOST("type");

+ 34 - 2
htdocs/compta/bank/class/account.class.php

@@ -278,6 +278,11 @@ class Account extends CommonObject
 	 */
 	public $ics_transfer;
 
+	/**
+	 * @var string The previous ref in case of rename on update to rename attachment folders
+	 */
+	public $oldref;
+
 
 	/**
 	 *  'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
@@ -712,6 +717,11 @@ class Account extends CommonObject
 			$balance = 0;
 		}
 
+		if (empty($this->address && !empty($this->domiciliation))) {
+			dol_syslog(get_class($this)."::create domiciliation is deprecated use address", LOG_NOTICE);
+			$this->address = $this->domiciliation;
+		}
+
 		// Load the library to validate/check a BAN account
 		require_once DOL_DOCUMENT_ROOT.'/core/lib/bank.lib.php';
 
@@ -763,7 +773,7 @@ class Account extends CommonObject
 		$sql .= ", '".$this->db->escape($this->cle_rib)."'";
 		$sql .= ", '".$this->db->escape($this->bic)."'";
 		$sql .= ", '".$this->db->escape($this->iban)."'";
-		$sql .= ", '".$this->db->escape($this->domiciliation)."'";
+		$sql .= ", '".$this->db->escape($this->address)."'";
 		$sql .= ", ".((int) $this->pti_in_ctti);
 		$sql .= ", '".$this->db->escape($this->proprio)."'";
 		$sql .= ", '".$this->db->escape($this->owner_address)."'";
@@ -889,7 +899,7 @@ class Account extends CommonObject
 		$sql .= ",cle_rib='".$this->db->escape($this->cle_rib)."'";
 		$sql .= ",bic='".$this->db->escape($this->bic)."'";
 		$sql .= ",iban_prefix = '".$this->db->escape($this->iban)."'";
-		$sql .= ",domiciliation='".$this->db->escape($this->domiciliation)."'";
+		$sql .= ",domiciliation='".$this->db->escape($this->address)."'";
 		$sql .= ",pti_in_ctti=".((int) $this->pti_in_ctti);
 		$sql .= ",proprio = '".$this->db->escape($this->proprio)."'";
 		$sql .= ",owner_address = '".$this->db->escape($this->owner_address)."'";
@@ -921,6 +931,28 @@ class Account extends CommonObject
 				}
 			}
 
+			if (!$error && !empty($this->oldref) && $this->oldref !== $this->ref) {
+				$sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'bank/".$this->db->escape($this->ref)."'";
+				$sql .= " WHERE filepath = 'bank/".$this->db->escape($this->oldref)."' and src_object_type='bank_account' and entity = ".((int) $conf->entity);
+				$resql = $this->db->query($sql);
+				if (!$resql) {
+					$error++;
+					$this->error = $this->db->lasterror();
+				}
+
+				// We rename directory in order not to lose the attachments
+				$oldref = dol_sanitizeFileName($this->oldref);
+				$newref = dol_sanitizeFileName($this->ref);
+				$dirsource = $conf->bank->dir_output.'/'.$oldref;
+				$dirdest = $conf->bank->dir_output.'/'.$newref;
+				if (file_exists($dirsource)) {
+					dol_syslog(get_class($this)."::update rename dir ".$dirsource." into ".$dirdest, LOG_DEBUG);
+					if (@rename($dirsource, $dirdest)) {
+						dol_syslog("Rename ok", LOG_DEBUG);
+					}
+				}
+			}
+
 			if (!$error && !$notrigger) {
 				// Call trigger
 				$result = $this->call_trigger('BANKACCOUNT_MODIFY', $user);

+ 3 - 0
htdocs/compta/bank/class/paymentvarious.class.php

@@ -630,6 +630,7 @@ class PaymentVarious extends CommonObject
 		// phpcs:enable
 		global $langs;
 
+		/*
 		if (empty($status)) {
 			$status = 0;
 		}
@@ -646,6 +647,8 @@ class PaymentVarious extends CommonObject
 		$statusType = 'status'.$status;
 
 		return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
+		*/
+		return '';
 	}
 
 

+ 2 - 1
htdocs/compta/bank/line.php

@@ -164,7 +164,8 @@ if ($user->hasRight('banque', 'modifier') && $action == "update") {
 		$sql .= " SET ";
 		// Always opened
 		if (GETPOSTISSET('value')) {
-			$sql .= " fk_type='".$db->escape(GETPOST('value'))."',";
+			$type = GETPOST('value');
+			$sql .= " fk_type='".$db->escape(empty($type) && $object->fk_type == 'SOLD' ? 'SOLD' : $type)."',";
 		}
 		if (GETPOSTISSET('num_chq')) {
 			$sql .= " num_chq='".$db->escape(GETPOST("num_chq"))."',";

+ 7 - 3
htdocs/compta/facture/card-rec.php

@@ -658,12 +658,14 @@ if (empty($reshook)) {
 				$info_bits |= 0x01;
 			}
 
+			$fk_parent_line = GETPOST('fk_parent_line', 'int');
+
 			if ($usercanproductignorepricemin && (!empty($price_min) && (price2num($pu_ht) * (1 - price2num($remise_percent) / 100) < price2num($price_min)))) {
 				$mesg = $langs->trans("CantBeLessThanMinPrice", price(price2num($price_min, 'MU'), 0, $langs, 0, 0, - 1, $conf->currency));
 				setEventMessages($mesg, null, 'errors');
 			} else {
 				// Insert line
-				$result = $object->addline($desc, $pu_ht, $qty, $tva_tx, $localtax1_tx, $localtax2_tx, $idprod, $remise_percent, $price_base_type, $info_bits, '', $pu_ttc, $type, -1, $special_code, $label, $fk_unit, 0, $date_start_fill, $date_end_fill, $fournprice, $buyingprice);
+				$result = $object->addline($desc, $pu_ht, $qty, $tva_tx, $localtax1_tx, $localtax2_tx, $idprod, $remise_percent, $price_base_type, $info_bits, '', $pu_ttc, $type, -1, $special_code, $label, $fk_unit, 0, $date_start_fill, $date_end_fill, $fournprice, $buyingprice, $fk_parent_line);
 
 				if ($result > 0) {
 					// Define output language and generate document
@@ -849,6 +851,7 @@ if (empty($reshook)) {
 
 		$date_start_fill = GETPOST('date_start_fill', 'int');
 		$date_end_fill = GETPOST('date_end_fill', 'int');
+		$fk_parent_line = GETPOST('fk_parent_line', 'int');
 
 		// Update line
 		if (!$error) {
@@ -876,7 +879,8 @@ if (empty($reshook)) {
 				$date_start_fill,
 				$date_end_fill,
 				$fournprice,
-				$buyingprice
+				$buyingprice,
+				$fk_parent_line
 			);
 
 			if ($result >= 0) {
@@ -1224,7 +1228,7 @@ if ($action == 'create') {
 		}
 
 		// Call Hook formConfirm
-		$parameters = array('formConfirm' => $formconfirm);
+		$parameters = array('formConfirm' => $formconfirm, 'lineid' => $lineid);
 		$reshook = $hookmanager->executeHooks('formConfirm', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 		if (empty($reshook)) {
 			$formconfirm .= $hookmanager->resPrint;

+ 5 - 1
htdocs/compta/facture/card.php

@@ -3943,8 +3943,9 @@ if ($action == 'create') {
 		include_once DOL_DOCUMENT_ROOT.'/core/modules/facture/modules_facture.php';
 		$liste = ModelePDFFactures::liste_modeles($db);
 		if (getDolGlobalString('INVOICE_USE_DEFAULT_DOCUMENT')) {
+			$type = GETPOSTISSET('type') ? GETPOSTINT('type') : $object->type;
 			// Hidden conf
-			$paramkey = 'FACTURE_ADDON_PDF_'.$object->type;
+			$paramkey = 'FACTURE_ADDON_PDF_'.$type;
 			$preselected = getDolGlobalString($paramkey, getDolGlobalString('FACTURE_ADDON_PDF'));
 		} else {
 			$preselected = getDolGlobalString('FACTURE_ADDON_PDF');
@@ -4556,6 +4557,9 @@ if ($action == 'create') {
 	$morehtmlref .= '</div>';
 
 	$object->totalpaid = $totalpaid; // To give a chance to dol_banner_tab to use already paid amount to show correct status
+	$object->totalcreditnotes = $totalcreditnotes;
+	$object->totaldeposits = $totaldeposits;
+	$object->remaintopay = price2num($object->total_ttc - $object->totalpaid - $object->totalcreditnotes - $object->totaldeposits, 'MT');
 
 	dol_banner_tab($object, 'ref', $linkback, 1, 'ref', 'ref', $morehtmlref, '', 0, '', '');
 

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

@@ -1358,6 +1358,14 @@ class FactureRec extends CommonInvoice
 						$facture->multicurrency_tx = $facturerec->multicurrency_tx;
 					}
 
+					if (isset($facture->array_options) && isset($facturerec->array_options)) {
+						foreach ($facturerec->array_options as $key => $value) {
+							if (isset($facture->array_options[$key])) {
+								$facture->array_options[$key] = $value;
+							}
+						}
+					}
+
 					$invoiceidgenerated = $facture->create($user);
 					if ($invoiceidgenerated <= 0) {
 						$this->errors = $facture->errors;

+ 3 - 3
htdocs/compta/facture/class/facture.class.php

@@ -463,8 +463,8 @@ class Facture extends CommonInvoice
 		$this->ref_client = trim($this->ref_client);
 
 		$this->note = (isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated
-		$this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note_private));
-		$this->note_public = trim($this->note_public);
+		$this->note_private = (isset($this->note_private) ? trim($this->note_private) : '');
+		$this->note_public = (isset($this->note_public) ? trim($this->note_public) : '');
 		if (!$this->cond_reglement_id) {
 			$this->cond_reglement_id = 0;
 		}
@@ -4051,7 +4051,7 @@ class Facture extends CommonInvoice
 	 *  @param     	double		$remise_percent  	Percentage discount of the line
 	 *  @param     	int		    $date_start      	Date de debut de validite du service
 	 *  @param     	int		    $date_end        	Date de fin de validite du service
-	 *  @param     	double		$txtva          	VAT Rate (Can be '8.5', '8.5 (ABC)')
+	 *  @param     	double|string	$txtva          	VAT Rate (Can be '8.5', '8.5 (ABC)')
 	 * 	@param		double		$txlocaltax1		Local tax 1 rate
 	 *  @param		double		$txlocaltax2		Local tax 2 rate
 	 * 	@param     	string		$price_base_type 	HT or TTC

+ 2 - 2
htdocs/compta/facture/contact.php

@@ -57,11 +57,11 @@ if ($id > 0 || !empty($ref)) {
 	$ret = $object->fetch($id, $ref, '', '', (getDolGlobalString('INVOICE_USE_SITUATION') ? $conf->global->INVOICE_USE_SITUATION : 0));
 }
 
-$result = restrictedArea($user, 'facture', $object->id);
 $hookmanager->initHooks(array('invoicecontactcard', 'globalcard'));
 
-$usercancreate = $user->hasRight("facture", "creer");
+$result = restrictedArea($user, 'facture', $object->id);
 
+$usercancreate = $user->hasRight("facture", "creer");
 
 /*
  * Actions

+ 1 - 0
htdocs/compta/facture/document.php

@@ -79,6 +79,7 @@ $permissiontoadd = $user->hasRight('facture', 'creer');
 if ($user->socid) {
 	$socid = $user->socid;
 }
+$hookmanager->initHooks(array('invoicedocument', 'globalcard'));
 $result = restrictedArea($user, 'facture', $object->id, '');
 
 $usercancreate = $user->hasRight("facture", "creer");

+ 13 - 10
htdocs/compta/facture/list.php

@@ -15,6 +15,7 @@
  * Copyright (C) 2018      Charlene Benke        <charlie@patas-monkey.com>
  * Copyright (C) 2019-2021 Alexandre Spangaro    <aspangaro@open-dsi.fr>
  * Copyright (C) 2023		Nick Fragoulis
+ * Copyright (C) 2024		William Mead		<william.mead@manchenumerique.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
@@ -147,8 +148,8 @@ $search_fac_rec_source_title = GETPOST("search_fac_rec_source_title", 'alpha');
 $search_btn = GETPOST('button_search', 'alpha');
 $search_remove_btn = GETPOST('button_removefilter', 'alpha');
 
-$search_late = GETPOST('search_late');
-if ($search_late == 'late') {
+$search_option = GETPOST('search_option');
+if ($search_option == 'late') {
 	$search_status = '1';
 }
 $filtre = GETPOST('filtre', 'alpha');
@@ -397,7 +398,7 @@ if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter',
 	$toselect = array();
 	$search_array_options = array();
 	$search_categ_cus = 0;
-	$search_late = '';
+	$search_option = '';
 	$socid = 0;
 }
 
@@ -440,8 +441,8 @@ if ($action == 'makepayment_confirm' && $user->hasRight('facture', 'paiement'))
 						$paiementAmount = $facture->getSommePaiement();
 						$totalcreditnotes = $facture->getSumCreditNotesUsed();
 						$totaldeposits = $facture->getSumDepositsUsed();
-						$totalpay = $paiementAmount + $totalcreditnotes + $totaldeposits;
-						$remaintopay = price2num($facture->total_ttc - $totalpay);
+						$totalallpayments = $paiementAmount + $totalcreditnotes + $totaldeposits;
+						$remaintopay = price2num($facture->total_ttc - $totalallpayments);
 						if ($remaintopay != 0) {
 							$resultBank = $facture->setBankAccount($bankid);
 							if ($resultBank < 0) {
@@ -853,7 +854,7 @@ if ($search_datelimit_start) {
 if ($search_datelimit_end) {
 	$sql .= " AND f.date_lim_reglement <= '".$db->idate($search_datelimit_end)."'";
 }
-if ($search_late == 'late') {
+if ($search_option == 'late') {
 	$sql .= " AND f.date_lim_reglement < '".$db->idate(dol_now() - $conf->facture->client->warning_delay)."'";
 }
 /*if ($search_sale > 0) {
@@ -1172,8 +1173,8 @@ if ($search_pos_source) {
 if ($show_files) {
 	$param .= '&show_files='.urlencode($show_files);
 }
-if ($search_late) {
-	$param .= "&search_late=".urlencode($search_late);
+if ($search_option) {
+	$param .= "&search_option=".urlencode($search_option);
 }
 if ($optioncss != '') {
 	$param .= '&optioncss='.urlencode($optioncss);
@@ -1313,7 +1314,7 @@ if (isModEnabled('categorie') && $user->hasRight("categorie", "lire")) {
 }
 // alert on due date
 $moreforfilter .= '<div class="divsearchfield">';
-$moreforfilter .= $langs->trans('Alert').' <input type="checkbox" name="search_late" value="late"'.($search_late == 'late' ? ' checked' : '').'>';
+$moreforfilter .= $langs->trans('Alert').' <input type="checkbox" name="search_option" value="late"'.($search_option == 'late' ? ' checked' : '').'>';
 $moreforfilter .= '</div>';
 
 $parameters = array();
@@ -2100,7 +2101,9 @@ if ($num > 0) {
 				}
 
 				$filename = dol_sanitizeFileName($obj->ref);
-				$filedir = $conf->facture->dir_output.'/'.dol_sanitizeFileName($obj->ref);
+				$filepath = $conf->facture->multidir_output[$obj->entity] ?? $conf->facture->dir_output;
+				$filedir = $filepath . '/' . $filename;
+
 				$urlsource = $_SERVER['PHP_SELF'].'?id='.$obj->id;
 				print $formfile->getDocumentsLink($facturestatic->element, $filename, $filedir);
 				print '</td>';

+ 13 - 3
htdocs/compta/facture/tpl/linkedobjectblock.tpl.php

@@ -86,11 +86,21 @@ foreach ($linkedObjectBlock as $key => $objectlink) {
 
 	print '</td>';
 	print '<td class="linkedcol-statut right">';
+	$totalallpayments = 0;
+	$totalcalculated = false;
 	if (method_exists($objectlink, 'getSommePaiement')) {
-		print $objectlink->getLibStatut(3, $objectlink->getSommePaiement());
-	} else {
-		print $objectlink->getLibStatut(3);
+		$totalcalculated = true;
+		$totalallpayments += $objectlink->getSommePaiement();
 	}
+	if (method_exists($objectlink, 'getSumDepositsUsed')) {
+		$totalcalculated = true;
+		$totalallpayments += $objectlink->getSumDepositsUsed();
+	}
+	if (method_exists($objectlink, 'getSumCreditNotesUsed')) {
+		$totalcalculated = true;
+		$totalallpayments += $objectlink->getSumCreditNotesUsed();
+	}
+	print $objectlink->getLibStatut(3, ($totalcalculated ? $totalallpayments : -1));
 	print '</td>';
 	print '<td class="linkedcol-action right"><a class="reposition" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=dellink&token='.newToken().'&dellinkid='.$key.'">'.img_picto($langs->transnoentitiesnoconv("RemoveLink"), 'unlink').'</a></td>';
 	print "</tr>\n";

+ 6 - 1
htdocs/compta/index.php

@@ -215,6 +215,9 @@ if (isModEnabled('facture') && $user->hasRight('facture', 'lire')) {
 				$thirdpartystatic->code_compta = $obj->code_compta;
 				//$thirdpartystatic->code_compta_fournisseur = $obj->code_compta_fournisseur;
 
+				$totalallpayments = $tmpinvoice->getSommePaiement(0);
+				$totalallpayments += $tmpinvoice->getSumCreditNotesUsed(0);
+				$totalallpayments += $tmpinvoice->getSumDepositsUsed(0);
 				print '<tr class="oddeven">';
 				print '<td class="nowrap">';
 
@@ -247,7 +250,7 @@ if (isModEnabled('facture') && $user->hasRight('facture', 'lire')) {
 
 				print '<td class="right" title="'.dol_escape_htmltag($langs->trans("DateModificationShort").' : '.dol_print_date($db->jdate($obj->tms), 'dayhour', 'tzuserrel')).'">'.dol_print_date($db->jdate($obj->tms), 'day', 'tzuserrel').'</td>';
 
-				print '<td>'.$tmpinvoice->getLibStatut(3, $obj->am).'</td>';
+				print '<td>'.$tmpinvoice->getLibStatut(3, $totalallpayments).'</td>';
 
 				print '</tr>';
 
@@ -377,6 +380,8 @@ if ((isModEnabled('fournisseur') && !getDolGlobalString('MAIN_USE_NEW_SUPPLIERMO
 				print '<td class="nowrap right"><span class="amount">'.price($obj->total_ttc).'</span></td>';
 				print '<td class="right" title="'.dol_escape_htmltag($langs->trans("DateModificationShort").' : '.dol_print_date($db->jdate($obj->tms), 'dayhour', 'tzuserrel')).'">'.dol_print_date($db->jdate($obj->tms), 'day', 'tzuserrel').'</td>';
 				$alreadypaid = $facstatic->getSommePaiement();
+				$alreadypaid += $facstatic->getSumCreditNotesUsed();
+				$alreadypaid += $facstatic->getSumDepositsUsed();
 				print '<td>'.$facstatic->getLibStatut(3, $alreadypaid).'</td>';
 				print '</tr>';
 				$total_ht += $obj->total_ht;

+ 1 - 1
htdocs/compta/paiement/class/paiement.class.php

@@ -1276,7 +1276,7 @@ class Paiement extends CommonObject
 				$facturestatic = new Facture($this->db);
 				foreach ($arraybill as $billid) {
 					$facturestatic->fetch($billid);
-					$label .= '<br> '.$facturestatic->getNomUrl(1, '', 0, 0, '', 1).' '.$facturestatic->getLibStatut(2, 1);
+					$label .= '<br> '.$facturestatic->getNomUrl(1, '', 0, 0, '', 1).' '.$facturestatic->getLibStatut(2, -1);
 				}
 			}
 		}

+ 7 - 4
htdocs/compta/prelevement/index.php

@@ -101,7 +101,7 @@ print '</span></td></tr></table></div><br>';
 /*
  * Invoices waiting for withdraw
  */
-$sql = "SELECT f.ref, f.rowid, f.total_ttc, f.fk_statut, f.paye, f.type,";
+$sql = "SELECT f.ref, f.rowid, f.total_ttc, f.fk_statut as status, f.paye, f.type,";
 $sql .= " pfd.date_demande, pfd.amount,";
 $sql .= " s.nom as name, s.email, s.rowid as socid, s.tva_intra, s.siren as idprof1, s.siret as idprof2, s.ape as idprof3, s.idprof4, s.idprof5, s.idprof6";
 $sql .= " FROM ".MAIN_DB_PREFIX."facture as f,";
@@ -141,10 +141,13 @@ if ($resql) {
 
 			$invoicestatic->id = $obj->rowid;
 			$invoicestatic->ref = $obj->ref;
-			$invoicestatic->statut = $obj->fk_statut;
+			$invoicestatic->statut = $obj->status;
+			$invoicestatic->status = $obj->status;
 			$invoicestatic->paye = $obj->paye;
 			$invoicestatic->type = $obj->type;
-			$alreadypayed = $invoicestatic->getSommePaiement();
+			$totalallpayments = $invoicestatic->getSommePaiement(0);
+			$totalallpayments += $invoicestatic->getSumCreditNotesUsed(0);
+			$totalallpayments += $invoicestatic->getSumDepositsUsed(0);
 
 			$thirdpartystatic->id = $obj->socid;
 			$thirdpartystatic->name = $obj->name;
@@ -177,7 +180,7 @@ if ($resql) {
 			print '</td>';
 
 			print '<td class="right">';
-			print $invoicestatic->getLibStatut(3, $alreadypayed);
+			print $invoicestatic->getLibStatut(3, $totalallpayments);
 			print '</td>';
 			print '</tr>';
 			$i++;

+ 4 - 2
htdocs/compta/recap-compta.php

@@ -159,7 +159,9 @@ if ($id > 0) {
 					print $fac->error."<br>";
 					continue;
 				}
-				$totalpaid = $fac->getSommePaiement();
+				$alreadypaid = $fac->getSommePaiement();
+				$alreadypaid += $fac->getSumDepositsUsed();
+				$alreadypaid += $fac->getSumCreditNotesUsed();
 
 				$userstatic->id = $objf->userid;
 				$userstatic->login = $objf->login;
@@ -169,7 +171,7 @@ if ($id > 0) {
 					'date' => $fac->date,
 					'datefieldforsort' => $fac->date.'-'.$fac->ref,
 					'link' => $fac->getNomUrl(1),
-					'status' => $fac->getLibStatut(2, $totalpaid),
+					'status' => $fac->getLibStatut(2, $alreadypaid),
 					'amount' => $fac->total_ttc,
 					'author' => $userstatic->getLoginUrl(1)
 				);

+ 1 - 1
htdocs/compta/stats/cabyprodserv.php

@@ -503,7 +503,7 @@ if ($modecompta == 'CREANCES-DETTES') {
 
 			// Quantity
 			print '<td class="right">';
-			print $qty[$key];
+			print price($qty[$key], 1, $langs, 0, 0);
 			print '</td>';
 
 			// Percent;

+ 2 - 1
htdocs/compta/tva/list.php

@@ -724,7 +724,8 @@ while ($i < $imaxinloop) {
 		}
 
 		if (!empty($arrayfields['t.status']['checked'])) {
-			print '<td class="nowrap right">' . $tva_static->getLibStatut(5, $obj->alreadypayed) . '</td>';
+			$totalallpayments = $obj->alreadypayed;
+			print '<td class="nowrap right">' . $tva_static->getLibStatut(5, $totalallpayments) . '</td>';
 			if (!$i) {
 				$totalarray['nbfield']++;
 			}

+ 4 - 2
htdocs/contrat/card.php

@@ -812,6 +812,8 @@ if (empty($reshook)) {
 			$result = $objectline->update($user);
 			if ($result < 0) {
 				$error++;
+				$action = 'editline';
+				$_GET['rowid'] = GETPOST('elrowid');
 				setEventMessages($objectline->error, $objectline->errors, 'errors');
 			}
 		}
@@ -1730,7 +1732,7 @@ if ($action == 'create') {
 						$line = new ContratLigne($db);
 						$line->id = $objp->rowid;
 						$line->fetch_optionals();
-						print $line->showOptionals($extrafields, 'view', array('class'=>'oddeven', 'style'=>$moreparam, 'colspan'=>$colspan), '', '', 1);
+						print $line->showOptionals($extrafields, 'view', array('class'=>'oddeven', 'style'=>$moreparam, 'colspan'=>$colspan, 'tdclass' => 'notitlefieldcreate'), '', '', 1);
 					}
 				} else {
 					// Line in mode update
@@ -1834,7 +1836,7 @@ if ($action == 'create') {
 						$line = new ContratLigne($db);
 						$line->id = $objp->rowid;
 						$line->fetch_optionals();
-						print $line->showOptionals($extrafields, 'edit', array('style'=>'class="oddeven"', 'colspan'=>$colspan), '', '', 1);
+						print $line->showOptionals($extrafields, 'edit', array('style'=>'class="oddeven"', 'colspan'=>$colspan, 'tdclass' => 'notitlefieldcreate'), '', '', 1);
 					}
 				}
 

+ 4 - 4
htdocs/contrat/services_list.php

@@ -78,22 +78,22 @@ $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'co
 $opouvertureprevuemonth = GETPOST('opouvertureprevuemonth');
 $opouvertureprevueday = GETPOST('opouvertureprevueday');
 $opouvertureprevueyear = GETPOST('opouvertureprevueyear');
-$filter_opouvertureprevue = GETPOST('filter_opouvertureprevue');
+$filter_opouvertureprevue = GETPOST('filter_opouvertureprevue', 'alphawithlgt');
 
 $op1month = GETPOST('op1month', 'int');
 $op1day = GETPOST('op1day', 'int');
 $op1year = GETPOST('op1year', 'int');
-$filter_op1 = GETPOST('filter_op1', 'alpha');
+$filter_op1 = GETPOST('filter_op1', 'alphawithlgt');
 
 $op2month = GETPOST('op2month', 'int');
 $op2day = GETPOST('op2day', 'int');
 $op2year = GETPOST('op2year', 'int');
-$filter_op2 = GETPOST('filter_op2', 'alpha');
+$filter_op2 = GETPOST('filter_op2', 'alphawithlgt');
 
 $opcloturemonth = GETPOST('opcloturemonth', 'int');
 $opclotureday = GETPOST('opclotureday', 'int');
 $opclotureyear = GETPOST('opclotureyear', 'int');
-$filter_opcloture = GETPOST('filter_opcloture', 'alpha');
+$filter_opcloture = GETPOST('filter_opcloture', 'alphalgt');
 
 
 // Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context

+ 5 - 0
htdocs/core/actions_massactions.inc.php

@@ -487,6 +487,11 @@ if (!$error && $massaction == 'confirm_presend') {
 					$substitutionarray['__EMAIL__'] = $thirdparty->email;
 					$substitutionarray['__CHECK_READ__'] = '<img src="'.DOL_MAIN_URL_ROOT.'/public/emailing/mailing-read.php?tag=undefined&securitykey='.dol_hash(getDolGlobalString('MAILING_EMAIL_UNSUBSCRIBE_KEY')."-undefined", 'md5').'" width="1" height="1" style="width:1px;height:1px" border="0"/>';
 
+					if ($oneemailperrecipient) {
+						$substitutionarray['__ONLINE_PAYMENT_URL__'] = '';
+						$substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = '';
+					}
+
 					$parameters = array('mode'=>'formemail');
 
 					if (!empty($listofobjectthirdparties)) {

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

@@ -322,7 +322,7 @@ if (($action == 'send' || $action == 'relance') && !GETPOST('addfile') && !GETPO
 			// <img alt="" src="'.$urlwithroot.'viewimage.php?modulepart=medias&amp;entity=1&amp;file=image/ldestailleur_166x166.jpg" style="height:166px; width:166px" />
 			$message = preg_replace('/(<img.*src=")[^\"]*viewimage\.php([^\"]*)modulepart=medias([^\"]*)file=([^\"]*)("[^\/]*\/>)/', '\1'.$urlwithroot.'/viewimage.php\2modulepart=medias\3file=\4\5', $message);
 
-			$sendtobcc = GETPOST('sendtoccc');
+			$sendtobcc = GETPOST('sendtoccc', 'alphawithlgt');
 			// Autocomplete the $sendtobcc
 			// $autocopy can be MAIN_MAIL_AUTOCOPY_PROPOSAL_TO, MAIN_MAIL_AUTOCOPY_ORDER_TO, MAIN_MAIL_AUTOCOPY_INVOICE_TO, MAIN_MAIL_AUTOCOPY_SUPPLIER_PROPOSAL_TO...
 			if (!empty($autocopy)) {

+ 1 - 1
htdocs/core/boxes/box_actions.php

@@ -106,7 +106,7 @@ class box_actions extends ModeleBoxes
 				$sql .= " AND s.rowid = ".((int) $user->socid);
 			}
 			if (!$user->hasRight('agenda', 'allactions', 'read')) {
-				$sql .= " AND (a.fk_user_author = ".((int) $user->id)." OR a.fk_user_action = ".((int) $user->id)." OR a.fk_user_done = ".((int) $user->id).")";
+				$sql .= " AND (a.fk_user_author = ".((int) $user->id)." OR a.fk_user_action = ".((int) $user->id).")";
 			}
 			$sql .= " ORDER BY a.datep ASC";
 			$sql .= $this->db->plimit($max, 0);

+ 6 - 5
htdocs/core/boxes/box_factures_imp.php

@@ -1,8 +1,9 @@
 <?php
-/* Copyright (C) 2003-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
- * Copyright (C) 2004-2007 Laurent Destailleur  <eldy@users.sourceforge.net>
- * Copyright (C) 2005-2009 Regis Houssin        <regis.houssin@inodbox.com>
- * Copyright (C) 2015-2019 Frederic France      <frederic.france@netlogic.fr>
+/* Copyright (C) 2003-2007	Rodolphe Quiedeville		<rodolphe@quiedeville.org>
+ * Copyright (C) 2004-2007	Laurent Destailleur			<eldy@users.sourceforge.net>
+ * Copyright (C) 2005-2009	Regis Houssin				<regis.houssin@inodbox.com>
+ * Copyright (C) 2015-2019	Frederic France				<frederic.france@netlogic.fr>
+ * Copyright (C) 2024		Alexandre Spangaro			<alexandre@inovea-conseil.com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -126,7 +127,7 @@ class box_factures_imp extends ModeleBoxes
 			}
 			$sql3 = " GROUP BY s.rowid, s.nom, s.name_alias, s.code_client, s.client, s.logo, s.email, s.entity, s.tva_intra, s.siren, s.siret, s.ape, s.idprof4, s.idprof5, s.idprof6,";
 			if (getDolGlobalString('MAIN_COMPANY_PERENTITY_SHARED')) {
-				$sql3 .= " spe.accountancy_code_customer as code_compta,";
+				$sql3 .= " spe.accountancy_code_customer,";
 			} else {
 				$sql3 .= " s.code_compta,";
 			}

+ 4 - 4
htdocs/core/class/CMailFile.class.php

@@ -585,7 +585,7 @@ class CMailFile
 
 			if (!empty($this->errors_to)) {
 				try {
-					$headers->addTextHeader('Errors-To', $this->getArrayAddress($this->errors_to));
+					$headers->addTextHeader('Errors-To', $this->getValidAddress($this->errors_to, 0));
 				} catch (Exception $e) {
 					$this->errors[] = $e->getMessage();
 				}
@@ -652,7 +652,6 @@ class CMailFile
 					$this->errors[] = $e->getMessage();
 				}
 			}
-			//if (!empty($this->errors_to)) $this->message->setErrorsTo($this->getArrayAddress($this->errors_to));
 			if (isset($this->deliveryreceipt) && $this->deliveryreceipt == 1) {
 				try {
 					$this->message->setReadReceiptTo($this->getArrayAddress($this->addr_from));
@@ -1208,9 +1207,10 @@ class CMailFile
 				$res = true;
 				if (!empty($this->error) || !empty($this->errors) || !$result) {
 					if (!empty($failedRecipients)) {
-						$this->errors[] = 'Transport failed for the following addresses: "' . join('", "', $failedRecipients) . '".';
+						$this->error = 'Transport failed for the following addresses: "' . join('", "', $failedRecipients) . '".';
+						$this->errors[] = $this->error;
 					}
-					dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
+					dol_syslog("CMailFile::sendfile: mail end error=". join(' ', $this->errors), LOG_ERR);
 					$res = false;
 
 					if (getDolGlobalString('MAIN_MAIL_DEBUG')) {

+ 8 - 1
htdocs/core/class/commondocgenerator.class.php

@@ -346,6 +346,10 @@ abstract class CommonDocGenerator
 		// phpcs:enable
 		global $extrafields;
 
+		if (!is_object($object)) {
+			return array();
+		}
+
 		if (empty($object->country) && !empty($object->country_code)) {
 			$object->country = $outputlangs->transnoentitiesnoconv("Country".$object->country_code);
 		}
@@ -954,7 +958,7 @@ abstract class CommonDocGenerator
 		// phpcs:enable
 		global $conf;
 
-		if (is_array($extrafields->attributes[$object->table_element]['label'])) {
+		if (isset($extrafields->attributes[$object->table_element]) && is_array($extrafields->attributes[$object->table_element]) && is_array($extrafields->attributes[$object->table_element]['label'])) {
 			foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) {
 				$formatedarrayoption = $object->array_options;
 
@@ -1473,6 +1477,9 @@ abstract class CommonDocGenerator
 				$field = new stdClass();
 				$field->rank = intval($extrafields->attributes[$object->table_element]['pos'][$key]);
 				$field->content = $this->getExtrafieldContent($object, $key, $outputlangs);
+				if (isset($extrafields->attributes[$object->table_element]['langfile'][$key])) {
+					$outputlangs->load($extrafields->attributes[$object->table_element]['langfile'][$key]);
+				}
 				$field->label = $outputlangs->transnoentities($label);
 				$field->type = $extrafields->attributes[$object->table_element]['type'][$key];
 

+ 8 - 7
htdocs/core/class/commonobject.class.php

@@ -1572,7 +1572,7 @@ abstract class CommonObject
 
 		$tab = array();
 
-		$sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position, tc.element";
+		$sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position, tc.element, tc.module";
 		$sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
 
 		$sqlWhere = array();
@@ -1609,7 +1609,7 @@ abstract class CommonObject
 				$langs->loadLangs(array("propal", "orders", "bills", "suppliers", "contracts", "supplier_proposal"));
 
 				while ($obj = $this->db->fetch_object($resql)) {
-					$modulename = $obj->element;
+					$modulename = $obj->module ?? $obj->element;
 					if (strpos($obj->element, 'project') !== false) {
 						$modulename = 'projet';
 					} elseif ($obj->element == 'contrat') {
@@ -3240,7 +3240,7 @@ abstract class CommonObject
 				while ($row = $this->db->fetch_row($resql)) {
 					$rows[] = $row[0];
 					if (!empty($includealltree)) {
-						$rows = array_merge($rows, $this->getChildrenOfLine($row[0]), $includealltree);
+						$rows = array_merge($rows, $this->getChildrenOfLine($row[0], $includealltree));
 					}
 				}
 			}
@@ -7045,9 +7045,9 @@ abstract class CommonObject
 			//var_dump('linealreadyfound='.$linealreadyfound.' sql='.$sql); exit;
 			if ($linealreadyfound) {
 				if ($this->array_options["options_".$key] === null) {
-					$sql = "UPDATE ".$this->db->prefix().$this->table_element."_extrafields SET ".$key." = null";
+					$sql = "UPDATE ".$this->db->prefix().$table_element."_extrafields SET ".$key." = null";
 				} else {
-					$sql = "UPDATE ".$this->db->prefix().$this->table_element."_extrafields SET ".$key." = '".$this->db->escape($new_array_options["options_".$key])."'";
+					$sql = "UPDATE ".$this->db->prefix().$table_element."_extrafields SET ".$key." = '".$this->db->escape($new_array_options["options_".$key])."'";
 				}
 				$sql .= " WHERE fk_object = ".((int) $this->id);
 
@@ -8651,7 +8651,7 @@ abstract class CommonObject
 									$value = $getposttemp;
 								}
 							} elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('int'))) {
-								$value =( !empty($this->array_options["options_".$key]) || $this->array_options["options_".$key] === '0' ) ? $this->array_options["options_".$key] : '';
+								$value =( !empty($this->array_options["options_".$key]) || (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] === '0')) ? $this->array_options["options_".$key] : '';
 							} else {
 								$value = (!empty($this->array_options["options_".$key]) ? $this->array_options["options_".$key] : ''); // No GET, no POST, no default value, so we take value of object.
 							}
@@ -8724,7 +8724,7 @@ abstract class CommonObject
 								}
 							}
 							$datekey = $keyprefix.'options_'.$key.$keysuffix;
-							$value = (GETPOSTISSET($datekey)) ? dol_mktime(12, 0, 0, GETPOST($datekey.'month', 'int', 3), GETPOST($datekey.'day', 'int', 3), GETPOST($datekey.'year', 'int', 3)) : $datenotinstring;
+							$value = (GETPOSTISSET($datekey) && $this->id == GETPOST('elrowid', 'int')) ? dol_mktime(12, 0, 0, GETPOST($datekey.'month', 'int', 3), GETPOST($datekey.'day', 'int', 3), GETPOST($datekey.'year', 'int', 3)) : $datenotinstring;
 						}
 						if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('datetime'))) {
 							$datenotinstring = null;
@@ -9928,6 +9928,7 @@ abstract class CommonObject
 		if ($resql) {
 			$num_rows = $this->db->num_rows($resql);
 			$i = 0;
+			$this->lines = array();
 			while ($i < $num_rows) {
 				$obj = $this->db->fetch_object($resql);
 				if ($obj) {

+ 2 - 0
htdocs/core/class/conf.class.php

@@ -224,9 +224,11 @@ class Conf extends stdClass
 		$this->user	= new stdClass();
 		$this->adherent = new stdClass();
 		$this->bank = new stdClass();
+		$this->mailing = new stdClass();
 		$this->notification = new stdClass();
 		$this->expensereport = new stdClass();
 		$this->productbatch = new stdClass();
+		$this->api = new stdClass();
 	}
 
 	/**

+ 15 - 0
htdocs/core/class/extrafields.class.php

@@ -148,6 +148,7 @@ class ExtraFields
 
 		$result = 0;
 
+		// Clean properties
 		if ($type == 'separate') {
 			$unique = 0;
 			$required = 0;
@@ -158,6 +159,11 @@ class ExtraFields
 		if ($elementtype == 'contact') {
 			$elementtype = 'socpeople';
 		}
+		// If property has a computed formula, it must not be a required or unique field
+		if (!empty($computed)) {
+			$required = 0;
+			$unique = 0;
+		}
 
 		// Create field into database except for separator type which is not stored in database
 		if ($type != 'separate') {
@@ -568,6 +574,7 @@ class ExtraFields
 		}
 
 		if (isset($attrname) && $attrname != '' && preg_match("/^\w[a-zA-Z0-9-_]*$/", $attrname)) {
+			// Clean parameters
 			if ($type == 'boolean') {
 				$typedb = 'int';
 				$lengthdb = '1';
@@ -604,6 +611,12 @@ class ExtraFields
 			}
 			$field_desc = array('type'=>$typedb, 'value'=>$lengthdb, 'null'=>($required ? 'NOT NULL' : 'NULL'), 'default'=>$default);
 
+			// If property has a computed formula, it must not be a required or unique field
+			if (!empty($computed)) {
+				$required = 0;
+				$unique = 0;
+			}
+
 			if (is_object($hookmanager)) {
 				$hookmanager->initHooks(array('extrafieldsdao'));
 				$parameters = array('field_desc'=>&$field_desc, 'table'=>$table, 'attr_name'=>$attrname, 'label'=>$label, 'type'=>$type, 'length'=>$length, 'unique'=>$unique, 'required'=>$required, 'pos'=>$pos, 'param'=>$param, 'alwayseditable'=>$alwayseditable, 'perms'=>$perms, 'list'=>$list, 'help'=>$help, 'default'=>$default, 'computed'=>$computed, 'entity'=>$entity, 'langfile'=>$langfile, 'enabled'=>$enabled, 'totalizable'=>$totalizable, 'printable'=>$printable);
@@ -1512,6 +1525,8 @@ class ExtraFields
 					// print $sql;
 
 					$sql .= $sqlwhere;
+					$sql .= ' ORDER BY '.implode(', ', $fields_label);
+
 					dol_syslog(get_class($this).'::showInputField type=chkbxlst', LOG_DEBUG);
 					$resql = $this->db->query($sql);
 					if ($resql) {

+ 5 - 3
htdocs/core/class/hookmanager.class.php

@@ -208,6 +208,7 @@ class HookManager
 
 		// Init return properties
 		$localResPrint = '';
+		$localResArray = array();
 		$this->resArray = array();
 		$this->resNbOfHooks = 0;
 
@@ -263,9 +264,9 @@ class HookManager
 
 						if (isset($actionclassinstance->results) && is_array($actionclassinstance->results)) {
 							if ($resactiontmp > 0) {
-								$this->resArray = $actionclassinstance->results;
+								$localResArray = $actionclassinstance->results;
 							} else {
-								$this->resArray = array_merge($this->resArray, $actionclassinstance->results);
+								$localResArray = array_merge($localResArray, $actionclassinstance->results);
 							}
 						}
 						if (!empty($actionclassinstance->resprints)) {
@@ -291,7 +292,7 @@ class HookManager
 						$resaction += $resactiontmp;
 
 						if (!empty($actionclassinstance->results) && is_array($actionclassinstance->results)) {
-							$this->resArray = array_merge($this->resArray, $actionclassinstance->results);
+							$localResArray = array_merge($localResArray, $actionclassinstance->results);
 						}
 						if (!empty($actionclassinstance->resprints)) {
 							$localResPrint .= $actionclassinstance->resprints;
@@ -321,6 +322,7 @@ class HookManager
 		}
 
 		$this->resPrint = $localResPrint;
+		$this->resArray = $localResArray;
 
 		return ($error ? -1 : $resaction);
 	}

+ 40 - 27
htdocs/core/class/html.form.class.php

@@ -3346,7 +3346,7 @@ class Form
 
 		if (isModEnabled('stock') && isset($objp->stock) && ($objp->fk_product_type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
 			if ($user->hasRight('stock', 'lire')) {
-				$opt .= ' - ' . $langs->trans("Stock") . ': ' . price(price2num($objp->stock, 'MS'));
+				$opt .= ' - ' . $langs->trans("Stock") . ': ' . price(price2num($objp->stock, 'MS'), 0, $langs, 0, 0);
 
 				if ($objp->stock > 0) {
 					$outval .= ' - <span class="product_line_stock_ok">';
@@ -3498,9 +3498,10 @@ class Form
 		}
 
 		$sql = "SELECT p.rowid, p.ref, p.label, p.price, p.duration, p.fk_product_type, p.stock, p.tva_tx as tva_tx_sale, p.default_vat_code as default_vat_code_sale,";
-		$sql .= " pfp.ref_fourn, pfp.rowid as idprodfournprice, pfp.price as fprice, pfp.quantity, pfp.remise_percent, pfp.remise, pfp.unitprice,";
-		$sql .= " pfp.fk_supplier_price_expression, pfp.fk_product, pfp.tva_tx, pfp.default_vat_code, pfp.fk_soc, s.nom as name,";
-		$sql .= " pfp.supplier_reputation";
+		$sql .= " pfp.ref_fourn, pfp.rowid as idprodfournprice, pfp.price as fprice, pfp.quantity, pfp.remise_percent, pfp.remise, pfp.unitprice";
+		$sql .= ", pfp.multicurrency_code, pfp.multicurrency_unitprice";
+		$sql .= ", pfp.fk_supplier_price_expression, pfp.fk_product, pfp.tva_tx, pfp.default_vat_code, pfp.fk_soc, s.nom as name";
+		$sql .= ", pfp.supplier_reputation";
 		// if we use supplier description of the products
 		if (getDolGlobalString('PRODUIT_FOURN_TEXTS')) {
 			$sql .= ", pfp.desc_fourn as description";
@@ -3823,6 +3824,10 @@ class Form
 					$optstart .= ' data-tvatx-formated="' . dol_escape_htmltag(price($objp->tva_tx, 0, $langs, 1, -1, 2)) . '"';
 					$optstart .= ' data-default-vat-code="' . dol_escape_htmltag($objp->default_vat_code) . '"';
 					$optstart .= ' data-supplier-ref="' . dol_escape_htmltag($objp->ref_fourn) . '"';
+					if (isModEnabled('multicurrency')) {
+						$optstart .= ' data-multicurrency-code="' . dol_escape_htmltag($objp->multicurrency_code) . '"';
+						$optstart .= ' data-multicurrency-up="' . dol_escape_htmltag($objp->multicurrency_unitprice) . '"';
+					}
 				}
 				$optstart .= ' data-description="' . dol_escape_htmltag($objp->description, 0, 1) . '"';
 
@@ -3844,6 +3849,10 @@ class Form
 					'disabled' => (empty($objp->idprodfournprice) ? true : false),
 					'description' => $objp->description
 				);
+				if (isModEnabled('multicurrency')) {
+					$outarrayentry['multicurrency_code'] = $objp->multicurrency_code;
+					$outarrayentry['multicurrency_unitprice'] = price2num($objp->multicurrency_unitprice, 'MU');
+				}
 
 				$parameters = array(
 					'objp' => &$objp,
@@ -3859,29 +3868,33 @@ class Form
 				// "key" value of json key array is used by jQuery automatically as selected value. Example: 'type' = product or service, 'price_ht' = unit price without tax
 				// "label" value of json key array is used by jQuery automatically as text for combo box
 				$out .= $optstart . ' data-html="' . dol_escape_htmltag($optlabel) . '">' . $optlabel . "</option>\n";
-				array_push(
-					$outarray,
-					array('key' => $outkey,
-						'value' => $outref,
-						'label' => $outvallabel,
-						'qty' => $outqty,
-						'price_qty_ht' => price2num($objp->fprice, 'MU'),        // Keep higher resolution for price for the min qty
-						'price_qty_ht_locale' => price($objp->fprice),
-						'price_unit_ht' => price2num($objp->unitprice, 'MU'),    // This is used to fill the Unit Price
-						'price_unit_ht_locale' => price($objp->unitprice),
-						'price_ht' => price2num($objp->unitprice, 'MU'),        // This is used to fill the Unit Price (for compatibility)
-						'tva_tx_formated' => price($objp->tva_tx),
-						'tva_tx' => price2num($objp->tva_tx),
-						'default_vat_code' => $objp->default_vat_code,
-						'discount' => $outdiscount,
-						'type' => $outtype,
-						'duration_value' => $outdurationvalue,
-						'duration_unit' => $outdurationunit,
-						'disabled' => (empty($objp->idprodfournprice) ? true : false),
-						'description' => $objp->description
-					)
+				$outarraypush = array(
+					'key' => $outkey,
+					'value' => $outref,
+					'label' => $outvallabel,
+					'qty' => $outqty,
+					'price_qty_ht' => price2num($objp->fprice, 'MU'),        // Keep higher resolution for price for the min qty
+					'price_qty_ht_locale' => price($objp->fprice),
+					'price_unit_ht' => price2num($objp->unitprice, 'MU'),    // This is used to fill the Unit Price
+					'price_unit_ht_locale' => price($objp->unitprice),
+					'price_ht' => price2num($objp->unitprice, 'MU'),        // This is used to fill the Unit Price (for compatibility)
+					'tva_tx_formated' => price($objp->tva_tx),
+					'tva_tx' => price2num($objp->tva_tx),
+					'default_vat_code' => $objp->default_vat_code,
+					'discount' => $outdiscount,
+					'type' => $outtype,
+					'duration_value' => $outdurationvalue,
+					'duration_unit' => $outdurationunit,
+					'disabled' => (empty($objp->idprodfournprice) ? true : false),
+					'description' => $objp->description
 				);
-				// Exemple of var_dump $outarray
+				if (isModEnabled('multicurrency')) {
+					$outarraypush['multicurrency_code'] = $objp->multicurrency_code;
+					$outarraypush['multicurrency_unitprice'] = price2num($objp->multicurrency_unitprice, 'MU');
+				}
+				array_push($outarray, $outarraypush);
+
+				// Example of var_dump $outarray
 				// array(1) {[0]=>array(6) {[key"]=>string(1) "2" ["value"]=>string(3) "ppp"
 				//           ["label"]=>string(76) "ppp (<strong>f</strong>ff2) - ppp - 20,00 Euros/1unité (20,00 Euros/unité)"
 				//      	 ["qty"]=>string(1) "1" ["discount"]=>string(1) "0" ["disabled"]=>bool(false)
@@ -8158,7 +8171,7 @@ class Form
 
 				$oldValueForShowOnCombobox = 0;
 				foreach ($objecttmp->fields as $fieldK => $fielV) {
-					if (!$fielV['showoncombobox'] || empty($objecttmp->$fieldK)) continue;
+					if (!array_key_exists('showoncombobox', $fielV) || !$fielV['showoncombobox'] || empty($objecttmp->$fieldK)) continue;
 
 					if (!$oldValueForShowOnCombobox) {
 						$selected_input_value = '';

+ 1 - 1
htdocs/core/class/html.formfile.class.php

@@ -906,7 +906,7 @@ class FormFile
 
 					// Show file size
 					$size = (!empty($file['size']) ? $file['size'] : dol_filesize($filedir."/".$file["name"]));
-					$out .= '<td class="nowraponall right">'.dol_print_size($size, 1, 1).'</td>';
+					$out .= '<td class="nowraponall right" title="'.dolPrintHTML($size.' '.$langs->trans("Bytes")).'">'.dol_print_size($size, 1, 1).'</td>';
 
 					// Show file date
 					$date = (!empty($file['date']) ? $file['date'] : dol_filemtime($filedir."/".$file["name"]));

+ 6 - 5
htdocs/core/class/html.formmail.class.php

@@ -901,12 +901,12 @@ class FormMail extends Form
 
 				// Complete substitution array with the url to make online payment
 				$paymenturl = '';
-				$validpaymentmethod = array();
+				// Set the online payment url link into __ONLINE_PAYMENT_URL__ key
+				require_once DOL_DOCUMENT_ROOT.'/core/lib/payments.lib.php';
+				$validpaymentmethod = getValidOnlinePaymentMethods('');
 				if (empty($this->substit['__REF__'])) {
 					$paymenturl = '';
 				} else {
-					// Set the online payment url link into __ONLINE_PAYMENT_URL__ key
-					require_once DOL_DOCUMENT_ROOT.'/core/lib/payments.lib.php';
 					$langs->loadLangs(array('paypal', 'other'));
 					$typeforonlinepayment = 'free';
 					if ($this->param["models"] == 'propal' || $this->param["models"] == 'propal_send') {
@@ -923,14 +923,15 @@ class FormMail extends Form
 					}
 					$url = getOnlinePaymentUrl(0, $typeforonlinepayment, $this->substit['__REF__']);
 					$paymenturl = $url;
-
-					$validpaymentmethod = getValidOnlinePaymentMethods('');
 				}
 
 				if (count($validpaymentmethod) > 0 && $paymenturl) {
 					$langs->load('other');
 					$this->substit['__ONLINE_PAYMENT_TEXT_AND_URL__'] = str_replace('\n', "\n", $langs->transnoentities("PredefinedMailContentLink", $paymenturl));
 					$this->substit['__ONLINE_PAYMENT_URL__'] = $paymenturl;
+				} elseif (count($validpaymentmethod) > 0) {
+					$this->substit['__ONLINE_PAYMENT_TEXT_AND_URL__'] = '__ONLINE_PAYMENT_TEXT_AND_URL__';
+					$this->substit['__ONLINE_PAYMENT_URL__'] = '__ONLINE_PAYMENT_URL__';
 				} else {
 					$this->substit['__ONLINE_PAYMENT_TEXT_AND_URL__'] = '';
 					$this->substit['__ONLINE_PAYMENT_URL__'] = '';

+ 4 - 4
htdocs/core/class/html.formticket.class.php

@@ -765,7 +765,7 @@ class FormTicket
 					print ' selected="selected"';
 				} elseif (in_array($id, $selected)) {
 					print ' selected="selected"';
-				} elseif ($arraytypes['use_default'] == "1" && !$selected && !$empty) {
+				} elseif ($arraytypes['use_default'] == "1" && empty($selected)) {
 					print ' selected="selected"';
 				}
 
@@ -1211,7 +1211,7 @@ class FormTicket
 					print ' selected="selected"';
 				} elseif (isset($selected) && $selected == $id) {
 					print ' selected="selected"';
-				} elseif ($arrayseverities['use_default'] == "1" && !$selected && !$empty) {
+				} elseif ($arrayseverities['use_default'] == "1" && empty($selected)) {
 					print ' selected="selected"';
 				}
 
@@ -1492,8 +1492,8 @@ class FormTicket
 			// Subject/topic
 			$topic = "";
 			foreach ($formmail->lines_model as $line) {
-				if ($this->param['models_id'] == $line->id) {
-					$topic = $line->topic;
+				if (!empty($this->substit) && $this->param['models_id'] == $line->id) {
+					$topic = make_substitutions($line->topic, $this->substit);
 					break;
 				}
 			}

+ 28 - 6
htdocs/core/class/ldap.class.php

@@ -307,7 +307,12 @@ class Ldap
 					if ($ldapdebug) {
 						dol_syslog(get_class($this)."::connect_bind serverPing true, we try ldap_connect to ".$host);
 					}
-					$this->connection = ldap_connect($host, $this->serverPort);
+					if (version_compare(PHP_VERSION, '8.3.0', '>=')) {
+						$uri = $host.':'.$this->serverPort;
+						$this->connection = ldap_connect($uri);
+					} else {
+						$this->connection = ldap_connect($host, $this->serverPort);
+					}
 				} else {
 					if (preg_match('/^ldaps/i', $host)) {
 						// With host = ldaps://server, the serverPing to ssl://server sometimes fails, even if the ldap_connect succeed, so
@@ -315,7 +320,12 @@ class Ldap
 						if ($ldapdebug) {
 							dol_syslog(get_class($this)."::connect_bind serverPing false, we try ldap_connect to ".$host);
 						}
-						$this->connection = ldap_connect($host, $this->serverPort);
+						if (version_compare(PHP_VERSION, '8.3.0', '>=')) {
+							$uri = $host.':'.$this->serverPort;
+							$this->connection = ldap_connect($uri);
+						} else {
+							$this->connection = ldap_connect($host, $this->serverPort);
+						}
 					} else {
 						continue;
 					}
@@ -463,14 +473,26 @@ class Ldap
 	/**
 	 * Unbind of LDAP server (close connection).
 	 *
-	 * @return	boolean					true or false
-	 * @see close()
+	 * @return		boolean		true or false
+	 * @see	close()
 	 */
 	public function unbind()
 	{
 		$this->result = true;
-		if (is_resource($this->connection) || is_object($this->connection)) {
-			$this->result = @ldap_unbind($this->connection);
+		if (version_compare(PHP_VERSION, '8.1.0', '>=')) {
+			if (is_object($this->connection)) {
+				try {
+					$this->result = ldap_unbind($this->connection);
+				} catch (Throwable $exception) {
+					$this->error = 'Failed to unbind LDAP connection: '.$exception;
+					$this->result = false;
+					dol_syslog(get_class($this).'::unbind - '.$this->error, LOG_WARNING);
+				}
+			}
+		} else {
+			if (is_resource($this->connection)) {
+				$this->result = @ldap_unbind($this->connection);
+			}
 		}
 		if ($this->result) {
 			return true;

+ 10 - 4
htdocs/core/class/utils.class.php

@@ -155,7 +155,13 @@ class Utils
 
 						$result = dol_delete_dir_recursive($filesarray[$key]['fullname'], $startcount, 1, 0, $tmpcountdeleted);
 
-						if (!in_array($filesarray[$key]['fullname'], array($conf->api->dir_temp, $conf->user->dir_temp))) {		// The 2 directories $conf->api->dir_temp and $conf->user->dir_temp are recreated at end, so we do not count them
+						$recreatedDirs = array($conf->user->dir_temp);
+
+						if (isModEnabled('api')) {
+							$recreatedDirs[] = $conf->api->dir_temp;
+						}
+
+						if (!in_array($filesarray[$key]['fullname'], $recreatedDirs)) {		// The 2 directories $conf->api->dir_temp and $conf->user->dir_temp are recreated at end, so we do not count them
 							$count += $result;
 							$countdeleted += $tmpcountdeleted;
 						}
@@ -538,13 +544,13 @@ class Utils
 				} elseif ($compression == 'zstd') {
 					fclose($handle);
 				}
-				if ($ok && preg_match('/^-- (MySql|MariaDB)/i', $errormsg)) {	// No error
+				if ($ok && preg_match('/^-- (MySql|MariaDB)/i', $errormsg) || preg_match('/^\/\*M?!999999/', $errormsg)) {	// Start of file is ok, NOT an error
 					$errormsg = '';
 				} else {
-					// Renommer fichier sortie en fichier erreur
+					// Rename file out into a file error
 					//print "$outputfile -> $outputerror";
 					@dol_delete_file($outputerror, 1, 0, 0, null, false, 0);
-					@rename($outputfile, $outputerror);
+					@dol_move($outputfile, $outputerror, '0', 1, 0, 0);
 					// Si safe_mode on et command hors du parametre exec, on a un fichier out vide donc errormsg vide
 					if (!$errormsg) {
 						$langs->load("errors");

+ 8 - 0
htdocs/core/lib/ajax.lib.php

@@ -161,6 +161,14 @@ function ajax_autocompleter($selected, $htmlname, $url, $urloption = '', $minLen
 												 price_ttc: item.price_ttc,
 												 price_unit_ht: item.price_unit_ht,
 												 price_unit_ht_locale: item.price_unit_ht_locale,
+		';
+	if (isModEnabled('multicurrency')) {
+		$script .= '
+												multicurrency_code: item.multicurrency_code,
+												multicurrency_unitprice: item.multicurrency_unitprice,
+		';
+	}
+		$script .= '
 												 description : item.description,
 												 ref_customer: item.ref_customer,
 												 tva_tx: item.tva_tx,

+ 2 - 1
htdocs/core/lib/company.lib.php

@@ -2249,7 +2249,7 @@ function show_subsidiaries($conf, $langs, $db, $object)
 
 	$i = -1;
 
-	$sql = "SELECT s.rowid, s.client, s.fournisseur, s.nom as name, s.name_alias, s.email, s.address, s.zip, s.town, s.code_client, s.code_fournisseur, s.code_compta, s.code_compta_fournisseur, s.canvas";
+	$sql = "SELECT s.rowid, s.client, s.fournisseur, s.nom as name, s.name_alias, s.email, s.address, s.zip, s.town, s.code_client, s.code_fournisseur, s.code_compta, s.code_compta_fournisseur, s.canvas, s.status";
 	$sql .= " FROM ".MAIN_DB_PREFIX."societe as s";
 	$sql .= " WHERE s.parent = ".((int) $object->id);
 	$sql .= " AND s.entity IN (".getEntity('societe').")";
@@ -2289,6 +2289,7 @@ function show_subsidiaries($conf, $langs, $db, $object)
 			$socstatic->canvas = $obj->canvas;
 			$socstatic->client = $obj->client;
 			$socstatic->fournisseur = $obj->fournisseur;
+			$socstatic->status = $obj->status;
 
 			print '<tr class="oddeven">';
 

+ 26 - 25
htdocs/core/lib/date.lib.php

@@ -36,31 +36,32 @@
 function get_tz_array()
 {
 	$tzarray = array(
-		-11=>"Pacific/Midway",
-		-10=>"Pacific/Fakaofo",
-		-9=>"America/Anchorage",
-		-8=>"America/Los_Angeles",
-		-7=>"America/Dawson_Creek",
-		-6=>"America/Chicago",
-		-5=>"America/Bogota",
-		-4=>"America/Anguilla",
-		-3=>"America/Araguaina",
-		-2=>"America/Noronha",
-		-1=>"Atlantic/Azores",
-		0=>"Africa/Abidjan",
-		1=>"Europe/Paris",
-		2=>"Europe/Helsinki",
-		3=>"Europe/Moscow",
-		4=>"Asia/Dubai",
-		5=>"Asia/Karachi",
-		6=>"Indian/Chagos",
-		7=>"Asia/Jakarta",
-		8=>"Asia/Hong_Kong",
-		9=>"Asia/Tokyo",
-		10=>"Australia/Sydney",
-		11=>"Pacific/Noumea",
-		12=>"Pacific/Auckland",
-		13=>"Pacific/Enderbury"
+		-11 => "Pacific/Pago_Pago",
+		-10 => "Pacific/Honolulu",
+		-9 => "America/Anchorage",
+		-8 => "America/Los_Angeles",
+		-7 => "America/Dawson_Creek",
+		-6 => "America/Chicago",
+		-5 => "America/Bogota",
+		-4 => "America/Asuncion",
+		-3 => "America/Araguaina",
+		-2 => "America/Noronha",
+		-1 => "Atlantic/Azores",
+		0 => "Europe/London",
+		1 => "Europe/Paris",
+		2 => "Europe/Helsinki",
+		3 => "Europe/Moscow",
+		4 => "Asia/Dubai",
+		5 => "Asia/Karachi",
+		6 => "Indian/Chagos",
+		7 => "Asia/Jakarta",
+		8 => "Asia/Hong_Kong",
+		9 => "Asia/Tokyo",
+		10 => "Australia/Sydney",
+		11 => "Pacific/Noumea",
+		12 => "Pacific/Auckland",
+		13 => "Pacific/Fakaofo",
+		14 => "Pacific/Kiritimati"
 	);
 	return $tzarray;
 }

+ 40 - 10
htdocs/core/lib/functions.lib.php

@@ -1628,6 +1628,17 @@ function dol_escape_php($stringtoescape, $stringforquotes = 2)
 	return 'Bad parameter for stringforquotes in dol_escape_php';
 }
 
+/**
+ *  Returns text escaped for all protocols (so only alpha chars and numbers)
+ *
+ *  @param      string		$stringtoescape		String to escape
+ *  @return     string     		 				Escaped string for XML content.
+ */
+function dol_escape_all($stringtoescape)
+{
+	return preg_replace('/[^a-z0-9_]/i', '', $stringtoescape);
+}
+
 /**
  *  Returns text escaped for inclusion into a XML string
  *
@@ -2537,7 +2548,16 @@ function dol_banner_tab($object, $paramid, $morehtml = '', $shownav = 1, $fieldi
 			$tmptxt = $object->getLibStatut(5, $object->alreadypaid);
 		}
 		$morehtmlstatus .= $tmptxt;
-	} elseif (in_array($object->element, array('facture', 'invoice', 'invoice_supplier', 'chargesociales', 'loan', 'tva'))) {	// TODO Move this to use ->alreadypaid
+	} elseif (in_array($object->element, array('facture', 'invoice', 'invoice_supplier'))) {	// TODO Move this to use ->alreadypaid
+		$totalallpayments = $object->getSommePaiement(0);
+		$totalallpayments += $object->getSumCreditNotesUsed(0);
+		$totalallpayments += $object->getSumDepositsUsed(0);
+		$tmptxt = $object->getLibStatut(6, $totalallpayments);
+		if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
+			$tmptxt = $object->getLibStatut(5, $totalallpayments);
+		}
+		$morehtmlstatus .= $tmptxt;
+	} elseif (in_array($object->element, array('chargesociales', 'loan', 'tva'))) {	// TODO Move this to use ->alreadypaid
 		$tmptxt = $object->getLibStatut(6, $object->totalpaid);
 		if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
 			$tmptxt = $object->getLibStatut(5, $object->totalpaid);
@@ -2621,7 +2641,6 @@ function dol_banner_tab($object, $paramid, $morehtml = '', $shownav = 1, $fieldi
 		$morehtmlref = $hookmanager->resPrint;
 	}
 
-
 	print '<div class="'.($onlybanner ? 'arearefnobottom ' : 'arearef ').'heightref valignmiddle centpercent">';
 	print $form->showrefnav($object, $paramid, $morehtml, $shownav, $fieldid, $fieldref, $morehtmlref, $moreparam, $nodbprefix, $morehtmlleft, $morehtmlstatus, $morehtmlright);
 	print '</div>';
@@ -6137,11 +6156,13 @@ function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $
 	if (dol_strlen($decpart) > $nbdecimal) {
 		$nbdecimal = dol_strlen($decpart);
 	}
-	// Si on depasse max
-	if ($trunc && $nbdecimal > $conf->global->MAIN_MAX_DECIMALS_SHOWN) {
-		$nbdecimal = $conf->global->MAIN_MAX_DECIMALS_SHOWN;
-		if (preg_match('/\.\.\./i', $conf->global->MAIN_MAX_DECIMALS_SHOWN)) {
-			// Si un affichage est tronque, on montre des ...
+
+	// If nbdecimal is higher than max to show
+	$nbdecimalmaxshown = (int) str_replace('...', '', getDolGlobalString('MAIN_MAX_DECIMALS_SHOWN'));
+	if ($trunc && $nbdecimal > $nbdecimalmaxshown) {
+		$nbdecimal = $nbdecimalmaxshown;
+		if (preg_match('/\.\.\./i', getDolGlobalString('MAIN_MAX_DECIMALS_SHOWN'))) {
+			// If output is truncated, we show ...
 			$end = '...';
 		}
 	}
@@ -6149,9 +6170,9 @@ function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $
 	// If force rounding
 	if ((string) $forcerounding != '-1') {
 		if ($forcerounding === 'MU') {
-			$nbdecimal = $conf->global->MAIN_MAX_DECIMALS_UNIT;
+			$nbdecimal = getDolGlobalInt('MAIN_MAX_DECIMALS_UNIT');
 		} elseif ($forcerounding === 'MT') {
-			$nbdecimal = $conf->global->MAIN_MAX_DECIMALS_TOT;
+			$nbdecimal = getDolGlobalInt('MAIN_MAX_DECIMALS_TOT');
 		} elseif ($forcerounding >= 0) {
 			$nbdecimal = $forcerounding;
 		}
@@ -6603,7 +6624,12 @@ function getTaxesFromId($vatrate, $buyer = null, $seller = null, $firstparamisid
 		$sql .= ", ".MAIN_DB_PREFIX."c_country as c";
 		/*if ($mysoc->country_code == 'ES') $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($buyer->country_code)."'";    // vat in spain use the buyer country ??
 		else $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";*/
-		$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";
+		$sql .= " WHERE t.fk_pays = c.rowid";
+		if (getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')) {
+			$sql .= " AND c.code = '".$db->escape($buyer->country_code)."'";
+		} else {
+			$sql .= " AND c.code = '".$db->escape($seller->country_code)."'";
+		}
 		$sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
 		$sql .= " AND t.entity IN (".getEntity('c_tva').")";
 		if ($vatratecode) {
@@ -12558,6 +12584,10 @@ function jsonOrUnserialize($stringtodecode)
  */
 function forgeSQLFromUniversalSearchCriteria($filter, &$errorstr = '', $noand = 0, $nopar = 0, $noerror = 0)
 {
+	if (empty($filter)) {
+		return ''; // to avoid the return "AND (())"
+	}
+
 	if (!preg_match('/^\(.*\)$/', $filter)) {    // If $filter does not start and end with ()
 		$filter = '(' . $filter . ')';
 	}

+ 1 - 1
htdocs/core/lib/functions2.lib.php

@@ -1342,7 +1342,7 @@ function get_next_value($db, $mask, $table, $field, $where = '', $objsoc = '', $
 			if ($bentityon) { // only if entity enable
 				$maskrefclient_sql .= " AND entity IN (".getEntity($sharetable).")";
 			} elseif (!empty($forceentity)) {
-				$sql .= " AND entity IN (".$db->sanitize($forceentity).")";
+				$maskrefclient_sql .= " AND entity IN (".$db->sanitize($forceentity).")";
 			}
 			if ($where) {
 				$maskrefclient_sql .= $where; //use the same optional where as general mask

+ 7 - 7
htdocs/core/lib/product.lib.php

@@ -452,7 +452,7 @@ function show_stats_for_company($product, $socid)
 		print '</td><td class="right">';
 		print $product->stats_propale['nb'];
 		print '</td><td class="right">';
-		print $product->stats_propale['qty'];
+		print price($product->stats_propale['qty'], 1, $langs, 0, 0);
 		print '</td>';
 		print '</tr>';
 	}
@@ -471,7 +471,7 @@ function show_stats_for_company($product, $socid)
 		print '</td><td class="right">';
 		print $product->stats_proposal_supplier['nb'];
 		print '</td><td class="right">';
-		print $product->stats_proposal_supplier['qty'];
+		print price($product->stats_proposal_supplier['qty'], 1, $langs, 0, 0);
 		print '</td>';
 		print '</tr>';
 	}
@@ -490,7 +490,7 @@ function show_stats_for_company($product, $socid)
 		print '</td><td class="right">';
 		print $product->stats_commande['nb'];
 		print '</td><td class="right">';
-		print $product->stats_commande['qty'];
+		print price($product->stats_commande['qty'], 1, $langs, 0, 0);
 		print '</td>';
 		print '</tr>';
 	}
@@ -509,7 +509,7 @@ function show_stats_for_company($product, $socid)
 		print '</td><td class="right">';
 		print $product->stats_commande_fournisseur['nb'];
 		print '</td><td class="right">';
-		print $product->stats_commande_fournisseur['qty'];
+		print price($product->stats_commande_fournisseur['qty'], 1, $langs, 0, 0);
 		print '</td>';
 		print '</tr>';
 	}
@@ -528,7 +528,7 @@ function show_stats_for_company($product, $socid)
 		print '</td><td class="right">';
 		print $product->stats_facture['nb'];
 		print '</td><td class="right">';
-		print $product->stats_facture['qty'];
+		print price($product->stats_facture['qty'], 1, $langs, 0, 0);
 		print '</td>';
 		print '</tr>';
 	}
@@ -566,7 +566,7 @@ function show_stats_for_company($product, $socid)
 		print '</td><td class="right">';
 		print $product->stats_facture_fournisseur['nb'];
 		print '</td><td class="right">';
-		print $product->stats_facture_fournisseur['qty'];
+		print price($product->stats_facture_fournisseur['qty'], 1, $langs, 0, 0);
 		print '</td>';
 		print '</tr>';
 	}
@@ -586,7 +586,7 @@ function show_stats_for_company($product, $socid)
 		print '</td><td class="right">';
 		print $product->stats_contrat['nb'];
 		print '</td><td class="right">';
-		print $product->stats_contrat['qty'];
+		print price($product->stats_contrat['qty'], 1, $langs, 0, 0);
 		print '</td>';
 		print '</tr>';
 	}

+ 10 - 5
htdocs/core/login/functions_ldap.php

@@ -1,6 +1,7 @@
 <?php
 /* Copyright (C) 2007-2011 Laurent Destailleur  <eldy@users.sourceforge.net>
  * Copyright (C) 2008-2021 Regis Houssin        <regis.houssin@inodbox.com>
+ * Copyright (C) 2024		William Mead		<william.mead@manchenumerique.fr>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -246,17 +247,21 @@ function check_user_password_ldap($usertotest, $passwordtotest, $entitytotest)
 			 */
 			dol_syslog("functions_ldap::check_user_password_ldap Authentication KO failed to connect to LDAP for '".$usertotest."'", LOG_NOTICE);
 			if (is_resource($ldap->connection) || is_object($ldap->connection)) {    // If connection ok but bind ko
-				$ldap->ldapErrorCode = ldap_errno($ldap->connection);
-				$ldap->ldapErrorText = ldap_error($ldap->connection);
-				dol_syslog("functions_ldap::check_user_password_ldap ".$ldap->ldapErrorCode." ".$ldap->ldapErrorText);
+				try {
+					$ldap->ldapErrorCode = ldap_errno($ldap->connection);
+					$ldap->ldapErrorText = ldap_error($ldap->connection);
+					dol_syslog("functions_ldap::check_user_password_ldap ".$ldap->ldapErrorCode." ".$ldap->ldapErrorText);
+				} catch (Throwable $exception) {
+					$ldap->ldapErrorCode = '';
+					$ldap->ldapErrorText = '';
+					dol_syslog('functions_ldap::check_user_password_ldap '.$exception, LOG_WARNING);
+				}
 			}
 			sleep(1); // Anti brut force protection. Must be same delay when user and password are not valid.
-
 			// Load translation files required by the page
 			$langs->loadLangs(array('main', 'other', 'errors'));
 			$_SESSION["dol_loginmesg"] = ($ldap->error ? $ldap->error : $langs->transnoentitiesnoconv("ErrorBadLoginPassword"));
 		}
-
 		$ldap->unbind();
 	}
 

+ 56 - 0
htdocs/core/modules/DolibarrModules.class.php

@@ -2529,4 +2529,60 @@ class DolibarrModules // Can not be abstract, because we need to instantiate it
 		}
 		return 0;
 	}
+
+	/**
+	 * Check for module compliance with Dolibarr rules and law
+	 * If a module is reported by this function,it is surely a malware. Delete it as soon as possible.
+	 *
+	 * @return int|string 	Return integer <0 if Error, 0 == not compliant, 'string' with message if module not compliant
+	 */
+	public function checkForCompliance()
+	{
+		global $conf, $langs;
+
+		// Get list of illegal modules name or ID
+		if (empty($conf->cache['noncompliantmodules'])) {
+			require_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
+
+			$urlforblacklistmodules = 'https://ping.dolibarr.org/modules-blacklist.txt';
+
+			$result = getURLContent($urlforblacklistmodules, 'GET', '', 1, array(), array('http', 'https'), 0);	// Accept http or https links on external remote server only
+			if (isset($result['content']) && $result['http_code'] == 200) {
+				$langs->load("errors");
+
+				// Security warning :  be careful with remote data content, the module editor could be hacked (or evil) so limit to a-z A-Z 0-9 _ . -
+				$arrayoflines = preg_split("/[\n,]/", $result['content']);
+				foreach ($arrayoflines as $line) {
+					$tmpfieldsofline = explode(';', $line);
+					$modulekey = strtolower($tmpfieldsofline[0]);
+					$conf->cache['noncompliantmodules'][$modulekey]['name'] = $tmpfieldsofline[0];
+					$conf->cache['noncompliantmodules'][$modulekey]['id'] = $tmpfieldsofline[1];
+					$conf->cache['noncompliantmodules'][$modulekey]['signature'] = $tmpfieldsofline[2];
+					$conf->cache['noncompliantmodules'][$modulekey]['message'] = $langs->trans(empty($tmpfieldsofline[3]) ? 'WarningModuleAffiliatedToAReportedCompany' : $tmpfieldsofline[3]);
+					if (!empty($tmpfieldsofline[4])) {
+						$message2 = $langs->trans("WarningModuleAffiliatedToAPiratPlatform", '{s}');
+						$listofillegalurl = '';
+						foreach (explode(" ", $tmpfieldsofline[4]) as $illegalurl) {
+							$listofillegalurl .= ($listofillegalurl ? ' '.$langs->trans("or").' ' : '').'<b>'.preg_replace('/[^a-z0-9\.\-]/', '', $illegalurl).'</b>';
+						}
+						$message2 = str_replace('{s}', $listofillegalurl, $message2);
+						$conf->cache['noncompliantmodules'][$modulekey]['message2'] = $message2;
+					}
+				}
+			}
+		}
+
+		if (!empty($conf->cache['noncompliantmodules'])) {
+			$modulekey = strtolower($this->name);
+			if (in_array($modulekey, array_keys($conf->cache['noncompliantmodules']))) {
+				$answer = trim($conf->cache['noncompliantmodules'][$modulekey]['message']);
+				if (!empty($conf->cache['noncompliantmodules'][$modulekey]['message2'])) {
+					$answer .= '<br>'.$conf->cache['noncompliantmodules'][$modulekey]['message2'];
+				}
+				return $answer;
+			}
+		}
+
+		return 0;
+	}
 }

+ 4 - 4
htdocs/core/modules/asset/doc/doc_generic_asset_odt.modules.php

@@ -327,10 +327,10 @@ class doc_generic_asset_odt extends ModelePDFAsset
 					$odfHandler = new Odf(
 						$srctemplatepath,
 						array(
-						'PATH_TO_TMP'	  => $conf->asset->dir_temp,
-						'ZIP_PROXY'		  => 'PclZipProxy', // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
-						'DELIMITER_LEFT'  => '{',
-						'DELIMITER_RIGHT' => '}'
+							'PATH_TO_TMP'	  => $conf->asset->dir_temp,
+							'ZIP_PROXY'		  => getDolGlobalString('MAIN_ODF_ZIP_PROXY', 'PclZipProxy'), // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
+							'DELIMITER_LEFT'  => '{',
+							'DELIMITER_RIGHT' => '}'
 						)
 					);
 				} catch (Exception $e) {

+ 1 - 1
htdocs/core/modules/asset/doc/pdf_standard_asset.modules.php

@@ -1008,7 +1008,7 @@ class pdf_standard_asset extends ModelePDFAsset
 				$posy += 4;
 				$pdf->SetXY($posx, $posy);
 				$pdf->SetTextColor(0, 0, 60);
-				$pdf->MultiCell($w, 3, $langs->transnoentities("SalesRepresentative")." : ".$usertmp->getFullName($langs), '', 'R');
+				$pdf->MultiCell($w, 3, $outputlangs->transnoentities("SalesRepresentative")." : ".$usertmp->getFullName($langs), '', 'R');
 			}
 		}
 

+ 4 - 4
htdocs/core/modules/bom/doc/doc_generic_bom_odt.modules.php

@@ -333,10 +333,10 @@ class doc_generic_bom_odt extends ModelePDFBom
 					$odfHandler = new Odf(
 						$srctemplatepath,
 						array(
-						'PATH_TO_TMP'	  => $conf->bom->dir_temp,
-						'ZIP_PROXY'		  => 'PclZipProxy', // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
-						'DELIMITER_LEFT'  => '{',
-						'DELIMITER_RIGHT' => '}'
+							'PATH_TO_TMP'	  => $conf->bom->dir_temp,
+							'ZIP_PROXY'		  => getDolGlobalString('MAIN_ODF_ZIP_PROXY', 'PclZipProxy'), // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
+							'DELIMITER_LEFT'  => '{',
+							'DELIMITER_RIGHT' => '}'
 						)
 					);
 				} catch (Exception $e) {

+ 4 - 4
htdocs/core/modules/commande/doc/doc_generic_order_odt.modules.php

@@ -343,10 +343,10 @@ class doc_generic_order_odt extends ModelePDFCommandes
 					$odfHandler = new Odf(
 						$srctemplatepath,
 						array(
-						'PATH_TO_TMP'	  => $conf->commande->dir_temp,
-						'ZIP_PROXY'		  => 'PclZipProxy', // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
-						'DELIMITER_LEFT'  => '{',
-						'DELIMITER_RIGHT' => '}'
+							'PATH_TO_TMP'	  => $conf->commande->dir_temp,
+							'ZIP_PROXY'		  => getDolGlobalString('MAIN_ODF_ZIP_PROXY', 'PclZipProxy'), // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
+							'DELIMITER_LEFT'  => '{',
+							'DELIMITER_RIGHT' => '}'
 						)
 					);
 				} catch (Exception $e) {

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

@@ -1380,7 +1380,7 @@ class pdf_einstein extends ModelePDFCommandes
 				$posy += 4;
 				$pdf->SetXY($posx, $posy);
 				$pdf->SetTextColor(0, 0, 60);
-				$pdf->MultiCell($w, 3, $langs->transnoentities("SalesRepresentative")." : ".$usertmp->getFullName($langs), '', 'R');
+				$pdf->MultiCell($w, 3, $outputlangs->transnoentities("SalesRepresentative")." : ".$usertmp->getFullName($langs), '', 'R');
 			}
 		}
 

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

@@ -1601,7 +1601,7 @@ class pdf_eratosthene extends ModelePDFCommandes
 				$posy += 4;
 				$pdf->SetXY($posx, $posy);
 				$pdf->SetTextColor(0, 0, 60);
-				$pdf->MultiCell($w, 3, $langs->transnoentities("SalesRepresentative")." : ".$usertmp->getFullName($langs), '', 'R');
+				$pdf->MultiCell($w, 3, $outputlangs->transnoentities("SalesRepresentative")." : ".$usertmp->getFullName($langs), '', 'R');
 			}
 		}
 

+ 4 - 4
htdocs/core/modules/contract/doc/doc_generic_contract_odt.modules.php

@@ -355,10 +355,10 @@ class doc_generic_contract_odt extends ModelePDFContract
 					$odfHandler = new Odf(
 						$srctemplatepath,
 						array(
-						'PATH_TO_TMP'	  => $conf->contrat->dir_temp,
-						'ZIP_PROXY'		  => 'PclZipProxy', // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
-						'DELIMITER_LEFT'  => '{',
-						'DELIMITER_RIGHT' => '}'
+							'PATH_TO_TMP'	  => $conf->contrat->dir_temp,
+							'ZIP_PROXY'		  => getDolGlobalString('MAIN_ODF_ZIP_PROXY', 'PclZipProxy'), // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
+							'DELIMITER_LEFT'  => '{',
+							'DELIMITER_RIGHT' => '}'
 						)
 					);
 				} catch (Exception $e) {

+ 16 - 2
htdocs/core/modules/contract/doc/pdf_strato.modules.php

@@ -7,6 +7,7 @@
  * Copyright (C) 2013-2020  Philippe Grand	            <philippe.grand@atoo-net.com>
  * Copyright (C) 2015       Marcos García               <marcosgdf@gmail.com>
  * Copyright (C) 2018-2020  Frédéric France             <frederic.france@netlogic.fr>
+ * Copyright (C) 2024		Éric Seigne             	<eric.seigne@cap-rel.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
@@ -259,11 +260,24 @@ class pdf_strato extends ModelePDFContract
 				$tab_top_newpage = (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD') ? 42 : 10);
 
 				// Display notes
-				if (!empty($object->note_public)) {
+				$notetoshow = empty($object->note_public) ? '' : $object->note_public;
+
+				// Extrafields in note
+				$extranote = $this->getExtrafieldsInHtml($object, $outputlangs);
+				if (!empty($extranote)) {
+					$notetoshow = dol_concatdesc($notetoshow, $extranote);
+				}
+
+				if (!empty($notetoshow)) {
 					$tab_top -= 2;
 
+					$substitutionarray = pdf_getSubstitutionArray($outputlangs, null, $object);
+					complete_substitutions_array($substitutionarray, $outputlangs, $object);
+					$notetoshow = make_substitutions($notetoshow, $substitutionarray, $outputlangs);
+					$notetoshow = convertBackOfficeMediasLinksToPublicLinks($notetoshow);
+
 					$pdf->SetFont('', '', $default_font_size - 1);
-					$pdf->writeHTMLCell(190, 3, $this->posxdesc - 1, $tab_top - 1, dol_htmlentitiesbr($object->note_public), 0, 1);
+					$pdf->writeHTMLCell(190, 3, $this->posxdesc - 1, $tab_top - 1, dol_htmlentitiesbr($notetoshow), 0, 1);
 					$nexY = $pdf->GetY();
 					$height_note = $nexY - $tab_top;
 

+ 4 - 4
htdocs/core/modules/expedition/doc/doc_generic_shipment_odt.modules.php

@@ -341,10 +341,10 @@ class doc_generic_shipment_odt extends ModelePdfExpedition
 					$odfHandler = new Odf(
 						$srctemplatepath,
 						array(
-						'PATH_TO_TMP'	  => $conf->expedition->dir_temp,
-						'ZIP_PROXY'		  => 'PclZipProxy', // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
-						'DELIMITER_LEFT'  => '{',
-						'DELIMITER_RIGHT' => '}'
+							'PATH_TO_TMP'	  => $conf->expedition->dir_temp,
+							'ZIP_PROXY'		  => getDolGlobalString('MAIN_ODF_ZIP_PROXY', 'PclZipProxy'), // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
+							'DELIMITER_LEFT'  => '{',
+							'DELIMITER_RIGHT' => '}'
 						)
 					);
 				} catch (Exception $e) {

+ 6 - 6
htdocs/core/modules/facture/doc/doc_generic_invoice_odt.modules.php

@@ -75,8 +75,8 @@ class doc_generic_invoice_odt extends ModelePDFFactures
 
 		$this->option_logo = 1; // Display logo
 		$this->option_tva = 0; // Manage the vat option FACTURE_TVAOPTION
-		$this->option_modereg = 0; // Display payment mode
-		$this->option_condreg = 0; // Display payment terms
+		$this->option_modereg = 1; // Display payment mode
+		$this->option_condreg = 1; // Display payment terms
 		$this->option_multilang = 1; // Available in several languages
 		$this->option_escompte = 0; // Displays if there has been a discount
 		$this->option_credit_note = 0; // Support credit notes
@@ -383,10 +383,10 @@ class doc_generic_invoice_odt extends ModelePDFFactures
 					$odfHandler = new Odf(
 						$srctemplatepath,
 						array(
-						'PATH_TO_TMP'	  => $conf->facture->dir_temp,
-						'ZIP_PROXY'		  => 'PclZipProxy', // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
-						'DELIMITER_LEFT'  => '{',
-						'DELIMITER_RIGHT' => '}'
+							'PATH_TO_TMP'	  => $conf->facture->dir_temp,
+							'ZIP_PROXY'		  => getDolGlobalString('MAIN_ODF_ZIP_PROXY', 'PclZipProxy'), // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
+							'DELIMITER_LEFT'  => '{',
+							'DELIMITER_RIGHT' => '}'
 						)
 					);
 				} catch (Exception $e) {

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

@@ -2011,7 +2011,7 @@ class pdf_crabe extends ModelePDFFactures
 				$posy += 4;
 				$pdf->SetXY($posx, $posy);
 				$pdf->SetTextColor(0, 0, 60);
-				$pdf->MultiCell($w, 3, $langs->transnoentities("SalesRepresentative")." : ".$usertmp->getFullName($langs), '', 'R');
+				$pdf->MultiCell($w, 3, $outputlangs->transnoentities("SalesRepresentative")." : ".$usertmp->getFullName($langs), '', 'R');
 			}
 		}
 

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

@@ -2443,7 +2443,7 @@ class pdf_sponge extends ModelePDFFactures
 				$posy += 4;
 				$pdf->SetXY($posx, $posy);
 				$pdf->SetTextColor(0, 0, 60);
-				$pdf->MultiCell($w, 3, $langs->transnoentities("SalesRepresentative")." : ".$usertmp->getFullName($langs), '', 'R');
+				$pdf->MultiCell($w, 3, $outputlangs->transnoentities("SalesRepresentative")." : ".$usertmp->getFullName($langs), '', 'R');
 			}
 		}
 
@@ -3026,7 +3026,7 @@ class pdf_sponge extends ModelePDFFactures
 		$this->cols['totalexcltax'] = array(
 			'rank' => $rank,
 			'width' => 26, // in mm
-			'status' => !getDolGlobalString('PDF_PROPAL_HIDE_PRICE_EXCL_TAX') ? true : false,
+			'status' => !getDolGlobalString('PDF_INVOICE_HIDE_PRICE_EXCL_TAX') ? true : false,
 			'title' => array(
 				'textkey' => 'TotalHTShort'
 			),
@@ -3037,7 +3037,7 @@ class pdf_sponge extends ModelePDFFactures
 		$this->cols['totalincltax'] = array(
 			'rank' => $rank,
 			'width' => 26, // in mm
-			'status' => !getDolGlobalString('PDF_PROPAL_SHOW_PRICE_INCL_TAX') ? false : true,
+			'status' => !getDolGlobalString('PDF_INVOICE_SHOW_PRICE_INCL_TAX') ? false : true,
 			'title' => array(
 				'textkey' => 'TotalTTCShort'
 			),

+ 1 - 1
htdocs/core/modules/member/doc/doc_generic_member_odt.class.php

@@ -312,7 +312,7 @@ class doc_generic_member_odt extends ModelePDFMember
 						$srctemplatepath,
 						array(
 							'PATH_TO_TMP'	  => $conf->adherent->dir_temp,
-							'ZIP_PROXY'		  => 'PclZipProxy', // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
+							'ZIP_PROXY'		  => getDolGlobalString('MAIN_ODF_ZIP_PROXY', 'PclZipProxy'), // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
 							'DELIMITER_LEFT'  => '{',
 							'DELIMITER_RIGHT' => '}'
 						)

+ 24 - 16
htdocs/core/modules/modSociete.class.php

@@ -523,10 +523,12 @@ class modSociete extends DolibarrModules
 		if (isModEnabled('socialnetworks')) {
 			$sql = "SELECT code, label FROM ".MAIN_DB_PREFIX."c_socialnetworks WHERE active = 1";
 			$resql = $this->db->query($sql);
-			while ($obj = $this->db->fetch_object($resql)) {
-				$fieldname = 's.socialnetworks_'.$obj->code;
-				$fieldlabel = ucfirst($obj->label);
-				$this->import_fields_array[$r][$fieldname] = $fieldlabel;
+			if ($resql) {
+				while ($obj = $this->db->fetch_object($resql)) {
+					$fieldname = 's.socialnetworks_'.$obj->code;
+					$fieldlabel = ucfirst($obj->label);
+					$this->import_fields_array[$r][$fieldname] = $fieldlabel;
+				}
 			}
 		}
 		// Add extra fields
@@ -695,10 +697,12 @@ class modSociete extends DolibarrModules
 		if (isModEnabled('socialnetworks')) {
 			$sql = "SELECT code, label FROM ".MAIN_DB_PREFIX."c_socialnetworks WHERE active = 1";
 			$resql = $this->db->query($sql);
-			while ($obj = $this->db->fetch_object($resql)) {
-				$fieldname = 's.socialnetworks_'.$obj->code;
-				$fieldlabel = ucfirst($obj->label);
-				$this->import_updatekeys_array[$r][$fieldname] = $fieldlabel;
+			if ($resql) {
+				while ($obj = $this->db->fetch_object($resql)) {
+					$fieldname = 's.socialnetworks_'.$obj->code;
+					$fieldlabel = ucfirst($obj->label);
+					$this->import_updatekeys_array[$r][$fieldname] = $fieldlabel;
+				}
 			}
 		}
 		// Add profids as criteria to search duplicates
@@ -759,10 +763,12 @@ class modSociete extends DolibarrModules
 		if (isModEnabled('socialnetworks')) {
 			$sql = "SELECT code, label FROM ".MAIN_DB_PREFIX."c_socialnetworks WHERE active = 1";
 			$resql = $this->db->query($sql);
-			while ($obj = $this->db->fetch_object($resql)) {
-				$fieldname = 's.socialnetworks_'.$obj->code;
-				$fieldlabel = ucfirst($obj->label);
-				$this->import_fields_array[$r][$fieldname] = $fieldlabel;
+			if ($resql) {
+				while ($obj = $this->db->fetch_object($resql)) {
+					$fieldname = 's.socialnetworks_'.$obj->code;
+					$fieldlabel = ucfirst($obj->label);
+					$this->import_fields_array[$r][$fieldname] = $fieldlabel;
+				}
 			}
 		}
 		// Add extra fields
@@ -837,10 +843,12 @@ class modSociete extends DolibarrModules
 		if (isModEnabled('socialnetworks')) {
 			$sql = "SELECT code, label FROM ".MAIN_DB_PREFIX."c_socialnetworks WHERE active = 1";
 			$resql = $this->db->query($sql);
-			while ($obj = $this->db->fetch_object($resql)) {
-				$fieldname = 's.socialnetworks_'.$obj->code;
-				$fieldlabel = ucfirst($obj->label);
-				$this->import_updatekeys_array[$r][$fieldname] = $fieldlabel;
+			if ($resql) {
+				while ($obj = $this->db->fetch_object($resql)) {
+					$fieldname = 's.socialnetworks_'.$obj->code;
+					$fieldlabel = ucfirst($obj->label);
+					$this->import_updatekeys_array[$r][$fieldname] = $fieldlabel;
+				}
 			}
 		}
 

+ 1 - 0
htdocs/core/modules/modTakePos.class.php

@@ -283,6 +283,7 @@ class modTakePos extends DolibarrModules
 				$societe->client = 1;
 				$societe->code_client = -1;
 				$societe->code_fournisseur = -1;
+				$societe->country_id = $mysoc->country_id ? $mysoc->country_id : 1; // By default we consider the default customer is in the same country than the company
 				$societe->note_private = "Default customer automaticaly created by Point Of Sale module activation. Can be used as the default generic customer in the Point Of Sale setup. Can also be edited or removed if you don't need a generic customer.";
 
 				$searchcompanyid = $societe->create($user);

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

@@ -264,7 +264,7 @@ class modTicket extends DolibarrModules
 			'type' => 'left',
 			'titre' => 'NewTicket',
 			'mainmenu' => 'ticket',
-			'url' => '/ticket/card.php?action=create',
+			'url' => '/ticket/card.php?action=create&mode=init',
 			'langs' => 'ticket',
 			'position' => 102,
 			'enabled' => 'isModEnabled("ticket")',

+ 1 - 1
htdocs/core/modules/mrp/doc/doc_generic_mo_odt.modules.php

@@ -327,7 +327,7 @@ class doc_generic_mo_odt extends ModelePDFMo
 						$srctemplatepath,
 						array(
 							'PATH_TO_TMP'	  => $conf->mrp->dir_temp,
-							'ZIP_PROXY'		  => 'PclZipProxy', // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
+							'ZIP_PROXY'		  => getDolGlobalString('MAIN_ODF_ZIP_PROXY', 'PclZipProxy'), // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
 							'DELIMITER_LEFT'  => '{',
 							'DELIMITER_RIGHT' => '}'
 						)

+ 1 - 1
htdocs/core/modules/product/doc/doc_generic_product_odt.modules.php

@@ -339,7 +339,7 @@ class doc_generic_product_odt extends ModelePDFProduct
 						$srctemplatepath,
 						array(
 							'PATH_TO_TMP'	  => $conf->product->dir_temp,
-							'ZIP_PROXY'		  => 'PclZipProxy', // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
+							'ZIP_PROXY'		  => getDolGlobalString('MAIN_ODF_ZIP_PROXY', 'PclZipProxy'), // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
 							'DELIMITER_LEFT'  => '{',
 							'DELIMITER_RIGHT' => '}'
 						)

+ 4 - 4
htdocs/core/modules/project/doc/doc_generic_project_odt.modules.php

@@ -614,10 +614,10 @@ class doc_generic_project_odt extends ModelePDFProjects
 					$odfHandler = new Odf(
 						$srctemplatepath,
 						array(
-						'PATH_TO_TMP'	  => $conf->project->dir_temp,
-						'ZIP_PROXY'		  => 'PclZipProxy', // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
-						'DELIMITER_LEFT'  => '{',
-						'DELIMITER_RIGHT' => '}'
+							'PATH_TO_TMP'	  => $conf->project->dir_temp,
+							'ZIP_PROXY'		  => getDolGlobalString('MAIN_ODF_ZIP_PROXY', 'PclZipProxy'), // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy.
+							'DELIMITER_LEFT'  => '{',
+							'DELIMITER_RIGHT' => '}'
 						)
 					);
 				} catch (Exception $e) {

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików