Compare commits

...

83 Commits

Author SHA1 Message Date
matthieu.saison
a5b2f746aa Add readme 2024-02-16 11:38:57 +01:00
matthieu.saison
e38606992a apply code review 2024-02-15 18:08:46 +01:00
matthieu.saison
c89f1b74dc [14.0][ADD] new module account_journal_display_type 2024-02-09 10:45:32 +01:00
Alexis de Lattre
f2172a5f06 base_usability: Add web-advanced_search_startswith.diff 2024-01-22 23:44:20 +01:00
Alexis de Lattre
e81e8e5a93 stock_usability: improve stock.move.line form view 2024-01-08 18:40:17 +01:00
beau sebastien
a7b0210a90 Merge pull request #202 from akretion/14.0-fix_pricelist_and_add_attr_value_tree
14.0 fix pricelist and add attr value tree
2023-12-21 06:22:21 +01:00
Sébastien BEAU
cd30298f88 product_usability: always show pricelist_id, just hide it when edit from pricelist 2023-12-20 16:07:26 +01:00
Alexis de Lattre
5039e56417 sale_order_route: propagate route_id to sale order lines.
This is important when you add lines AFTER sale order confirmation.
2023-12-01 10:25:31 +01:00
Alexis de Lattre
3c338c9c78 project_usability: add comment 2023-11-23 18:57:11 +01:00
Alexis de Lattre
2c87670281 sale_crm_usability: add comment 2023-11-23 18:51:53 +01:00
Alexis de Lattre
59e34d0166 product_priority_star: update module description 2023-11-23 12:29:52 +01:00
Kev-Roche
0e87b385d4 add pricelist by default from smart button 2023-11-20 13:26:01 +01:00
Kev-Roche
4a434d69f9 [ADD] values tree view on product attribute 2023-11-20 13:21:54 +01:00
Kévin Roche
bade674c41 Merge pull request #195 from akretion/14.0-remove-duplicated-button
14.0 remove duplicated button
2023-11-02 17:27:51 +01:00
Alexis de Lattre
def89c0a5d base_usability: now depend on 'web' and not only 'base' 2023-10-27 11:29:50 +02:00
Alexis de Lattre
88b12d86a2 base_partner_one2many_phone: reformat wizard now works 2023-10-25 23:02:43 +02:00
Alexis de Lattre
e3c55047b3 account_usability: pivot view of account.move.line
Pivot table of account.move.line: by default, date is split by month... but if you've been using Odoo for several years, the pivot table becomes very big by default: so we split by year
2023-10-25 21:51:43 +02:00
Alexis de Lattre
b701586edc base_usability: form view of partner: zip/city/state (instead of state/city/zip) 2023-10-25 21:38:06 +02:00
Alexis de Lattre
aae3ac2898 Add patch for pos_usability 2023-10-19 21:19:53 +02:00
Alexis de Lattre
8a2e23cff5 stock_usability: clean-up zero-qty quants when opening quants from stock.location
When accessing stock.move.line from the show reservation on quants, in
the form view of stock.move.line, have a link to the picking
2023-10-19 16:29:45 +02:00
Alexis de Lattre
0781cb7ba7 pos_usability: order pos.payment.method by sequence 2023-10-19 11:12:09 +02:00
Alexis de Lattre
a7022e899f translate product_category_tax 2023-10-16 22:36:16 +02:00
Alexis de Lattre
068509066d [FIX] stock_quant_package_move_wizard: missing picking_id on stock.move.line 2023-10-05 21:56:08 +02:00
Alexis de Lattre
e594d15417 base_partner_one2many_phone: improve search view of res.partner 2023-09-12 19:54:34 +02:00
Alexis de Lattre
4121d8466d pos_usability: add sequence on pos.config 2023-09-12 16:22:32 +02:00
Alexis de Lattre
6df9756479 base_partner_one2many_phone: code improvements 2023-09-06 23:14:06 +02:00
Alexis de Lattre
0331cc8eda [MIG] intrastat_product_type from v12 to v14 2023-08-31 11:00:02 +02:00
Alexis de Lattre
54bc3d8af8 Add module mrp_subcontracting_usability 2023-08-30 15:00:47 +02:00
Alexis de Lattre
15edfe5603 account_usability: add warning banner when blocked=True on invoice
Add "Dispute" filter in search view of invoices
2023-08-29 16:24:35 +02:00
Alexis de Lattre
8bf2116630 purchase_stock_usability: add picking_type_id in tree view (hide) 2023-08-28 14:47:45 +02:00
Alexis de Lattre
6b5282994a stock_usability: Add computed field is_dropship (needed to customize some reports) 2023-08-28 11:07:02 +02:00
Alexis de Lattre
993aab7d18 Fix string 2023-07-15 15:40:32 +02:00
Alexis de Lattre
73137ee1fb product_print_zpl_barcode: print multi labels at once
Print from product tree view and form view (product.product and product.template)
Print from done stock picking using must_print_barcode
2023-07-15 15:29:02 +02:00
Alexis de Lattre
c4ec388380 stock_usability: Add supplier in orderpoint list view
Hide unneeded "Automate orders" in orderpoint list view
product_usability: add seller_id in tree/search view of product.product and product.template
2023-07-14 15:41:02 +02:00
Alexis de Lattre
8afcd49bc3 purchase_stock_usability: add supplier in tree view
stock_usability: allow to hide preferred route
2023-07-14 14:41:06 +02:00
Alexis de Lattre
1e8f00d4e1 pos_usability: add product_id in pos.order.line form view 2023-07-13 11:55:19 +02:00
Hpar
061efb2197 Merge pull request #198 from akretion/14.0-fix-stock-valuation-layer-multi-company
stock_valuation_xlsx: fix multicompany issues
2023-06-15 10:15:51 +02:00
hparfr
e698746dd1 stock_valuation_xlsx: fix multicompany issues
fix: ref('stock.warehouse0') may belong to a different
company
add: allow to select stock parent location like physical
in order to export all the wh at once
2023-06-15 09:31:33 +02:00
Alexis de Lattre
d2bc7be9fe purchase_usability: don't write ref on account.move, because ref is for the supplier invoice number 2023-06-12 23:24:25 +02:00
Alexis de Lattre
5f6b731e50 account_usability: add blocked on account.move 2023-06-12 23:11:24 +02:00
Alexis de Lattre
6c81ade7d0 account_usability: implement alternative to account_invoice_supplier_ref_unique that keeps the original Odoo datamodel 2023-06-12 14:20:16 +02:00
Alexis de Lattre
209de4f6db Add module project_usability 2023-06-12 12:32:48 +02:00
Alexis de Lattre
fd31627fa6 account_usability: add link from/to reverse journal entry in chatter 2023-05-16 19:43:40 +02:00
Alexis de Lattre
2863de99f4 Add module stock_move_line_auto_fill_all
Same as the OCA module stock_move_line_auto_fill but applies on all
moves lines (including lines with lot)
2023-04-28 12:49:37 +02:00
Alexis de Lattre
16ed41187f purchase_usability: improve purchase.report tree view 2023-04-25 19:21:56 +02:00
Hpar
7129dd1cce Merge pull request #193 from akretion/14.0-add-setup
[CI] add setup/account_usability
2023-04-12 12:18:43 +02:00
Sébastien BEAU
2d3a792ce8 sale_usability: remove duplicated button 2023-04-04 12:34:06 +02:00
David Beal
d2cf9b73d8 FIX dev_menu: black 2023-03-23 13:55:37 +01:00
David Beal
d6cf5f82e7 Update menu_view.xml 2023-03-23 13:46:55 +01:00
Alexis de Lattre
e9350bac57 account_usability: reversal wizard: don't set D+1 default date when we try to generate a refund 2023-03-21 14:29:32 +01:00
Raphaël Valyi
2e267d717f [CI] add some setup/ modules 2023-03-10 01:43:39 -03:00
Alexis de Lattre
e8b43b67c1 account_bank_reconciliation_summary_xlsx: fix logic error in reconcile filter 2023-03-09 23:30:13 +01:00
Alexis de Lattre
f2b5b0b4dd account_usability: remove acc_type from res.partner.bank tree view
I now recommand the use of partner_bank_acc_type_constraint from https://github.com/OCA/partner-contact/pull/1474
2023-03-09 23:16:16 +01:00
Alexis de Lattre
a64c60a540 product_print_zpl_barcode: fix warning and improve code 2023-03-01 23:25:45 +01:00
Alexis de Lattre
b48db5492d Add module stock_quant_package_move_wizard 2023-02-21 15:10:38 +01:00
Alexis de Lattre
9026660416 product_category_tax: add field in constraint 2023-02-13 22:24:05 +01:00
Alexis de Lattre
d2a9f953b9 account_usability: Add confirm pop-up on 'reset to new' button 2023-01-23 17:23:46 +01:00
Alexis de Lattre
acff0a421d product_print_zpl_barcode: 2 fields: png and svg 2023-01-23 09:14:44 +01:00
Alexis de Lattre
64f54a9389 product_print_zpl_barcode: Add field barcode_image for EAN13 and EAN8 on product.product 2023-01-22 22:39:08 +01:00
Alexis de Lattre
08db759977 mail_usability: improve mail_activity views 2023-01-20 19:31:11 +01:00
Alexis de Lattre
e07df6b45a mail_usability: Improve view of mail.activity 2023-01-20 12:02:43 +01:00
Alexis de Lattre
fc58a9adf5 Add module product_detailed_type and product_detailed_type_stock 2023-01-20 12:02:24 +01:00
Alexis de Lattre
1d463a744d Add module product_priority_star 2023-01-20 12:01:57 +01:00
Alexis de Lattre
dbad21c13a Add module sale_crm_usability 2023-01-19 17:15:56 +01:00
Alexis de Lattre
aa16b70bdd Add module account_bank_reconciliation_summary_xlsx 2023-01-13 11:33:48 +01:00
Alexis de Lattre
eff63f2be2 crm_usability: CRM>Sales>My activity should not be limited to Sales Manager 2023-01-03 10:36:13 +01:00
Alexis de Lattre
937595ca2c product_usability: use ondelete='restrict' instead of 'cascade' on supplierinfo/partner_id 2022-12-12 21:45:12 +01:00
Raphaël Valyi
9397bb9fce Merge pull request #189 from akretion/14.0-style-fix
[14.0][account_usability] style fix
2022-12-06 11:42:11 -03:00
Raphaël Valyi
390ce75827 [FIX] 4 chars indent + reordered imports 2022-12-05 20:04:50 -03:00
Raphaël Valyi
819d145763 Merge pull request #188 from akretion/14.0-fix-account_invoice_update_wizard_payment_mode
[14.0][FIX] account_invoice_update_wizard_payment_mode view dependency
2022-11-29 17:15:26 -03:00
clementmbr
45bbcc0cb3 [FIX] account_invoice_update_wizard_payment_mode view dependency 2022-11-29 16:58:25 -03:00
Hpar
6ddc1b86d5 Merge pull request #187 from akretion/eradicate-quick-create-fix-hook
eradicate_quickreate: fix hook
2022-11-07 14:54:29 +01:00
Alexis de Lattre
d0b315f648 [MIG] sale_quotation_title to v14 2022-11-07 12:41:08 +01:00
Hpar
f1eeaa2e8a eradicate_quickreate: fix hook
if the record already exists and is False, we shouldn't create a new record
2022-10-31 15:44:37 +01:00
beau sebastien
553f05c58f Merge pull request #186 from akretion/14.0-add-sale-show-transaction
add sale_show_transaction, that make transaction really visible on sale order
2022-10-28 09:20:27 +02:00
Alexis de Lattre
50ae5dc9cb purchase_usability: remove code now that upstream fix is merged 2022-10-27 23:17:34 +02:00
beau sebastien
9c7775dabb Merge pull request #179 from akretion/14.0-imp-account_invoice_update_wizard
account_invoice_update_wizard: improve UI
2022-10-27 23:13:17 +02:00
Sébastien BEAU
70f1f13edd account_invoice_update_wizard: inactive update of broken payment term
We should add test and fix it
2022-10-27 23:12:02 +02:00
Sébastien BEAU
989960c7c8 account_invoice_update_wizard: fix UI add translation
Add section support and keep order based on the line sequence
Add Fr translation
2022-10-27 23:12:02 +02:00
Sébastien BEAU
9b186028c3 account_usability: aml search on move_id first, then on name/ref 2022-10-27 23:00:22 +02:00
Sébastien BEAU
c2c4957686 account_usability: improve partial reconcile matching_number 2022-10-27 23:00:22 +02:00
Sébastien BEAU
d382aea22f sale_show_transaction: better naming 2022-10-21 22:48:28 +02:00
Sébastien BEAU
688a07fc5e add sale_show_transaction, that make transaction really visible on sale order 2022-10-21 16:30:39 +02:00
167 changed files with 4892 additions and 362 deletions

View File

@@ -0,0 +1,27 @@
===============================
Bank Reconciliation Report XLSX
===============================
In Odoo v13+, a bank reconciliation report is not really needed because all the payments executed that are not debited/credited on the bank account are in separate waiting accounts. But accountants want a bank reconciliation report, so this module adds one, even if it is quite different from a classic bank reconciliation report.
Configuration
=============
This module doesn't require any configuration.
Usage
=====
You can launch the Bank Reconciliation Report wizard from:
* the menu *Accounting > Reports > Bank > Bank Reconciliation*,
* the invoicing dashboard: on a bank journal, click on the options, then select *Bank Reconciliation*.
* the form view of a bank statement: click on the button *Bank Reconciliation Report*.
Credits
=======
Contributors
------------
* Alexis de Lattre <alexis.delattre@akretion.com>

View File

@@ -0,0 +1,2 @@
from . import report
from . import wizard

View File

@@ -0,0 +1,21 @@
# Copyright 2017-2023 Akretion France (http://www.akretion.com/)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Bank Reconciliation Report",
"version": "14.0.1.0.0",
"license": "AGPL-3",
"author": "Akretion",
"website": "https://github.com/akretion/odoo-usability",
"summary": "Bank reconciliation XLSX report",
"depends": ["account", "report_xlsx"],
"data": [
"report/report.xml",
"wizard/bank_reconciliation_report_wizard_view.xml",
"views/account_bank_statement.xml",
"views/account_journal.xml",
"security/ir.model.access.csv",
],
"installable": True,
}

View File

@@ -0,0 +1,213 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * account_bank_reconciliation_summary_xlsx
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-01-13 10:31+0000\n"
"PO-Revision-Date: 2023-01-13 10:31+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Amount"
msgstr "Montant"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Balance %s:"
msgstr "Solde %s :"
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_bank_reconciliation_report_wizard__journal_ids
msgid "Bank Journals"
msgstr "Journaux de banque"
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.actions.act_window,name:account_bank_reconciliation_summary_xlsx.bank_reconciliation_report_wizard_action
#: model:ir.ui.menu,name:account_bank_reconciliation_summary_xlsx.bank_reconciliation_report_wizard_menu
#: model_terms:ir.ui.view,arch_db:account_bank_reconciliation_summary_xlsx.account_journal_dashboard_kanban_view
msgid "Bank Reconciliation"
msgstr "Rapprochement bancaire"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#: model_terms:ir.ui.view,arch_db:account_bank_reconciliation_summary_xlsx.view_bank_statement_form
#, python-format
msgid "Bank Reconciliation Report"
msgstr "Rapport de rapprochement bancaire"
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model,name:account_bank_reconciliation_summary_xlsx.model_bank_reconciliation_report_wizard
msgid "Bank Reconciliation Report Wizard"
msgstr "Assistant rapport de rapprochement bancaire"
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.actions.report,name:account_bank_reconciliation_summary_xlsx.bank_reconciliation_xlsx
msgid "Bank Reconciliation XLSX"
msgstr "Rapprochement bancaire XLSX"
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model,name:account_bank_reconciliation_summary_xlsx.model_report_bank_reconciliation_xlsx
msgid "Bank Reconciliation XLSX Report"
msgstr "Rapport de rapprochement bancaire XLSX"
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.ui.menu,name:account_bank_reconciliation_summary_xlsx.menu_report_bank_root
msgid "Bank Reports"
msgstr "Rapports bancaires"
#. module: account_bank_reconciliation_summary_xlsx
#: model_terms:ir.ui.view,arch_db:account_bank_reconciliation_summary_xlsx.bank_reconciliation_report_wizard_form
msgid "Cancel"
msgstr "Annuler"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_bank_reconciliation_report_wizard__company_id
#, python-format
msgid "Company"
msgstr "Société"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Counter-part"
msgstr "Contre partie"
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_bank_reconciliation_report_wizard__create_uid
msgid "Created by"
msgstr "Créé par"
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_bank_reconciliation_report_wizard__create_date
msgid "Created on"
msgstr "Créé le"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_bank_reconciliation_report_wizard__date
#, python-format
msgid "Date"
msgstr ""
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_bank_reconciliation_report_wizard__display_name
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_report_bank_reconciliation_xlsx__display_name
msgid "Display Name"
msgstr ""
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model.fields.selection,name:account_bank_reconciliation_summary_xlsx.selection__bank_reconciliation_report_wizard__move_state__draft_posted
msgid "Draft and Posted Entries"
msgstr "Écritures brouillon et comptabilisées"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_bank_reconciliation_report_wizard__move_state
#, python-format
msgid "Entries"
msgstr "Écritures"
#. module: account_bank_reconciliation_summary_xlsx
#: model_terms:ir.ui.view,arch_db:account_bank_reconciliation_summary_xlsx.bank_reconciliation_report_wizard_form
msgid "Export XLSX"
msgstr ""
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Generated on %s"
msgstr "Généré le %s"
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_bank_reconciliation_report_wizard__id
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_report_bank_reconciliation_xlsx__id
msgid "ID"
msgstr ""
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Journal"
msgstr ""
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Label"
msgstr "Libellé"
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_bank_reconciliation_report_wizard____last_update
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_report_bank_reconciliation_xlsx____last_update
msgid "Last Modified on"
msgstr ""
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_bank_reconciliation_report_wizard__write_uid
msgid "Last Updated by"
msgstr ""
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model.fields,field_description:account_bank_reconciliation_summary_xlsx.field_bank_reconciliation_report_wizard__write_date
msgid "Last Updated on"
msgstr ""
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Move Number"
msgstr "Numéro de pièce"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "No bank journal selected."
msgstr "Aucun journal de banque sélectionné."
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "None"
msgstr "Aucun"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Partner"
msgstr "Partenaire"
#. module: account_bank_reconciliation_summary_xlsx
#: model:ir.model.fields.selection,name:account_bank_reconciliation_summary_xlsx.selection__bank_reconciliation_report_wizard__move_state__posted
msgid "Posted Entries"
msgstr "Écritures comptabilisées"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Ref."
msgstr "Réf."
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "Sub-total:"
msgstr "Sous-total :"
#. module: account_bank_reconciliation_summary_xlsx
#: code:addons/account_bank_reconciliation_summary_xlsx/report/bank_reconciliation_xlsx.py:0
#, python-format
msgid "TOTAL:"
msgstr "TOTAL :"

View File

@@ -0,0 +1 @@
from . import bank_reconciliation_xlsx

View File

@@ -0,0 +1,296 @@
# Copyright 2017-2023 Akretion France (http://www.akretion.com/)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, models
from odoo.exceptions import UserError
from datetime import datetime
from odoo.tools.misc import format_datetime
import pytz
class BankReconciliationXlsx(models.AbstractModel):
_name = "report.bank.reconciliation.xlsx"
_description = "Bank Reconciliation XLSX Report"
_inherit = "report.report_xlsx.abstract"
def _domain_add_move_state(self, wizard, domain):
if wizard.move_state == 'posted':
domain.append(('parent_state', '=', 'posted'))
elif wizard.move_state == 'draft_posted':
domain.append(('parent_state', 'in', ('draft', 'posted')))
def _get_account_balance(self, account, wizard):
domain = [
('account_id', '=', account.id),
('date', '<=', wizard.date),
('company_id', '=', wizard.company_id.id),
]
self._domain_add_move_state(wizard, domain)
res_rg = self.env['account.move.line'].read_group(domain, ['balance:sum'], [])
account_bal = res_rg and res_rg[0].get('balance', 0.0) or 0.0
return account_bal
def _prepare_payment_move_lines(self, journal, account, wizard, unreconciled_only=True):
domain = [
("company_id", "=", wizard.company_id.id),
("account_id", "=", account.id),
("journal_id", "=", journal.id),
("date", "<=", wizard.date),
]
if unreconciled_only:
limit_datetime_naive = datetime.combine(wizard.date, datetime.max.time())
tz = pytz.timezone(self.env.user.tz)
limit_datetime_aware = tz.localize(limit_datetime_naive)
limit_datetime_utc = limit_datetime_aware.astimezone(pytz.utc)
limit_datetime = limit_datetime_utc.replace(tzinfo=None)
domain += [
'|', ('full_reconcile_id', '=', False),
('full_reconcile_id.create_date', '>', limit_datetime)]
self._domain_add_move_state(wizard, domain)
mlines = self.env["account.move.line"].search(domain)
res = []
for mline in mlines:
move = mline.move_id
cpart = []
for line in move.line_ids:
if (
line.account_id != account
and line.account_id.code not in cpart
):
cpart.append(line.account_id.code)
counterpart = " ,".join(cpart)
res.append(
{
"date": mline.date,
"ref": move.ref or "",
"label": mline.name,
"partner": mline.partner_id.display_name or "",
"amount": mline.balance,
"move_name": move.name,
"counterpart": counterpart,
}
)
return res
def _write_move_lines_block(self, jdi, row, account, add2total=True):
sheet = jdi['sheet']
style = jdi['style']
style_suffix = not add2total and '_warn' or ''
subtotal = 0.0
mlines = self._prepare_payment_move_lines(jdi['journal'], account, jdi['wizard'])
if mlines or add2total:
sheet.write(row, 0, '%s %s' % (account.name, account.code), style['title' + style_suffix])
sheet.write(row, 1, "", style['title' + style_suffix])
if not mlines:
if add2total:
sheet.write(row, 2, _("None"), style['none'])
else:
return
else:
row += 1
col_labels = [
_("Date"),
_("Partner"),
_("Amount"),
_("Move Number"),
_("Counter-part"),
_("Ref."),
_("Label"),
]
col = 0
for col_label in col_labels:
sheet.write(row, col, col_label, style['col_header'])
col += 1
row += 1
start_line = row + 1
for mline in mlines:
sheet.write(row, 0, mline["date"], style['regular_date'])
sheet.write(row, 1, mline["partner"], style['regular'])
sheet.write(row, 2, mline["amount"], style['currency'])
sheet.write(row, 3, mline["move_name"], style['regular'])
sheet.write(row, 4, mline["counterpart"], style['regular'])
sheet.write(row, 5, mline["ref"], style['regular'])
sheet.write(row, 6, mline["label"], style['regular'])
subtotal += mline["amount"]
row += 1
end_line = row
for col in range(1):
sheet.write(row, col, "", style['title' + style_suffix])
sheet.write(row, 1, _("Sub-total:") + ' ', style['title_right' + style_suffix])
formula = '=SUM(%s%d:%s%d)' % (
jdi['total_col'], start_line, jdi['total_col'], end_line)
sheet.write_formula(row, 2, formula, style['currency_bg' + style_suffix], subtotal)
if add2total:
jdi['total'] += subtotal
jdi['total_formula'] += '+%s%d' % (jdi['total_col'], row + 1)
return row
def generate_xlsx_report(self, workbook, data, wizard):
if not wizard.journal_ids:
raise UserError(_("No bank journal selected."))
date_dt = wizard.date
company = wizard.company_id
style = self._get_style(workbook, company)
move_state_label = dict(
wizard.fields_get('move_state', 'selection')['move_state']['selection'])
generated_on_label = _('Generated on %s') % format_datetime(
self.env, datetime.utcnow())
for journal in wizard.journal_ids:
row = 0
sheet = workbook.add_worksheet(journal.code or journal.name)
jdi = {
'wizard': wizard,
'journal': journal,
'style': style,
'sheet': sheet,
'total': 0.0,
'total_formula': '=',
'total_col': 'C',
}
sheet.write(
row,
0,
_("Bank Reconciliation Report"),
style['doc_title'],
)
row += 1
sheet.write(row, 0, generated_on_label, style['small'])
sheet.set_row(0, 26)
sheet.set_column(0, 0, 10)
sheet.set_column(1, 1, 35)
sheet.set_column(2, 2, 15)
sheet.set_column(3, 3, 15)
sheet.set_column(4, 4, 25)
sheet.set_column(5, 5, 30)
sheet.set_column(6, 6, 60)
row += 3
sheet.write(row, 0, _("Company"), style['wizard_field'])
sheet.write(row, 1, wizard.company_id.display_name, style['wizard_value'])
row += 1
sheet.write(row, 0, _("Date"), style['wizard_field'])
sheet.write(row, 1, date_dt, style['wizard_value_date'])
row += 1
sheet.write(row, 0, _("Journal"), style['wizard_field'])
sheet.write(row, 1, journal.display_name, style['wizard_value'])
row += 1
sheet.write(row, 0, _("Entries"), style['wizard_field'])
sheet.write(row, 1, move_state_label[wizard.move_state], style['wizard_value'])
# 1) Show balance of bank account
row += 3
bank_account = journal.default_account_id
for col in range(1):
sheet.write(row, col, "", style['title'])
sheet.write(row, 1, _("Balance %s:") % bank_account.code + ' ', style['title_right'])
account_bal = self._get_account_balance(bank_account, wizard)
sheet.write(row, 2, account_bal, style['currency_bg'])
jdi['total'] += account_bal
jdi['total_formula'] += '%s%d' % (jdi['total_col'], row + 1)
row += 2
# 2) Show payment lines IN (debit)
debit_account = journal.payment_debit_account_id
row = self._write_move_lines_block(jdi, row, debit_account)
row += 2
# 3) Show payment lines OUT (credit)
credit_account = journal.payment_credit_account_id
row = self._write_move_lines_block(jdi, row, credit_account)
row += 2
for col in range(1):
sheet.write(row, col, "", style['title'])
sheet.write(row, 1, _("TOTAL:") + ' ', style['title_right'])
sheet.write_formula(
row, 2, jdi['total_formula'], style['currency_bg'], jdi['total'])
row += 3
# 4) Show suspense account lines
row = self._write_move_lines_block(
jdi, row, journal.suspense_account_id, add2total=False)
def _get_style(self, workbook, company):
style = {}
font_size = 10
light_grey = "#eeeeee"
title_blue = "#e6e6fa"
subtotal_orange = "#ffcc00"
title_warn = "#ff9999"
subtotal_warn = "#ffff99"
light_purple = "#ffdeff"
lang_code = self.env.user.lang
lang = False
if lang_code:
lang = self.env["res.lang"].search([("code", "=", lang_code)])
if not lang:
lang = self.env["res.lang"].search([], limit=1)
xls_date_format = (
lang.date_format.replace("%Y", "yyyy")
.replace("%m", "mm")
.replace("%d", "dd")
.replace("%y", "yy")
)
style['doc_title'] = workbook.add_format(
{"bold": True, "font_size": font_size + 4})
style['small'] = workbook.add_format({"font_size": font_size - 3})
style['col_header'] = workbook.add_format(
{
"bold": True,
"bg_color": light_grey,
"text_wrap": True,
"font_size": font_size,
"align": "center",
}
)
title_style = {
"bold": True,
"bg_color": title_blue,
"font_size": font_size,
"align": "left",
}
style['title_right'] = workbook.add_format(dict(title_style, align="right"))
style['title'] = workbook.add_format(dict(title_style))
style['wizard_field'] = workbook.add_format(dict(title_style, bg_color=light_grey))
wizard_value_style = {
"bg_color": light_purple,
"bold": True,
"font_size": font_size,
"align": "left",
}
style['wizard_value'] = workbook.add_format(wizard_value_style)
style['wizard_value_date'] = workbook.add_format(
dict(wizard_value_style, num_format=xls_date_format))
style['none'] = workbook.add_format(
{"bold": True, "font_size": font_size, "align": "right", "bg_color": subtotal_orange}
)
# WARN for suspense account
style['title_warn'] = workbook.add_format(
dict(title_style, align="left", bg_color=title_warn))
style['title_right_warn'] = workbook.add_format(
dict(title_style, align="right", bg_color=title_warn))
style['regular'] = workbook.add_format({"font_size": font_size})
if "%" in xls_date_format:
# fallback
xls_date_format = "yyyy-mm-dd"
style['regular_date'] = workbook.add_format(
{"num_format": xls_date_format, "font_size": font_size, "align": "left"}
)
cur_format = "#,##0.00 %s" % (
company.currency_id.symbol or company.currency_id.name
)
# It seems that Excel replaces automatically the decimal
# and thousand separator by those of the language under which
# Excel runs
currency_style = {"num_format": cur_format, "font_size": font_size}
style['currency'] = workbook.add_format(currency_style)
style['currency_bg'] = workbook.add_format(
dict(currency_style, bg_color=subtotal_orange))
style['currency_bg_warn'] = workbook.add_format(
dict(currency_style, bg_color=subtotal_warn))
return style

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2017-2023 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="bank_reconciliation_xlsx" model="ir.actions.report">
<field name="name">Bank Reconciliation XLSX</field>
<field name="model">bank.reconciliation.report.wizard</field>
<field name="report_type">xlsx</field>
<field name="report_name">bank.reconciliation.xlsx</field>
<field name="report_file">bank.reconciliation.xlsx</field>
<!-- print_report_name doesn't work here... -->
<field name="print_report_name">'bank_reconciliation-%s' % (object.date)</field>
</record>
</odoo>

View File

@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_bank_reconciliation_report_wizard_user,Full access on bank.reconciliation.report.wizard,model_bank_reconciliation_report_wizard,account.group_account_user,1,1,1,1
access_bank_reconciliation_report_wizard_readonly,Full access on bank.reconciliation.report.wizard,model_bank_reconciliation_report_wizard,account.group_account_readonly,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_bank_reconciliation_report_wizard_user Full access on bank.reconciliation.report.wizard model_bank_reconciliation_report_wizard account.group_account_user 1 1 1 1
3 access_bank_reconciliation_report_wizard_readonly Full access on bank.reconciliation.report.wizard model_bank_reconciliation_report_wizard account.group_account_readonly 1 1 1 1

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2017-2020 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_bank_statement_form" model="ir.ui.view">
<field name="name">bank_rec_summary.account.bank.statement.form</field>
<field name="model">account.bank.statement</field>
<field name="inherit_id" ref="account.view_bank_statement_form" />
<field name="arch" type="xml">
<button name="button_reprocess" position="after">
<button
name="%(bank_reconciliation_report_wizard_action)d"
type="action"
string="Bank Reconciliation Report"
context="{'default_journal_ids': [journal_id]}"
/>
</button>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2018-2023 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<!-- Accounting Dashboard -->
<record id="account_journal_dashboard_kanban_view" model="ir.ui.view">
<field
name="name"
>bank_reconciliation_summarry.account_journal_dashboard</field>
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.account_journal_dashboard_kanban_view" />
<field name="arch" type="xml">
<xpath expr="//a[@name='open_collect_money']/.." position="before">
<div name="bank_reconciliation_report">
<a
role="menuitem"
type="action"
name="%(bank_reconciliation_report_wizard_action)d"
context="{'default_journal_ids': [active_id]}"
>Bank Reconciliation</a>
</div>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1 @@
from . import bank_reconciliation_report_wizard

View File

@@ -0,0 +1,42 @@
# Copyright 2017-2023 Akretion France (http://www.akretion.com/)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class BankReconciliationReportWizard(models.TransientModel):
_name = "bank.reconciliation.report.wizard"
_description = "Bank Reconciliation Report Wizard"
_check_company_auto = True
company_id = fields.Many2one(
'res.company', string='Company',
ondelete='cascade', required=True,
default=lambda self: self.env.company)
date = fields.Date(required=True, default=fields.Date.context_today)
move_state = fields.Selection(
[("posted", "Posted Entries"), ("draft_posted", "Draft and Posted Entries")],
string="Entries",
required=True,
default="posted",
)
journal_ids = fields.Many2many(
"account.journal",
string="Bank Journals",
domain="[('type', '=', 'bank'), ('company_id', '=', company_id)]",
required=True,
check_company=True,
default=lambda self: self._default_journal_ids(),
)
@api.model
def _default_journal_ids(self):
journals = self.env["account.journal"].search(
[
("type", "=", "bank"),
("bank_account_id", "!=", False),
("company_id", "=", self.env.company.id),
]
)
return journals

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2017-2023 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="bank_reconciliation_report_wizard_form" model="ir.ui.view">
<field name="name">bank.reconciliation.report.wizard.form</field>
<field name="model">bank.reconciliation.report.wizard</field>
<field name="arch" type="xml">
<form>
<group name="main">
<field name="company_id" invisible="1" />
<field name="date" />
<field name="journal_ids" widget="many2many_tags" options="{'no_open': True, 'no_create': True}"/>
<field name="move_state" widget="radio"/>
</group>
<footer>
<button
name="%(account_bank_reconciliation_summary_xlsx.bank_reconciliation_xlsx)d"
string="Export XLSX"
type="action"
class="btn-primary"
/>
<button special="cancel" string="Cancel" />
</footer>
</form>
</field>
</record>
<record id="bank_reconciliation_report_wizard_action" model="ir.actions.act_window">
<field name="name">Bank Reconciliation</field>
<field name="res_model">bank.reconciliation.report.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem
id="menu_report_bank_root"
parent="account.menu_finance_reports"
name="Bank Reports"
sequence="12"
/>
<menuitem
id="bank_reconciliation_report_wizard_menu"
action="bank_reconciliation_report_wizard_action"
parent="menu_report_bank_root"
sequence="10"
/>
</odoo>

View File

@@ -0,0 +1,243 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * account_invoice_update_wizard
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__price_subtotal
msgid "Amount"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__analytic_account_id
msgid "Analytic Account"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__analytic_tag_ids
msgid "Analytic Tags"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__partner_bank_id
msgid "Bank Account"
msgstr ""
#. module: account_invoice_update_wizard
#: model_terms:ir.ui.view,arch_db:account_invoice_update_wizard.account_invoice_update_form
msgid "Bill Reference"
msgstr ""
#. module: account_invoice_update_wizard
#: model_terms:ir.ui.view,arch_db:account_invoice_update_wizard.account_invoice_update_form
msgid "Cancel"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__company_id
msgid "Company"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__create_uid
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__create_uid
msgid "Created by"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__create_date
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__create_date
msgid "Created on"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__currency_id
msgid "Currency"
msgstr ""
#. module: account_invoice_update_wizard
#: model_terms:ir.ui.view,arch_db:account_invoice_update_wizard.account_invoice_update_form
msgid "Customer Reference"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__name
msgid "Description"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move__display_name
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__display_name
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__display_name
msgid "Display Name"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__display_type
msgid "Display Type"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move__id
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__id
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__id
msgid "ID"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__invoice_id
msgid "Invoice"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__invoice_line_id
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__line_ids
msgid "Invoice Lines"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.actions.act_window,name:account_invoice_update_wizard.account_invoice_update_action
msgid "Invoice Update Wizard"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model,name:account_invoice_update_wizard.model_account_move
msgid "Journal Entry"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move____last_update
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update____last_update
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update____last_update
msgid "Last Modified on"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__write_uid
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__write_uid
msgid "Last Updated by"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__write_date
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__write_date
msgid "Last Updated on"
msgstr ""
#. module: account_invoice_update_wizard
#: code:addons/account_invoice_update_wizard/wizard/account_move_update.py:0
#, python-format
msgid "Non-legal fields of invoice updated via the Invoice Update wizard."
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields.selection,name:account_invoice_update_wizard.selection__account_move_line_update__display_type__line_note
msgid "Note"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__partner_id
msgid "Partner"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__invoice_payment_term_id
msgid "Payment Term"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__quantity
msgid "Quantity"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__ref
msgid "Reference"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__user_id
msgid "Salesperson"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields.selection,name:account_invoice_update_wizard.selection__account_move_line_update__display_type__line_section
msgid "Section"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__sequence
msgid "Sequence"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__invoice_origin
msgid "Source Document"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,help:account_invoice_update_wizard.field_account_move_line_update__display_type
msgid "Technical field for UX purpose."
msgstr ""
#. module: account_invoice_update_wizard
#: code:addons/account_invoice_update_wizard/wizard/account_move_update.py:0
#, python-format
msgid ""
"The original payment term '%s' doesn't have the same terms (number of terms "
"and/or amount) as the new payment term '%s'. You can only switch to a "
"payment term that has the same number of terms with the same amount."
msgstr ""
#. module: account_invoice_update_wizard
#: code:addons/account_invoice_update_wizard/wizard/account_move_update.py:0
#, python-format
msgid ""
"This wizard doesn't support the update of payment terms on an invoice which "
"is partially or fully paid."
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__move_type
msgid "Type"
msgstr ""
#. module: account_invoice_update_wizard
#: model_terms:ir.ui.view,arch_db:account_invoice_update_wizard.account_invoice_update_form
msgid "Update"
msgstr ""
#. module: account_invoice_update_wizard
#: model_terms:ir.ui.view,arch_db:account_invoice_update_wizard.view_move_form_inherit
msgid "Update Invoice"
msgstr ""
#. module: account_invoice_update_wizard
#: model_terms:ir.ui.view,arch_db:account_invoice_update_wizard.account_invoice_update_form
msgid "Update Invoice Wizard"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model,name:account_invoice_update_wizard.model_account_move_line_update
msgid "Update non-legal fields of invoice lines"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__parent_id
msgid "Wizard"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model,name:account_invoice_update_wizard.model_account_move_update
msgid "Wizard to update non-legal fields of invoice"
msgstr ""

View File

@@ -0,0 +1,250 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * account_invoice_update_wizard
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__price_subtotal
msgid "Amount"
msgstr "Montant"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__analytic_account_id
msgid "Analytic Account"
msgstr "Compte Analytique"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__analytic_tag_ids
msgid "Analytic Tags"
msgstr "Tag Analytique"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__partner_bank_id
msgid "Bank Account"
msgstr "Compte Bancaire"
#. module: account_invoice_update_wizard
#: model_terms:ir.ui.view,arch_db:account_invoice_update_wizard.account_invoice_update_form
#, fuzzy
msgid "Bill Reference"
msgstr "Reference Client"
#. module: account_invoice_update_wizard
#: model_terms:ir.ui.view,arch_db:account_invoice_update_wizard.account_invoice_update_form
msgid "Cancel"
msgstr "Annuler"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__company_id
msgid "Company"
msgstr "Société"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__create_uid
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__create_uid
msgid "Created by"
msgstr "Créé par"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__create_date
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__create_date
msgid "Created on"
msgstr "Créé le"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__currency_id
msgid "Currency"
msgstr "Devise"
#. module: account_invoice_update_wizard
#: model_terms:ir.ui.view,arch_db:account_invoice_update_wizard.account_invoice_update_form
#, fuzzy
msgid "Customer Reference"
msgstr "Reference Client"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__name
msgid "Description"
msgstr "Description"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move__display_name
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__display_name
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__display_name
msgid "Display Name"
msgstr "Nom"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__display_type
msgid "Display Type"
msgstr "Type Affichage"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move__id
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__id
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__id
msgid "ID"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__invoice_id
msgid "Invoice"
msgstr "Facture"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__invoice_line_id
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__line_ids
msgid "Invoice Lines"
msgstr "Ligne de factures"
#. module: account_invoice_update_wizard
#: model:ir.actions.act_window,name:account_invoice_update_wizard.account_invoice_update_action
msgid "Invoice Update Wizard"
msgstr "Assistance de mise à jour de la facture"
#. module: account_invoice_update_wizard
#: model:ir.model,name:account_invoice_update_wizard.model_account_move
msgid "Journal Entry"
msgstr "Entrée comptable"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move____last_update
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update____last_update
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update____last_update
msgid "Last Modified on"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__write_uid
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__write_uid
msgid "Last Updated by"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__write_date
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__write_date
msgid "Last Updated on"
msgstr ""
#. module: account_invoice_update_wizard
#: code:addons/account_invoice_update_wizard/wizard/account_move_update.py:0
#, python-format
msgid "Non-legal fields of invoice updated via the Invoice Update wizard."
msgstr "Champs non légaux mis à jour via l'assistant"
#. module: account_invoice_update_wizard
#: model:ir.model.fields.selection,name:account_invoice_update_wizard.selection__account_move_line_update__display_type__line_note
msgid "Note"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__partner_id
msgid "Partner"
msgstr "Client"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__invoice_payment_term_id
msgid "Payment Term"
msgstr "Condition de paiement"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__quantity
msgid "Quantity"
msgstr "Quantité"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__ref
#, fuzzy
msgid "Reference"
msgstr "Reference Client"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__user_id
msgid "Salesperson"
msgstr "Vendeur"
#. module: account_invoice_update_wizard
#: model:ir.model.fields.selection,name:account_invoice_update_wizard.selection__account_move_line_update__display_type__line_section
msgid "Section"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__sequence
msgid "Sequence"
msgstr "Sequence"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__invoice_origin
msgid "Source Document"
msgstr "Origine du document"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,help:account_invoice_update_wizard.field_account_move_line_update__display_type
msgid "Technical field for UX purpose."
msgstr ""
#. module: account_invoice_update_wizard
#: code:addons/account_invoice_update_wizard/wizard/account_move_update.py:0
#, python-format
msgid ""
"The original payment term '%s' doesn't have the same terms (number of terms "
"and/or amount) as the new payment term '%s'. You can only switch to a "
"payment term that has the same number of terms with the same amount."
msgstr ""
#. module: account_invoice_update_wizard
#: code:addons/account_invoice_update_wizard/wizard/account_move_update.py:0
#, python-format
msgid ""
"This wizard doesn't support the update of payment terms on an invoice which "
"is partially or fully paid."
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_update__move_type
msgid "Type"
msgstr ""
#. module: account_invoice_update_wizard
#: model_terms:ir.ui.view,arch_db:account_invoice_update_wizard.account_invoice_update_form
msgid "Update"
msgstr "Mettre à jour"
#. module: account_invoice_update_wizard
#: model_terms:ir.ui.view,arch_db:account_invoice_update_wizard.view_move_form_inherit
msgid "Update Invoice"
msgstr "Mettre à jour"
#. module: account_invoice_update_wizard
#: model_terms:ir.ui.view,arch_db:account_invoice_update_wizard.account_invoice_update_form
msgid "Update Invoice Wizard"
msgstr "Assistant de mise à jour"
#. module: account_invoice_update_wizard
#: model:ir.model,name:account_invoice_update_wizard.model_account_move_line_update
msgid "Update non-legal fields of invoice lines"
msgstr "Mettre à jour les champs non légaux des lignes de facture"
#. module: account_invoice_update_wizard
#: model:ir.model.fields,field_description:account_invoice_update_wizard.field_account_move_line_update__parent_id
msgid "Wizard"
msgstr ""
#. module: account_invoice_update_wizard
#: model:ir.model,name:account_invoice_update_wizard.model_account_move_update
msgid "Wizard to update non-legal fields of invoice"
msgstr "Assistant pour mettre à jours les champs non légaux"
#~ msgid "Account"
#~ msgstr "Compte"

View File

@@ -48,6 +48,7 @@ class AccountMoveUpdate(models.TransientModel):
aa_tags = [(6, 0, aa_tags.ids)] if aa_tags else False
res['line_ids'].append([0, 0, {
'invoice_line_id': line.id,
'sequence': line.sequence,
'name': line.name,
'quantity': line.quantity,
'price_subtotal': line.price_subtotal,
@@ -231,7 +232,9 @@ class AccountMoveUpdate(models.TransientModel):
class AccountMoveLineUpdate(models.TransientModel):
_name = 'account.move.line.update'
_description = 'Update non-legal fields of invoice lines'
_order = "sequence, name"
sequence = fields.Integer()
parent_id = fields.Many2one(
'account.move.update', string='Wizard', ondelete='cascade')
invoice_line_id = fields.Many2one(

View File

@@ -18,12 +18,13 @@
<field string="Bill Reference" attrs="{'invisible': [('move_type', 'not in', ('in_invoice', 'in_refund'))]}" name="ref"/>
<field string="Customer Reference" attrs="{'invisible': [('move_type', 'not in', ('out_invoice', 'out_refund'))]}" name="ref"/>
<field name="invoice_origin"/>
<field name="invoice_payment_term_id" widget="selection"/>
<!-- update of payment term is broken -->
<!-- <field name="invoice_payment_term_id" widget="selection"/>-->
<field name="partner_bank_id"/>
<field name="user_id" options="{'no_open': True, 'no_create': True, 'no_create_edit': True}"/>
</group>
<group name="lines">
<field name="line_ids" nolabel="1">
<field name="line_ids" nolabel="1" widget="section_and_note_one2many">
<tree editable="bottom" create="false" delete="false" edit="true">
<field name="invoice_line_id" invisible="1"/>
<field name="display_type" invisible="1"/>

View File

@@ -12,7 +12,7 @@
<field name="model">account.move.update</field>
<field name="inherit_id" ref="account_invoice_update_wizard.account_invoice_update_form"/>
<field name="arch" type="xml">
<field name="invoice_payment_term_id" position="after">
<field name="partner_bank_id" position="before">
<field name="payment_mode_filter_type_domain" invisible="1"/>
<field name="partner_bank_filter_type_domain" invisible="1"/>
<field name="bank_account_required" invisible="1"/>

View File

@@ -0,0 +1,4 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
from . import models
from .hook import post_init_hook

View File

@@ -0,0 +1,17 @@
# Copyright 2024 Akretion (https://www.akretion.com).
# @author Matthieu SAISON <matthieu.saison@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Account Journal Display Type",
"summary": "Account Journal type for payment",
"version": "14.0.1.0.0",
"development_status": "Beta",
"category": "Accounting",
"website": "https://github.com/OCA/account-financial-tools",
"author": "Akretion, " "Odoo Community Association (OCA)",
"maintainers": ["matthieu_saison"],
"license": "AGPL-3",
"depends": ["account", "account_statement_completion_label_simple"],
"data": ["views/account_journal_view.xml"],
"post_init_hook": "post_init_hook",
}

View File

@@ -0,0 +1,8 @@
from odoo import SUPERUSER_ID, api
def post_init_hook(cr, registry):
env = api.Environment(cr, SUPERUSER_ID, {})
journal_ids = env["account.journal"].search([])
for journal in journal_ids:
journal.display_type = journal.type

View File

@@ -0,0 +1 @@
from . import account_journal

View File

@@ -0,0 +1,80 @@
from odoo import api, fields, models
class AccountJournal(models.Model):
_inherit = "account.journal"
display_type = fields.Selection(
[
("sale", "Sales"),
("purchase", "Purchase"),
("cash", "Cash"),
("bank", "Bank"),
("payment", "Payment"),
("general", "Miscellaneous"),
],
required=True,
help="Select 'Sale' for customer invoices journals.\n"
"Select 'Purchase' for vendor bills journals.\n"
"Select 'Cash' or 'Bank' for journals that are used in customer or vendor payments.\n"
"Select 'General' for miscellaneous operations journals.",
)
type = fields.Selection(compute="_compute_type", store=True)
default_account_id = fields.Many2one(
comodel_name="account.account",
compute="_compute_default_account_id",
readonly=False,
store=True,
)
payment_debit_account_id = fields.Many2one(
comodel_name="account.account",
compute="_compute_payment_account_id",
readonly=False,
store=True,
)
payment_credit_account_id = fields.Many2one(
comodel_name="account.account",
compute="_compute_payment_account_id",
readonly=False,
store=True,
)
@api.depends("display_type")
def _compute_type(self):
for record in self:
if record.display_type == "payment":
record.type = "bank"
else:
record.type = record.display_type
@api.depends("display_type", "payment_debit_account_id")
def _compute_default_account_id(self):
for record in self:
if record.display_type == "payment":
record.default_account_id = record.payment_debit_account_id
@api.depends("display_type", "default_account_id")
def _compute_payment_account_id(self):
for record in self:
if record.type == "cash":
record.payment_debit_account_id = record.default_account_id
record.payment_credit_account_id = record.default_account_id
@api.model
def _fill_missing_values(self, vals):
# _fill_missing_values automaticly create a account if not set,
# this code bypass this behavior
if vals.get("display_type") == "payment":
vals["default_account_id"] = True
elif vals.get("display_type") == "cash":
vals["payment_debit_account_id"] = True
vals["payment_credit_account_id"] = True
super()._fill_missing_values(vals)
if vals.get("display_type") == "payment":
vals.pop("default_account_id")
# allow journal creation if display_type not define
if not vals.get("display_type"):
vals["display_type"] = vals["type"]
elif vals.get("display_type") == "cash":
vals.pop("payment_debit_account_id")
vals.pop("payment_credit_account_id")

View File

@@ -0,0 +1 @@
* Matthieu Saison <matthieu.saison@akretion.com>

View File

@@ -0,0 +1,8 @@
This module introduce improvement on journal type to simplify configuration of Payment and Cash Journal.
The payment journal does not exist in odoo, it's associate to a bank journal but in payment case,
the default_account_id will have the same value as payment_debit_account_id.
This module introduce a new field type called display_type who hide the default type from UI, and make possible to had new journal type. Payment display_type is hadded here, and the associated legacy journal type is bank. The default_account_id is hided and will have the same value as payment_debit_account_id
For Cash type, the only field we kept is default_account_id. The payment config tab is useless for a cash journal and was remove from UI. (in this case payment_debit_account_id and payment_credit_account_id are set with value of default_account_id)

View File

@@ -0,0 +1 @@
from . import test_journal_display_type

View File

@@ -0,0 +1,81 @@
from odoo.tests import SavepointCase
from odoo.tests.common import new_test_user, users
class TestJournalDisplayType(SavepointCase):
def setUp(self):
super().setUp()
self.account = self.env["account.account"].create(
{
"name": "Test account",
"code": "TAC",
"user_type_id": self.env.ref("account.data_account_type_payable").id,
"reconcile": True,
}
)
self.manager = new_test_user(
self.env, "test_manager", "account.group_account_manager"
)
@users("test_manager")
def test_journal_payment_with_account(self):
journal = self.env["account.journal"].create(
{
"name": "Bank with account",
"display_type": "cash",
"type": "cash",
"code": "BK100",
"payment_debit_account_id": self.account.id,
"payment_credit_account_id": self.account.id,
}
)
journal.display_type = "payment"
self.assertEqual(journal.type, "bank")
self.assertEqual(journal.default_account_id, journal.payment_debit_account_id)
@users("test_manager")
def test_journal_payment_without_account(self):
journal = self.env["account.journal"].create(
{
"name": "Bank without account",
"display_type": "payment",
"type": "bank",
"code": "BK101",
}
)
self.assertTrue(journal.payment_debit_account_id)
self.assertTrue(journal.payment_credit_account_id)
self.assertEqual(journal.type, "bank")
self.assertEqual(journal.default_account_id, journal.payment_debit_account_id)
@users("test_manager")
def test_journal_cash_with_account(self):
journal = self.env["account.journal"].create(
{
"name": "Cash with account",
"display_type": "cash",
"type": "cash",
"code": "BK102",
"default_account_id": self.account.id,
}
)
self.assertEqual(journal.type, "cash")
self.assertEqual(journal.default_account_id, self.account)
self.assertEqual(journal.default_account_id, journal.payment_debit_account_id)
self.assertEqual(journal.default_account_id, journal.payment_credit_account_id)
@users("test_manager")
def test_journal_bank_without_account(self):
journal = self.env["account.journal"].create(
{
"name": "Bank without account",
"type": "bank",
"code": "BK103",
}
)
self.assertTrue(journal.payment_debit_account_id)

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data>
<record id="view_account_journal_form" model="ir.ui.view">
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.view_account_journal_form" />
<field name="priority">1</field>
<field name="arch" type="xml">
<field name="type" position="attributes">
<attribute name="attrs">{'invisible': True}</attribute>
</field>
<field name="type" position="after">
<field name="display_type" string="Type"/>
</field>
<xpath
expr="//page[@name='bank_account']/group/group[1]/field[@name='default_account_id'][1]"
position="attributes"
>
<attribute name="attrs">
{'required': [ ('type', '=', 'bank'), ('display_type', '=', 'bank')], 'invisible': [('display_type', '!=', 'bank')]}</attribute>
</xpath>
<xpath
expr="//page[@name='bank_account']/group/group[1]/field[@name='default_account_id'][2]"
position="attributes"
>
<attribute name="attrs">
{'required': [('type', '=', 'cash')], 'invisible': [('type', '!=', 'cash')]}
</attribute>
</xpath>
<xpath
expr="//page[@name='bank_account']/group/group[1]/field[@name='suspense_account_id'][1]"
position="attributes"
>
<attribute name="attrs">
{'required': [('display_type', 'in', ('bank', 'cash'))], 'invisible': [('display_type', 'not in', ('bank', 'cash'))]}</attribute>
</xpath>
<xpath
expr="//page[@name='bank_account']/group/group[2]"
position="attributes"
>
<attribute name="attrs">
{'invisible': ['|', ('type', '!=', 'bank'), ('display_type', '=', 'payment') ]}</attribute>
</xpath>
<xpath
expr="//page[@name='journal_entries']/group/group[1]/field[@name='payment_debit_account_id'][1]"
position="attributes"
>
<attribute name="attrs">
{'required': [('type', 'in', ('bank', 'cash'))], 'invisible': [('type', 'not in', ('bank', 'cash'))]}</attribute>
</xpath>
<xpath
expr="//page[@name='journal_entries']/group/group[2]/field[@name='payment_credit_account_id'][1]"
position="attributes"
>
<attribute name="attrs">
{'required': [('type', 'in', ('bank', 'cash'))], 'invisible': [('type', 'not in', ('bank', 'cash'))]}</attribute>
</xpath>
<xpath expr="//page[@name='journal_entries']" position="attributes">
<attribute name="attrs">
{'invisible': [('type', 'in', ['sale', 'purchase', 'general','cash'])]}</attribute>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@@ -1,2 +1,3 @@
from . import models
from . import wizard
from .hooks import post_init_hook

View File

@@ -4,7 +4,7 @@
{
'name': 'Account Usability',
'version': '14.0.1.0.0',
'version': '14.0.1.1.0',
'category': 'Accounting & Finance',
'license': 'AGPL-3',
'summary': 'Small usability enhancements in account module',
@@ -29,7 +29,6 @@
'views/account_tax.xml',
'views/product.xml',
'views/res_config_settings.xml',
'views/res_partner.xml',
'views/res_company.xml',
'views/account_report.xml',
'views/account_reconcile_model.xml',
@@ -41,4 +40,5 @@
],
'qweb': ['static/src/xml/account_payment.xml'],
'installable': True,
"post_init_hook": "post_init_hook",
}

View File

@@ -0,0 +1,9 @@
# Copyright 2022 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <sebastien.beau@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import SUPERUSER_ID, api
def post_init_hook(cr, registry):
env = api.Environment(cr, SUPERUSER_ID, {})
env["account.move.line"].update_matching_number()

View File

@@ -524,11 +524,6 @@ msgstr ""
msgid "Recipient Bank"
msgstr ""
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_move_line__reconcile_string
msgid "Reconcile"
msgstr ""
#. module: account_usability
#: model:ir.model.fields,field_description:account_usability.field_account_bank_statement_line__ref
#: model:ir.model.fields,field_description:account_usability.field_account_move__ref

View File

@@ -0,0 +1,9 @@
# Copyright 2020 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import SUPERUSER_ID, api
def migrate(cr, version):
env = api.Environment(cr, SUPERUSER_ID, {})
env["account.move.line"].update_matching_number()

View File

@@ -2,12 +2,16 @@
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from datetime import timedelta
import logging
from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
from odoo.osv import expression
from odoo.tools import float_is_zero
from odoo.tools.misc import format_date
from odoo.osv import expression
from datetime import timedelta
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
class AccountMove(models.Model):
@@ -36,6 +40,34 @@ class AccountMove(models.Model):
compute="_compute_sales_dates", readonly=True,
help="This information appear on invoice qweb report "
"(you may use it for your own report)")
# There is a native "blocked" field (bool) on account.move.line
# We want to have that field on invoices to improve usability
# while keeping compatibility with the standard Odoo datamodel
blocked = fields.Boolean(
compute="_compute_blocked",
inverse="_inverse_blocked",
store=True,
string="Dispute",
tracking=True,
)
@api.depends("line_ids", "line_ids.blocked")
def _compute_blocked(self):
for move in self:
move.blocked = any(
[
l.blocked
for l in move.line_ids
if l.account_id.internal_type in ("payable", "receivable")
]
)
def _inverse_blocked(self):
for move in self:
for line in move.line_ids.filtered(
lambda l: l.account_id.internal_type in ("payable", "receivable")
):
line.blocked = move.blocked
def _compute_has_discount(self):
prec = self.env['decimal.precision'].precision_get('Discount')
@@ -108,6 +140,16 @@ class AccountMove(models.Model):
# self.invalidate_cache()
# return res
def _reverse_moves(self, default_values_list=None, cancel=False):
reverse_moves = super()._reverse_moves(
default_values_list=default_values_list, cancel=cancel)
# In the simple scenario 1 invoice -> 1 refund, we add a message in the chatter
# of the invoice and in the chatter of the refund
if len(self) == 1 and len(reverse_moves) == 1:
self.message_post(body=_("A reverse journal entry <a href=# data-oe-model=account.move data-oe-id=%d>%s</a> has been generated.") % (reverse_moves.id, reverse_moves.display_name))
reverse_moves.message_post(body=_("This journal entry has been generated as the reverse of <a href=# data-oe-model=account.move data-oe-id=%d>%s</a>.") % (self.id, self.display_name))
return reverse_moves
def delete_lines_qty_zero(self):
lines = self.env['account.move.line'].search([
('display_type', '=', False),
@@ -217,10 +259,44 @@ class AccountMove(models.Model):
if self.is_purchase_document(include_receipts=True):
tax_lock_date = self.company_id.tax_lock_date
if invoice_date and tax_lock_date and has_tax and invoice_date <= tax_lock_date:
invoice_date = tax_lock_date + timedelta(days=1)
invoice_date = tax_lock_date + timedelta(days=1)
date = invoice_date
return date
# I don't use account_invoice_supplier_ref_unique because it adds
# a field supplier_invoice_number on account.move instead of using the native field
# cf https://github.com/OCA/account-invoicing/issues/1484
# So I take inspiration from the code of account_invoice_supplier_ref_unique
# but I use the native "ref" field
@api.constrains("ref", "partner_id")
def _check_in_invoice_ref_unique_insensitive(self):
for move in self:
if move.ref and move.is_purchase_document(
include_receipts=True
):
in_invoice_same_ref = self.search(
[
("commercial_partner_id", "=", move.commercial_partner_id.id),
("move_type", "in", ("in_invoice", "in_refund")),
("company_id", "=", move.company_id.id),
("ref", "=ilike", move.ref),
("id", "!=", move.id),
],
limit=1,
)
if in_invoice_same_ref:
raise ValidationError(
_(
"An invoice already exists in Odoo with the same "
"bill reference '%s' for the same supplier '%s': %s."
)
% (
in_invoice_same_ref.ref,
in_invoice_same_ref.partner_id.display_name,
in_invoice_same_ref.display_name,
)
)
class AccountMoveLine(models.Model):
_inherit = 'account.move.line'
@@ -238,8 +314,6 @@ class AccountMoveLine(models.Model):
full_reconcile_id = fields.Many2one(string='Full Reconcile')
matched_debit_ids = fields.One2many(string='Partial Reconcile Debit')
matched_credit_ids = fields.One2many(string='Partial Reconcile Credit')
reconcile_string = fields.Char(
compute='_compute_reconcile_string', string='Reconcile', store=True)
# for optional display in tree view
product_barcode = fields.Char(related='product_id.barcode', string="Product Barcode")
@@ -255,17 +329,21 @@ class AccountMoveLine(models.Model):
})
return action
@api.depends(
'full_reconcile_id', 'matched_debit_ids', 'matched_credit_ids')
def _compute_reconcile_string(self):
for line in self:
rec_str = False
if line.full_reconcile_id:
rec_str = line.full_reconcile_id.name
else:
rec_str = ', '.join([
'a%d' % pr.id for pr in line.matched_debit_ids + line.matched_credit_ids])
line.reconcile_string = rec_str
def update_matching_number(self):
records = self.search([("matching_number", "=", "P")])
_logger.info(f"Update partial reconcile number for {len(records)} lines")
records._compute_matching_number()
def _compute_matching_number(self):
# TODO maybe it will be better to have the same maching_number for
# all partial so it will be easier to group by
super()._compute_matching_number()
for record in self:
if record.matching_number == "P":
record.matching_number = ", ".join([
"a%d" % pr.id
for pr in record.matched_debit_ids + record.matched_credit_ids
])
def _get_computed_name(self):
# This is useful when you want to have the product code in a dedicated

View File

@@ -13,6 +13,9 @@
<field name="model">account.bank.statement</field>
<field name="inherit_id" ref="account.view_bank_statement_form"/>
<field name="arch" type="xml">
<button name="button_reopen" position="attributes">
<attribute name="confirm">Are you sure ? Don't do 'Reset to New' if you just want to modify the bank journal entry of an existing statement line.</attribute>
</button>
<button name="button_reopen" position="after">
<button
name="button_undo_reconciliation"

View File

@@ -42,12 +42,21 @@
<attribute name="optional">show</attribute>
</xpath>
<xpath expr="//field[@name='line_ids']/tree/field[@name='tax_tag_ids']" position="after">
<field name="matching_number" optional="hide"/>
<field name="reconcile_string" optional="show"/>
<field name="matching_number" optional="show"/>
</xpath>
<xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='product_id']" position="after">
<field name="product_barcode" optional="hide"/>
</xpath>
<field name="auto_post" position="before">
<field name="blocked"/>
</field>
<div role="alert" position="after">
<div id="warn_blocked" groups="account.group_account_invoice,account.group_account_readonly"
class="alert alert-warning" role="alert" style="margin-bottom:0px;"
attrs="{'invisible': ['|', ('move_type', 'not in', ('in_invoice', 'in_refund', 'out_invoice', 'out_refund')), ('blocked', '=', False)]}">
This <field name="move_type"/> is marked as <b>disputed</b>.
</div>
</div>
</field>
</record>
@@ -72,6 +81,8 @@
<filter name="sent" string="Sent" domain="[('is_move_sent', '=', True), ('move_type', 'in', ('out_invoice', 'out_refund'))]"/>
<separator/>
<filter name="no_attachment" string="Missing Attachment" domain="[('has_attachment', '=', False)]"/>
<separator/>
<filter name="dispute" string="Dispute" domain="[('blocked', '=', True)]"/>
</filter>
<filter name="salesperson" position="before">
<filter name="commercial_partner_groupby" string="Commercial Partner" context="{'group_by': 'commercial_partner_id'}"/>
@@ -82,6 +93,23 @@
</field>
</record>
<record id="view_move_line_form" model="ir.ui.view">
<field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_move_line_form"/>
<field name="arch" type="xml">
<!-- The field 'blocked' is alone in it's block
We don't want to display an empty block, so we put the attrs on the group
The drawback of this is that, if someone added a field in that group,
he won't see the field when internal_type is not payable/receivable -->
<xpath expr="//field[@name='blocked']/.." position="attributes">
<attribute name="attrs">{'invisible': [('account_internal_type', 'not in', ('payable', 'receivable'))]}</attribute>
</xpath>
<field name="company_id" position="after">
<field name="account_internal_type" invisible="1"/>
</field>
</field>
</record>
<record id="view_move_line_tree" model="ir.ui.view">
<field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_move_line_tree"/>
@@ -106,7 +134,7 @@
<separator/>
</filter>
<field name="partner_id" position="after">
<field name="reconcile_string" />
<field name="matching_number" />
<field name="debit" filter_domain="['|', ('debit', '=', self), ('credit', '=', self)]" string="Debit or Credit"/>
</field>
<filter name="unreconciled" position="before">
@@ -118,12 +146,27 @@
<field name="name" position="attributes">
<attribute name="string">Label, Reference, Account or Partner</attribute>
</field>
<field name="name" position="before">
<field name="move_id" position="move"/>
</field>
<field name="partner_id" position="attributes">
<attribute name="domain">['|', ('parent_id', '=', False), ('is_company', '=', True)]</attribute>
</field>
</field>
</record>
<record id="view_move_line_pivot" model="ir.ui.view">
<field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_move_line_pivot"/>
<field name="arch" type="xml">
<!-- By default, date is split by month... but if you've been using Odoo for several years,
the pivot table becomes very big by default: so we split by year -->
<field name="date" position="attributes">
<attribute name="interval">year</attribute>
</field>
</field>
</record>
<record id="account.action_move_journal_line" model="ir.actions.act_window">
<field name="context">{'default_move_type': 'entry', 'view_no_maturity': True}</field>
<!-- Remove 'search_default_misc_filter': 1 -->

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2017-2020 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_partner_property_form" model="ir.ui.view">
<field name="name">account_usability.res.partner.form</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="account.view_partner_property_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='bank_ids']/tree/field[@name='acc_number']" position="after">
<field name="acc_type"/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -12,13 +12,14 @@ class AccountMoveReversal(models.TransientModel):
# Set default reversal date to original move + 1 day
# and raise error if original move has already been reversed
# WARNING: this wizard is also used to generate refunds
@api.model
def default_get(self, fields_list):
res = super().default_get(fields_list)
assert self._context.get('active_model') == 'account.move'
amo = self.env['account.move']
moves = amo.browse(self._context['active_ids'])
if len(moves) == 1:
if len(moves) == 1 and moves.move_type not in ('out_invoice', 'in_invoice'):
res['date'] = moves.date + relativedelta(days=1)
reversed_move = amo.search([('reversed_entry_id', 'in', moves.ids)], limit=1)
if reversed_move:

View File

@@ -15,6 +15,7 @@ class ResPartnerPhone(models.Model):
_name = 'res.partner.phone'
_order = 'partner_id, type'
_phone_name_sequence = 8
_phone_name_fields = ["phone"]
_inherit = ['phone.validation.mixin']
_description = 'Multiple emails and phones for partners'
@@ -73,7 +74,7 @@ class ResPartnerPhone(models.Model):
if self._context.get('callerid'):
name = pphone.partner_id.display_name
else:
name = u'%s (%s)' % (pphone.phone, pphone.partner_id.name)
name = '%s (%s)' % (pphone.phone, pphone.partner_id.name)
else:
name = pphone.phone
res.append((pphone.id, name))
@@ -104,6 +105,7 @@ class ResPartnerPhone(models.Model):
class ResPartner(models.Model):
_inherit = 'res.partner'
_phone_name_fields = []
# in v10, we are supposed to have in DB E.164 format
# with the current implementation, we have:
@@ -147,6 +149,8 @@ class ResPartner(models.Model):
if vals.get(partner_field):
vals['phone_ids'].append(
(0, 0, {'type': type, partner_phone_field: vals[partner_field]}))
if partner_field in vals:
vals.pop(partner_field)
@api.model
def create(self, vals):
@@ -155,7 +159,6 @@ class ResPartner(models.Model):
self._update_create_vals(vals, '1_email_primary', 'email', 'email')
self._update_create_vals(vals, '3_phone_primary', 'phone', 'phone')
self._update_create_vals(vals, '5_mobile_primary', 'mobile', 'phone')
# self._update_create_vals(vals, '7_fax_primary', 'fax', 'phone')
return super().create(vals)
def _update_write_vals(
@@ -178,16 +181,16 @@ class ResPartner(models.Model):
else:
if pphone:
vals['phone_ids'].append((2, pphone.id))
vals.pop(partner_field)
def write(self, vals):
if 'phone_ids' not in vals:
for rec in self:
vals['phone_ids'] = []
rec._update_write_vals(vals, '1_email_primary', 'email', 'email')
rec._update_write_vals(vals, '3_phone_primary', 'phone', 'phone')
rec._update_write_vals(vals, '5_mobile_primary', 'mobile', 'phone')
rec._update_write_vals(vals, '7_fax_primary', 'fax', 'phone')
super(ResPartner, rec).write(vals)
cvals = dict(vals, phone_ids=[])
rec._update_write_vals(cvals, '1_email_primary', 'email', 'email')
rec._update_write_vals(cvals, '3_phone_primary', 'phone', 'phone')
rec._update_write_vals(cvals, '5_mobile_primary', 'mobile', 'phone')
super(ResPartner, rec).write(cvals)
return True
else:
return super().write(vals)

View File

@@ -161,6 +161,12 @@
<field name="name" position="attributes">
<attribute name="filter_domain">['|', '|', ('display_name', 'ilike', self), ('ref', '=ilike', self + '%'), ('phone_ids.email', 'ilike', self)]</attribute>
</field>
<field name="email" position="attributes">
<attribute name="filter_domain">[('phone_ids.email', 'ilike', self)]</attribute>
</field>
<field name="phone" position="attributes">
<attribute name="filter_domain">[('phone_ids.phone', 'ilike', self)]</attribute>
</field>
</field>
</record>

View File

@@ -10,7 +10,7 @@
'summary': 'Better usability in base module',
'author': 'Akretion',
'website': 'http://www.akretion.com',
'depends': ['base'],
'depends': ['web'],
'data': [
'security/group.xml',
'security/ir.model.access.csv',
@@ -21,6 +21,7 @@
'views/ir_module.xml',
'views/ir_sequence.xml',
'views/ir_property.xml',
'views/assets.xml',
],
'installable': True,
}

View File

@@ -0,0 +1,10 @@
.o_form_view {
.o_address_format {
.o_address_state {
margin-right: 0;
}
.o_address_zip {
margin-right: 2%;
}
}
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<template
id="assets_backend"
name="web_sheet_full_width"
inherit_id="web.assets_backend"
>
<xpath expr="." position="inside">
<link
rel="stylesheet"
href="/base_usability/static/src/scss/form_view.scss"
/>
</xpath>
</template>
</odoo>

View File

@@ -20,6 +20,10 @@
<div attrs="{'invisible': [('same_vat_partner_id', '=', False)]}" position="attributes">
<attribute name="class">alert alert-warning</attribute>
</div>
<!-- Native order: city / state_id / zip. New order: zip / city / state_id -->
<xpath expr="//div[hasclass('o_address_format')]/field[@name='city']" position="before">
<field name="zip" position="move"/>
</xpath>
</field>
</record>

View File

@@ -0,0 +1,28 @@
diff --git a/addons/web/static/src/js/control_panel/custom_filter_item.js b/addons/web/static/src/js/control_panel/custom_filter_item.js
index 6682b660b78..b2ff23344d9 100644
--- a/addons/web/static/src/js/control_panel/custom_filter_item.js
+++ b/addons/web/static/src/js/control_panel/custom_filter_item.js
@@ -180,6 +180,10 @@ odoo.define('web.CustomFilterItem', function (require) {
[field.name, '>=', domainValue[0]],
[field.name, '<=', domainValue[1]]
);
+ } else if (operator.symbol === 'startswith') {
+ domainArray.push([field.name, '=ilike', domainValue[0] + '%']);
+ } else if (operator.symbol === 'endswith') {
+ domainArray.push([field.name, '=ilike', '%' + domainValue[0]]);
} else {
domainArray.push([field.name, operator.symbol, domainValue[0]]);
}
diff --git a/addons/web/static/src/js/control_panel/search_utils.js b/addons/web/static/src/js/control_panel/search_utils.js
index 8fce5b23ef6..d240d2e1fb2 100644
--- a/addons/web/static/src/js/control_panel/search_utils.js
+++ b/addons/web/static/src/js/control_panel/search_utils.js
@@ -18,6 +18,8 @@ odoo.define('web.searchUtils', function (require) {
char: [
{ symbol: "ilike", description: _lt("contains") },
{ symbol: "not ilike", description: _lt("doesn't contain") },
+ { symbol: "startswith", description: _lt("starts with") },
+ { symbol: "endswith", description: _lt("ends with") },
{ symbol: "=", description: _lt("is equal to") },
{ symbol: "!=", description: _lt("is not equal to") },
{ symbol: "!=", description: _lt("is set"), value: false },

View File

@@ -19,4 +19,10 @@
</field>
</record>
<!-- By default, the menu entry "CRM > Sales > My Activities" is limited to
the sales Manager. The Sale user should be able to see it -->
<record id="crm.crm_lead_menu_my_activities" model="ir.ui.menu">
<field name="groups_id" eval="[(6, 0, [])]"/>
</record>
</odoo>

View File

@@ -1,12 +1,12 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'Developer Menu',
'version': '12.0.0.0.0',
'category': 'Tools',
'license': 'AGPL-3',
'summary': "Menu Shortcut for developer usage",
'description': """
"name": "Developer Menu",
"version": "14.0.0.0.0",
"category": "Tools",
"license": "AGPL-3",
"summary": "Menu Shortcut for developer usage",
"description": """
Developer menu
==============
@@ -21,11 +21,13 @@ near `Technical` menu
This module has been written by David Béal
from Akretion <david.beal@akretion.com>.
""",
'author': 'Akretion',
'website': 'http://www.akretion.com',
'depends': ['mail'],
'data': [
'menu_view.xml'
"author": "Akretion",
"website": "https://www.akretion.com",
"depends": [
"mail",
],
'installable': False,
"data": [
"menu_view.xml",
],
"installable": True,
}

View File

@@ -2,7 +2,7 @@
<odoo>
<menuitem id="conf_tech" parent="base.menu_administration" name="🧰" groups="base.group_erp_manager" sequence="100"/>
<menuitem id="conf_tech" parent="base.menu_administration" name="🧰" groups="base.group_erp_manager" sequence="1"/>
<menuitem id="model" name="Model" parent="conf_tech" action="base.action_model_model" sequence="10"/>
<menuitem id="view" name="View" parent="conf_tech" action="base.action_ui_view" sequence="20" />
<menuitem id="rec_rule" name="Record Rule" parent="conf_tech" action="base.action_rule" sequence="30" />

View File

@@ -10,7 +10,7 @@ def web_m2x_options_create(cr, registry):
env = Environment(cr, SUPERUSER_ID, {})
config_parameter = env['ir.config_parameter'].search(
[('key', '=', 'web_m2x_options.create')])
if config_parameter and config_parameter.value != 'False':
if config_parameter:
config_parameter.write({'value': 'False'})
else:
env['ir.config_parameter'].create({

View File

@@ -1,4 +1 @@
# -*- coding: utf-8 -*-
from . import intrastat_product_type
from .post_install import set_intrastat_type_on_products

View File

@@ -4,7 +4,7 @@
{
'name': 'Intrastat Product Type',
'version': '12.0.1.0.0',
'version': '14.0.1.0.0',
'category': 'Accounting',
'license': 'AGPL-3',
'summary': 'Adds a special field Intrastat Type on Products',
@@ -19,9 +19,8 @@ This module adds a field *Intrastat Type* on the Product Form with 2 possible op
This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>.
""",
'author': 'Akretion',
'website': 'http://www.akretion.com',
'website': 'https://github.com/akretion/odoo-usability',
'depends': ['intrastat_product', 'l10n_fr_intrastat_service'],
'data': ['product_view.xml'],
'post_init_hook': 'set_intrastat_type_on_products',
'installable': False,
'installable': True,
}

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright 2016-2019 Akretion (http://www.akretion.com)
# Copyright 2016-2023 Akretion (http://www.akretion.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
# @author Alexis de Lattre <alexis.delattre@akretion.com>
@@ -14,50 +13,36 @@ class ProductTemplate(models.Model):
intrastat_type = fields.Selection([
('product', 'Product'),
('service', 'Service'),
], string='Intrastat Type', default='product', required=True,
],
compute='_compute_intrastat_type', readonly=False, store=True,
required=True, string='Intrastat Type',
help="Type of product used for the intrastat declarations. "
"For example, you can configure a product with "
"'Product Type' = 'Consumable' and 'Intrastat Type' = 'Service'.")
@api.multi
@api.constrains('type', 'intrastat_type')
def check_intrastat_type(self):
for pt in self:
if pt.intrastat_type == 'product' and pt.type == 'service':
raise ValidationError(_(
"On the product %s, you cannot set Product Type to "
"'Service' and Intrastat Type to 'Product'.") % pt.name)
"On the product '%s', you cannot set Product Type to "
"'Service' and Intrastat Type to 'Product'.")
% pt.display_name)
if pt.intrastat_type == 'service' and pt.type == 'product':
raise ValidationError(_(
"On the product %s, you cannot set Intrastat Type to "
"On the product '%s', you cannot set Intrastat Type to "
"'Service' and Product Type to 'Stockable product' "
"(but you can set Product Type to 'Consumable' or "
"'Service').") % pt.name)
"'Service').") % pt.display_name)
@api.onchange('type')
def intrastat_type_onchange(self):
if self.type in ('product', 'consu'):
self.intrastat_type = 'product'
elif self.type == 'service':
self.intrastat_type = 'service'
@api.model
def create(self, vals):
if vals.get('type'):
if not vals.get('intrastat_type'):
if vals['type'] in ('product', 'consu'):
vals['intrastat_type'] = 'product'
elif vals['type'] == 'service':
vals['intrastat_type'] = 'service'
elif (
vals.get('intrastat_type') == 'product' and
vals['type'] == 'service'):
# usefull because intrastat_type = 'product' by default and
# wizards in other modules that don't depend on this module
# (e.g. sale_rental) may create a product with only
# {'type': 'service'} and no 'intrastat_type'
vals['intrastat_type'] = 'service'
return super(ProductTemplate, self).create(vals)
@api.depends('type')
def _compute_intrastat_type(self):
for pt in self:
if pt.type in ('product', 'consu'):
intrastat_type = 'product'
else:
intrastat_type = 'service'
pt.intrastat_type = intrastat_type
class L10nFrIntrastatServiceDeclaration(models.Model):

View File

@@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2016-2019 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
def set_intrastat_type_on_products(cr, registry):
cr.execute(
"UPDATE product_template SET intrastat_type='service' "
"WHERE type='service'")
return

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2016-2019 Akretion (http://www.akretion.com/)
Copyright 2016-2023 Akretion France (http://www.akretion.com/)
@author Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->

View File

@@ -23,7 +23,7 @@ Small usability improvements on mails:
'website': 'http://www.akretion.com',
'depends': ['mail'],
'data': [
#'views/mail_view.xml',
'views/mail_activity.xml',
#'data/mail_data.xml',
#'wizard/email_template_preview_view.xml',
#'wizard/mail_compose_message_view.xml',

View File

@@ -1,2 +1,3 @@
from . import res_partner
from . import mail_activity
from . import mail_template

View File

@@ -0,0 +1,24 @@
# Copyright 2023 Akretion France (http://www.akretion.com).
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class MailActivity(models.Model):
_inherit = 'mail.activity'
res_model_name = fields.Char(related='res_model_id.name', string='Document Type')
def jump_to_record(self):
self.ensure_one()
action = {}
if self.res_id and self.res_model and self.res_name:
action.update({
'type': 'ir.actions.act_window',
'name': self.res_name,
'res_model': self.res_model,
'view_mode': 'form',
'res_id': self.res_id,
})
return action

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="mail_activity_view_search" model="ir.ui.view">
<field name="model">mail.activity</field>
<field name="inherit_id" ref="mail.mail_activity_view_search"/>
<field name="arch" type="xml">
<filter name="activities_upcoming_all" position="after">
<separator/>
<filter string="My Activities" domain="[('user_id', '=', uid)]" name="my_activities"/>
</filter>
</field>
</record>
<record id="mail_activity_view_tree" model="ir.ui.view">
<field name="model">mail.activity</field>
<field name="inherit_id" ref="mail.mail_activity_view_tree"/>
<field name="arch" type="xml">
<field name="summary" position="after">
<field name="user_id" optional="show" widget="many2one_avatar_user"/>
</field>
<field name="res_name" position="before">
<button name="jump_to_record" type="object" attrs="{'invisible': [('res_name', '=', False)]}" icon="fa-binoculars" string="Go to source" class="text-warning" invisible="not context.get('mail_activity_main_view')"/>
</field>
<field name="res_name" position="after">
<field name="res_model_name" invisible="not context.get('mail_activity_main_view')"/>
</field>
</field>
</record>
<record id="mail_activity_view_form_popup" model="ir.ui.view">
<field name="model">mail.activity</field>
<field name="inherit_id" ref="mail.mail_activity_view_form_popup"/>
<field name="arch" type="xml">
<field name="activity_type_id" position="before">
<field name="res_name" invisible="not context.get('mail_activity_main_view')"/>
<field name="res_model_name" invisible="not context.get('mail_activity_main_view')"/>
</field>
<button name="action_close_dialog" position="before">
<button name="jump_to_record" type="object" attrs="{'invisible': [('res_id', '=', False)]}" class="btn-primary" string="Go to source" invisible="not context.get('mail_activity_main_view')"/>
</button>
</field>
</record>
</odoo>

View File

View File

@@ -0,0 +1,18 @@
# Copyright 2023 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'MRP Subcontracting Usability',
'version': '14.0.1.0.0',
'category': 'Manufacturing',
'license': 'AGPL-3',
'summary': 'Usability improvements on mrp_subcontracting',
'author': 'Akretion',
'website': 'https://github.com/akretion/odoo-usability',
'depends': ['mrp_subcontracting'],
'data': [
'views/mrp_bom.xml',
],
'installable': True,
}

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_mrp_bom_filter" model="ir.ui.view">
<field name="model">mrp.bom</field>
<field name="inherit_id" ref="mrp.view_mrp_bom_filter"/>
<field name="arch" type="xml">
<filter name="phantom" position="after">
<filter name="subcontract" domain="[('type', '=', 'subcontract')]" string="Subcontracting"/>
</filter>
</field>
</record>
</odoo>

View File

@@ -31,7 +31,10 @@ Akretion:
"views/report_pos_order.xml",
"views/pos_category.xml",
"views/pos_session.xml",
"views/pos_order.xml",
"views/pos_config.xml",
"views/product.xml",
"views/pos_payment_method.xml",
],
"installable": True,
}

View File

@@ -1,3 +1,4 @@
from . import product
from . import pos_category
from . import pos_config
from . import pos_payment_method

View File

@@ -0,0 +1,12 @@
# Copyright 2023 Akretion France (http://www.akretion.com/)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class PosConfig(models.Model):
_inherit = 'pos.config'
_order = 'sequence, id'
sequence = fields.Integer(default=10)

View File

@@ -8,5 +8,7 @@ from odoo import fields, models
class PosPaymentMethod(models.Model):
_inherit = 'pos.payment.method'
_check_company_auto = True
_order = 'sequence, id'
cash_journal_id = fields.Many2one(check_company=True)
sequence = fields.Integer(default=10)

View File

@@ -0,0 +1,35 @@
diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js
index 55aa635aa10..d91ea933a71 100644
--- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js
+++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js
@@ -8,6 +8,7 @@ odoo.define('point_of_sale.PaymentScreen', function (require) {
const { useListener } = require('web.custom_hooks');
const Registries = require('point_of_sale.Registries');
const { onChangeOrder } = require('point_of_sale.custom_hooks');
+ const utils = require('web.utils');
class PaymentScreen extends PosComponent {
constructor() {
@@ -20,6 +21,7 @@ odoo.define('point_of_sale.PaymentScreen', function (require) {
useListener('send-payment-cancel', this._sendPaymentCancel);
useListener('send-payment-reverse', this._sendPaymentReverse);
useListener('send-force-done', this._sendForceDone);
+ useListener('validate-order', () => this.validateOrder(false));
this.lockedValidateOrder = useAsyncLockedMethod(this.validateOrder);
NumberBuffer.use(this._getNumberBufferConfig);
onChangeOrder(this._onPrevOrder, this._onNewOrder);
@@ -333,6 +335,14 @@ odoo.define('point_of_sale.PaymentScreen', function (require) {
if (isPaymentSuccessful) {
line.set_payment_status('done');
line.can_be_reversed = payment_terminal.supports_reversals;
+ // Automatically validate the order if, after an electronic payment,
+ // the current order is fully paid (BACKPORT FROM v16)
+ if (
+ this.currentOrder.is_paid() &&
+ utils.float_is_zero(this.currentOrder.get_due(), this.env.pos.currency.decimals)
+ ) {
+ this.trigger('validate-order');
+ }
} else {
line.set_payment_status('retry');
}

View File

@@ -0,0 +1,22 @@
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index 86a7b44bcfb..06b46ba5645 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -514,16 +514,7 @@ exports.PosModel = Backbone.Model.extend({
fields: ['name', 'is_cash_count', 'use_payment_terminal'],
domain: function(self){return ['|',['active', '=', false], ['active', '=', true]]; },
loaded: function(self, payment_methods) {
- self.payment_methods = payment_methods.sort(function(a,b){
- // prefer cash payment_method to be first in the list
- if (a.is_cash_count && !b.is_cash_count) {
- return -1;
- } else if (!a.is_cash_count && b.is_cash_count) {
- return 1;
- } else {
- return a.id - b.id;
- }
- });
+ self.payment_methods = payment_methods;
self.payment_methods_by_id = {};
_.each(self.payment_methods, function(payment_method) {
self.payment_methods_by_id[payment_method.id] = payment_method;

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_pos_config_tree" model="ir.ui.view">
<field name="model">pos.config</field>
<field name="inherit_id" ref="point_of_sale.view_pos_config_tree"/>
<field name="arch" type="xml">
<field name="name" position="before">
<field name="sequence" widget="handle"/>
</field>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_pos_pos_form" model="ir.ui.view">
<field name="model">pos.order</field>
<field name="inherit_id" ref="point_of_sale.view_pos_pos_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='lines']/form//field[@name='full_product_name']" position="before">
<field name="product_id"/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="pos_payment_method_view_tree" model="ir.ui.view">
<field name="model">pos.payment.method</field>
<field name="inherit_id" ref="point_of_sale.pos_payment_method_view_tree"/>
<field name="arch" type="xml">
<field name="name" position="before">
<field name="sequence" widget="handle"/>
</field>
</field>
</record>
</odoo>

View File

@@ -1,20 +1,50 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * product_category_tax
# * product_category_tax
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0-20150727\n"
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-12-04 17:16+0000\n"
"PO-Revision-Date: 2015-12-04 17:16+0000\n"
"Last-Translator: <>\n"
"POT-Creation-Date: 2023-10-16 20:34+0000\n"
"PO-Revision-Date: 2023-10-16 20:34+0000\n"
"Last-Translator: Alexis de Lattre <alexis.delattre@akretion.com>\n"
"Language-Team: \n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: product_category_tax
#: model:ir.model,name:product_category_tax.model_product_categ_tax_mixin
msgid "Common code for taxes on product categories"
msgstr "Code commun pour les taxes sur les catégories d'articles"
#. module: product_category_tax
#: model:ir.model.fields,field_description:product_category_tax.field_product_categ_tax_mixin__display_name
#: model:ir.model.fields,field_description:product_category_tax.field_product_category__display_name
#: model:ir.model.fields,field_description:product_category_tax.field_product_product__display_name
#: model:ir.model.fields,field_description:product_category_tax.field_product_template__display_name
msgid "Display Name"
msgstr "Nom affiché"
#. module: product_category_tax
#: model:ir.model.fields,field_description:product_category_tax.field_product_categ_tax_mixin__id
#: model:ir.model.fields,field_description:product_category_tax.field_product_category__id
#: model:ir.model.fields,field_description:product_category_tax.field_product_product__id
#: model:ir.model.fields,field_description:product_category_tax.field_product_template__id
msgid "ID"
msgstr "ID"
#. module: product_category_tax
#: model:ir.model.fields,field_description:product_category_tax.field_product_categ_tax_mixin____last_update
#: model:ir.model.fields,field_description:product_category_tax.field_product_category____last_update
#: model:ir.model.fields,field_description:product_category_tax.field_product_product____last_update
#: model:ir.model.fields,field_description:product_category_tax.field_product_template____last_update
msgid "Last Modified on"
msgstr "Dernière modification le"
#. module: product_category_tax
#: model:ir.model,name:product_category_tax.model_product_product
msgid "Product"
@@ -23,7 +53,7 @@ msgstr "Article"
#. module: product_category_tax
#: model:ir.model,name:product_category_tax.model_product_category
msgid "Product Category"
msgstr "Catégorie d'articles"
msgstr "Catégorie d'article"
#. module: product_category_tax
#: model:ir.model,name:product_category_tax.model_product_template
@@ -31,23 +61,31 @@ msgid "Product Template"
msgstr "Modèle d'article"
#. module: product_category_tax
#: field:product.category,purchase_tax_ids:0
#: model:ir.model.fields,field_description:product_category_tax.field_product_category__purchase_tax_ids
msgid "Purchase Taxes"
msgstr "Taxes fournisseurs"
msgstr "Taxes à l'achat"
#. module: product_category_tax
#: field:product.category,sale_tax_ids:0
#: model:ir.model.fields,field_description:product_category_tax.field_product_category__sale_tax_ids
msgid "Sale Taxes"
msgstr "Taxes à la vente"
#. module: product_category_tax
#: code:addons/product_category_tax/product.py:57
#: code:addons/product_category_tax/product.py:0
#, python-format
msgid "The purchase taxes configured on the product '%s' are not the same as the purchase taxes configured on it's related product category '%s'."
msgstr "Les taxes fournisseurs paramétrées sur l'article '%s' ne sont pas les mêmes que les taxes fournisseurs paramétrées sur la catégorie interne '%s'."
msgid ""
"The purchase taxes configured on the product '%s' are not the same as the "
"purchase taxes configured on it's related internal category '%s'."
msgstr ""
"Les taxes à l'achat configurées sur l'article '%s' ne sont pas les mêmes que "
"les taxes à l'achat configurées sur sa catégorie interne '%s'."
#. module: product_category_tax
#: code:addons/product_category_tax/product.py:49
#: code:addons/product_category_tax/product.py:0
#, python-format
msgid "The sale taxes configured on the product '%s' are not the same as the sale taxes configured on it's related product category '%s'."
msgstr "Les taxes à la vente paramétrées sur l'article '%s' ne sont pas les mêmes que les taxes à la vente paramétrées sur la catégorie interne '%s'."
msgid ""
"The sale taxes configured on the product '%s' are not the same as the sale "
"taxes configured on it's related internal category '%s'."
msgstr ""
"Les taxes à la vente configurées sur l'article '%s' ne sont pas les mêmes que "
"les taxes à la vente configurées sur sa catégorie interne '%s'."

View File

@@ -48,7 +48,7 @@ class ProductTemplate(models.Model):
_inherit = ['product.template', 'product.categ.tax.mixin']
_name = 'product.template'
@api.constrains('taxes_id', 'supplier_taxes_id')
@api.constrains('taxes_id', 'supplier_taxes_id', 'categ_id')
def _check_tax_categ(self):
# self.name != 'Pay Debt' is a stupid hack to avoid blocking the
# installation of the module 'pos_debt_notebook'

View File

@@ -0,0 +1,2 @@
from . import models
from .post_install import set_product_detailed_type

View File

@@ -0,0 +1,19 @@
# Copyright 2023 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'Product Detailed Type',
'version': '14.0.1.0.0',
'category': 'Product',
'license': 'LGPL-3',
'summary': 'Backport detailed_type from v16 to v14',
'author': 'Akretion',
'website': 'https://github.com/akretion/odoo-usability',
'depends': ['product'],
'data': [
'views/product.xml',
],
'post_init_hook': 'set_product_detailed_type',
'installable': True,
}

View File

@@ -0,0 +1 @@
from . import product_template

View File

@@ -0,0 +1,25 @@
# Copyright 2023 Akretion France (http://www.akretion.com/)
# Copyright 2023 Odoo SA (contains code copy-pasted from Odoo v16)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class ProductTemplate(models.Model):
_inherit = 'product.template'
detailed_type = fields.Selection([
('consu', 'Consumable'),
('service', 'Service'),
], string='Product Type', default='consu', required=True, tracking=True)
type = fields.Selection(compute='_compute_type', store=True, string="Type")
def _detailed_type_mapping(self):
return {}
@api.depends('detailed_type')
def _compute_type(self):
type_mapping = self._detailed_type_mapping()
for record in self:
record.type = type_mapping.get(record.detailed_type, record.detailed_type)

View File

@@ -0,0 +1,7 @@
# Copyright 2023 Akretion France (http://www.akretion.com/)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
def set_product_detailed_type(cr, registry):
cr.execute('UPDATE product_template SET detailed_type=type')

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="product_template_tree_view" model="ir.ui.view">
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_tree_view"/>
<field name="arch" type="xml">
<field name="type" position="after">
<field name="detailed_type" optional="hide" readonly="1"/>
</field>
</field>
</record>
<record id="product_template_form_view" model="ir.ui.view">
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<field name="type" position="after">
<field name="detailed_type"/>
</field>
<field name="type" position="attributes">
<attribute name="invisible">1</attribute>
</field>
</field>
</record>
<record id="product_template_search_view" model="ir.ui.view">
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_search_view"/>
<field name="arch" type="xml">
<filter name="type" position="attributes">
<attribute name="invisible">1</attribute>
</filter>
<filter name="type" position="after">
<filter name="detailed_type_groupby" string="Product Type" context="{'group_by': 'detailed_type'}"/>
</filter>
</field>
</record>
</odoo>

View File

@@ -0,0 +1 @@
from . import models

View File

@@ -0,0 +1,16 @@
# Copyright 2023 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'Product Detailed Type Stock',
'version': '14.0.1.0.0',
'category': 'Product',
'license': 'LGPL-3',
'summary': 'Glue module between product_detailed_type and stock',
'author': 'Akretion',
'website': 'https://github.com/akretion/odoo-usability',
'depends': ['product_detailed_type', 'stock'],
'installable': True,
'auto_install': True,
}

View File

@@ -0,0 +1 @@
from . import product_template

View File

@@ -0,0 +1,13 @@
# Copyright 2023 Akretion France (http://www.akretion.com/)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ProductTemplate(models.Model):
_inherit = 'product.template'
detailed_type = fields.Selection(selection_add=[
('product', 'Storable Product')
], ondelete={'product': 'set default'})

View File

@@ -32,7 +32,7 @@ This module has been written by Alexis de Lattre from Akretion
<alexis.delattre@akretion.com>.
""",
'author': 'Akretion',
'website': 'http://www.akretion.com',
'website': 'https://github.com/akretion/odoo-usability',
# We depend on point_of_sale and not only 'product'
# because the price barcode rule is added by the point_of_sale module
# (the weight barcode rule is added by the stock module)
@@ -41,10 +41,12 @@ This module has been written by Alexis de Lattre from Akretion
'barcodes',
'base_report_to_printer',
],
'external_dependencies': {'python': ['python-barcode>=0.14.0']},
'data': [
'security/ir.model.access.csv',
'wizard/product_print_zpl_barcode_view.xml',
'views/product.xml',
'views/stock_picking.xml',
'data/barcode_sequence.xml',
],
'installable': True,

View File

@@ -0,0 +1,412 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * product_print_zpl_barcode
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-15 13:39+0000\n"
"PO-Revision-Date: 2023-07-15 13:39+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__copies
msgid "# Labels"
msgstr "Nb étiquettes"
#. module: product_print_zpl_barcode
#: model:ir.model.fields.selection,name:product_print_zpl_barcode.selection__product_print_zpl_barcode__label_size__38x25
msgid "38x25 mm"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__barcode
msgid "Barcode"
msgstr "Code-barres"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__nomenclature_id
msgid "Barcode Nomenclature"
msgstr "Nomenclature des codes-barres"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__rule_id
msgid "Barcode Rule"
msgstr "Règle de codes-barres"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__barcode_type
msgid "Barcode Type"
msgstr "Type de code-barres"
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_print_zpl_barcode_form
msgid "Cancel"
msgstr "Annuler"
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_print_zpl_barcode_form
msgid "Close"
msgstr "Fermer"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__company_id
msgid "Company"
msgstr "Société"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__create_uid
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__create_uid
msgid "Created by"
msgstr "Créé par"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__create_date
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__create_date
msgid "Created on"
msgstr "Créé le"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__currency_id
msgid "Currency"
msgstr "Devise"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,help:product_print_zpl_barcode.field_product_print_zpl_barcode_line__uom_id
msgid "Default unit of measure used for all stock operations."
msgstr ""
"Unité de mesure par défaut utilisée pour toutes les opérations de stock."
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__display_name
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__display_name
msgid "Display Name"
msgstr "Nom affiché"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,help:product_print_zpl_barcode.field_product_product__must_print_barcode
#: model:ir.model.fields,help:product_print_zpl_barcode.field_product_template__must_print_barcode
msgid ""
"Enable that option for products for which you must print a barcode upon "
"reception in stock."
msgstr ""
"Activez cette option sur les articles pour lesquels vous devez imprimer un "
"code-barres dès leur réception en stock."
#. module: product_print_zpl_barcode
#: model:ir.actions.act_window,name:product_print_zpl_barcode.product_print_zpl_barcode_action
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_normal_form_view
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_template_only_form_view
msgid "Generate Barcode"
msgstr "Générer un code-barres"
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_print_zpl_barcode_form
msgid "Generate Labels"
msgstr "Générer les étiquettes"
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_product_print_zpl_barcode
msgid "Generate and print product barcodes in ZPL"
msgstr "Générer et imprimer des codes-barres d'articles en ZPL"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__id
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__id
msgid "ID"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__label_size
msgid "Label Size"
msgstr "Taille de l'étiquette"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode____last_update
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line____last_update
msgid "Last Modified on"
msgstr "Dernière modification le"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__write_uid
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__write_uid
msgid "Last Updated by"
msgstr "Dernière mise à jour par"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__write_date
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__write_date
msgid "Last Updated on"
msgstr "Dernière mise à jour le"
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"Line '%s': barcode '%s' has %d digits. This wizard only supports EAN8 and "
"EAN13 for the moment."
msgstr ""
"Ligne '%s' : le code-barres '%s' comporte %d chiffres. Cet assistant ne "
"prend en charge que les EAN8 et EAN13 pour le moment."
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "Line '%s': barcode type '%s' is not supported for the moment"
msgstr ""
"Ligne '%s' : le type de code-barres '%s' n'est pas supporté pour le moment"
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"Line '%s': the barcode '%s' is not a valid EAN barcode (wrong checksum)."
msgstr ""
"Ligne '%s' : le code-barres '%s' n'est pas un code-barres EAN valide "
"(mauvaise somme de contrôle)."
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_product_print_zpl_barcode_line
msgid "Line of the print ZPL barcode wizard"
msgstr "Ligne de l'assistant d'impression du code-barres ZPL"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__line_ids
msgid "Lines"
msgstr "Lignes"
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "Missing Products"
msgstr "Produits manquants"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_product__must_print_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_template__must_print_barcode
msgid "Must Print Barcode"
msgstr "Code-barres à imprimer"
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "On line '%s', the number of copies must be strictly positive."
msgstr ""
"Sur la ligne '%s', le nombre d'étiquettes doit être strictement positif."
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_product__barcode_image_png
msgid "PNG Barcode Image"
msgstr "Image PNG du code-barres"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__parent_id
msgid "Parent"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__price
msgid "Price"
msgstr "Prix"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__price_uom
msgid "Price/UoM"
msgstr "Prix/unité"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__pricelist_id
msgid "Pricelist"
msgstr "Liste de prix"
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_print_zpl_barcode_form
msgid "Print"
msgstr "Imprimer"
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_normal_form_view
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_template_only_form_view
msgid "Print Barcode"
msgstr "Imprimer le code-barres"
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_product_tree_view
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_template_tree_view
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.view_picking_form
msgid "Print Barcodes"
msgstr "Imprimer les code-barres"
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_product_template
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__product_id
msgid "Product"
msgstr "Article"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__product_name
msgid "Product Label"
msgstr "Étiquette de l'article"
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_product_product
msgid "Product Variant"
msgstr "Variante d'article"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__quantity
msgid "Qty"
msgstr "Qté"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_product__barcode_image_svg
msgid "SVG Barcode Image"
msgstr "Image SVG du code-barres"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_stock_picking__show_print_zpl_barcode
msgid "Show Print Zpl Barcode"
msgstr "Afficher le bouton imprimer le code-barres ZPL"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__state
msgid "State"
msgstr "État"
#. module: product_print_zpl_barcode
#: model:ir.model.fields.selection,name:product_print_zpl_barcode.selection__product_print_zpl_barcode__state__step1
msgid "Step1"
msgstr "Étape 1"
#. module: product_print_zpl_barcode
#: model:ir.model.fields.selection,name:product_print_zpl_barcode.selection__product_print_zpl_barcode__state__step2
msgid "Step2"
msgstr "Étape 2"
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"The barcode of the product (%s) has %d characters, which is smaller than the"
" %d characters of the prefix of the barcode pattern (%s)."
msgstr ""
"Le code-barres de l'article (%s) comporte %d caractères, ce qui est plus "
"petit que les %d caractères du préfixe du modèle de code-barres (%s)."
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"The barcode rule '%s' has a pattern '%s' which doesn't contain a integer and"
" decimal part between '{}'."
msgstr ""
"La règle de code-barres '%s' a un motif '%s' qui ne contient pas de partie "
"entière et décimale entre '{}'."
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/models/product.py:0
#, python-format
msgid "The product '%s' already has a barcode."
msgstr "L'article '%s' a déjà un code-barres."
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "The quantity (%s) must be positive !"
msgstr "La quantité (%s) doit être positive !"
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/models/product.py:0
#, python-format
msgid ""
"The sequence 'private.product.barcode' is not properly configured. The "
"generated sequence should have 7 digits (for EAN-8) or 12 digits (for "
"EAN-13). It currently has %d digits."
msgstr ""
"La séquence 'private.product.barcode' n'est pas correctement configurée. La "
"séquence générée devrait avoir 7 chiffres (pour EAN-8) ou 12 chiffres (pour "
"EAN-13). Elle comporte actuellement %d chiffres."
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"The value to encode in the barcode (%s) is superior to the maximum value "
"allowed by the barcode pattern (%s)."
msgstr ""
"La valeur à encoder dans le code-barres (%s) est supérieure à la valeur "
"maximale autorisée par le motif du code-barres (%s)."
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "There are no pricelist in company '%s'."
msgstr "Il n'y a pas de liste de prix dans la société '%s'."
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_stock_picking
msgid "Transfer"
msgstr "Transfert"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__uom_id
msgid "UoM"
msgstr "Unité"
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "Wrong active_model in context (%s)."
msgstr "Mauvais active_model dans le contexte (%s)."
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/models/product.py:0
#, python-format
msgid ""
"You cannot call the method generate_barcode_from_product_template on product"
" '%s' because it has %d variants and not just one."
msgstr ""
"Vous ne pouvez pas appeler la méthode generate_barcode_from_product_template"
" sur l'article '%s' parce qu'il a %d variantes et non une seule."
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "You must select a ZPL Printer."
msgstr "Vous devez sélectionner une imprimante ZPL."
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__zpl_file
msgid "ZPL File"
msgstr "Fichier ZPL"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__zpl_filename
msgid "ZPL Filename"
msgstr "Nom du fichier ZPL"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__zpl_printer_id
msgid "ZPL Printer"
msgstr "Imprimante ZPL"

View File

@@ -0,0 +1,392 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * product_print_zpl_barcode
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-15 13:39+0000\n"
"PO-Revision-Date: 2023-07-15 13:39+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__copies
msgid "# Labels"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields.selection,name:product_print_zpl_barcode.selection__product_print_zpl_barcode__label_size__38x25
msgid "38x25 mm"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__barcode
msgid "Barcode"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__nomenclature_id
msgid "Barcode Nomenclature"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__rule_id
msgid "Barcode Rule"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__barcode_type
msgid "Barcode Type"
msgstr ""
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_print_zpl_barcode_form
msgid "Cancel"
msgstr ""
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_print_zpl_barcode_form
msgid "Close"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__company_id
msgid "Company"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__create_uid
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__create_uid
msgid "Created by"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__create_date
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__create_date
msgid "Created on"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__currency_id
msgid "Currency"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,help:product_print_zpl_barcode.field_product_print_zpl_barcode_line__uom_id
msgid "Default unit of measure used for all stock operations."
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__display_name
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__display_name
msgid "Display Name"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,help:product_print_zpl_barcode.field_product_product__must_print_barcode
#: model:ir.model.fields,help:product_print_zpl_barcode.field_product_template__must_print_barcode
msgid ""
"Enable that option for products for which you must print a barcode upon "
"reception in stock."
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.actions.act_window,name:product_print_zpl_barcode.product_print_zpl_barcode_action
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_normal_form_view
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_template_only_form_view
msgid "Generate Barcode"
msgstr ""
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_print_zpl_barcode_form
msgid "Generate Labels"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_product_print_zpl_barcode
msgid "Generate and print product barcodes in ZPL"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__id
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__id
msgid "ID"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__label_size
msgid "Label Size"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode____last_update
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line____last_update
msgid "Last Modified on"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__write_uid
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__write_uid
msgid "Last Updated by"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__write_date
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__write_date
msgid "Last Updated on"
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"Line '%s': barcode '%s' has %d digits. This wizard only supports EAN8 and "
"EAN13 for the moment."
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "Line '%s': barcode type '%s' is not supported for the moment"
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"Line '%s': the barcode '%s' is not a valid EAN barcode (wrong checksum)."
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_product_print_zpl_barcode_line
msgid "Line of the print ZPL barcode wizard"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__line_ids
msgid "Lines"
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "Missing Products"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_product__must_print_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_template__must_print_barcode
msgid "Must Print Barcode"
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "On line '%s', the number of copies must be strictly positive."
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_product__barcode_image_png
msgid "PNG Barcode Image"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__parent_id
msgid "Parent"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__price
msgid "Price"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__price_uom
msgid "Price/UoM"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__pricelist_id
msgid "Pricelist"
msgstr ""
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_print_zpl_barcode_form
msgid "Print"
msgstr ""
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_normal_form_view
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_template_only_form_view
msgid "Print Barcode"
msgstr ""
#. module: product_print_zpl_barcode
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_product_tree_view
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.product_template_tree_view
#: model_terms:ir.ui.view,arch_db:product_print_zpl_barcode.view_picking_form
msgid "Print Barcodes"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_product_template
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__product_id
msgid "Product"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__product_name
msgid "Product Label"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_product_product
msgid "Product Variant"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__quantity
msgid "Qty"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_product__barcode_image_svg
msgid "SVG Barcode Image"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_stock_picking__show_print_zpl_barcode
msgid "Show Print Zpl Barcode"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__state
msgid "State"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields.selection,name:product_print_zpl_barcode.selection__product_print_zpl_barcode__state__step1
msgid "Step1"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields.selection,name:product_print_zpl_barcode.selection__product_print_zpl_barcode__state__step2
msgid "Step2"
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"The barcode of the product (%s) has %d characters, which is smaller than the"
" %d characters of the prefix of the barcode pattern (%s)."
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"The barcode rule '%s' has a pattern '%s' which doesn't contain a integer and"
" decimal part between '{}'."
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/models/product.py:0
#, python-format
msgid "The product '%s' already has a barcode."
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "The quantity (%s) must be positive !"
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/models/product.py:0
#, python-format
msgid ""
"The sequence 'private.product.barcode' is not properly configured. The "
"generated sequence should have 7 digits (for EAN-8) or 12 digits (for "
"EAN-13). It currently has %d digits."
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid ""
"The value to encode in the barcode (%s) is superior to the maximum value "
"allowed by the barcode pattern (%s)."
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "There are no pricelist in company '%s'."
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model,name:product_print_zpl_barcode.model_stock_picking
msgid "Transfer"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode_line__uom_id
msgid "UoM"
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "Wrong active_model in context (%s)."
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/models/product.py:0
#, python-format
msgid ""
"You cannot call the method generate_barcode_from_product_template on product"
" '%s' because it has %d variants and not just one."
msgstr ""
#. module: product_print_zpl_barcode
#. odoo-python
#: code:addons/product_print_zpl_barcode/wizard/product_print_zpl_barcode.py:0
#, python-format
msgid "You must select a ZPL Printer."
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__zpl_file
msgid "ZPL File"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__zpl_filename
msgid "ZPL Filename"
msgstr ""
#. module: product_print_zpl_barcode
#: model:ir.model.fields,field_description:product_print_zpl_barcode.field_product_print_zpl_barcode__zpl_printer_id
msgid "ZPL Printer"
msgstr ""

View File

@@ -1 +1,2 @@
from . import product
from . import stock_picking

View File

@@ -1,11 +1,14 @@
# -*- coding: utf-8 -*-
# Copyright 2020 Akretion France (http://www.akretion.com/)
# Copyright 2020-2023 Akretion France (http://www.akretion.com/)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from stdnum.ean import calc_check_digit
from stdnum.ean import calc_check_digit, is_valid
from barcode import EAN13, EAN8
from barcode.writer import ImageWriter, SVGWriter
import base64
import io
class ProductTemplate(models.Model):
@@ -26,26 +29,54 @@ class ProductTemplate(models.Model):
% (self.display_name, self.product_variant_count))
return self.product_variant_ids[0].generate_barcode_from_product_product()
def print_zpl_barcode_from_product_template(self):
self.ensure_one()
if self.product_variant_count != 1:
raise UserError(_(
"You cannot call the method "
"print_zpl_barcode_from_product_template on product '%s' "
"because it has %d variants and not just one.")
% (self.display_name, self.product_variant_count))
action = self.env.ref(
'product_print_zpl_barcode.product_print_zpl_barcode_action').sudo().read()[0]
action['context'] = {
'active_id': self.product_variant_ids[0].id,
'active_model': 'product.product',
}
return action
class ProductProduct(models.Model):
_inherit = 'product.product'
# Not useful for ZPL, but it is often useful to have a barcode image field
barcode_image_png = fields.Binary(
compute='_compute_barcode_image_png',
string='PNG Barcode Image')
barcode_image_svg = fields.Binary(
compute='_compute_barcode_image_svg',
string='SVG Barcode Image')
def _get_barcode_image(self, img_format):
self.ensure_one()
barcode = self.barcode
if not barcode:
return False
res = False
if isinstance(barcode, str) and len(barcode) in (8, 13) and is_valid(barcode):
barcode_obj = False
if img_format == 'svg':
writer = SVGWriter()
elif img_format == 'png':
writer = ImageWriter()
else:
return False
if len(barcode) == 13:
barcode_obj = EAN13(barcode, writer=writer, guardbar=True)
elif len(barcode) == 8:
barcode_obj = EAN8(barcode, writer=writer, guardbar=True)
if barcode_obj:
barcode_file = io.BytesIO()
barcode_obj.write(barcode_file)
barcode_file.seek(0)
barcode_img = barcode_file.read()
res = base64.b64encode(barcode_img)
return res
@api.depends('barcode')
def _compute_barcode_image_svg(self):
for product in self:
product.barcode_image_svg = product._get_barcode_image('svg')
@api.depends('barcode')
def _compute_barcode_image_png(self):
for product in self:
product.barcode_image_png = product._get_barcode_image('png')
def generate_barcode_from_product_product(self):
self.ensure_one()
if self.barcode:

View File

@@ -0,0 +1,25 @@
# Copyright 2023 Akretion France (http://www.akretion.com/)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
from odoo.tools import float_compare
class StockPicking(models.Model):
_inherit = "stock.picking"
show_print_zpl_barcode = fields.Boolean(compute='_compute_show_print_zpl_barcode')
@api.depends('state')
def _compute_show_print_zpl_barcode(self):
prec = self.env['decimal.precision'].precision_get('Product Unit of Measure')
for picking in self:
show = False
if picking.state == 'done' and picking.picking_type_code != 'outgoing':
for line in picking.move_line_ids:
if (
line.product_id.must_print_barcode and
float_compare(line.qty_done, 0, precision_digits=prec) > 0):
show = True
picking.show_print_zpl_barcode = show

View File

@@ -1,2 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_product_print_zpl_barcode,Full access to product.print.zpl.barcode wizard,model_product_print_zpl_barcode,base_report_to_printer.printing_group_user,1,1,1,1
access_product_print_zpl_barcode_line,Full access to product.print.zpl.barcode.line wizard,model_product_print_zpl_barcode_line,base_report_to_printer.printing_group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_product_print_zpl_barcode Full access to product.print.zpl.barcode wizard model_product_print_zpl_barcode base_report_to_printer.printing_group_user 1 1 1 1
3 access_product_print_zpl_barcode_line Full access to product.print.zpl.barcode.line wizard model_product_print_zpl_barcode_line base_report_to_printer.printing_group_user 1 1 1 1

View File

@@ -27,11 +27,23 @@
<field name="arch" type="xml">
<header position="inside">
<button name="generate_barcode_from_product_template" type="object" string="Generate Barcode" attrs="{'invisible': ['|', ('product_variant_count', '>', 1), ('barcode', '!=', False)]}"/>
<button name="print_zpl_barcode_from_product_template" type="object" string="Print Barcode" groups="base_report_to_printer.printing_group_user" attrs="{'invisible': ['|', ('product_variant_count', '>', 1), ('barcode', '=', False)]}"/>
<button name="%(product_print_zpl_barcode.product_print_zpl_barcode_action)d" type="action" string="Print Barcode" groups="base_report_to_printer.printing_group_user" attrs="{'invisible': ['|', ('product_variant_count', '>', 1), ('barcode', '=', False)]}"/>
</header>
</field>
</record>
<record id="product_template_tree_view" model="ir.ui.view">
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_tree_view"/>
<field name="arch" type="xml">
<field name="product_variant_count" position="before">
<header>
<button name="%(product_print_zpl_barcode.product_print_zpl_barcode_action)d" type="action" string="Print Barcodes" groups="base_report_to_printer.printing_group_user"/>
</header>
</field>
</field>
</record>
<record id="product_normal_form_view" model="ir.ui.view">
<field name="model">product.product</field>
@@ -44,5 +56,16 @@
</field>
</record>
<record id="product_product_tree_view" model="ir.ui.view">
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_product_tree_view"/>
<field name="arch" type="xml">
<field name="default_code" position="before">
<header>
<button name="%(product_print_zpl_barcode.product_print_zpl_barcode_action)d" type="action" string="Print Barcodes" groups="base_report_to_printer.printing_group_user"/>
</header>
</field>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 Akretion France (http://www.akretion.com/)
@author Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_picking_form" model="ir.ui.view">
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/>
<field name="arch" type="xml">
<button name="action_toggle_is_locked" position="after">
<button name="%(product_print_zpl_barcode.product_print_zpl_barcode_action)d" type="action" string="Print Barcodes" attrs="{'invisible': [('show_print_zpl_barcode', '=', False)]}"/>
<field name="show_print_zpl_barcode" invisible="1"/>
</button>
</field>
</record>
</odoo>

View File

@@ -1,31 +1,26 @@
# Copyright 2016-2020 Akretion France (http://www.akretion.com/)
# Copyright 2016-2023 Akretion France (http://www.akretion.com/)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import float_compare, float_is_zero
from stdnum.ean import is_valid
from stdnum.ean import is_valid, calc_check_digit
import base64
import re
import logging
logger = logging.getLogger(__name__)
class ProductPrintZplBarcode(models.TransientModel):
_name = 'product.print.zpl.barcode'
_description = 'Generate and print product barcodes in ZPL'
_check_company_auto = True
@api.model
def default_get(self, fields_list):
res = super().default_get(fields_list)
assert self._context.get('active_model') == 'product.product',\
'wrong active_model, should be product.product'
product_id = self._context.get('active_id')
product = self.env['product.product'].browse(product_id)
if not product:
raise UserError(_('Missing Product'))
if not product.barcode:
raise UserError(_(
"Product '%s' doesn't have a barcode") % product.display_name)
nomenclature = self.env.ref('barcodes.default_barcode_nomenclature')
company = self.env.company
posconfig = self.env['pos.config'].sudo().search(
@@ -39,85 +34,200 @@ class ProductPrintZplBarcode(models.TransientModel):
], limit=1)
if not pricelist:
raise UserError(_(
"There are no pricelist in company %s ?") % company.name)
"There are no pricelist in company '%s'.") % company.name)
printer = self.env['printing.printer'].get_default()
line_ids = []
if self._context.get('active_model') == 'product.product':
product_ids = self._context.get('active_ids')
products = self.env['product.product'].browse(product_ids)
if not products:
raise UserError(_('Missing Products'))
for product in products:
self._update_line_ids(line_ids, product)
elif self._context.get('active_model') == 'product.template':
product_tmpl_ids = self._context.get('active_ids')
product_tmpls = self.env['product.template'].browse(product_tmpl_ids)
for product_tmpl in product_tmpls:
for product in product_tmpl.product_variant_ids:
self._update_line_ids(line_ids, product)
elif self._context.get('active_model') == 'stock.picking':
prec = self.env['decimal.precision'].precision_get(
'Product Unit of Measure')
picking = self.env['stock.picking'].browse(self._context['active_id'])
for ml in picking.move_line_ids:
if (
ml.product_id and
ml.product_id.must_print_barcode and
float_compare(ml.qty_done, 0, precision_digits=prec) > 0):
self._update_line_ids(
line_ids, ml.product_id, int(round(ml.qty_done)))
else:
raise UserError(_(
"Wrong active_model in context (%s).")
% self._context.get('active_model'))
res.update({
'company_id': company.id,
'nomenclature_id': nomenclature.id,
'pricelist_id': pricelist.id,
'currency_id': pricelist.currency_id.id,
'barcode': product.barcode,
'product_name': product.name,
'product_id': product_id,
'zpl_printer_id': printer and printer.id or False,
'line_ids': line_ids,
})
return res
product_id = fields.Many2one(
'product.product', string='Product', required=True, readonly=True)
uom_id = fields.Many2one(
related='product_id.uom_id', readonly=True)
# 1 line = un peu moins de 30
product_name = fields.Char('Product Label', required=True, size=56)
@api.model
def _update_line_ids(self, line_ids, product, copies=1):
if product.barcode:
line_ids.append((0, 0, {
'barcode': product.barcode,
'product_name': product.name,
'product_id': product.id,
'copies': copies,
}))
else:
logger.warning("Product '%s' doesn't have a barcode", product.display_name)
company_id = fields.Many2one( # default value set by default_get
'res.company', required=True, ondelete='cascade')
nomenclature_id = fields.Many2one(
'barcode.nomenclature', 'Barcode Nomenclature', required=True)
rule_id = fields.Many2one(
'barcode.rule', string='Barcode Rule', readonly=True,
compute='_compute_rule_id')
barcode_type = fields.Selection(
related='rule_id.type', readonly=True, string="Barcode Type")
'barcode.nomenclature', 'Barcode Nomenclature', required=True,
states={'step2': [('readonly', True)]})
# label_size: remove readonly=True when we will support more labels
label_size = fields.Selection([
('38x25', '38x25 mm'),
], required=True, default='38x25', string='Label Size')
], required=True, default='38x25', readonly=True)
pricelist_id = fields.Many2one(
'product.pricelist', string='Pricelist', required=True)
currency_id = fields.Many2one(
related='pricelist_id.currency_id', readonly=True)
# TODO: for the moment, we only support weight, but...
quantity = fields.Float(digits='Stock Weight')
price_uom = fields.Monetary(
readonly=True, string="Price per Unit of Measure",
compute='_compute_price') # given by pricelist
price = fields.Monetary(compute='_compute_price', readonly=True)
currency_id = fields.Many2one('res.currency', string='Currency')
'product.pricelist', string='Pricelist', required=True,
states={'step2': [('readonly', True)]}, check_company=True,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]"
)
state = fields.Selection([
('step1', 'Step1'),
('step2', 'Step2'),
], default='step1', readonly=True)
zpl_file = fields.Binary(string='ZPL File', readonly=True)
zpl_filename = fields.Char('ZPL Filename')
barcode = fields.Char(readonly=True)
copies = fields.Integer(
string='Number of Labels', default=1, required=True)
zpl_printer_id = fields.Many2one(
'printing.printer', string='ZPL Printer')
line_ids = fields.One2many(
'product.print.zpl.barcode.line', 'parent_id',
string='Lines', states={'step2': [('readonly', True)]})
@api.depends('pricelist_id', 'quantity', 'product_id')
def generate(self):
"""Called by button for the wizard, 1st step"""
self.ensure_one()
zpl_strings = []
for line in self.line_ids:
barcode = line.barcode
product_name = line.product_name
assert barcode
barcode_len = len(barcode)
if barcode_len not in (8, 13):
raise UserError(_(
"Line '%s': barcode '%s' has %d digits. "
"This wizard only supports EAN8 and EAN13 for the moment.")
% (product_name, barcode, barcode_len))
if not is_valid(barcode):
raise UserError(_(
"Line '%s': the barcode '%s' is not a valid EAN barcode "
"(wrong checksum).") % (product_name, barcode))
if line.copies <= 0:
raise UserError(_(
"On line '%s', the number of copies must be strictly positive."
) % product_name)
if line.barcode_type in ('price', 'weight'):
barcode, zpl_str = line._prepare_price_weight_barcode_type()
elif line.barcode_type == 'product':
barcode, zpl_str = line._prepare_product_barcode_type()
else:
raise UserError(_(
"Line '%s': barcode type '%s' is not supported for the moment")
% (product_name, line.barcode_type))
line.write({'barcode': barcode})
zpl_strings.append(zpl_str)
zpl_filename = "barcodes.zpl"
if len(self.line_ids) == 1:
zpl_filename = "barcode_%s.zpl" % self.line_ids[0].barcode
zpl_str = '\n'.join(zpl_strings)
zpl_bytes = zpl_str.encode('utf-8')
vals = {
'zpl_file': base64.encodebytes(zpl_bytes),
'state': 'step2',
'zpl_filename': zpl_filename,
}
self.write(vals)
action = self.env["ir.actions.actions"]._for_xml_id(
'product_print_zpl_barcode.product_print_zpl_barcode_action')
action.update({
'res_id': self.id,
'context': self._context,
'views': False})
return action
def print_zpl(self):
if not self.zpl_printer_id:
raise UserError(_(
"You must select a ZPL Printer."))
self.zpl_printer_id.print_document(
self.zpl_filename, base64.decodebytes(self.zpl_file), format='raw')
class ProductPrintZplBarcodeLine(models.TransientModel):
_name = 'product.print.zpl.barcode.line'
_description = 'Line of the print ZPL barcode wizard'
parent_id = fields.Many2one(
'product.print.zpl.barcode', ondelete='cascade')
product_id = fields.Many2one(
'product.product', string='Product', readonly=True)
uom_id = fields.Many2one(related='product_id.uom_id', string='UoM')
# 1 line = a bit less than 30
# I don't make product_name a stored computed field because I'm afraid
# that we may not take the lang of the user
product_name = fields.Char('Product Label', required=True, size=56)
rule_id = fields.Many2one(
'barcode.rule', string='Barcode Rule', compute='_compute_rule_id')
barcode_type = fields.Selection(related='rule_id.type', string="Barcode Type")
currency_id = fields.Many2one(related='parent_id.pricelist_id.currency_id')
# TODO: for the moment, we only support weight, but...
quantity = fields.Float(digits='Stock Weight', string='Qty')
price_uom = fields.Monetary(
string="Price/UoM", compute='_compute_price') # given by pricelist
price = fields.Monetary(compute='_compute_price')
barcode = fields.Char(readonly=True)
copies = fields.Integer(string='# Labels', default=1, required=True)
@api.depends('parent_id.pricelist_id', 'quantity', 'product_id')
def _compute_price(self):
# for regular barcodes
for wiz in self:
if wiz.pricelist_id and wiz.product_id:
price_uom = wiz.pricelist_id.get_product_price(
wiz.product_id, 1, False)
wiz.price_uom = price_uom
wiz.price = price_uom * wiz.quantity
for line in self:
pricelist = line.parent_id.pricelist_id
price_uom = price = 0.0
if pricelist and line.product_id:
price_uom = pricelist.get_product_price(line.product_id, 1, False)
price = price_uom * line.quantity
line.price_uom = price_uom
line.price = price
@api.depends('nomenclature_id')
@api.depends('parent_id.nomenclature_id')
def _compute_rule_id(self):
for wiz in self:
for line in self:
nomenclature = line.parent_id.nomenclature_id
match_rule = False
if wiz.nomenclature_id and wiz.barcode:
for rule in wiz.nomenclature_id.rule_ids:
match = wiz.nomenclature_id.match_pattern(
wiz.barcode, rule.pattern)
if nomenclature and line.barcode:
for rule in nomenclature.rule_ids:
match = nomenclature.match_pattern(
line.barcode, rule.pattern)
if match.get('match'):
match_rule = rule.id
break
wiz.rule_id = match_rule
line.rule_id = match_rule
def _prepare_price_weight_barcode_type(self):
dpo = self.env['decimal.precision']
bno = self.env['barcode.nomenclature']
prec = dpo.precision_get('Stock Weight')
value = self.quantity
pbarcode = self.barcode
@@ -140,14 +250,14 @@ class ProductPrintZplBarcode(models.TransientModel):
"of the barcode pattern (%s).")
% (pbarcode, len(pbarcode), len(prefix), prefix))
barcode = pbarcode[0:len(prefix)]
# print "barcode=", barcode
# print "pattern=", pattern
m = re.search('\{N+D+\}', pattern)
# print "m=", m
# print("barcode=", barcode)
# print("pattern=", pattern)
m = re.search(r'\{N+D+\}', pattern)
# print("m=", m)
assert m
pattern_val = m.group(0)
pattern_val = pattern_val[1:-1]
# print "pattern_val=", pattern_val
# print("pattern_val=", pattern_val)
max_value = 10**pattern_val.count('N')
if float_compare(value, max_value, precision_digits=prec) != -1:
raise UserError(_(
@@ -166,12 +276,16 @@ class ProductPrintZplBarcode(models.TransientModel):
value_d_ext = value_d + '0' * pattern_val.count('D')
# 2) cut at the right size
barcode += value_d_ext[0:pattern_val.count('D')]
# print "barcode=", barcode
# print("barcode=", barcode)
# Add checksum
if self.rule_id.encoding == 'ean13':
barcode = bno.sanitize_ean(barcode)
# print "barcode FINAL=", barcode
zpl_unicode = self._price_weight_barcode_type_zpl() % {
# I don't call bno.sanitize_ean() due to this bug:
# https://github.com/odoo/odoo/pull/114112
barcode = barcode + calc_check_digit(barcode)
assert len(barcode) == 13
assert is_valid(barcode)
# print("barcode FINAL=", barcode)
zpl_str = self._price_weight_barcode_type_zpl() % {
'product_name': self.product_name,
'ean_zpl_command': len(self.barcode) == 8 and 'B8' or 'BE',
'ean_no_checksum': barcode[:-1],
@@ -182,12 +296,7 @@ class ProductPrintZplBarcode(models.TransientModel):
'quantity': value,
'uom_name': self.uom_id.name,
}
zpl_bytes = zpl_unicode.encode('utf-8')
vals = {
'zpl_file': base64.encodebytes(zpl_bytes),
'barcode': barcode,
}
return vals
return (barcode, zpl_str)
@api.model
def _price_weight_barcode_type_zpl(self):
@@ -228,7 +337,7 @@ class ProductPrintZplBarcode(models.TransientModel):
return label
def _prepare_product_barcode_type(self):
zpl_unicode = self._product_barcode_type_zpl() % {
zpl_str = self._product_barcode_type_zpl() % {
'product_name': self.product_name,
'ean_zpl_command': len(self.barcode) == 8 and 'B8' or 'BE',
'ean_no_checksum': self.barcode[:-1],
@@ -236,58 +345,4 @@ class ProductPrintZplBarcode(models.TransientModel):
'currency_symbol': self.currency_id.symbol, # symbol is a required field
'copies': self.copies,
}
zpl_bytes = zpl_unicode.encode('utf-8')
vals = {
'zpl_file': base64.encodebytes(zpl_bytes),
'barcode': self.barcode, # unchanged
}
return vals
def generate(self):
assert self.barcode
if len(self.barcode) not in (8, 13):
raise UserError(_(
"This wizard only supports EAN8 and EAN13 for the moment. "
"Barcode '%s' has %d digits.") % (
self.barcode,
len(self.barcode)))
if not is_valid(self.barcode):
raise UserError(_(
"The barcode '%s' is not a valid EAN barcode "
"(wrong checksum).") % self.barcode)
if not self.copies:
raise UserError(_("The number of copies cannot be 0"))
if self.barcode_type in ('price', 'weight'):
vals = self._prepare_price_weight_barcode_type()
elif self.barcode_type == 'product':
vals = self._prepare_product_barcode_type()
else:
raise UserError(_(
"Barcode Type %s is not supported for the moment")
% self.barcode_type)
vals.update({
'state': 'step2',
'zpl_filename': 'barcode_%s.zpl' % vals['barcode'],
})
self.write(vals)
action = self.env.ref('product_print_zpl_barcode.product_print_zpl_barcode_action').sudo().read()[0]
action.update({
'res_id': self.id,
'context': self._context,
'views': False})
return action
def print_zpl(self):
if not self.zpl_printer_id:
raise UserError(_(
"You must select a ZPL Printer."))
self.zpl_printer_id.print_document(
self.zpl_filename, base64.decodebytes(self.zpl_file), format='raw')
action = True
if self._context.get('print_and_new'):
action = self.env.ref('product_print_zpl_barcode.product_print_zpl_barcode_action').sudo().read()[0]
action.update({
'views': False,
'context': self._context,
})
return action
return (self.barcode, zpl_str)

View File

@@ -11,38 +11,41 @@
<field name="name">product_print_zpl_barcode.form</field>
<field name="model">product.print.zpl.barcode</field>
<field name="arch" type="xml">
<form string="Generate and Print Product Barcode">
<group name="step1" string="Configuration">
<form>
<group name="step1">
<field name="state" invisible="1"/>
<field name="currency_id" invisible="1"/>
<field name="product_id"/>
<field name="product_name" attrs="{'readonly': [('state', '=', 'step2')]}"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="company_id" invisible="1"/>
<field name="pricelist_id" attrs="{'readonly': [('state', '=', 'step2')]}"/>
<field name="price_uom"/>
<field name="label_size" attrs="{'readonly': [('state', '=', 'step2')]}"/>
<field name="nomenclature_id" attrs="{'readonly': [('state', '=', 'step2')]}"/>
<field name="rule_id"/>
<field name="barcode_type"/>
<field name="barcode"/>
<field name="copies" attrs="{'readonly': [('state', '=', 'step2')]}"/>
</group>
<group string="Enter Quantity" attrs="{'invisible': [('barcode_type', '=', 'product')]}">
<div name="qty_uom">
<field name="quantity" attrs="{'readonly': [('state', '=', 'step2')]}" class="oe_inline"/>
<field name="uom_id" class="oe_inline"/>
</div>
</group>
<group name="step2" states="step2" string="Label">
<field name="price" attrs="{'invisible': [('barcode_type', 'not in', ('price', 'weight'))]}"/>
<group name="step2" states="step2">
<field name="zpl_file" filename="zpl_filename" />
<field name="zpl_filename" invisible="1"/>
<field name="zpl_printer_id" attrs="{'required': [('state', '=', 'step2')]}"/>
</group>
<group name="lines">
<field name="line_ids" colspan="2" nolabel="1">
<tree editable="bottom">
<field name="currency_id" invisible="1"/>
<field name="product_id" optional="hide" force_save="1"/>
<field name="product_name"/>
<field name="price_uom"/>
<field name="rule_id" optional="show"/>
<field name="barcode_type" optional="hide"/>
<field name="barcode" force_save="1"/>
<field name="price" attrs="{'invisible': [('barcode_type', 'not in', ('price', 'weight'))]}"/>
<field name="quantity" attrs="{'invisible': [('barcode_type', '=', 'product')]}" optional="show"/>
<field name="uom_id" attrs="{'invisible': [('barcode_type', '=', 'product')]}" optional="show"/>
<field name="copies" />
</tree>
</field>
</group>
<footer>
<button name="generate" type="object" string="Generate Label" class="btn-primary" states="step1"/>
<button name="generate" type="object" string="Generate Labels" class="btn-primary" states="step1"/>
<button special="cancel" string="Cancel" class="btn-default" states="step1"/>
<button name="print_zpl" type="object" string="Print" class="btn-primary" states="step2"/>
<button name="print_zpl" type="object" string="Print and New" class="btn-primary" context="{'print_and_new': True}" attrs="{'invisible': ['|', ('state', '!=', 'step2'), ('barcode_type', '=', 'product')]}"/>
<button special="cancel" string="Close" class="btn-default" states="step2"/>
</footer>
</form>

View File

@@ -0,0 +1 @@
from . import models

View File

@@ -0,0 +1,28 @@
# Copyright 2023 Akretion France (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'Product Priority Star',
'version': '14.0.1.0.0',
'category': 'Product',
'license': 'AGPL-3',
'summary': 'Add a priority star on product',
'description': """
Product Priority Star
=====================
This module adds a priority star on products (like on pickings and manufacturing order). If the star is yellow, the product will be displayed at the top.
This feature is native in Odoo 16.0.
This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>.
""",
'author': 'Akretion',
'website': 'https://github.com/akretion/odoo-usability',
'depends': ['product'],
'excludes': ['product_priority'],
'data': ['views/product_template.xml'],
'installable': True,
}

View File

@@ -0,0 +1 @@
from . import product

View File

@@ -0,0 +1,20 @@
# Copyright 2023 Akretion France (http://www.akretion.com/)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ProductTemplate(models.Model):
_inherit = 'product.template'
_order = "priority desc, name, id"
priority = fields.Selection([
('0', 'Normal'),
('1', 'Top List'),
], default='0', index=True)
class ProductProduct(models.Model):
_inherit = 'product.product'
_order = 'priority desc, default_code, name, id'

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="product_template_tree_view" model="ir.ui.view">
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_tree_view"/>
<field name="arch" type="xml">
<field name="sequence" position="after">
<field name="priority" widget="priority" optional="show" nolabel="1"/>
</field>
</field>
</record>
<record id="product_template_form_view" model="ir.ui.view">
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<field name="name" position="before">
<field name="priority" widget="priority" class="mr-3"/>
</field>
</field>
</record>
<record id="product_template_kanban_view" model="ir.ui.view">
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_kanban_view"/>
<field name="arch" type="xml">
<field name="lst_price" position="after">
<field name="priority" widget="priority"/>
</field>
</field>
</record>
</odoo>

View File

@@ -37,6 +37,7 @@ This module has been written by Alexis de Lattre from Akretion <alexis.delattre@
'views/product_product.xml',
'views/product_config_menu.xml',
'views/product_category_view.xml',
'views/product_attribute_view.xml',
],
'installable': True,
}

View File

@@ -3,3 +3,4 @@ from . import product_template
from . import product_supplierinfo
from . import product_pricelist
from . import product_category
from . import product_attribute

Some files were not shown because too many files have changed in this diff Show More