[MERGE] forward port branch saas-12.1 up to 4524ad06a8

This commit is contained in:
Christophe Simonis
2019-02-04 13:27:22 +01:00
180 changed files with 2445 additions and 532 deletions
+64 -55
View File
@@ -71,6 +71,8 @@
# Nicolás Broggi <rnbroggi@gmail.com>, 2019
# Luis González <lgonzalez@vauxoo.com>, 2019
# Gabriel Umaña <gabriel.umana@delfixcr.com>, 2019
# Luis Marin <marin.guadarrama@gmail.com>, 2019
# Edilianny Sánchez <esanchez@vauxoo.com>, 2019
#
msgid ""
msgstr ""
@@ -78,7 +80,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-16 07:59+0000\n"
"PO-Revision-Date: 2016-08-05 12:55+0000\n"
"Last-Translator: Gabriel Umaña <gabriel.umana@delfixcr.com>, 2019\n"
"Last-Translator: Edilianny Sánchez <esanchez@vauxoo.com>, 2019\n"
"Language-Team: Spanish (https://www.transifex.com/odoo/teams/41243/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -2104,7 +2106,7 @@ msgstr "Completar automáticamente"
#. module: account
#: model:ir.model.fields,help:account.field_account_invoice__vendor_bill_id
msgid "Auto-complete from a past bill."
msgstr ""
msgstr "Auto-completar de una factura anterior"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.invoice_supplier_form
@@ -2120,7 +2122,7 @@ msgstr "Auto-validar"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
msgid "Autocomplete Vendor Bills (OCR + AI)"
msgstr ""
msgstr "Auto-completar facturas de proveedor (OCR + IA)"
#. module: account
#: model:ir.model.fields,field_description:account.field_res_config_settings__module_account_invoice_extract
@@ -2347,7 +2349,7 @@ msgstr "Extractos bancarios"
#. module: account
#: model:ir.model.fields,help:account.field_account_bank_statement_line__bank_account_id
msgid "Bank account that was used in this transaction."
msgstr ""
msgstr "Cuenta bancaria usada en esta transacción"
#. module: account
#: model:ir.model.fields,help:account.field_account_setup_bank_manual_config__acc_type
@@ -2435,7 +2437,7 @@ msgstr "Basado en pago"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
msgid "Batch Payments"
msgstr ""
msgstr "Lote de pagos"
#. module: account
#: model:ir.model.fields,field_description:account.field_account_journal__belongs_to_company
@@ -2972,7 +2974,7 @@ msgstr "Elija plantilla del plan contable"
#: model_terms:ir.ui.view,arch_db:account.account_invoice_onboarding_sale_tax_form
#: model_terms:ir.ui.view,arch_db:account.onboarding_sale_tax_step
msgid "Choose a default sales tax for your products."
msgstr ""
msgstr "Seleccione los impuestos de venta predeterminados para sus productos."
#. module: account
#. openerp-web
@@ -3372,7 +3374,7 @@ msgstr "Contrapartida"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.view_account_reconcile_model_form
msgid "Counterpart Values"
msgstr ""
msgstr "Valores de la Contrapartida"
#. module: account
#: model:ir.model.fields,field_description:account.field_account_fiscal_position__country_id
@@ -3447,22 +3449,22 @@ msgstr "Crear una nueva posición fiscal"
#. module: account
#: model_terms:ir.actions.act_window,help:account.action_incoterms_tree
msgid "Create a new incoterm"
msgstr ""
msgstr "Crear un nuevo incoterm"
#. module: account
#: model_terms:ir.actions.act_window,help:account.product_product_action_purchasable
msgid "Create a new purchasable product"
msgstr ""
msgstr "Crear un nuevo producto comprable"
#. module: account
#: model_terms:ir.actions.act_window,help:account.action_account_reconcile_model
msgid "Create a new reconciliation model"
msgstr ""
msgstr "Crear un nuevo modelo de reconciliación"
#. module: account
#: model_terms:ir.actions.act_window,help:account.product_product_action_sellable
msgid "Create a new sellable product"
msgstr ""
msgstr "Crear un nuevo producto vendible"
#. module: account
#: model_terms:ir.actions.act_window,help:account.action_tax_form
@@ -3472,7 +3474,7 @@ msgstr "Crea un nuevo impuesto"
#. module: account
#: model_terms:ir.actions.act_window,help:account.action_invoice_in_refund
msgid "Create a vendor credit note"
msgstr ""
msgstr "Crear una nota de crédito de proveedor"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.tax_adjustments_wizard
@@ -3508,7 +3510,7 @@ msgstr "Crea modelo"
#. module: account
#: selection:account.setup.bank.manual.config,create_or_link_option:0
msgid "Create new journal"
msgstr ""
msgstr "Crear un diario nuevo"
#. module: account
#: model_terms:ir.actions.act_window,help:account.rounding_list_action
@@ -3849,7 +3851,7 @@ msgstr "Personalizar"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.onboarding_invoice_layout_step
msgid "Customize the look of your invoices."
msgstr ""
msgstr "Personalizar la apariencia de sus facturas"
#. module: account
#: model:account.incoterms,name:account.incoterm_DAF
@@ -3960,6 +3962,10 @@ msgid ""
"greater than the last day of a month, this number will instead select the "
"last day of this month."
msgstr ""
"Día del mes en que la factura debe llegar a su término. Si es cero o "
"negativo, este valor se ignorará y no se establecerá un día específico. Si "
"es mayor que el último día de un mes, este número seleccionará el último día"
" de este mes."
#. module: account
#: code:addons/account/models/company.py:51
@@ -4024,7 +4030,7 @@ msgstr "Secuencia de notas de crédito dedicada"
#. module: account
#: model:ir.model.fields,field_description:account.field_res_company__invoice_reference_type
msgid "Default Communication Type"
msgstr ""
msgstr "Tipo de Comunicación por defecto"
#. module: account
#: model:ir.model.fields,field_description:account.field_account_journal__default_credit_account_id
@@ -4050,7 +4056,7 @@ msgstr "Impuesto de compra por defecto"
#. module: account
#: model:ir.model.fields,help:account.field_res_config_settings__invoice_reference_type
msgid "Default Reference Type on Invoices."
msgstr ""
msgstr "Tipo de Referencia por defecto en facturas."
#. module: account
#: model:ir.model.fields,field_description:account.field_res_company__account_sale_tax_id
@@ -4075,12 +4081,12 @@ msgstr "Impuestos por defecto"
#: model:ir.model.fields,field_description:account.field_res_company__incoterm_id
#: model:ir.model.fields,field_description:account.field_res_config_settings__incoterm_id
msgid "Default incoterm"
msgstr ""
msgstr "Por defecto Incoterm"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
msgid "Default payment communication on customer invoices"
msgstr ""
msgstr "Comunicación de pago por defecto en facturas de clientes"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
@@ -4091,13 +4097,13 @@ msgstr "Impuestos por defecto aplicados a transacciones locales"
#: model:ir.model.fields,help:account.field_product_product__supplier_taxes_id
#: model:ir.model.fields,help:account.field_product_template__supplier_taxes_id
msgid "Default taxes used when buying the product."
msgstr ""
msgstr "Impuestos por defecto usados cuando se compra el producto"
#. module: account
#: model:ir.model.fields,help:account.field_product_product__taxes_id
#: model:ir.model.fields,help:account.field_product_template__taxes_id
msgid "Default taxes used when selling the product."
msgstr ""
msgstr "Impuestos por defecto usados cuando se vende el producto"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
@@ -4253,7 +4259,7 @@ msgstr ""
#. module: account
#: model_terms:ir.ui.view,arch_db:account.invoice_form
msgid "Disc (%)"
msgstr ""
msgstr "Descuento (%)"
#. module: account
#: model:ir.model.fields,field_description:account.field_account_invoice_line__discount
@@ -4327,7 +4333,7 @@ msgstr "Nombre a mostrar"
#: model:ir.model.fields,field_description:account.field_res_company__qr_code
#: model:ir.model.fields,field_description:account.field_res_config_settings__qr_code
msgid "Display SEPA QR code"
msgstr ""
msgstr "Desplegar código SEPA QR"
#. module: account
#: model:ir.model.fields,field_description:account.field_account_invoice_line__display_type
@@ -4447,7 +4453,7 @@ msgstr "Fecha vencimiento"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.view_payment_term_line_form
msgid "Due the"
msgstr ""
msgstr "Vencido al "
#. module: account
#: code:addons/account/models/account_invoice.py:1285
@@ -4787,12 +4793,12 @@ msgstr "Ejercicio fiscal"
#. module: account
#: model:ir.model.fields,help:account.field_account_financial_year_op__fiscalyear_last_day
msgid "Fiscal year last day."
msgstr ""
msgstr "Último día del año fiscal."
#. module: account
#: model:ir.model.fields,help:account.field_account_financial_year_op__fiscalyear_last_month
msgid "Fiscal year last month."
msgstr ""
msgstr "Último mes del año fiscal."
#. module: account
#: model:ir.model.fields,field_description:account.field_account_financial_year_op__fiscalyear_last_day
@@ -4867,12 +4873,12 @@ msgstr "Seguidores (Empresas)"
#. module: account
#: model:ir.model.fields,field_description:account.field_account_journal__inbound_payment_method_ids
msgid "For Incoming Payments"
msgstr ""
msgstr "Para pagos recibidos"
#. module: account
#: model:ir.model.fields,field_description:account.field_account_journal__outbound_payment_method_ids
msgid "For Outgoing Payments"
msgstr ""
msgstr "Para pagos salientes"
#. module: account
#: model:ir.model.fields,help:account.field_account_payment_term_line__value_amount
@@ -5451,7 +5457,7 @@ msgstr "Importar archivos .qif"
#. module: account
#: model:ir.actions.act_window,name:account.account_invoice_import_wizard_action
msgid "Import Vendor Bills"
msgstr ""
msgstr "Importar facturas de proveedor"
#. module: account
#: model:ir.model,name:account.model_account_invoice_import_wizard
@@ -5815,7 +5821,7 @@ msgstr "Número de Factura:"
#. module: account
#: model:ir.model.fields,field_description:account.field_res_config_settings__module_account_payment
msgid "Invoice Online Payment"
msgstr ""
msgstr "Pago en línea de la factura"
#. module: account
#: model:ir.model.fields,field_description:account.field_account_invoice_line__invoice_id
@@ -5869,7 +5875,7 @@ msgstr "Factura pagada"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.account_invoice_send_wizard_form
msgid "Invoice send & Print"
msgstr ""
msgstr "Imprimir y enviar factura"
#. module: account
#: model:mail.message.subtype,description:account.mt_invoice_validated
@@ -5965,7 +5971,7 @@ msgstr "Facturación"
#: selection:account.reconcile.model,match_amount:0
#: selection:account.reconcile.model.template,match_amount:0
msgid "Is Between"
msgstr ""
msgstr "Es entre"
#. module: account
#: model:ir.model.fields,field_description:account.field_account_bank_statement__message_is_follower
@@ -5978,18 +5984,18 @@ msgstr "Es un seguidor"
#: selection:account.reconcile.model,match_amount:0
#: selection:account.reconcile.model.template,match_amount:0
msgid "Is Greater Than"
msgstr ""
msgstr "es mayor que"
#. module: account
#: selection:account.reconcile.model,match_amount:0
#: selection:account.reconcile.model.template,match_amount:0
msgid "Is Lower Than"
msgstr ""
msgstr "es menor que"
#. module: account
#: model:ir.model.fields,field_description:account.field_account_invoice_send__printed
msgid "Is Printed"
msgstr ""
msgstr "es impreso"
#. module: account
#: model:ir.model.fields,field_description:account.field_account_reconcile_model__is_second_tax_price_included
@@ -6344,7 +6350,7 @@ msgstr "Etiqueta"
#: model:ir.model.fields,field_description:account.field_account_reconcile_model__match_label_param
#: model:ir.model.fields,field_description:account.field_account_reconcile_model_template__match_label_param
msgid "Label Parameter"
msgstr ""
msgstr "Parámetro de la etiqueta"
#. module: account
#: model:ir.model.fields,field_description:account.field_account_tax__description
@@ -6574,7 +6580,7 @@ msgstr "Fecha de la última conciliación de facturas y pagos"
#. module: account
#: model:ir.model.fields,field_description:account.field_account_invoice_send__layout
msgid "Layout"
msgstr ""
msgstr "Diseño"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.view_account_position_form
@@ -6678,7 +6684,7 @@ msgstr "Registrar una nota interna"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.onboarding_invoice_layout_step
msgid "Looks great!"
msgstr ""
msgstr "¡Se ve muy bien!"
#. module: account
#: code:addons/account/models/account_bank_statement.py:175
@@ -7383,7 +7389,7 @@ msgstr ""
#. module: account
#: model_terms:ir.ui.view,arch_db:account.view_payment_term_line_form
msgid "On the"
msgstr ""
msgstr "En el "
#. module: account
#: model_terms:ir.ui.view,arch_db:account.account_invoice_confirm_view
@@ -7844,7 +7850,7 @@ msgstr "Diferencia en pago"
#: model:ir.model.fields,field_description:account.field_account_payment__payment_difference_handling
#: model:ir.model.fields,field_description:account.field_account_register_payments__payment_difference_handling
msgid "Payment Difference Handling"
msgstr ""
msgstr "Manejo de diferencias de pago"
#. module: account
#: model:ir.model.fields,field_description:account.field_account_abstract_payment__journal_id
@@ -7906,7 +7912,7 @@ msgstr "Payment Receipt:"
#. module: account
#: model:ir.model.fields,field_description:account.field_account_invoice__reference
msgid "Payment Ref."
msgstr ""
msgstr "Ref. de Pago"
#. module: account
#: model:ir.model.fields,field_description:account.field_account_payment__payment_reference
@@ -8234,7 +8240,7 @@ msgstr "Previsualizar"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.account_invoice_send_wizard_form
msgid "Preview as a PDF"
msgstr ""
msgstr "Vista previa como un PDF"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.invoice_form
@@ -8631,14 +8637,14 @@ msgstr "Registrar pagos"
#. module: account
#: model_terms:ir.actions.act_window,help:account.action_bank_statement_tree
msgid "Register a bank statement"
msgstr ""
msgstr "Registrar un extracto bancario"
#. module: account
#: model_terms:ir.actions.act_window,help:account.action_account_payments
#: model_terms:ir.actions.act_window,help:account.action_account_payments_payable
#: model_terms:ir.actions.act_window,help:account.action_account_payments_transfer
msgid "Register a payment"
msgstr ""
msgstr "Registrar un pago"
#. module: account
#: selection:account.account.type,type:0
@@ -8921,7 +8927,7 @@ msgstr "Impuesto de Ventas"
#. module: account
#: model:ir.actions.act_window,name:account.action_open_account_onboarding_sale_tax
msgid "Sales tax"
msgstr ""
msgstr "Impuesto de venta"
#. module: account
#: model:ir.model.fields,field_description:account.field_account_invoice__user_id
@@ -8940,7 +8946,7 @@ msgstr ""
#. module: account
#: model_terms:ir.ui.view,arch_db:account.onboarding_sample_invoice_step
msgid "Sample Invoice"
msgstr ""
msgstr "Factura ejemplo"
#. module: account
#: code:addons/account/models/company.py:485
@@ -9201,7 +9207,7 @@ msgstr "Enviar correo electrónico"
#: code:addons/account/models/account_invoice.py:622
#, python-format
msgid "Send Invoice"
msgstr ""
msgstr "Enviar factura"
#. module: account
#: selection:account.abstract.payment,payment_type:0
@@ -9218,12 +9224,12 @@ msgstr "Enviar recibo por correo electrónico"
#. module: account
#: model:ir.actions.act_window,name:account.action_open_account_onboarding_sample_invoice
msgid "Send a sample invoice"
msgstr ""
msgstr "Enviar una factura ejemplo"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.onboarding_sample_invoice_step
msgid "Send an invoice to test the customer portal."
msgstr ""
msgstr "Enviar una factura para probar el portal del cliente."
#. module: account
#: model_terms:ir.ui.view,arch_db:account.onboarding_sample_invoice_step
@@ -9265,7 +9271,7 @@ msgstr "Secuencia"
#: code:addons/account/static/src/xml/account_reconciliation.xml:249
#, python-format
msgid "Set"
msgstr ""
msgstr "Establecer"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.view_account_payment_form
@@ -9292,7 +9298,7 @@ msgstr "Establezca activo en falso para ocultar el impuesto sin eliminarlo."
#. module: account
#: model_terms:ir.ui.view,arch_db:account.onboarding_sale_tax_step
msgid "Set taxes"
msgstr ""
msgstr "Establecer impuestos"
#. module: account
#: model:ir.model.fields,help:account.field_account_chart_template__visible
@@ -9368,7 +9374,7 @@ msgstr "Mostrar características de contabilidad completas"
#: model:ir.model.fields,field_description:account.field_account_payment__show_partner_bank_account
#: model:ir.model.fields,field_description:account.field_account_register_payments__show_partner_bank_account
msgid "Show Partner Bank Account"
msgstr ""
msgstr "Mostrar cuenta bancaria del Contacto"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.view_account_tax_search
@@ -9933,7 +9939,7 @@ msgstr "Impuestos"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.view_move_form
msgid "Taxes Applied"
msgstr ""
msgstr "Impuestos aplicados"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.view_account_position_template_form
@@ -10405,6 +10411,7 @@ msgstr "La divisa utilizada para introducir asientos."
#, python-format
msgid "The day of the month used for this term must be stricly positive."
msgstr ""
"El día del mes utilizado para este término debe ser estrictamente positivo."
#. module: account
#: code:addons/account/models/account_bank_statement.py:193
@@ -10594,6 +10601,8 @@ msgid ""
"The payment communication that will be automatically populated once the "
"invoice validation. You can also write a free communication."
msgstr ""
"El comentario de pago que se completará automáticamente una vez que la "
"factura sea validada. También puedes escribir un comentario libre."
#. module: account
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
@@ -11694,7 +11703,7 @@ msgstr "Nota de crédito de proveedor"
#. module: account
#: model:ir.model.fields,field_description:account.field_account_invoice__vendor_display_name
msgid "Vendor Display Name"
msgstr ""
msgstr "Nombre para mostrar del proveedor"
#. module: account
#: code:addons/account/models/account_payment.py:774
@@ -12207,7 +12216,7 @@ msgstr ""
#: code:addons/account/models/account_invoice.py:1726
#, python-format
msgid "You must first select a partner."
msgstr ""
msgstr "Primero debe seleccionar un Contacto"
#. module: account
#. openerp-web
@@ -12303,7 +12312,7 @@ msgstr "código"
#. module: account
#: selection:account.payment.term.line,option:0
msgid "day(s) after the end of the invoice month"
msgstr ""
msgstr "día(s) después del final del mes de la factura"
#. module: account
#: selection:account.payment.term.line,option:0
+6 -2
View File
@@ -77,6 +77,8 @@
# Martin Trigaux, 2019
# Eloïse Stilmant <est@odoo.com>, 2019
# Alain van de Werve <avw@openerp.com>, 2019
# Khalyomede Khalyomede <khalyomede@gmail.com>, 2019
# Fabien Bourgeois <fabien@yaltik.com>, 2019
#
msgid ""
msgstr ""
@@ -84,7 +86,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-16 07:59+0000\n"
"PO-Revision-Date: 2016-08-05 12:55+0000\n"
"Last-Translator: Alain van de Werve <avw@openerp.com>, 2019\n"
"Last-Translator: Fabien Bourgeois <fabien@yaltik.com>, 2019\n"
"Language-Team: French (https://www.transifex.com/odoo/teams/41243/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -646,7 +648,7 @@ msgstr ""
#. module: account
#: model_terms:ir.ui.view,arch_db:account.report_invoice_document
msgid "<span>Unit Price</span>"
msgstr ""
msgstr "<span>Prix Unitaire</span>"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.account_journal_dashboard_kanban_view
@@ -12207,6 +12209,8 @@ msgid ""
"You cannot set a currency on this account as it already has some journal "
"entries having a different foreign currency."
msgstr ""
"Vous ne pouvez pas choisir une devise pour ce compte car il existe déjà des "
"entrées dans le journal ayant des devises différentes."
#. module: account
#: code:addons/account/models/account.py:301
+1 -1
View File
@@ -6487,7 +6487,7 @@ msgstr "Levelezőlista"
#: model:ir.model.fields,field_description:account.field_account_invoice__message_main_attachment_id
#: model:ir.model.fields,field_description:account.field_account_payment__message_main_attachment_id
msgid "Main Attachment"
msgstr ""
msgstr "Fő melléklet"
#. module: account
#: model:ir.model.fields,help:account.field_res_config_settings__currency_id
+4 -4
View File
@@ -11,8 +11,8 @@
# Nils Fredrik Gjerull, 2018
# Jorunn D. Newth, 2019
# Jan Pedro Tumusok <jpt@eyenetworks.no>, 2019
# Marius Stedjan <marius@stedjan.com>, 2019
# Martin Trigaux, 2019
# Marius Stedjan <marius@stedjan.com>, 2019
#
msgid ""
msgstr ""
@@ -20,7 +20,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-16 07:59+0000\n"
"PO-Revision-Date: 2016-08-05 12:55+0000\n"
"Last-Translator: Martin Trigaux, 2019\n"
"Last-Translator: Marius Stedjan <marius@stedjan.com>, 2019\n"
"Language-Team: Norwegian Bokmål (https://www.transifex.com/odoo/teams/41243/nb/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -2500,12 +2500,12 @@ msgstr ""
#. module: account
#: model:ir.model,name:account.model_cash_box_in
msgid "Cash Box In"
msgstr ""
msgstr "Kasseinnskudd"
#. module: account
#: model:ir.model,name:account.model_cash_box_out
msgid "Cash Box Out"
msgstr ""
msgstr "Kasseuttak"
#. module: account
#: code:addons/account/models/account_bank_statement.py:212
+8 -4
View File
@@ -13,12 +13,12 @@
# Cas Vissers <casvissers@brahoo.nl>, 2019
# Gunther Clauwaert <gclauwae@hotmail.com>, 2019
# Martin Trigaux, 2019
# Erwin van der Ploeg <erwin@odooexperts.nl>, 2019
# Cas Vissers <c.vissers@brahoo.nl>, 2019
# Jordy Blankestijn <jblankestijn@erpopen.nl>, 2019
# Wynand Tastenhoye <wta@odoo.com>, 2019
# dpms <pieter.van.de.wygaert@telenet.be>, 2019
# Erik-Jan Bulthuis <ejbulthuis@erpopen.nl>, 2019
# Erwin van der Ploeg <erwin@odooexperts.nl>, 2019
# Yenthe Van Ginneken <yenthespam@gmail.com>, 2019
#
msgid ""
@@ -252,12 +252,12 @@ msgstr "<em>Concept factuur</em>"
#: model_terms:ir.ui.view,arch_db:account.account_move_line_view_kanban
#: model_terms:ir.ui.view,arch_db:account.view_account_move_kanban
msgid "<i class=\"fa fa-clock-o\" aria-label=\"Date\" role=\"img\" title=\"Date\"/>"
msgstr ""
msgstr "<i class=\"fa fa-clock-o\" aria-label=\"Date\" role=\"img\" title=\"Date\"/>"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.view_account_payment_kanban
msgid "<i class=\"fa fa-clock-o\" role=\"img\" aria-label=\"Date\" title=\"Date\"/>"
msgstr ""
msgstr "<i class=\"fa fa-clock-o\" role=\"img\" aria-label=\"Datum\" title=\"Datum\"/>"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.portal_invoice_page
@@ -270,6 +270,8 @@ msgid ""
"<i class=\"fa fa-ellipsis-v\" aria-label=\"Selection\" role=\"img\" "
"title=\"Selection\"/>"
msgstr ""
"<i class=\"fa fa-ellipsis-v\" aria-label=\"Selection\" role=\"img\" "
"title=\"Selection\"/>"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.portal_invoice_page
@@ -365,6 +367,8 @@ msgid ""
"<span class=\"fa fa-lg fa-building-o\" title=\"Values set here are company-"
"specific.\"/>"
msgstr ""
"<span class=\"fa fa-lg fa-building-o\" title=\"De waardes die u hier instelt"
" zijn bedrijf specifiek.\"/>"
#. module: account
#: model_terms:ir.ui.view,arch_db:account.invoice_supplier_form
@@ -10321,7 +10325,7 @@ msgstr "Het dagboek waarin automatisch koersverschillen worden geboekt"
#: code:addons/account/static/src/js/reconciliation/reconciliation_model.js:585
#, python-format
msgid "The amount %s is not a valid partial amount"
msgstr ""
msgstr "Het bedrag %s is geen geldig gedeeltelijk bedrag"
#. module: account
#: model:ir.model.fields,help:account.field_account_bank_statement_line__amount_currency
+3 -3
View File
@@ -7,10 +7,10 @@
# sharkutz <sharkutz4life@yahoo.com>, 2018
# Iulian Cutui <iulian.cutui@gmail.com>, 2018
# Bogdan Nistor <inactive+BogdanNistor.Ro@transifex.com>, 2018
# Dorin Hongu <dhongu@gmail.com>, 2019
# Sorin Trifu <sorin.trifu@bizconsulting.ro>, 2019
# Cozmin Candea <office@terrabit.ro>, 2019
# Martin Trigaux, 2019
# Dorin Hongu <dhongu@gmail.com>, 2019
#
msgid ""
msgstr ""
@@ -18,7 +18,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-16 07:59+0000\n"
"PO-Revision-Date: 2016-08-05 12:55+0000\n"
"Last-Translator: Martin Trigaux, 2019\n"
"Last-Translator: Dorin Hongu <dhongu@gmail.com>, 2019\n"
"Language-Team: Romanian (https://www.transifex.com/odoo/teams/41243/ro/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -9058,7 +9058,7 @@ msgstr ""
#. module: account
#: model:res.groups,name:account.group_account_user
msgid "Show Full Accounting Features"
msgstr ""
msgstr "Afișați toate funcțiile din contabilitate"
#. module: account
#: model:ir.model.fields,field_description:account.field_account_abstract_payment__show_partner_bank_account
+5 -9
View File
@@ -67,12 +67,11 @@ class AccountAccount(models.Model):
@api.constrains('user_type_id')
def _check_user_type_id(self):
data_unaffected_earnings = self.env.ref('account.data_unaffected_earnings')
for company in self.mapped('company_id'):
account_unaffected_earnings = self.search([
('company_id', '=', company.id),
('user_type_id', '=', data_unaffected_earnings.id),
])
if len(account_unaffected_earnings) >= 2:
result = self.read_group([('user_type_id', '=', data_unaffected_earnings.id)], ['company_id'], ['company_id'])
for res in result:
if res.get('company_id_count', 0) >= 2:
account_unaffected_earnings = self.search([('company_id', '=', res['company_id'][0]),
('user_type_id', '=', data_unaffected_earnings.id)])
raise ValidationError(_('You cannot have more than one account with "Current Year Earnings" as type. (accounts: %s)') % [a.code for a in account_unaffected_earnings])
name = fields.Char(required=True, index=True)
@@ -228,7 +227,6 @@ class AccountAccount(models.Model):
self.group_id = group
@api.multi
@api.depends('name', 'code')
def name_get(self):
result = []
for account in self:
@@ -802,7 +800,6 @@ class AccountJournal(models.Model):
}).id
@api.multi
@api.depends('name', 'currency_id', 'company_id', 'company_id.currency_id')
def name_get(self):
res = []
for journal in self:
@@ -947,7 +944,6 @@ class AccountTax(models.Model):
default = dict(default or {}, name=_("%s (Copy)") % self.name)
return super(AccountTax, self).copy(default=default)
@api.depends('name', 'type_tax_use')
def name_get(self):
if not self._context.get('append_type_to_tax_name'):
return super(AccountTax, self).name_get()
@@ -551,6 +551,8 @@ class AccountBankStatementLine(models.Model):
:returns: The journal entries with which the transaction was matched. If there was at least an entry in counterpart_aml_dicts or new_aml_dicts, this list contains
the move created by the reconciliation, containing entries for the statement.line (1), the counterpart move lines (0..*) and the new move lines (0..*).
"""
payable_account_type = self.env.ref('account.data_account_type_payable')
receivable_account_type = self.env.ref('account.data_account_type_receivable')
counterpart_aml_dicts = counterpart_aml_dicts or []
payment_aml_rec = payment_aml_rec or self.env['account.move.line']
new_aml_dicts = new_aml_dicts or []
@@ -571,10 +573,16 @@ class AccountBankStatementLine(models.Model):
raise UserError(_('A selected move line was already reconciled.'))
if isinstance(aml_dict['move_line'], int):
aml_dict['move_line'] = aml_obj.browse(aml_dict['move_line'])
account_types = self.env['account.account.type']
for aml_dict in (counterpart_aml_dicts + new_aml_dicts):
if aml_dict.get('tax_ids') and isinstance(aml_dict['tax_ids'][0], int):
# Transform the value in the format required for One2many and Many2many fields
aml_dict['tax_ids'] = [(4, id, None) for id in aml_dict['tax_ids']]
user_type_id = self.env['account.account'].browse(aml_dict.get('account_id')).user_type_id
if user_type_id in [payable_account_type, receivable_account_type] and user_type_id not in account_types:
account_types |= user_type_id
if any(line.journal_entry_ids for line in self):
raise UserError(_('A selected statement line was already reconciled with an account move.'))
@@ -610,6 +618,8 @@ class AccountBankStatementLine(models.Model):
payment = self.env['account.payment']
if abs(total)>0.00001:
payment_vals = self._prepare_payment_vals(total)
if self.partner_id and len(account_types) == 1:
payment_vals['partner_type'] = 'customer' if account_types == receivable_account_type else 'supplier'
payment = payment.create(payment_vals)
# Complete dicts to create both counterpart move lines and write-offs
+25 -1
View File
@@ -147,8 +147,13 @@ class AccountInvoice(models.Model):
amount_to_show = currency._convert(abs(line.amount_residual), self.currency_id, self.company_id, line.date or fields.Date.today())
if float_is_zero(amount_to_show, precision_rounding=self.currency_id.rounding):
continue
if line.ref :
title = '%s : %s' % (line.move_id.name, line.ref)
else:
title = line.move_id.name
info['content'].append({
'journal_name': line.ref or line.move_id.name,
'title': title,
'amount': amount_to_show,
'currency': currency_id.symbol,
'id': line.id,
@@ -1402,6 +1407,20 @@ class AccountInvoice(models.Model):
return result
@api.model
def _refund_tax_lines_account_change(self, lines, taxes_to_change):
# Let's change the account on tax lines when
# @param {list} lines: a list of orm commands
# @param {dict} taxes_to_change
# key: tax ID, value: refund account
if not taxes_to_change:
return lines
for line in lines:
if isinstance(line[2], dict) and line[2]['tax_id'] in taxes_to_change:
line[2]['account_id'] = taxes_to_change[line[2]['tax_id']]
return lines
def _get_refund_common_fields(self):
return ['partner_id', 'payment_term_id', 'account_id', 'currency_id', 'journal_id']
@@ -1447,7 +1466,12 @@ class AccountInvoice(models.Model):
values['invoice_line_ids'] = self._refund_cleanup_lines(invoice.invoice_line_ids)
tax_lines = invoice.tax_line_ids
values['tax_line_ids'] = self._refund_cleanup_lines(tax_lines)
taxes_to_change = {
line.tax_id.id: line.tax_id.refund_account_id.id
for line in tax_lines.filtered(lambda l: l.tax_id.refund_account_id != l.tax_id.account_id)
}
cleaned_tax_lines = self._refund_cleanup_lines(tax_lines)
values['tax_line_ids'] = self._refund_tax_lines_account_change(cleaned_tax_lines, taxes_to_change)
if journal_id:
journal = self.env['account.journal'].browse(journal_id)
@@ -185,13 +185,14 @@ class account_journal(models.Model):
self.env.cr.execute(query, query_args)
query_results_drafts = self.env.cr.dictfetchall()
today = datetime.today()
query = """SELECT amount_total, currency_id AS currency, type, date_invoice, company_id FROM account_invoice WHERE journal_id = %s AND date <= %s AND state = 'open';"""
today = fields.Date.today()
query = """SELECT residual_signed as amount_total, currency_id AS currency, type, date_invoice, company_id FROM account_invoice WHERE journal_id = %s AND date <= %s AND state = 'open';"""
self.env.cr.execute(query, (self.id, today))
late_query_results = self.env.cr.dictfetchall()
(number_waiting, sum_waiting) = self._count_results_and_sum_amounts(query_results_to_pay, currency)
(number_draft, sum_draft) = self._count_results_and_sum_amounts(query_results_drafts, currency)
(number_late, sum_late) = self._count_results_and_sum_amounts(late_query_results, currency)
curr_cache = {}
(number_waiting, sum_waiting) = self._count_results_and_sum_amounts(query_results_to_pay, currency, curr_cache=curr_cache)
(number_draft, sum_draft) = self._count_results_and_sum_amounts(query_results_drafts, currency, curr_cache=curr_cache)
(number_late, sum_late) = self._count_results_and_sum_amounts(late_query_results, currency, curr_cache=curr_cache)
difference = currency.round(last_balance-account_sum) + 0.0
return {
@@ -226,23 +227,45 @@ class account_journal(models.Model):
gather the bills in draft state data, and the arguments
dictionary to use to run it as its second.
"""
return ("""SELECT state, amount_total, currency_id AS currency, type, date_invoice, company_id
FROM account_invoice
# there is no account_move_lines for draft invoices, so no relevant residual_signed value
return ("""SELECT state,
(CASE WHEN inv.type in ('out_invoice', 'in_invoice')
THEN inv.amount_total
ELSE (-1 * inv.amount_total)
END) AS amount_total,
inv.currency_id AS currency,
inv.type,
inv.date_invoice,
inv.company_id
FROM account_invoice inv
WHERE journal_id = %(journal_id)s AND state = 'draft';""", {'journal_id':self.id})
def _count_results_and_sum_amounts(self, results_dict, target_currency):
def _count_results_and_sum_amounts(self, results_dict, target_currency, curr_cache=None):
""" Loops on a query result to count the total number of invoices and sum
their amount_total field (expressed in the given target currency).
amount_total must be signed !
"""
rslt_count = 0
rslt_sum = 0.0
# Create a cache with currency rates to avoid unnecessary SQL requests. Do not copy
# curr_cache on purpose, so the dictionary is modified and can be re-used for subsequent
# calls of the method.
curr_cache = {} if curr_cache is None else curr_cache
for result in results_dict:
cur = self.env['res.currency'].browse(result.get('currency'))
company = self.env['res.company'].browse(result.get('company_id')) or self.env.user.company_id
rslt_count += 1
type_factor = result.get('type') in ('in_refund', 'out_refund') and -1 or 1
rslt_sum += type_factor * cur._convert(
result.get('amount_total'), target_currency, company, result.get('date_invoice') or fields.Date.today())
date = result.get('date_invoice') or fields.Date.today()
amount = result.get('amount_total', 0)
if cur != target_currency:
key = (cur, target_currency, company, date)
# Using setdefault will call _get_conversion_rate, so we explicitly check the
# existence of the key in the cache instead.
if key not in curr_cache:
curr_cache[key] = self.env['res.currency']._get_conversion_rate(*key)
amount *= curr_cache[key]
rslt_sum += target_currency.round(amount)
return (rslt_count, rslt_sum)
@api.multi
@@ -360,7 +383,7 @@ class account_journal(models.Model):
action['view_id'] = False
if self.type == 'purchase':
new_help = self.env['account.invoice'].with_context(ctx).complete_empty_list_help()
action.update({'help': action.get('help', '') + new_help})
action.update({'help': (action.get('help') or '') + new_help})
return action
@api.multi
+26 -17
View File
@@ -429,9 +429,6 @@ class AccountMove(models.Model):
journal_id=journal_id,
auto=auto)
reversed_moves |= reversed_move
#unreconcile all lines reversed
aml = ac_move.line_ids.filtered(lambda x: x.account_id.reconcile or x.account_id.internal_type == 'liquidity')
aml.remove_move_reconcile()
#reconcile together the reconcilable (or the liquidity aml) and their newly created counterpart
for account in set([x.account_id for x in aml]):
to_rec = aml.filtered(lambda y: y.account_id == account)
@@ -448,6 +445,18 @@ class AccountMove(models.Model):
def open_reconcile_view(self):
return self.line_ids.open_reconcile_view()
# FIXME: Clarify me and change me in master
@api.multi
def action_duplicate(self):
self.ensure_one()
action = self.env.ref('account.action_move_journal_line').read()[0]
action['target'] = 'inline'
action['context'] = dict(self.env.context)
action['context']['view_no_maturity'] = False
action['views'] = [(self.env.ref('account.view_move_form').id, 'form')]
action['res_id'] = self.copy().id
return action
@api.model
def _run_reverses_entries(self):
''' This method is called from a cron job. '''
@@ -638,7 +647,7 @@ class AccountMoveLine(models.Model):
index=True, store=True, copy=False) # related is required
blocked = fields.Boolean(string='No Follow-up', default=False,
help="You can check this box to mark this journal item as a litigation with the associated partner")
date_maturity = fields.Date(string='Due date', index=True, required=True,
date_maturity = fields.Date(string='Due date', index=True, required=True, copy=False,
help="This field is used for payable and receivable journal entries. You can put the limit date for the payment of this line.")
date = fields.Date(related='move_id.date', string='Date', index=True, store=True, copy=False, readonly=False) # related is required
analytic_line_ids = fields.One2many('account.analytic.line', 'move_id', string='Analytic lines', oldname="analytic_lines")
@@ -897,22 +906,13 @@ class AccountMoveLine(models.Model):
ret = self._reconcile_lines(debit_moves, credit_moves, field)
return ret
@api.multi
def reconcile(self, writeoff_acc_id=False, writeoff_journal_id=False):
# Empty self can happen if the user tries to reconcile entries which are already reconciled.
# The calling method might have filtered out reconciled lines.
if not self:
return True
def _check_reconcile_validity(self):
#Perform all checks on lines
company_ids = set()
all_accounts = []
partners = set()
for line in self:
company_ids.add(line.company_id.id)
all_accounts.append(line.account_id)
if (line.account_id.internal_type in ('receivable', 'payable')):
partners.add(line.partner_id.id)
if line.reconciled:
raise UserError(_('You are trying to reconcile some entries that are already reconciled.'))
if len(company_ids) > 1:
@@ -922,6 +922,14 @@ class AccountMoveLine(models.Model):
if not (all_accounts[0].reconcile or all_accounts[0].internal_type == 'liquidity'):
raise UserError(_('Account %s (%s) does not allow reconciliation. First change the configuration of this account to allow it.') % (all_accounts[0].name, all_accounts[0].code))
@api.multi
def reconcile(self, writeoff_acc_id=False, writeoff_journal_id=False):
# Empty self can happen if the user tries to reconcile entries which are already reconciled.
# The calling method might have filtered out reconciled lines.
if not self:
return True
self._check_reconcile_validity()
#reconcile everything that can be
remaining_moves = self.auto_reconcile_lines()
@@ -939,7 +947,7 @@ class AccountMoveLine(models.Model):
#add writeoff line to reconcile algorithm and finish the reconciliation
remaining_moves = (remaining_moves + writeoff_to_reconcile).auto_reconcile_lines()
# Check if reconciliation is total or needs an exchange rate entry to be created
(self+writeoff_to_reconcile).check_full_reconcile()
(self + writeoff_to_reconcile).check_full_reconcile()
return True
def _create_writeoff(self, writeoff_vals):
@@ -983,8 +991,9 @@ class AccountMoveLine(models.Model):
raise UserError(_("Either pass both debit and credit or none."))
if 'date' not in vals:
vals['date'] = self._context.get('date_p') or fields.Date.today()
if vals['date'] < date:
date = vals['date']
vals['date'] = fields.Date.to_date(vals['date'])
if vals['date'] and vals['date'] < date:
date = vals['date']
if 'name' not in vals:
vals['name'] = self._context.get('comment') or _('Write-Off')
if 'analytic_account_id' not in vals:
+8 -1
View File
@@ -324,7 +324,7 @@ class account_register_payments(models.TransientModel):
pmt_communication = self.show_communication_field and self.communication \
or self.group_invoices and ' '.join([inv.reference or inv.number for inv in invoices]) \
or invoices[0].reference # in this case, invoices contains only one element, since group_invoices is False
return {
values = {
'journal_id': self.journal_id.id,
'payment_method_id': self.payment_method_id.id,
'payment_date': self.payment_date,
@@ -337,8 +337,13 @@ class account_register_payments(models.TransientModel):
'partner_type': MAP_INVOICE_TYPE_PARTNER_TYPE[invoices[0].type],
'partner_bank_account_id': bank_account.id,
'multi': False,
'payment_difference_handling': self.payment_difference_handling,
'writeoff_account_id': self.writeoff_account_id.id,
'writeoff_label': self.writeoff_label,
}
return values
@api.multi
def get_payments_vals(self):
'''Compute the values for payments.
@@ -429,6 +434,8 @@ class account_payment(models.Model):
if move_line.account_id.reconcile:
move_line_id = move_line.id
break;
if not self.partner_id:
raise UserError(_("Payments without a customer can't be matched"))
action_context = {'company_ids': [self.company_id.id], 'partner_ids': [self.partner_id.commercial_partner_id.id]}
if self.partner_type == 'customer':
action_context.update({'mode': 'customers'})
+1 -1
View File
@@ -173,7 +173,7 @@ class AccountChartTemplate(models.Model):
# (won't work well for multi-company)
company = self.env.user.company_id
# If we don't have any chart of account on this company, install this chart of account
if not company.chart_template_id:
if not self.existing_accounting(company):
self.load_for_current_company(15.0, 15.0)
def load_for_current_company(self, sale_tax_rate, purchase_tax_rate):
+6 -1
View File
@@ -397,8 +397,13 @@ Best Regards,'''))
('user_type_id', '=', unaffected_earnings_type.id)])
if account:
return account[0]
# Do not assume '999999' doesn't exist since the user might have created such an account
# manually.
code = 999999
while self.env['account.account'].search([('code', '=', str(code)), ('company_id', '=', self.id)]):
code -= 1
return self.env['account.account'].create({
'code': '999999',
'code': str(code),
'name': _('Undistributed Profits/Losses'),
'user_type_id': unaffected_earnings_type.id,
'company_id': self.id,
+1 -1
View File
@@ -204,7 +204,7 @@ class ResPartner(models.Model):
@api.multi
def _credit_debit_get(self):
tables, where_clause, where_params = self.env['account.move.line']._query_get()
tables, where_clause, where_params = self.env['account.move.line'].with_context(company_id=self.env.user.company_id.id)._query_get()
where_params = [tuple(self.ids)] + where_params
if where_clause:
where_clause = 'AND ' + where_clause
+21 -1
View File
@@ -224,6 +224,27 @@ class AccountReconciliation(models.AbstractModel):
""" Returns the data required for the invoices & payments matching of partners/accounts.
If an argument is None, fetch all related reconciliations. Use [] to fetch nothing.
"""
MoveLine = self.env['account.move.line']
aml_ids = self._context.get('active_ids') and self._context.get('active_model') == 'account.move.line' and tuple(self._context.get('active_ids'))
if aml_ids:
aml = MoveLine.browse(aml_ids)
aml._check_reconcile_validity()
account = aml[0].account_id
currency = account.currency_id or account.company_id.currency_id
return {
'accounts': [{
'reconciliation_proposition': self._prepare_move_lines(aml, target_currency=currency),
'company_id': account.company_id.id,
'currency_id': currency.id,
'mode': 'accounts',
'account_id': account.id,
'account_name': account.name,
'account_code': account.code,
}],
'customers': [],
'suppliers': [],
}
return {
'customers': self.get_data_for_manual_reconciliation('partner', partner_ids, 'receivable'),
'suppliers': self.get_data_for_manual_reconciliation('partner', partner_ids, 'payable'),
@@ -344,7 +365,6 @@ class AccountReconciliation(models.AbstractModel):
# be reconciled.
return [r for r in rows if r['reconciliation_proposition']] + [r for r in rows if not r['reconciliation_proposition']]
@api.model
def process_move_lines(self, data):
""" Used to validate a batch of reconciliations in a single call
@@ -142,6 +142,7 @@ class ReportAgedPartnerBalance(models.AbstractModel):
if not self.env.user.company_id.currency_id.is_zero(line_amount):
partners_amount[partner_id] += line_amount
lines.setdefault(partner_id, [])
lines[partner_id].append({
'line': line,
'amount': line_amount,
@@ -179,6 +180,7 @@ class ReportAgedPartnerBalance(models.AbstractModel):
line_amount -= partial_line.company_id.currency_id._convert(partial_line.amount, user_currency, user_company, date_from)
if not self.env.user.company_id.currency_id.is_zero(line_amount):
undue_amounts[partner_id] += line_amount
lines.setdefault(partner_id, [])
lines[partner_id].append({
'line': line,
'amount': line_amount,
@@ -162,4 +162,8 @@ var ShowPaymentLineWidget = AbstractField.extend({
field_registry.add('payment', ShowPaymentLineWidget);
return {
ShowPaymentLineWidget: ShowPaymentLineWidget
};
});
@@ -858,6 +858,7 @@ var StatementModel = BasicModel.extend({
'tax_id': [tax.id, null],
'amount': tax.amount,
'label': tax.name,
'date': prop.date,
'account_id': tax.account_id ? [tax.account_id, null] : prop.account_id,
'analytic': tax.analytic,
'is_tax': true,
@@ -1072,6 +1073,7 @@ var StatementModel = BasicModel.extend({
*/
_formatQuickCreate: function (line, values) {
values = values || {};
var today = new moment().utc().format();
var account = this._formatNameGet(values.account_id);
var formatOptions = {
currency_id: line.st_line.currency_id,
@@ -1088,6 +1090,7 @@ var StatementModel = BasicModel.extend({
'tax_id': this._formatNameGet(values.tax_id),
'debit': 0,
'credit': 0,
'date': values.date ? values.date : field_utils.parse.date(today, {}, {isUTC: true}),
'base_amount': values.amount_type !== "percentage" ?
(amount) : line.balance.amount * values.amount / 100,
'percent': values.amount_type === "percentage" ? values.amount : null,
@@ -1239,7 +1242,7 @@ var StatementModel = BasicModel.extend({
* datas allowing manual reconciliation
*/
var ManualModel = StatementModel.extend({
quickCreateFields: ['account_id', 'journal_id', 'amount', 'analytic_account_id', 'label', 'tax_id', 'force_tax_included', 'analytic_tag_ids'],
quickCreateFields: ['account_id', 'journal_id', 'amount', 'analytic_account_id', 'label', 'tax_id', 'force_tax_included', 'analytic_tag_ids', 'date'],
//--------------------------------------------------------------------------
// Public
@@ -1637,6 +1640,12 @@ var ManualModel = StatementModel.extend({
})
.then(this._formatMoveLine.bind(this, handle));
},
_formatToProcessReconciliation: function (line, prop) {
var result = this._super(line, prop);
result['date'] = prop.date;
return result;
},
});
return {
@@ -542,10 +542,13 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
}, {
type: 'float',
name: 'amount',
}, {
type: 'char', //TODO is it a bug or a feature when type date exists ?
name: 'date',
}], {
account_id: {string: _t("Account")},
label: {string: _t("Label")},
amount: {string: _t("Account")}
amount: {string: _t("Account")},
}).then(function (recordID) {
self.handleCreateRecord = recordID;
var record = self.model.get(self.handleCreateRecord);
@@ -573,6 +576,9 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
self.fields.amount = new basic_fields.FieldFloat(self,
'amount', record, {mode: 'edit'});
self.fields.date = new basic_fields.FieldDate(self,
'date', record, {mode: 'edit'});
var $create = $(qweb.render("reconciliation.line.create", {'state': state}));
self.fields.account_id.appendTo($create.find('.create_account_id .o_td_field'))
@@ -586,6 +592,7 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
.then(addRequiredStyle.bind(self, self.fields.label));
self.fields.amount.appendTo($create.find('.create_amount .o_td_field'))
.then(addRequiredStyle.bind(self, self.fields.amount));
self.fields.date.appendTo($create.find('.create_date .o_td_field'))
self.$('.create').append($create);
function addRequiredStyle(widget) {
@@ -893,6 +900,7 @@ var ManualLineRenderer = LineRenderer.extend({
_renderCreate: function (state) {
this._super(state);
this.$('.create .create_journal_id').show();
this.$('.create .create_date').removeClass('d-none')
this.$('.create .create_journal_id .o_input').addClass('o_required_modifier');
},
@@ -16,8 +16,8 @@
<td>
<a title="assign to invoice" role="button" class="oe_form_field btn btn-link outstanding_credit_assign" t-att-data-id="line.id" style="margin-right: 10px;" href="#">Add</a>
</td>
<td>
<span class="oe_form_field" style="margin-right: 30px;"><t t-esc="line.journal_name"></t></span>
<td style="max-width: 10em;">
<div class="oe_form_field" style="margin-right: 30px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap;" t-att-title="line.title"><t t-esc="line.journal_name"></t></div>
</td>
</t>
<t t-if="!outstanding">
@@ -212,6 +212,10 @@
<td class="o_td_label">Tax Included in Price</td>
<td class="o_td_field"></td>
</tr>
<tr class="create_date d-none">
<td class="o_td_label">Writeoff Date</td>
<td class="o_td_field"></td>
</tr>
</tbody>
</table>
<div class="add_line_container">
@@ -649,8 +649,8 @@ QUnit.module('account', {
assert.strictEqual(widget.$('.o_input_dropdown input').val(), "Agrolait", "the partner many2one should display agrolait");
assert.strictEqual(clientAction.widgets[2].$('.o_input_dropdown input').val(), "Camptocamp", "the partner many2one should display Camptocamp");
widget.$('.accounting_view tfoot td:first').trigger('click');
assert.containsN(widget, '.create input.o_input', 7,
"create panel should contain 7 fields (account_id, tax_id, journal_id, analytic_account_id, analytic_tag_ids, label, amount)");
assert.containsN(widget, '.create input.o_input', 8,
"create panel should contain 8 fields (account_id, tax_id, journal_id, analytic_account_id, analytic_tag_ids, label, amount, date)");
assert.containsN(widget, '.create .create_account_id .o_required_modifier, .create .create_label .o_required_modifier, .create .create_amount .o_required_modifier', 3,
"account_id, label and amount should be required fields");
assert.strictEqual(widget.$('.create .create_label input').val(), 'SAJ/2014/002 and SAJ/2014/003',
@@ -1,3 +1,5 @@
from unittest.mock import patch
from odoo.addons.account.tests.account_test_users import AccountTestUsers
import datetime
from odoo.tests import tagged
@@ -165,3 +167,174 @@ class TestAccountCustomerInvoice(AccountTestUsers):
))
self.assertEquals(invoice.amount_untaxed, sum([x.base for x in invoice.tax_line_ids]))
def test_customer_invoice_tax_refund(self):
company = self.env.user.company_id
tax_account = self.env['account.account'].create({
'name': 'TAX',
'code': 'TAX',
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
'company_id': company.id,
})
tax_refund_account = self.env['account.account'].create({
'name': 'TAX_REFUND',
'code': 'TAX_R',
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
'company_id': company.id,
})
journalrec = self.env['account.journal'].search([('type', '=', 'sale')])[0]
partner3 = self.env.ref('base.res_partner_3')
account_id = self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_revenue').id)], limit=1).id
tax = self.env['account.tax'].create({
'name': 'Tax 15.0',
'amount': 15.0,
'amount_type': 'percent',
'type_tax_use': 'sale',
'account_id': tax_account.id,
'refund_account_id': tax_refund_account.id
})
invoice_line_data = [
(0, 0,
{
'product_id': self.env.ref('product.product_product_1').id,
'quantity': 40.0,
'account_id': account_id,
'name': 'product test 1',
'discount': 10.00,
'price_unit': 2.27,
'invoice_line_tax_ids': [(6, 0, [tax.id])],
}
)]
invoice = self.env['account.invoice'].create(dict(
name="Test Customer Invoice",
journal_id=journalrec.id,
partner_id=partner3.id,
invoice_line_ids=invoice_line_data
))
invoice.action_invoice_open()
refund = invoice.refund()
self.assertEqual(invoice.tax_line_ids.mapped('account_id'), tax_account)
self.assertEqual(refund.tax_line_ids.mapped('account_id'), tax_refund_account)
def test_customer_invoice_dashboard(self):
def patched_today(*args, **kwargs):
return '2019-01-22'
date_invoice = '2019-01-21'
partner3 = self.env.ref('base.res_partner_3')
account_id = self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_revenue').id)], limit=1).id
journal = self.env['account.journal'].create({
'name': 'sale_0',
'code': 'SALE0',
'type': 'sale',
})
invoice_line_data = [
(0, 0,
{
'product_id': self.env.ref('product.product_product_1').id,
'quantity': 40.0,
'account_id': account_id,
'name': 'product test 1',
'discount': 10.00,
'price_unit': 2.27,
}
)
]
invoice = self.env['account.invoice'].create(dict(
name="Test Customer Invoice",
journal_id=journal.id,
partner_id=partner3.id,
invoice_line_ids=invoice_line_data,
date_invoice=date_invoice,
))
refund_line_data = [
(0, 0,
{
'product_id': self.env.ref('product.product_product_1').id,
'quantity': 1.0,
'account_id': account_id,
'name': 'product test 1',
'price_unit': 13.3,
}
)]
refund = self.env['account.invoice'].create(dict(
name="Test Customer Refund",
type='out_refund',
journal_id=journal.id,
partner_id=partner3.id,
invoice_line_ids=refund_line_data,
date_invoice=date_invoice,
))
# Check Draft
dashboard_data = journal.get_journal_dashboard_datas()
self.assertEquals(dashboard_data['number_draft'], 2)
self.assertEquals(dashboard_data['sum_draft'], '$ 68.42')
self.assertEquals(dashboard_data['number_waiting'], 0)
self.assertEquals(dashboard_data['sum_waiting'], '$ 0.00')
# Check Both
invoice.action_invoice_open()
dashboard_data = journal.get_journal_dashboard_datas()
self.assertEquals(dashboard_data['number_draft'], 1)
self.assertEquals(dashboard_data['sum_draft'], '$ -13.30')
self.assertEquals(dashboard_data['number_waiting'], 1)
self.assertEquals(dashboard_data['sum_waiting'], '$ 81.72')
# Check waiting payment
refund.action_invoice_open()
dashboard_data = journal.get_journal_dashboard_datas()
self.assertEquals(dashboard_data['number_draft'], 0)
self.assertEquals(dashboard_data['sum_draft'], '$ 0.00')
self.assertEquals(dashboard_data['number_waiting'], 2)
self.assertEquals(dashboard_data['sum_waiting'], '$ 68.42')
# Check partial
receivable_account = refund.move_id.line_ids.mapped('account_id').filtered(lambda a: a.internal_type == 'receivable')
payment_move = self.env['account.move'].create({
'journal_id': journal.id,
})
payment_move_line = self.env['account.move.line'].with_context(check_move_validity=False).create({
'move_id': payment_move.id,
'account_id': receivable_account.id,
'debit': 10.00,
})
self.env['account.move.line'].with_context(check_move_validity=False).create({
'move_id': payment_move.id,
'account_id': self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_liquidity').id)], limit=1).id,
'credit': 10.00,
})
payment_move.post()
refund.register_payment(payment_move_line)
dashboard_data = journal.get_journal_dashboard_datas()
self.assertEquals(dashboard_data['number_draft'], 0)
self.assertEquals(dashboard_data['sum_draft'], '$ 0.00')
self.assertEquals(dashboard_data['number_waiting'], 2)
self.assertEquals(dashboard_data['sum_waiting'], '$ 78.42')
with patch('odoo.fields.Date.today', patched_today):
dashboard_data = journal.get_journal_dashboard_datas()
self.assertEquals(dashboard_data['number_late'], 2)
self.assertEquals(dashboard_data['sum_late'], '$ 78.42')
+34
View File
@@ -595,3 +595,37 @@ class TestPayment(AccountingTestCase):
# The invoice should now be paid
self.assertEqual(invoice.state, 'paid', "Invoice should be in 'paid' state after having reconciled the two payments with a bank statement")
def test_register_payments_multi_invoices(self):
inv1 = self.create_invoice(amount=40)
inv2 = inv1.copy()
inv2.action_invoice_open()
batch_payment = self.env['account.register.payments'].with_context(active_ids=(inv1 + inv2).ids).create({
'amount': 70,
'partner_id': inv1.partner_id.id,
'journal_id': self.bank_journal_usd.id,
'invoice_ids': [(6, False, (inv1 + inv2).ids)],
'partner_type': 'customer',
'payment_difference_handling': 'reconcile',
'payment_type': 'inbound',
'payment_method_id': self.payment_method_manual_in.id,
'writeoff_account_id': self.account_revenue.id,
'writeoff_label': "why can't we live together"})
payment_id = batch_payment.create_payments()['res_id']
payment_id = self.env['account.payment'].browse(payment_id)
self.assertEqual(inv1.state, 'paid')
self.assertEqual(inv2.state, 'paid')
receivable_inv1 = inv1.move_id.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
receivable_inv2 = inv2.move_id.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
receivable_payment = payment_id.move_line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
self.assertEqual(
receivable_inv1.full_reconcile_id.reconciled_line_ids,
receivable_inv1 + receivable_inv2 + receivable_payment
)
write_off_line = payment_id.move_line_ids.filtered(lambda l: l.account_id == payment_id.writeoff_account_id)
self.assertEqual(write_off_line.debit, 10)
+2 -1
View File
@@ -1569,10 +1569,11 @@
<field name="name">account.move.form</field>
<field name="model">account.move</field>
<field name="arch" type="xml">
<form string="Account Entry">
<form string="Account Entry" duplicate="false">
<header>
<button name="action_post" states="draft" string="Post" type="object" class="oe_highlight" groups="account.group_account_invoice"/>
<button name="%(action_view_account_move_reversal)d" states="posted" string="Reverse Entry" type="action" groups="account.group_account_invoice"/>
<button name="action_duplicate" string="Duplicate" type="object" groups="account.group_account_invoice"/>
<field name="state" widget="statusbar"/>
</header>
<sheet>
+10 -2
View File
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo.exceptions import UserError
from odoo import api, fields, models, _
from odoo.addons.mail.wizard.mail_compose_message import _reopen
class AccountInvoiceSend(models.TransientModel):
@@ -67,3 +67,11 @@ class AccountInvoiceSend(models.TransientModel):
if self.is_print:
return self._print_document()
return {'type': 'ir.actions.act_window_close'}
@api.multi
def save_as_template(self):
self.ensure_one()
self.composer_id.save_as_template()
action = _reopen(self, self.id, self.model, context=self._context)
action.update({'name': _('Send Invoice')})
return action
@@ -23,6 +23,7 @@
# Nicolás Broggi <rnbroggi@gmail.com>, 2018
# Jesús Alan Ramos Rodríguez <alan.ramos@jarsa.com.mx>, 2018
# Pedro M. Baeza <pedro.baeza@gmail.com>, 2019
# Gabriel Umaña <gabriel.umana@delfixcr.com>, 2019
#
msgid ""
msgstr ""
@@ -30,7 +31,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-21 13:17+0000\n"
"PO-Revision-Date: 2016-08-05 12:55+0000\n"
"Last-Translator: Pedro M. Baeza <pedro.baeza@gmail.com>, 2019\n"
"Last-Translator: Gabriel Umaña <gabriel.umana@delfixcr.com>, 2019\n"
"Language-Team: Spanish (https://www.transifex.com/odoo/teams/41243/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -305,12 +306,12 @@ msgstr "Nombre de archivo"
#. module: account_bank_statement_import
#: model:ir.model.fields,field_description:account_bank_statement_import.field_account_bank_statement_import_journal_creation__inbound_payment_method_ids
msgid "For Incoming Payments"
msgstr ""
msgstr "Para pagos recibidos"
#. module: account_bank_statement_import
#: model:ir.model.fields,field_description:account_bank_statement_import.field_account_bank_statement_import_journal_creation__outbound_payment_method_ids
msgid "For Outgoing Payments"
msgstr ""
msgstr "Para pagos salientes"
#. module: account_bank_statement_import
#: model:ir.model.fields,help:account_bank_statement_import.field_account_bank_statement_import__data_file
+23 -5
View File
@@ -3,16 +3,34 @@
# * account_voucher
#
# Translators:
# Martin Trigaux, 2018
# Cristopher Cravioto <ccr@odoo.com>, 2018
# Leonardo Chianea <noamixcontenidos@gmail.com>, 2016
# David Perez <david@closemarketing.es>, 2016
# Luis M. Triana <luis.triana@jarsa.com.mx>, 2016
# Carles Antoli <carlesantoli@hotmail.com>, 2016
# Antonio Trueba, 2016
# Ivan Nieto <ivan.n.s@tuta.io>, 2016
# Mateo Tibaquirá Palacios <nestormateo@gmail.com>, 2016
# Luis M. Ontalba <luis.martinez@tecnativa.com>, 2016
# Christopher Ormaza <chris.ormaza@gmail.com>, 2017
# Pedro M. Baeza <pedro.baeza@gmail.com>, 2018
# Ana Juaristi <ajuaristio@gmail.com>, 2018
# oihane <oihanecruce@gmail.com>, 2018
# Javier Ramirez <elbomba887@gmail.com>, 2018
# José Vicente <txusev@gmail.com>, 2018
# RGB Consulting <odoo@rgbconsulting.com>, 2018
# Nicolás Broggi <rnbroggi@gmail.com>, 2018
# Miquel Torner <miq@odoo.com>, 2018
# Martin Trigaux, 2019
# Cristopher Cravioto <ccr@odoo.com>, 2019
# Luis Marin <marin.guadarrama@gmail.com>, 2019
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~11.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-21 13:17+0000\n"
"PO-Revision-Date: 2018-09-21 13:17+0000\n"
"Last-Translator: Miquel Torner <miq@odoo.com>, 2018\n"
"PO-Revision-Date: 2016-08-05 12:55+0000\n"
"Last-Translator: Luis Marin <marin.guadarrama@gmail.com>, 2019\n"
"Language-Team: Spanish (https://www.transifex.com/odoo/teams/41243/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -692,4 +710,4 @@ msgstr ""
#: code:addons/account_voucher/models/account_voucher.py:457
#, python-format
msgid "You must first select a partner."
msgstr ""
msgstr "Primero debe seleccionar un Contacto"
+1 -1
View File
@@ -325,7 +325,7 @@ msgstr "Utoljára frissítve "
#. module: account_voucher
#: model:ir.model.fields,field_description:account_voucher.field_account_voucher__message_main_attachment_id
msgid "Main Attachment"
msgstr ""
msgstr "Fő melléklet"
#. module: account_voucher
#: model:ir.model.fields,field_description:account_voucher.field_account_voucher__message_has_error
+1 -1
View File
@@ -387,7 +387,7 @@ msgstr "Utoljára frissítve "
#. module: analytic
#: model:ir.model.fields,field_description:analytic.field_account_analytic_account__message_main_attachment_id
msgid "Main Attachment"
msgstr ""
msgstr "Fő melléklet"
#. module: analytic
#: model:ir.model.fields,field_description:analytic.field_account_analytic_account__message_has_error
@@ -9,7 +9,7 @@ import zipfile
from odoo import api, models
_logger = logging.getLogger(__name__)
FTYPES = ['docx', 'pptx', 'xlsx', 'opendoc', 'pdf']
FTYPES = ['docx', 'pptx', 'xlsx', 'opendoc']
def textToString(element):
buff = u""
@@ -92,6 +92,9 @@ class IrAttachment(models.Model):
def _index_pdf(self, bin_data):
'''Index PDF documents'''
# extractText gives very bad results for indexing, hence we don't index PDF anymore. A
# better alternative is probably PDFMiner.six, but not for stable.
# See POC at https://github.com/odoo/odoo/pull/27568.
buf = u""
if bin_data.startswith(b'%PDF-'):
f = io.BytesIO(bin_data)
@@ -15,9 +15,10 @@ class Partner(models.Model):
@api.onchange('city_id')
def _onchange_city_id(self):
self.city = self.city_id.name
self.zip = self.city_id.zipcode
self.state_id = self.city_id.state_id
if self.city_id:
self.city = self.city_id.name
self.zip = self.city_id.zipcode
self.state_id = self.city_id.state_id
@api.model
def _fields_view_get_address(self, arch):
@@ -37,7 +38,10 @@ class Partner(models.Model):
}"
/>
<field name='city_id' placeholder="%(placeholder)s" string="%(placeholder)s"
context="{'default_country_id': country_id}"
context="{'default_country_id': country_id,
'default_name': city,
'default_zipcode': zip,
'default_state_id': state_id}"
domain="[('country_id', '=', country_id)]"
attrs="{
'invisible': [('country_enforce_cities', '=', False)],
@@ -24,6 +24,7 @@ class BaseGengoTranslations(models.TransientModel):
GROUPS = ['base.group_system']
_name = 'base.gengo.translations'
_description = 'Base Gengo Translations'
@api.model
def default_get(self, fields):
+3 -2
View File
@@ -15,13 +15,14 @@ class ResPartner(models.Model):
self.env.cr.execute("""
SELECT
U.partner_id as id,
CASE WHEN age(now() AT TIME ZONE 'UTC', B.last_poll) > interval %s THEN 'offline'
WHEN age(now() AT TIME ZONE 'UTC', B.last_presence) > interval %s THEN 'away'
CASE WHEN age(now() AT TIME ZONE 'UTC', max(B.last_poll)) > interval %s THEN 'offline'
WHEN age(now() AT TIME ZONE 'UTC', max(B.last_presence)) > interval %s THEN 'away'
ELSE 'online'
END as status
FROM bus_presence B
JOIN res_users U ON B.user_id = U.id
WHERE U.partner_id IN %s AND U.active = 't'
GROUP BY U.partner_id
""", ("%s seconds" % DISCONNECTION_TIMER, "%s seconds" % AWAY_TIMER, tuple(self.ids)))
res = dict(((status['id'], status['status']) for status in self.env.cr.dictfetchall()))
for partner in self:
+1
View File
@@ -898,6 +898,7 @@ for rec in records:
name="Team Pipelines"
parent="crm_menu_sales"
action="sales_team.crm_team_salesteams_pipelines_act"
groups="sales_team.group_sale_manager"
sequence="4"/>
<menuitem id="res_partner_menu_customer"
+1 -1
View File
@@ -70,7 +70,7 @@ class DeliveryCarrier(models.Model):
def install_more_provider(self):
return {
'name': 'New Providers',
'view_mode': 'kanban',
'view_mode': 'kanban,form',
'res_model': 'ir.module.module',
'domain': [['name', 'ilike', 'delivery_']],
'type': 'ir.actions.act_window',
@@ -21,6 +21,7 @@ class ChooseDeliveryPackage(models.TransientModel):
string='Shipping Weight',
default=lambda self: self._default_shipping_weight()
)
weight_uom_name = fields.Char(string='Weight unit of measure label', compute='_compute_weight_uom_name')
def _default_stock_quant_package_id(self):
if self.env.context.get('default_stock_quant_package_id'):
@@ -45,6 +46,12 @@ class ChooseDeliveryPackage(models.TransientModel):
total_weight = sum([po.qty_done * po.product_id.weight for po in move_line_ids])
return total_weight
@api.depends('stock_quant_package_id', 'delivery_packaging_id')
def _compute_weight_uom_name(self):
weight_uom_id = self.env['product.template']._get_weight_uom_id_from_ir_config_parameter()
for package in self:
package.weight_uom_name = weight_uom_id.name
@api.onchange('delivery_packaging_id', 'shipping_weight')
def _onchange_packaging_weight(self):
if self.delivery_packaging_id.max_weight and self.shipping_weight > self.delivery_packaging_id.max_weight:
@@ -11,7 +11,7 @@
<label for="shipping_weight" attrs="{'invisible': [('delivery_packaging_id', '=', False)]}"/>
<div class="o_row" attrs="{'invisible': [('delivery_packaging_id', '=', False)]}">
<field name="shipping_weight"/>
<span>kg</span>
<field name="weight_uom_name"/>
</div>
</group>
<footer>
+2 -7
View File
@@ -23,7 +23,7 @@ class FleetVehicle(models.Model):
license_plate = fields.Char(tracking=True,
help='License plate number of the vehicle (i = plate number for a car)')
vin_sn = fields.Char('Chassis Number', help='Unique number written on the vehicle motor (VIN/SN number)', copy=False)
driver_id = fields.Many2one('res.partner', 'Driver', tracking=True, help='Driver of the vehicle', copy=False)
driver_id = fields.Many2one('res.partner', 'Driver', tracking=True, help='Driver of the vehicle', copy=False, auto_join=True)
model_id = fields.Many2one('fleet.vehicle.model', 'Model',
tracking=True, required=True, help='Model of the vehicle')
brand_id = fields.Many2one('fleet.vehicle.model.brand', 'Brand', related="model_id.brand_id", store=True, readonly=False)
@@ -233,12 +233,7 @@ class FleetVehicle(models.Model):
@api.model
def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
domain = args or []
domain = expression.AND([domain, [('name', operator, name)]])
# we don't want to override the domain's filter on driver_id if present
if not any(['driver_id' in element for element in domain]):
partner_ids = self.env['res.partner']._search([('name', operator, name)], access_rights_uid=name_get_uid)
if partner_ids:
domain = expression.OR([domain, ['|', ('driver_id', 'in', partner_ids), ('driver_id', '=', False)]])
domain = expression.AND([domain, ['|', ('name', operator, name), ('driver_id.name', operator, name)]])
rec = self._search(domain, limit=limit, access_rights_uid=name_get_uid)
return self.browse(rec).name_get()
+115
View File
@@ -0,0 +1,115 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * crm_project
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~11.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-21 13:17+0000\n"
"PO-Revision-Date: 2018-08-24 09:17+0000\n"
"Language-Team: Filipino (https://www.transifex.com/odoo/teams/41243/fil/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: fil\n"
"Plural-Forms: nplurals=2; plural=(n == 1 || n==2 || n==3) || (n % 10 != 4 || n % 10 != 6 || n % 10 != 9);\n"
#. module: crm_project
#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_view_form
msgid ""
"<span class=\"o_stat_text\">Convert To</span>\n"
" <span class=\"o_stat_text\">Task</span>"
msgstr ""
#. module: crm_project
#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_convert2task_view_form
msgid "Cancel"
msgstr ""
#. module: crm_project
#: model:ir.actions.act_window,name:crm_project.crm_lead_convert2task_action
#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_convert2task_view_form
#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_view_form
msgid "Convert to Task"
msgstr ""
#. module: crm_project
#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_convert2task_view_form
msgid "Create Task"
msgstr ""
#. module: crm_project
#: selection:crm.lead.convert2task,action:0
msgid "Create a new customer"
msgstr ""
#. module: crm_project
#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__create_uid
msgid "Created by"
msgstr ""
#. module: crm_project
#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__create_date
msgid "Created on"
msgstr ""
#. module: crm_project
#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__partner_id
msgid "Customer"
msgstr ""
#. module: crm_project
#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__display_name
msgid "Display Name"
msgstr ""
#. module: crm_project
#: selection:crm.lead.convert2task,action:0
msgid "Do not link to a customer"
msgstr ""
#. module: crm_project
#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__id
msgid "ID"
msgstr ""
#. module: crm_project
#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task____last_update
msgid "Last Modified on"
msgstr ""
#. module: crm_project
#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__write_uid
msgid "Last Updated by"
msgstr ""
#. module: crm_project
#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__write_date
msgid "Last Updated on"
msgstr ""
#. module: crm_project
#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__lead_id
msgid "Lead"
msgstr ""
#. module: crm_project
#: model:ir.model,name:crm_project.model_crm_lead_convert2task
msgid "Lead convert to Task"
msgstr ""
#. module: crm_project
#: selection:crm.lead.convert2task,action:0
msgid "Link to an existing customer"
msgstr ""
#. module: crm_project
#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__project_id
msgid "Project"
msgstr ""
#. module: crm_project
#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__action
msgid "Related Customer"
msgstr ""
+2 -1
View File
@@ -501,7 +501,8 @@ class HolidaysRequest(models.Model):
holiday.sudo().action_validate()
holiday.message_subscribe(partner_ids=[holiday._get_responsible_for_approval().partner_id.id])
holiday.sudo().message_post(body=_("The time off has been automatically approved"), subtype="mt_comment") # Message from OdooBot (sudo)
holiday.activity_update()
if not self._context.get('import_file'):
holiday.activity_update()
return holiday
def _read_from_database(self, field_names, inherited_field_names=[]):
@@ -57,7 +57,7 @@ class FleetVehicle(models.Model):
for car in self:
car.co2_fee = self._get_co2_fee(car.co2, car.fuel_type)
@api.depends('fuel_type', 'car_value', 'acquisition_date')
@api.depends('fuel_type', 'car_value', 'acquisition_date', 'co2')
def _compute_car_atn(self):
for car in self:
car.atn = car._get_car_atn(car.acquisition_date, car.car_value, car.fuel_type, car.co2)
@@ -239,7 +239,7 @@
<record id="pcg_120" model="account.account.template">
<field name="name">Résultat de l'exercice (bénéfice)</field>
<field name="code">120</field>
<field name="user_type_id" ref="account.data_unaffected_earnings"/>
<field name="user_type_id" ref="account.data_account_type_current_liabilities"/>
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
</record>
+7 -6
View File
@@ -158,7 +158,7 @@
<field name="applicability">taxes</field>
</record>
<record id="tag_nl_06" model="account.account.tag">
<field name="name">1c. Leveringen/diensten belast met overige tarieven behalve 0% (omzet)</field>
<field name="name">1c. Leveringen/diensten belast met overige tarieven, behalve 0% (omzet)</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_nl_07" model="account.account.tag">
@@ -174,7 +174,7 @@
<field name="applicability">taxes</field>
</record>
<record id="tag_nl_10" model="account.account.tag">
<field name="name">2a. Leveringen/diensten waarbij de heffing van omzetbelasting naar u is verlegd (omzet)</field>
<field name="name">2a. Leveringen/diensten waarbij de omzetbelasting naar u is verlegd (omzet)</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_nl_11" model="account.account.tag">
@@ -187,11 +187,11 @@
</record>
<!-- Keep tag_nl_40 & tag_nl_41 to avoid migration issue. -->
<record id="tag_nl_40" model="account.account.tag">
<field name="name">3b. Leveringen naar/diensten in landen binnen de EU (omzet) (producten)</field>
<field name="name">3b. Leveringen naar of diensten in landen binnen de EU (omzet) (producten)</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_nl_41" model="account.account.tag">
<field name="name">3b. Leveringen naar/diensten in landen binnen de EU (omzet) (diensten)</field>
<field name="name">3b. Leveringen naar of diensten in landen binnen de EU (omzet) (diensten)</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_nl_14" model="account.account.tag">
@@ -231,7 +231,7 @@
<field name="applicability">taxes</field>
</record>
<record id="tag_nl_23" model="account.account.tag">
<field name="name">1c. Leveringen/diensten belast met overige tarieven behalve 0% (BTW)</field>
<field name="name">1c. Leveringen/diensten belast met overige tarieven, behalve 0% (BTW)</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_nl_24" model="account.account.tag">
@@ -247,7 +247,7 @@
<field name="applicability">taxes</field>
</record>
<record id="tag_nl_27" model="account.account.tag">
<field name="name">2a. Leveringen/diensten waarbij de heffing van Heffing van omzetbelasting naar u is verlegd (BTW)</field>
<field name="name">2a. Leveringen/diensten waarbij de omzetbelasting naar u is verlegd (BTW)</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_nl_28" model="account.account.tag">
@@ -297,3 +297,4 @@
</data>
</odoo>
@@ -331,8 +331,6 @@
<field name="description">21% BTW verlegd</field>
<field eval="21" name="amount"/>
<field name="amount_type">group</field>
<field name="refund_account_id" ref="vat_payable_v"/>
<field name="account_id" ref="vat_payable_v"/>
<field name="type_tax_use">purchase</field>
<field name="tag_ids" eval="[(6,0,[ref('tag_nl_10')])]"/>
<field name="children_tax_ids" eval="[(6, 0, [ref('btw_ink_0_1'), ref('btw_ink_0_2')])]"/>
+2 -2
View File
@@ -264,8 +264,8 @@
Best regards,
</div>
<div>&amp;nbsp;</div>
<div t-if="record.user_id and record.user_id.signature" style="font-size: 13px;">
<div t-raw="record.user_id.signature"/>
<div t-if="record.user_id and record.user_id.sudo().signature" style="font-size: 13px;">
<div t-raw="record.user_id.sudo().signature"/>
</div>
</td>
</tr>
+10
View File
@@ -538,6 +538,16 @@ class Message(models.Model):
failures_infos = []
# for each channel, build the information header and include the logged partner information
for message in self:
# Check if user has access to the record before displaying a notification about it.
# In case the user switches from one company to another, it might happen that he doesn't
# have access to the record related to the notification. In this case, we skip it.
if message.model and message.res_id:
record = self.env[message.model].browse(message.res_id)
try:
record.check_access_rights('read')
record.check_access_rule('read')
except AccessError:
continue
info = {
'message_id': message.id,
'record_name': message.record_name,
+3 -2
View File
@@ -1339,7 +1339,8 @@ class MailThread(models.AbstractModel):
subtype = 'mail.mt_note'
if message_dict.get('parent_id'):
parent_message = self.env['mail.message'].sudo().browse(message_dict['parent_id'])
partner_ids = [(4, parent_message.author_id.id)]
if parent_message.author_id:
partner_ids = [(4, parent_message.author_id.id)]
else:
subtype = 'mail.mt_comment'
@@ -2066,7 +2067,7 @@ class MailThread(models.AbstractModel):
prioritary_attachments = all_attachments.filtered(lambda x: x.mimetype.endswith('pdf')) \
or all_attachments.filtered(lambda x: x.mimetype.startswith('image')) \
or all_attachments
self.write({'message_main_attachment_id': prioritary_attachments[0].id})
self.sudo().write({'message_main_attachment_id': prioritary_attachments[0].id})
# Notify recipients of the newly-created message (Inbox / Email + channels)
if msg_vals.get('moderation_status') != 'pending_moderation':
message._notify(
+1 -1
View File
@@ -333,7 +333,7 @@ var BasicActivity = AbstractField.extend({
var self = this;
var $markDoneBtn = $(ev.currentTarget);
var activityID = $markDoneBtn.data('activity-id');
var previousActivityTypeID = $markDoneBtn.data('previous-activity-type-id');
var previousActivityTypeID = $markDoneBtn.data('previous-activity-type-id') || false;
var forceNextActivity = $markDoneBtn.data('force-next-activity');
if ($markDoneBtn.data('toggle') == 'collapse') {
+3 -4
View File
@@ -300,6 +300,7 @@ var Chatter = Widget.extend({
recordName: this.recordName,
defaultBody: oldComposer && oldComposer.$input && oldComposer.$input.val(),
defaultMentionSelections: oldComposer && oldComposer.getMentionListenerSelections(),
attachmentIds: (oldComposer && oldComposer.get('attachment_ids')) || [],
});
this._composer.on('input_focused', this, function () {
this._composer.mentionSetPrefetchedPartners(this._mentionSuggestions || []);
@@ -309,9 +310,7 @@ var Chatter = Widget.extend({
if (oldComposer) {
oldComposer.destroy();
}
if (!config.device.isMobile) {
self._composer.focus();
}
self._composer.focus();
self._composer.on('post_message', self, function (messageData) {
self._discardOnReload(messageData).then(function () {
self._disableComposer();
@@ -321,7 +320,7 @@ var Chatter = Widget.extend({
self.trigger_up('reload');
} else if (messageData.attachment_ids.length) {
self._reloadAttachmentBox();
self.trigger_up('reload', {fieldNames: ['message_attachment_count']});
self.trigger_up('reload', {fieldNames: ['message_attachment_count'], keepChanges: true});
}
}).fail(function () {
self._enableComposer();
@@ -26,7 +26,7 @@ var BasicComposer = Widget.extend({
'click .o_composer_button_add_attachment': '_onClickAddAttachment',
'click .o_composer_button_emoji': '_onEmojiButtonClick',
'focusout .o_composer_button_emoji': '_onEmojiButtonFocusout',
'click .o_mail_emoji_container .o_mail_emoji': '_onEmojiImageClick',
'mousedown .o_mail_emoji_container .o_mail_emoji': '_onEmojiImageClick',
'focus .o_mail_emoji_container .o_mail_emoji': '_onEmojiImageFocus',
'dragover .o_file_drop_zone_container': '_onFileDragover',
'drop .o_file_drop_zone_container': '_onFileDrop',
@@ -58,7 +58,7 @@ var BasicComposer = Widget.extend({
// Attachments
this._attachmentDataSet = new data.DataSetSearch(this, 'ir.attachment', this.context);
this.fileuploadID = _.uniqueId('o_chat_fileupload');
this.set('attachment_ids', []);
this.set('attachment_ids', options.attachmentIds || []);
// Mention
this._mentionManager = new MentionManager(this);
@@ -120,6 +120,7 @@ var BasicComposer = Widget.extend({
});
// Attachments
this._renderAttachments();
$(window).on(this.fileuploadID, this._onAttachmentLoaded.bind(this));
this.on('change:attachment_ids', this, this._renderAttachments);
@@ -101,31 +101,37 @@ MailManager.include({
// valid threadID, therefore no check
var thread = this.getThread(threadID);
var threadWindow = this._getThreadWindow(threadID);
var def = $.when();
if (!threadWindow) {
threadWindow = this._makeNewThreadWindow(thread, options);
this._placeNewThreadWindow(threadWindow, options.passively);
threadWindow.appendTo($(this.THREAD_WINDOW_APPENDTO))
.then(function () {
self._repositionThreadWindows();
return thread.fetchMessages();
}).then(function () {
threadWindow.render();
threadWindow.scrollToBottom();
if (
!self._areAllThreadWindowsHidden() &&
!thread.isFolded() &&
!threadWindow.isPassive()
) {
thread.markAsRead();
}
});
def = thread.fetchMessages().then(function () {
threadWindow = self._makeNewThreadWindow(thread, options);
self._placeNewThreadWindow(threadWindow, options.passively);
return threadWindow.appendTo($(self.THREAD_WINDOW_APPENDTO));
}).then(function () {
self._repositionThreadWindows();
threadWindow.render();
threadWindow.scrollToBottom();
if (
!self._areAllThreadWindowsHidden() &&
!thread.isFolded() &&
!threadWindow.isPassive()
) {
thread.markAsRead();
}
}).fail(function () {
// thread window could not be open, which may happen due to
// access error while fetching messages to the document.
// abort opening the thread window in this case.
thread.close();
});
} else if (!options.passively) {
if (threadWindow.isHidden()) {
this._makeThreadWindowVisible(threadWindow);
}
}
threadWindow.updateVisualFoldState();
def.then(function () {
threadWindow.updateVisualFoldState();
});
},
/**
* Called when a thread has its window state that has been changed, so its
@@ -46,7 +46,7 @@ var ActivityMenu = Widget.extend({
kwargs: {context: session.user_context},
}).then(function (data) {
self._activities = data;
self.activityCounter = _.reduce(data, function (total_count, p_data) { return total_count + p_data.total_count; }, 0);
self.activityCounter = _.reduce(data, function (total_count, p_data) { return total_count + p_data.total_count || 0; }, 0);
self.$('.o_notification_counter').text(self.activityCounter);
self.$el.toggleClass('o_no_notification', !self.activityCounter);
});
@@ -118,7 +118,8 @@ var ActivityMenu = Widget.extend({
name: targetAction.data('model_name'),
views: [[false, 'activity'], [false, 'kanban'], [false, 'list']],
view_mode: 'activity',
res_model: targetAction.data('res_model')
res_model: targetAction.data('res_model'),
context: { search_default_activities_my: true },
});
}
},
@@ -280,8 +280,10 @@ var ThreadWindow = AbstractThreadWindow.extend({
clear_breadcrumbs: false,
active_id: this.hasThread() ? this._getThreadID() : undefined,
on_reverse_breadcrumb: function () {
self.call('mail_service', 'getMailBus')
.trigger('discuss_open', false);
var mailBus = self.call('mail_service', 'getMailBus');
if (mailBus) {
mailBus.trigger('discuss_open', false);
}
},
});
}
@@ -84,6 +84,8 @@
.o_followers_list {
font-size: small;
max-height: 50vh; // ancestor has size 33px
overflow: auto;
.o_partner {
position: relative; // required for old design compatibility
+2 -2
View File
@@ -1366,13 +1366,13 @@ QUnit.test('select emoji replaces cursor position', function (assert) {
$composer[0].setSelectionRange(2, 4);
testUtils.dom.click(discuss.$('.o_composer_button_emoji'));
testUtils.dom.click(discuss.$('.o_mail_emoji[data-emoji=":)"]'));
testUtils.dom.triggerMouseEvent(discuss.$('.o_mail_emoji[data-emoji=":)"]'), 'mousedown');
assert.strictEqual($composer.val(), "ab :) efgh",
"should have inserted emoji ");
testUtils.dom.click(discuss.$('.o_composer_button_emoji'));
testUtils.dom.click(discuss.$('.o_mail_emoji[data-emoji=":)"]'));
testUtils.dom.triggerMouseEvent(discuss.$('.o_mail_emoji[data-emoji=":)"]'), 'mousedown');
assert.strictEqual($composer.val(), "ab :) :) efgh",
"should have inserted emoji after previously inserted emoji");
@@ -271,6 +271,37 @@ QUnit.test('open, fold, unfold and close a document thread window', function (as
messagingMenu.destroy();
});
QUnit.test('do not open thread window on fetch message failure', function (assert) {
// this may happen when the user receives a notification from a document
// that he does not have access rights at the moment.
assert.expect(4);
var messagingMenu = new MessagingMenu();
testUtils.addMockEnvironment(messagingMenu, {
services: this.services,
data: this.data,
session: this.session,
mockRPC: function (route, args) {
if (args.method === 'read' && args.model === 'some.res.model' && args.args[0][0] === 1) {
assert.step('some.res.model/1/read');
return $.Deferred().reject(); // simulate failure
}
return this._super.apply(this, arguments);
},
});
messagingMenu.appendTo($('#qunit-fixture'));
testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.containsOnce(messagingMenu, '.o_mail_preview');
testUtils.dom.click(messagingMenu.$('.o_mail_preview'));
assert.verifySteps(['some.res.model/1/read']);
assert.strictEqual($('.o_thread_window').length, 0,
"should not have open the DocumentThread in a thread window on fetch messages failure");
messagingMenu.destroy();
});
});
});
});
@@ -151,7 +151,7 @@ QUnit.test('activity menu widget: activity menu with 3 records', function (asser
});
QUnit.test('activity menu widget: activity view icon', function (assert) {
assert.expect(8);
assert.expect(10);
var self = this;
var activityMenu = new ActivityMenu();
testUtils.mock.addMockEnvironment(activityMenu, {
@@ -177,9 +177,15 @@ QUnit.test('activity menu widget: activity view icon', function (assert) {
"Second activity action should link to 'Note'");
assert.hasClass($second,'fa-clock-o', "should display the activity action icon");
testUtils.mock.intercept(activityMenu, 'do_action', function (event) {
assert.step('do_action:' +
(event.data.action.name ? event.data.action.name : event.data.action));
testUtils.mock.intercept(activityMenu, 'do_action', function (ev) {
if (ev.data.action.name) {
assert.ok(ev.data.action.context, "should define a context on the action");
assert.ok(ev.data.action.context.search_default_activities_my,
"should auto-set search to 'My Activities' in the context of the action");
assert.step('do_action:' + ev.data.action.name);
} else {
assert.step('do_action:' + ev.data.action);
}
}, true);
// click on the "Issue" activity icon
+1
View File
@@ -4,6 +4,7 @@
from . import mass_mailing
from . import mass_mailing_stats
from . import mail_mail
from . import mail_template
from . import mail_thread
from . import res_config_settings
from . import mass_mailing_report
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
class MailTemplate(models.Model):
_inherit = "mail.template"
@api.model
def render_post_process(self, html):
# super will transform relative url to absolute
html = super(MailTemplate, self).render_post_process(html)
# apply shortener after
if self.env.context.get('post_convert_links'):
html = self.env['link.tracker'].convert_links(
html,
self.env.context['post_convert_links'],
blacklist=['/unsubscribe_from_list']
)
return html
+39 -13
View File
@@ -792,19 +792,47 @@ class MassMailing(models.Model):
_logger.info("Mass-mailing %s targets %s, no opt out list available", self, target._name)
return opt_out
def _get_convert_links(self):
self.ensure_one()
utm_mixin = self.mass_mailing_campaign_id if self.mass_mailing_campaign_id else self
vals = {'mass_mailing_id': self.id}
if self.mass_mailing_campaign_id:
vals['mass_mailing_campaign_id'] = self.mass_mailing_campaign_id.id
if utm_mixin.campaign_id:
vals['campaign_id'] = utm_mixin.campaign_id.id
if utm_mixin.source_id:
vals['source_id'] = utm_mixin.source_id.id
if utm_mixin.medium_id:
vals['medium_id'] = utm_mixin.medium_id.id
return vals
def _get_seen_list(self):
"""Returns a set of emails already targeted by current mailing/campaign (no duplicates)"""
self.ensure_one()
target = self.env[self.mailing_model_real]
mail_field = 'email' if 'email' in target._fields else 'email_from'
# avoid loading a large number of records in memory
# + use a basic heuristic for extracting emails
query = """
SELECT lower(substring(t.%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)'))
FROM mail_mail_statistics s
JOIN %(target)s t ON (s.res_id = t.id)
WHERE substring(t.%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)') IS NOT NULL
"""
if set(['email', 'email_from']) & set(target._fields):
mail_field = 'email' if 'email' in target._fields else 'email_from'
# avoid loading a large number of records in memory
# + use a basic heuristic for extracting emails
query = """
SELECT lower(substring(t.%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)'))
FROM mail_mail_statistics s
JOIN %(target)s t ON (s.res_id = t.id)
WHERE substring(t.%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)') IS NOT NULL
"""
elif 'partner_id' in target._fields:
mail_field = 'email'
query = """
SELECT lower(substring(p.%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)'))
FROM mail_mail_statistics s
JOIN %(target)s t ON (s.res_id = t.id)
JOIN res_partner p ON (t.partner_id = p.id)
WHERE substring(p.%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)') IS NOT NULL
"""
else:
raise UserError(_("Unsupported mass mailing model %s") % self.mailing_model_id.name)
if self.mass_mailing_campaign_id.unique_ab_testing:
query +="""
AND s.mass_mailing_campaign_id = %%(mailing_campaign_id)s;
@@ -827,6 +855,7 @@ class MassMailing(models.Model):
return {
'mass_mailing_opt_out_list': self._get_opt_out_list(),
'mass_mailing_seen_list': self._get_seen_list(),
'post_convert_links': self._get_convert_links(),
}
def get_recipients(self):
@@ -868,13 +897,10 @@ class MassMailing(models.Model):
if not res_ids:
raise UserError(_('There is no recipients selected.'))
# Convert links in absolute URLs before the application of the shortener
mailing.body_html = self.env['mail.thread']._replace_local_links(mailing.body_html)
composer_values = {
'author_id': author_id,
'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids],
'body': mailing.convert_links()[mailing.id],
'body': mailing.body_html,
'subject': mailing.subject,
'model': mailing.mailing_model_real,
'email_from': mailing.email_from,
+1
View File
@@ -2,3 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_mass_mailing_list_merge
from . import test_mass_mailing_shortener
@@ -0,0 +1,132 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import common
from lxml import etree
try:
from unittest.mock import patch
except ImportError:
from mock import patch
class TestMassMailingShortener(common.TransactionCase):
def getHrefFor(self, html, id):
return html.xpath("*[@id='%s']" % id)[0].attrib.get('href')
def shorturl_to_link(self, short_url):
return self.env['link.tracker.code'].search([('code', '=', short_url.split('/r/')[-1])]).link_id
def setUp(self):
super(TestMassMailingShortener, self).setUp()
def _get_title_from_url(u):
return "Hello"
def _compute_favicon():
# 1px to avoid real request
return 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg=='
patcher = patch('odoo.addons.link_tracker.models.link_tracker.LinkTracker._compute_favicon', wraps=_compute_favicon)
patcher2 = patch('odoo.addons.link_tracker.models.link_tracker.LinkTracker._get_title_from_url', wraps=_get_title_from_url)
patcher.start()
patcher2.start()
self.addCleanup(patcher.stop)
self.addCleanup(patcher2.stop)
def test_00_test_mass_mailing_shortener(self):
mailing_list_A = self.env['mail.mass_mailing.list'].create({
'name': 'A',
})
self.env['mail.mass_mailing.contact'].create({
'name': 'User 1', 'email': 'user1@example.com', 'list_ids': [(4, mailing_list_A.id)]
})
self.env['mail.mass_mailing.contact'].create({
'name': 'User 2', 'email': 'user2@example.com', 'list_ids': [(4, mailing_list_A.id)]
})
self.env['mail.mass_mailing.contact'].create({
'name': 'User 3', 'email': 'user3@example.com', 'list_ids': [(4, mailing_list_A.id)]
})
mass_mailing = self.env['mail.mass_mailing'].create({
"reply_to_mode": "email",
"reply_to": "Administrator <admin@yourcompany.example.com>",
"mailing_model_id": self.env.ref('mass_mailing.model_mail_mass_mailing_list').id,
"mailing_domain": "[('list_ids', 'in', [%d])]" % mailing_list_A.id,
"contact_list_ids": [[6, False, [mailing_list_A.id]]],
"mass_mailing_campaign_id": False,
"name": "sdf",
"body_html": """
Hi,
% set url = "www.odoo.com"
% set httpurl = "https://www.odoo.eu"
Website0: <a id="url0" href="https://www.odoo.tz/my/${object.name}">https://www.odoo.tz/my/${object.name}</h1>
Website1: <a id="url1" href="https://www.odoo.be">https://www.odoo.be</h1>
Website2: <a id="url2" href="https://${url}">https://${url}</h1>
Website3: <a id="url3" href="${httpurl}">${httpurl}</h1>
Email: <a id="url4" href="mailto:test@odoo.com">test@odoo.com</h1>
""",
"schedule_date": False,
"state": "draft",
"keep_archives": True,
})
mass_mailing.put_in_queue()
mass_mailing._process_mass_mailing_queue()
sent_mails = self.env['mail.mail'].search([('mailing_id', '=', mass_mailing.id)])
sent_messages = sent_mails.mapped('mail_message_id')
self.assertEqual(mailing_list_A.contact_nbr, len(sent_messages),
'Some message has not been sent')
xbody = etree.fromstring(sent_messages[0].body)
after_url0 = self.getHrefFor(xbody, 'url0')
after_url1 = self.getHrefFor(xbody, 'url1')
after_url2 = self.getHrefFor(xbody, 'url2')
after_url3 = self.getHrefFor(xbody, 'url3')
after_url4 = self.getHrefFor(xbody, 'url4')
self.assertTrue('/r/' in after_url0, 'URL0 should be shortened: %s' % after_url0)
self.assertTrue('/r/' in after_url1, 'URL1 should be shortened: %s' % after_url1)
self.assertTrue('/r/' in after_url2, 'URL2 should be shortened: %s' % after_url2)
self.assertTrue('/r/' in after_url3, 'URL3 should be shortened: %s' % after_url3)
self.assertEqual(after_url4, "mailto:test@odoo.com", 'mailto: has been converted')
short0 = self.shorturl_to_link(after_url0)
short1 = self.shorturl_to_link(after_url1)
short2 = self.shorturl_to_link(after_url2)
short3 = self.shorturl_to_link(after_url3)
self.assertTrue("https://www.odoo.tz/my/User" in short0.url, 'URL mismatch')
self.assertEqual(short1.url, "https://www.odoo.be", 'URL mismatch')
self.assertEqual(short2.url, "https://www.odoo.com", 'URL mismatch')
self.assertEqual(short3.url, "https://www.odoo.eu", 'URL mismatch')
_xbody = etree.fromstring(sent_messages[1].body)
_after_url0 = self.getHrefFor(_xbody, 'url0')
_after_url1 = self.getHrefFor(_xbody, 'url1')
_after_url2 = self.getHrefFor(_xbody, 'url2')
_after_url3 = self.getHrefFor(_xbody, 'url3')
_after_url4 = self.getHrefFor(_xbody, 'url4')
self.assertTrue('/r/' in _after_url0, 'URL0 should be shortened: %s' % _after_url0)
self.assertTrue('/r/' in _after_url1, 'URL1 should be shortened: %s' % _after_url1)
self.assertTrue('/r/' in _after_url2, 'URL2 should be shortened: %s' % _after_url2)
self.assertTrue('/r/' in _after_url3, 'URL3 should be shortened: %s' % _after_url3)
self.assertEqual(_after_url4, "mailto:test@odoo.com", 'mailto: has been converted')
_short0 = self.shorturl_to_link(_after_url0)
_short1 = self.shorturl_to_link(_after_url1)
_short2 = self.shorturl_to_link(_after_url2)
_short3 = self.shorturl_to_link(_after_url3)
self.assertTrue("https://www.odoo.tz/my/User" in _short0.url, 'URL mismatch')
self.assertEqual(_short1.url, "https://www.odoo.be", 'URL mismatch')
self.assertEqual(_short2.url, "https://www.odoo.com", 'URL mismatch')
self.assertEqual(_short3.url, "https://www.odoo.eu", 'URL mismatch')
self.assertNotEqual(short0.url, _short0.url)
self.assertEqual(short1.url, _short1.url)
self.assertEqual(short2.url, _short2.url)
self.assertEqual(short3.url, _short3.url)
+13 -1
View File
@@ -8,7 +8,7 @@ import math
from odoo import api, fields, models, _
from odoo.addons import decimal_precision as dp
from odoo.exceptions import UserError
from odoo.exceptions import AccessError, UserError
from odoo.tools import float_compare, float_round, DEFAULT_SERVER_DATETIME_FORMAT
class MrpProduction(models.Model):
@@ -33,6 +33,10 @@ class MrpProduction(models.Model):
location = self.env['stock.picking.type'].browse(self.env.context['default_picking_type_id']).default_location_src_id
if not location:
location = self.env.ref('stock.stock_location_stock', raise_if_not_found=False)
try:
location.check_access_rule('read')
except (AttributeError, AccessError):
location = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.company_id.id)], limit=1).lot_stock_id
return location and location.id or False
@api.model
@@ -42,6 +46,10 @@ class MrpProduction(models.Model):
location = self.env['stock.picking.type'].browse(self.env.context['default_picking_type_id']).default_location_dest_id
if not location:
location = self.env.ref('stock.stock_location_stock', raise_if_not_found=False)
try:
location.check_access_rule('read')
except (AttributeError, AccessError):
location = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.company_id.id)], limit=1).lot_stock_id
return location and location.id or False
name = fields.Char(
@@ -425,6 +433,10 @@ class MrpProduction(models.Model):
@api.onchange('picking_type_id')
def onchange_picking_type(self):
location = self.env.ref('stock.stock_location_stock')
try:
location.check_access_rule('read')
except (AttributeError, AccessError):
location = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.company_id.id)], limit=1).lot_stock_id
self.move_raw_ids.update({'picking_type_id': self.picking_type_id})
self.location_src_id = self.picking_type_id.default_location_src_id.id or location.id
self.location_dest_id = self.picking_type_id.default_location_dest_id.id or location.id
+13 -3
View File
@@ -2,7 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.exceptions import AccessError, UserError
from odoo.tools import float_compare
@@ -13,10 +13,20 @@ class MrpUnbuild(models.Model):
_order = 'id desc'
def _get_default_location_id(self):
return self.env.ref('stock.stock_location_stock', raise_if_not_found=False)
stock_location = self.env.ref('stock.stock_location_stock', raise_if_not_found=False)
try:
stock_location.check_access_rule('read')
return stock_location.id
except (AttributeError, AccessError):
return self.env['stock.warehouse'].search([('company_id', '=', self.env.user.company_id.id)], limit=1).lot_stock_id.id
def _get_default_location_dest_id(self):
return self.env.ref('stock.stock_location_stock', raise_if_not_found=False)
stock_location = self.env.ref('stock.stock_location_stock', raise_if_not_found=False)
try:
stock_location.check_access_rule('read')
return stock_location.id
except (AttributeError, AccessError):
return self.env['stock.warehouse'].search([('company_id', '=', self.env.user.company_id.id)], limit=1).lot_stock_id.id
name = fields.Char('Reference', copy=False, readonly=True, default=lambda x: _('New'))
product_id = fields.Many2one(
+2 -4
View File
@@ -45,8 +45,7 @@ class ProductTemplate(models.Model):
action['domain'] = [('state', '=', 'done'), '&', ('product_tmpl_id', 'in', self.ids)]
action['context'] = {
'graph_measure': 'product_uom_qty',
'search_default_confirmed': 0,
'time_ranges': {'field': 'date_finished', 'range': 'last_365_days'}
'time_ranges': {'field': 'date_planned_start', 'range': 'last_365_days'}
}
return action
@@ -133,8 +132,7 @@ class ProductProduct(models.Model):
action = self.env.ref('mrp.mrp_production_report').read()[0]
action['domain'] = [('state', '=', 'done'), '&', ('product_id', 'in', self.ids)]
action['context'] = {
'search_default_last_year_mo_order': 1,
'search_default_status': 1, 'search_default_scheduled_month': 1,
'time_ranges': {'field': 'date_planned_start', 'range': 'last_365_days'},
'graph_measure': 'product_uom_qty',
}
return action
+2 -2
View File
@@ -128,8 +128,8 @@
</group>
<group>
<field name="mo_id" domain="[('state','=','done')]"/>
<field name="location_id" groups="stock.group_stock_multi_locations"/>
<field name="location_dest_id" groups="stock.group_stock_multi_locations"/>
<field name="location_id" groups="stock.group_stock_multi_locations" domain="[('usage','=','internal')]"/>
<field name="location_dest_id" groups="stock.group_stock_multi_locations" domain="[('usage','=','internal')]"/>
<field name="has_tracking" invisible="1"/>
<field name="lot_id" attrs="{'invisible': [('has_tracking', '=', 'none')], 'required': [('has_tracking', '!=', 'none')]}" groups="stock.group_production_lot"/>
</group>
+1 -1
View File
@@ -171,7 +171,7 @@
<div class="col-6 o_kanban_primary_left">
<div class="btn-group" name="o_wo">
<t t-if="record.workorder_count.raw_value &gt; 0">
<button class="btn btn-primary" name="%(action_work_orders)d" type="action" context="{'search_default_ready': 1, 'search_default_progress': 1, 'search_default_pending': 1}">
<button class="btn btn-primary" name="%(action_work_orders)d" type="action" context="{'search_default_ready': 1, 'search_default_progress': 1}">
<span>WORK ORDERS</span>
</button>
</t>
+1
View File
@@ -18,6 +18,7 @@ _logger = logging.getLogger(__name__)
class PadCommon(models.AbstractModel):
_name = 'pad.common'
_description = 'Pad Common'
@api.model
def pad_is_configured(self):
+4 -1
View File
@@ -100,7 +100,10 @@ class PaymentProcessing(http.Controller):
class WebsitePayment(http.Controller):
@http.route(['/my/payment_method'], type='http', auth="user", website=True)
def payment_method(self, **kwargs):
acquirers = list(request.env['payment.acquirer'].search([('website_published', '=', True), ('registration_view_template_id', '!=', False), ('payment_flow', '=', 's2s')]))
acquirers = list(request.env['payment.acquirer'].search([
('website_published', '=', True), ('registration_view_template_id', '!=', False),
('payment_flow', '=', 's2s'), ('company_id', '=', request.env.user.company_id.id)
]))
partner = request.env.user.partner_id
payment_tokens = partner.payment_token_ids
payment_tokens |= partner.commercial_partner_id.sudo().payment_token_ids
@@ -34,7 +34,7 @@
<!-- TDE FIXME: was customize_show=True -->
<template id="pay_meth_link" inherit_id="portal.portal_layout">
<xpath expr="//div[hasclass('o_portal_my_details')]" position="inside">
<t t-if="request.env['payment.acquirer'].search([('website_published', '=', True), ('registration_view_template_id', '!=', False), ('payment_flow', '=', 's2s')])">
<t t-if="request.env['payment.acquirer'].search([('website_published', '=', True), ('registration_view_template_id', '!=', False), ('payment_flow', '=', 's2s'), ('company_id', '=', request.env.user.company_id.id)])">
<div class='manage_payment_method mt16'><a href="/my/payment_method">Manage your payment methods</a></div>
</t>
</xpath>
+6 -6
View File
@@ -583,21 +583,21 @@ class PosOrder(models.Model):
@api.onchange('statement_ids', 'lines')
def _onchange_amount_all(self):
amounts = {order_id: {'paid': 0, 'return': 0, 'untaxed': 0} for order_id in self.ids}
amounts = {order_id: {'paid': 0, 'return': 0, 'taxed': 0, 'taxes': 0} for order_id in self.ids}
for order in self.env['account.bank.statement.line'].read_group([('pos_statement_id', 'in', self.ids)], ['pos_statement_id', 'amount'], ['pos_statement_id']):
amounts[order['pos_statement_id'][0]]['paid'] = order['amount']
for order in self.env['account.bank.statement.line'].read_group(['&', ('pos_statement_id', 'in', self.ids), ('amount', '<', 0)], ['pos_statement_id', 'amount'], ['pos_statement_id']):
amounts[order['pos_statement_id'][0]]['return'] = order['amount']
for order in self.env['pos.order.line'].read_group([('order_id', 'in', self.ids)], ['order_id', 'price_subtotal'], ['order_id']):
amounts[order['order_id'][0]]['untaxed'] = order['price_subtotal']
for order in self.env['pos.order.line'].read_group([('order_id', 'in', self.ids)], ['order_id', 'price_subtotal', 'price_subtotal_incl'], ['order_id']):
amounts[order['order_id'][0]]['taxed'] = order['price_subtotal_incl']
amounts[order['order_id'][0]]['taxes'] = order['price_subtotal_incl'] - order['price_subtotal']
for order in self:
currency = order.pricelist_id.currency_id
order.amount_paid = amounts[order.id]['paid']
order.amount_return = amounts[order.id]['return']
order.amount_tax = currency.round(sum(self._amount_line_tax(line, order.fiscal_position_id) for line in order.lines))
amount_untaxed = currency.round(amounts[order.id]['untaxed'])
order.amount_total = order.amount_tax + amount_untaxed
order.amount_tax = currency.round(amounts[order.id]['taxes'])
order.amount_total = currency.round(amounts[order.id]['taxed'])
@api.onchange('partner_id')
def _onchange_partner_id(self):
+14 -6
View File
@@ -876,12 +876,20 @@ exports.PosModel = Backbone.Model.extend({
transfer.pipe(function(order_server_id){
// generate the pdf and download it
self.chrome.do_action('point_of_sale.pos_invoice_report',{additional_context:{
active_ids:order_server_id,
}}).done(function () {
invoiced.resolve();
done.resolve();
});
if (order_server_id.length) {
self.chrome.do_action('point_of_sale.pos_invoice_report',{additional_context:{
active_ids:order_server_id,
}}).done(function () {
invoiced.resolve();
done.resolve();
});
} else {
// The order has been pushed separately in batch when
// the connection came back.
// The user has to go to the backend to print the invoice
invoiced.reject({code:401, message:'Backend Invoice', data:{order: order}});
done.reject();
}
});
return done;
@@ -2092,6 +2092,17 @@ var PaymentScreenWidget = ScreenWidget.extend({
self.gui.show_screen('clientlist');
},
});
} else if (error.message === 'Backend Invoice') {
self.gui.show_popup('confirm',{
'title': _t('Please print the invoice from the backend'),
'body': _t('The order has been synchronized earlier. Please make the invoice from the backend for the order: ') + error.data.order.name,
confirm: function () {
this.gui.show_screen('receipt');
},
cancel: function () {
this.gui.show_screen('receipt');
},
});
} else if (error.code < 0) { // XmlHttpRequest Errors
self.gui.show_popup('error',{
'title': _t('The order could not be sent'),
+13
View File
@@ -159,6 +159,19 @@ li > p {
.container-fluid .container-fluid {
@extend %o-double-container-no-padding;
}
#wrap {
> .container, > .container-fluid {
// BS3 used to do this on all containers so that margins and floats are
// cleared inside containers. As lots of current odoo layouts may rely
// on this for some alignments, this is restored (at least for a while)
// here only for main containers of the frontend.
&::before, &::after {
content: "";
display: table;
clear: both;
}
}
}
[class^="col-lg-"] {
min-height: 24px;
}
+1 -1
View File
@@ -439,7 +439,7 @@
<t t-if='prev_record or next_record'>
<div class="record_pager btn-group" role="group">
<a role="button" t-att-class="'btn btn-link %s' % ('disabled' if not prev_record else '')" t-att-href="prev_record or '#'" ><i class="fa fa-chevron-left" role="img" aria-label="Previous" title="Previous"></i></a>
<a role="button" t-att-class="'btn btn-link %s' % ('disabled' if not next_record else '')" t-att-href="next_record or '#'" ><i class="fa fa-chevron-right" role="img" aria-label="Next" title="Previous"></i></a>
<a role="button" t-att-class="'btn btn-link %s' % ('disabled' if not next_record else '')" t-att-href="next_record or '#'" ><i class="fa fa-chevron-right" role="img" aria-label="Next" title="Next"></i></a>
</div>
</t>
</template>
+1
View File
@@ -9,6 +9,7 @@ from odoo import models, fields, api
class pos_cache(models.Model):
_name = 'pos.cache'
_description = 'Point of Sale Cache'
cache = fields.Binary(attachment=True)
product_domain = fields.Text(required=True)
@@ -1,75 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<template id="report_simple_label">
<div class="col-4" style="padding:0;">
<table class="table table-condensed" style="border-bottom: 0px solid white !important;width: 3in;">
<tr>
<th style="text-align: left;">
<strong t-field="product.display_name"/>
</th>
</tr>
<tr>
<td>
<strong>Price:</strong>
<strong t-field="product.lst_price" t-options="{'widget': 'monetary', 'display_currency': product.company_id.currency_id}"/>
</td>
</tr>
<t t-if="product.barcode">
<tr>
<td style="text-align: center; vertical-align: middle;" class="col-5">
<img alt="Barcode" t-if="len(product.barcode) == 13" t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('EAN13', product.barcode, 600, 150)" style="width:100%;height:20%;"/>
<img alt="Barcode" t-elif="len(product.barcode) == 8" t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('EAN8', product.barcode, 600, 150)" style="width:100%;height:20%;"/>
<img alt="Barcode" t-else="" t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('Code128', product.barcode, 600, 150)" style="width:100%;height:20%;"/>
<span t-field="product.barcode"/>
</td>
</tr>
</t>
</table>
</div>
</template>
<template id="report_productlabel">
<t t-call="web.basic_layout">
<div class="page">
<t t-foreach="docs" t-as="product">
<t t-call="product.report_simple_label">
<t t-set="product" t-value="product"/>
</t>
</t>
</div>
</t>
</template>
<template id="report_simple_barcode">
<div class="col-4" style="padding:0;">
<table class="table table-condensed" style="border-bottom: 0px solid white !important;width: 3in;">
<tr>
<th style="text-align: left;">
<strong t-field="product.display_name"/>
</th>
</tr>
<t t-if="product.barcode">
<tr>
<td style="text-align: center; vertical-align: middle;" class="col-5">
<img alt="Barcode" t-if="len(product.barcode) == 13" t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('EAN13', product.barcode, 600, 150)" style="width:100%;height:20%;"/>
<img alt="Barcode" t-elif="len(product.barcode) == 8" t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('EAN8', product.barcode, 600, 150)" style="width:100%;height:20%;"/>
<img alt="Barcode" t-else="" t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('Code128', product.barcode, 600, 150)" style="width:100%;height:20%;"/>
<span t-field="product.barcode"/>
</td>
</tr>
</t>
</table>
</div>
</template>
<template id="report_productbarcode">
<t t-call="web.basic_layout">
<div class="page">
<t t-foreach="docs" t-as="product">
<t t-call="product.report_simple_barcode">
<t t-set="product" t-value="product"/>
</t>
</t>
</div>
</t>
</template>
</data>
</odoo>
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<template id="report_simple_label">
<div style="width: 32%; display: inline-table; height:14rem;">
<table class="table table-bordered mb-0" style="border: 2px solid black;">
<tr>
<th class="table-active text-left" style="height: 4rem;">
<strong t-field="product.display_name"/>
</th>
</tr>
<tr>
<td style="height: 2rem">
<strong>Price:</strong>
<strong t-field="product.lst_price" t-options="{'widget': 'monetary', 'display_currency': product.company_id.currency_id}"/>
</td>
</tr>
<tr>
<td class="text-center align-middle" style="height: 6rem">
<t t-if="product.barcode">
<img alt="Barcode" t-if="len(product.barcode) == 13" t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('EAN13', product.barcode, 600, 150)" style="width:100%;height::4rem;"/>
<img alt="Barcode" t-elif="len(product.barcode) == 8" t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('EAN8', product.barcode, 600, 150)" style="width:100%;height::4rem;"/>
<img alt="Barcode" t-else="" t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('Code128', product.barcode, 600, 150)" style="width:100%;height::4rem;"/>
<span t-field="product.barcode"/>
</t>
<t t-else=""><span class="text-muted">No barcode available</span></t>
</td>
</tr>
</table>
</div>
</template>
<template id="report_productlabel">
<t t-call="web.basic_layout">
<div class="page">
<t t-foreach="docs" t-as="product">
<t t-call="product.report_simple_label">
<t t-set="product" t-value="product"/>
</t>
</t>
</div>
</t>
</template>
<template id="report_simple_barcode">
<div style="width: 32%; display: inline-table; height: 10rem;">
<table class="table table-bordered mb-0" style="border: 2px solid black;">
<tr>
<th class="table-active text-left" style="height: 4rem;">
<strong t-field="product.display_name"/>
</th>
</tr>
<tr>
<td class="text-center align-middle" style="height: 6rem;">
<t t-if="product.barcode">
<img alt="Barcode" t-if="len(product.barcode) == 13" t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('EAN13', product.barcode, 600, 150)" style="width:100%;height:4rem;"/>
<img alt="Barcode" t-elif="len(product.barcode) == 8" t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('EAN8', product.barcode, 600, 150)" style="width:100%;height:4rem;"/>
<img alt="Barcode" t-else="" t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('Code128', product.barcode, 600, 150)" style="width:100%;height:4rem"/>
<span t-field="product.barcode"/>
</t>
<t t-else=""><span class="text-muted">No barcode available</span></t>
</td>
</tr>
</table>
</div>
</template>
<template id="report_productbarcode">
<t t-call="web.basic_layout">
<div class="page">
<t t-foreach="docs" t-as="product">
<t t-call="product.report_simple_barcode">
<t t-set="product" t-value="product"/>
</t>
</t>
</div>
</t>
</template>
</data>
</odoo>
+5 -1
View File
@@ -8,7 +8,7 @@ class MailMessage(models.Model):
_inherit = 'mail.message'
rating_ids = fields.One2many('rating.rating', 'message_id', string='Related ratings')
rating_value = fields.Float("Rating Value", compute='_compute_rating_value', store=False)
rating_value = fields.Float("Rating Value", compute='_compute_rating_value', store=False, search='_search_rating_value')
@api.multi
@api.depends('rating_ids', 'rating_ids.rating')
@@ -17,3 +17,7 @@ class MailMessage(models.Model):
mapping = dict((r.message_id.id, r.rating) for r in ratings)
for message in self:
message.rating_value = mapping.get(message.id, 0.0)
@api.multi
def _search_rating_value(self, operator, operand):
return [('rating_ids.rating', operator, operand)]
+2 -1
View File
@@ -196,7 +196,8 @@ class ResourceCalendar(models.Model):
hour_count = 0.0
for attendance in attendances:
hour_count += attendance.hour_to - attendance.hour_from
self.hours_per_day = float_round(hour_count / float(len(set(attendances.mapped('dayofweek')))), precision_digits=2)
if attendances:
self.hours_per_day = float_round(hour_count / float(len(set(attendances.mapped('dayofweek')))), precision_digits=2)
# --------------------------------------------------
# Computation API
+1 -1
View File
@@ -103,7 +103,7 @@
</div>
</field>
<field name="report_template" ref="action_report_saleorder"/>
<field name="report_name">${(object.name or '').replace('/','_')}${object.state == 'draft' and '_draft' or ''}</field>
<field name="report_name">${(object.name or '').replace('/','_')}</field>
<field name="lang">${object.partner_id.lang}</field>
<field name="user_signature" eval="False"/>
<field name="auto_delete" eval="True"/>
+3 -2
View File
@@ -19,7 +19,7 @@ class AccountAnalyticLine(models.Model):
@api.model
def create(self, values):
result = super(AccountAnalyticLine, self).create(values)
if 'so_line' not in values and not result.so_line and result.product_id and result.product_id.expense_policy != 'no' and result.amount <= 0: # allow to force a False value for so_line
if 'so_line' not in values and not result.so_line and result.product_id and result.product_id.expense_policy not in [False, 'no'] and result.amount <= 0: # allow to force a False value for so_line
result.sudo()._sale_determine_order_line()
return result
@@ -28,7 +28,8 @@ class AccountAnalyticLine(models.Model):
result = super(AccountAnalyticLine, self).write(values)
if 'so_line' not in values: # allow to force a False value for so_line
# only take the AAL from expense or vendor bill, meaning having a negative amount
self.sudo().filtered(lambda aal: not aal.so_line and aal.product_id and aal.product_id.expense_policy != 'no' and aal.amount <= 0)._sale_determine_order_line()
self.sudo().filtered(lambda aal: not aal.so_line and aal.product_id and aal.product_id.expense_policy not in [False, 'no'] and aal.amount <= 0)._sale_determine_order_line()
return result
# ----------------------------------------------------------
# Vendor Bill / Expense : determine the Sale Order to reinvoice
+9 -7
View File
@@ -47,8 +47,8 @@ class SaleOrder(models.Model):
amount_untaxed += line.price_subtotal
amount_tax += line.price_tax
order.update({
'amount_untaxed': order.pricelist_id.currency_id.round(amount_untaxed),
'amount_tax': order.pricelist_id.currency_id.round(amount_tax),
'amount_untaxed': amount_untaxed,
'amount_tax': amount_tax,
'amount_total': amount_untaxed + amount_tax,
})
@@ -66,12 +66,14 @@ class SaleOrder(models.Model):
for possible refunds created directly from existing invoices. This is necessary since such a
refund is not directly linked to the SO.
"""
# Ignore the status of the deposit product
deposit_product_id = self.env['sale.advance.payment.inv']._default_product_id()
line_invoice_status_all = [(d['order_id'][0], d['invoice_status']) for d in self.env['sale.order.line'].read_group([('order_id', 'in', self.ids), ('product_id', '!=', deposit_product_id.id)], ['order_id', 'invoice_status'], ['order_id', 'invoice_status'], lazy=False)]
for order in self:
invoice_ids = order.order_line.mapped('invoice_lines.invoice_id').filtered(lambda r: r.type in ['out_invoice', 'out_refund'])
# Ignore the status of the deposit product
deposit_product_id = self.env['sale.advance.payment.inv']._default_product_id()
line_invoice_status = [line.invoice_status for line in order.order_line if line.product_id != deposit_product_id]
line_invoice_status = [d[1] for d in line_invoice_status_all if d[0] == order.id]
if order.state not in ('sale', 'done'):
invoice_status = 'no'
@@ -654,7 +656,7 @@ class SaleOrder(models.Model):
# create an analytic account if at least an expense product
for order in self:
if any([expense_policy != 'no' for expense_policy in order.order_line.mapped('product_id.expense_policy')]):
if any([expense_policy not in [False, 'no'] for expense_policy in order.order_line.mapped('product_id.expense_policy')]):
if not order.analytic_account_id:
order._create_analytic_account()
@@ -1018,7 +1020,7 @@ class SaleOrderLine(models.Model):
msg = _("Extra line with %s ") % (line.product_id.display_name,)
line.order_id.message_post(body=msg)
# create an analytic account if at least an expense product
if line.product_id.expense_policy != 'no' and not self.order_id.analytic_account_id:
if line.product_id.expense_policy not in [False, 'no'] and not self.order_id.analytic_account_id:
self.order_id._create_analytic_account()
return line
+6 -1
View File
@@ -6,7 +6,6 @@
name="Sales"
web_icon="sale,static/description/icon.png"
active="False"
groups="sales_team.group_sale_salesman,sales_team.group_sale_salesman_all_leads,sales_team.group_sale_manager"
sequence="7"/>
<menuitem id="sale_order_menu"
@@ -17,6 +16,7 @@
<menuitem id="report_sales_team"
name="Sales Teams"
parent="sale_order_menu"
groups="sales_team.group_sale_manager"
action="sales_team.crm_team_salesteams_act"
sequence="3"/>
@@ -314,6 +314,7 @@
<field name="qty_delivered_manual" invisible="1"/>
<field name="qty_delivered_method" invisible="1"/>
<field name="price_total" invisible="1"/>
<field name="price_tax" invisible="1"/>
<field name="price_subtotal" invisible="1"/>
<label for="product_uom_qty" string="Ordered Quantity"/>
<div>
@@ -322,6 +323,7 @@
name="product_uom_qty" class="oe_inline"/>
<field
name="product_uom"
force_save="1"
groups="uom.group_uom"
class="oe_inline oe_no_button"
attrs="{
@@ -442,6 +444,7 @@
<field name="qty_to_invoice" invisible="1"/>
<field
name="product_uom"
force_save="1"
attrs="{
'readonly': [('state', 'in', ('sale','done', 'cancel'))],
'required': [('display_type', '=', False)],
@@ -473,6 +476,7 @@
<field name="invoice_status" invisible="1"/>
<field name="customer_lead" invisible="1"/>
<field name="currency_id" invisible="1"/>
<field name="price_tax" invisible="1"/>
</tree>
<kanban class="o_kanban_mobile">
<field name="name"/>
@@ -480,6 +484,7 @@
<field name="product_uom_qty"/>
<field name="product_uom" groups="uom.group_uom"/>
<field name="price_subtotal"/>
<field name="price_tax" invisible="1"/>
<field name="price_total" invisible="1"/>
<field name="price_unit"/>
<field name="display_type"/>
+1 -1
View File
@@ -10,7 +10,7 @@ class AccountAnalyticLine(models.Model):
@api.multi
def _sale_determine_order(self):
mapping = super(AccountAnalyticLine, self)._sale_determine_order()
for analytic_line in self.sudo().filtered(lambda aal: not aal.so_line and aal.product_id and aal.product_id.expense_policy != 'no'):
for analytic_line in self.sudo().filtered(lambda aal: not aal.so_line and aal.product_id and aal.product_id.expense_policy not in [False, 'no']):
if analytic_line.move_id.expense_id.sale_order_id:
mapping[analytic_line.id] = analytic_line.move_id.expense_id.sale_order_id
return mapping
+1 -1
View File
@@ -51,7 +51,7 @@ class SaleOrder(models.Model):
def _action_confirm(self):
""" On SO confirmation, some lines should generate a task or a project. """
result = super(SaleOrder, self)._action_confirm()
self.mapped('order_line').sudo()._timesheet_service_generation()
self.mapped('order_line').sudo().with_context(default_company_id=self.company_id.id)._timesheet_service_generation()
return result
@api.multi
+1 -1
View File
@@ -1095,7 +1095,7 @@ class StockMove(models.Model):
# this operation could trigger a merge of moves.
for move in moves:
if move.quantity_done <= 0:
if float_compare(move.product_uom_qty, 0.0, precision_rounding=move.product_uom.rounding) == 0:
if float_compare(move.product_uom_qty, 0.0, precision_rounding=move.product_uom.rounding) == 0 or cancel_backorder:
move._action_cancel()
# Create extra moves where necessary
+1 -1
View File
@@ -146,7 +146,7 @@ class StockMoveLine(models.Model):
"""
res = {}
if self.qty_done and self.product_id.tracking == 'serial':
if float_compare(self.qty_done, 1.0, precision_rounding=self.move_id.product_id.uom_id.rounding) != 0:
if float_compare(self.qty_done, 1.0, precision_rounding=self.product_id.uom_id.rounding) != 0:
message = _('You can only process 1.0 %s of products with unique serial number.') % self.product_id.uom_id.name
res['warning'] = {'title': _('Warning'), 'message': message}
return res
+2 -1
View File
@@ -131,7 +131,8 @@ class StockScrap(models.Model):
self.package_id,
self.owner_id,
strict=True).mapped('quantity'))
if float_compare(available_qty, self.scrap_qty, precision_digits=precision) >= 0:
scrap_qty = self.product_uom_id._compute_quantity(self.scrap_qty, self.product_id.uom_id)
if float_compare(available_qty, scrap_qty, precision_digits=precision) >= 0:
return self.do_scrap()
else:
return {
+1 -1
View File
@@ -971,7 +971,7 @@ class Orderpoint(models.Model):
# These days will be substracted when creating the PO
days += self.product_id._select_seller(
quantity=product_qty,
date=start_date,
date=fields.Date.context_today(self,start_date),
uom_id=self.product_uom).delay or 0.0
date_planned = start_date + relativedelta.relativedelta(days=days)
return date_planned.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
@@ -155,7 +155,7 @@
</tbody>
</table>
<t t-set="no_reserved_product" t-value="o.move_lines.filtered(lambda x: x.product_uom_qty != x.reserved_availability and x.move_line_ids and x.state!='done')"/>
<p t-if="o.state in ['draft', 'waiting', 'confirmed'] or no_reserved_product" class="fa fa-exclamation-triangle">
<p t-if="o.state in ['draft', 'waiting', 'confirmed'] or no_reserved_product"><i class="fa fa-exclamation-triangle" />
All products could not be reserved. Click on the "Check Availability" button to try to reserve products
</p>
<p t-field="o.note"/>
+18
View File
@@ -3569,6 +3569,24 @@ class StockMove(TransactionCase):
self.assertEqual(scrap.state, 'done')
self.assertEqual(move1.reserved_availability, 0.25)
def test_scrap_6(self):
""" Check that scrap correctly handle UoM. """
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1)
scrap = self.env['stock.scrap'].create({
'product_id': self.product1.id,
'product_uom_id': self.uom_dozen.id,
'scrap_qty': 1,
})
warning_message = scrap.action_validate()
self.assertEqual(warning_message.get('res_model', 'Wrong Model'), 'stock.warn.insufficient.qty.scrap')
insufficient_qty_wizard = self.env['stock.warn.insufficient.qty.scrap'].create({
'product_id': self.product1.id,
'location_id': self.stock_location.id,
'scrap_id': scrap.id
})
insufficient_qty_wizard.action_done()
self.assertEqual(self.env['stock.quant']._gather(self.product1, self.stock_location).quantity, -11)
def test_in_date_1(self):
""" Check that moving a tracked quant keeps the incoming date.
"""
+49
View File
@@ -830,6 +830,55 @@ class TestSinglePicking(TestStockCommon):
backorder = self.env['stock.picking'].search([('backorder_id', '=', delivery_order.id)])
self.assertEqual(backorder.state, 'confirmed')
def test_backorder_4(self):
""" Check the good behavior if no backorder are created
for a picking with a missing product.
"""
delivery_order = self.env['stock.picking'].create({
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 2,
'product_uom': self.productA.uom_id.id,
'picking_id': delivery_order.id,
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
})
self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productB.id,
'product_uom_qty': 2,
'product_uom': self.productB.uom_id.id,
'picking_id': delivery_order.id,
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
})
# Update available quantities for each products
pack_location = self.env['stock.location'].browse(self.pack_location)
self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 2)
self.env['stock.quant']._update_available_quantity(self.productB, pack_location, 2)
delivery_order.action_confirm()
delivery_order.action_assign()
self.assertEqual(delivery_order.state, 'assigned')
# Process only one product without creating a backorder
delivery_order.move_lines[0].move_line_ids[0].qty_done = 2
backorder_wizard = self.env['stock.backorder.confirmation'].create({'pick_ids': [(4, delivery_order.id)]})
backorder_wizard.process_cancel_backorder()
# No backorder should be created and the move corresponding to the missing product should be cancelled
backorder = self.env['stock.picking'].search([('backorder_id', '=', delivery_order.id)])
self.assertFalse(backorder)
self.assertEquals(delivery_order.state, 'done')
self.assertEquals(delivery_order.move_lines[1].state, 'cancel')
def test_extra_move_1(self):
""" Check the good behavior of creating an extra move in a delivery order. This usecase
simulates the delivery of 2 item while the initial stock move had to move 1 and there's
+1 -1
View File
@@ -221,7 +221,7 @@
<button name="%(act_stock_return_picking)d" string="Return" attrs="{'invisible': ['|', ('state', '!=', 'done'), ('is_locked', '=', False)]}" type="action" groups="base.group_user"/>
<button name="action_cancel" attrs="{'invisible': ['|', ('state', 'not in', ('assigned', 'confirmed', 'partially_available', 'draft', 'waiting')), ('is_locked', '=', False)]}" string="Cancel" groups="base.group_user" type="object"/>
<field name="picking_type_code" invisible="1"/>
<button name="do_unreserve" string="Unreserve" groups="base.group_user" type="object" attrs="{'invisible': ['|', '|', '|', ('picking_type_code', '=', 'incoming'), ('state', 'not in', ('assigned', 'partially_available')), ('is_locked', '=', False), ('immediate_transfer', '=', True)]}"/>
<button name="do_unreserve" string="Unreserve" groups="base.group_user" type="object" attrs="{'invisible': ['|', '|', '|', '|', ('picking_type_code', '=', 'incoming'), ('immediate_transfer', '=', True), ('is_locked', '=', False), '&amp;', ('state', 'not in', ('assigned', 'partially_available')), ('move_type', '!=', 'one'), '&amp;', ('state', 'not in', ('assigned', 'partially_available', 'confirmed')), ('move_type', '=', 'one')]}"/>
<field name="show_operations" invisible="1" readonly="1"/>
<field name="move_line_exist" invisible="1"/>
<field name="has_packages" invisible="1"/>
+34 -21
View File
@@ -138,23 +138,38 @@ class ProductProduct(models.Model):
StockMove = self.env['stock.move']
to_date = self.env.context.get('to_date')
self.env['account.move.line'].check_access_rights('read')
fifo_automated_values = {}
query = """SELECT aml.product_id, aml.account_id, sum(aml.debit) - sum(aml.credit), sum(quantity), array_agg(aml.id)
FROM account_move_line AS aml
WHERE aml.product_id IS NOT NULL AND aml.company_id=%%s %s
GROUP BY aml.product_id, aml.account_id"""
params = (self.env.user.company_id.id,)
if to_date:
query = query % ('AND aml.date <= %s',)
params = params + (to_date,)
else:
query = query % ('',)
self.env.cr.execute(query, params=params)
real_time_product_ids = [product.id for product in self if product.product_tmpl_id.valuation == 'real_time']
if real_time_product_ids:
self.env['account.move.line'].check_access_rights('read')
fifo_automated_values = {}
query = """SELECT aml.product_id, aml.account_id, sum(aml.debit) - sum(aml.credit), sum(quantity), array_agg(aml.id)
FROM account_move_line AS aml
WHERE aml.product_id IN %%s AND aml.company_id=%%s %s
GROUP BY aml.product_id, aml.account_id"""
params = (tuple(real_time_product_ids), self.env.user.company_id.id)
if to_date:
query = query % ('AND aml.date <= %s',)
params = params + (to_date,)
else:
query = query % ('',)
self.env.cr.execute(query, params=params)
res = self.env.cr.fetchall()
for row in res:
fifo_automated_values[(row[0], row[1])] = (row[2], row[3], list(row[4]))
res = self.env.cr.fetchall()
for row in res:
fifo_automated_values[(row[0], row[1])] = (row[2], row[3], list(row[4]))
product_values = {product: 0 for product in self}
product_move_ids = {product: [] for product in self}
if to_date:
domain = [('product_id', 'in', self.ids), ('date', '<=', to_date)] + StockMove._get_all_base_domain()
for move in StockMove.search(domain).with_context(prefetch_fields=False):
product_values[move.product_id] += move.value
product_move_ids[move.product_id].append(move.id)
else:
domain = [('product_id', 'in', self.ids)] + StockMove._get_all_base_domain()
for move in StockMove.search(domain).with_context(prefetch_fields=False):
product_values[move.product_id] += move.remaining_value
product_move_ids[move.product_id].append(move.id)
for product in self:
if product.cost_method in ['standard', 'average']:
@@ -170,11 +185,9 @@ class ProductProduct(models.Model):
elif product.cost_method == 'fifo':
if to_date:
if product.product_tmpl_id.valuation == 'manual_periodic':
domain = [('product_id', '=', product.id), ('date', '<=', to_date)] + StockMove._get_all_base_domain()
moves = StockMove.search(domain)
product.stock_value = sum(moves.mapped('value'))
product.stock_value = product_values[product]
product.qty_at_date = product.with_context(company_owned=True, owner_id=False).qty_available
product.stock_fifo_manual_move_ids = StockMove.browse(moves.ids)
product.stock_fifo_manual_move_ids = StockMove.browse(product_move_ids[product])
elif product.product_tmpl_id.valuation == 'real_time':
valuation_account_id = product.categ_id.property_stock_valuation_account_id.id
value, quantity, aml_ids = fifo_automated_values.get((product.id, valuation_account_id)) or (0, 0, [])
@@ -182,7 +195,7 @@ class ProductProduct(models.Model):
product.qty_at_date = quantity
product.stock_fifo_real_time_aml_ids = self.env['account.move.line'].browse(aml_ids)
else:
product.stock_value, moves = product._sum_remaining_values()
product.stock_value, moves = product_values[product], StockMove.browse(product_move_ids[product])
product.qty_at_date = product.with_context(company_owned=True, owner_id=False).qty_available
if product.product_tmpl_id.valuation == 'manual_periodic':
product.stock_fifo_manual_move_ids = moves
+3 -6
View File
@@ -387,7 +387,7 @@ class StockMove(models.Model):
product_tot_qty_available = move.product_id.qty_available + tmpl_dict[move.product_id.id]
rounding = move.product_id.uom_id.rounding
qty_done = 0.0
qty_done = move.product_uom._compute_quantity(move.quantity_done, move.product_id.uom_id)
if float_is_zero(product_tot_qty_available, precision_rounding=rounding):
new_std_price = move._get_price_unit()
elif float_is_zero(product_tot_qty_available + move.product_qty, precision_rounding=rounding) or \
@@ -396,7 +396,6 @@ class StockMove(models.Model):
else:
# Get the standard price
amount_unit = std_price_update.get((move.company_id.id, move.product_id.id)) or move.product_id.standard_price
qty_done = move.product_uom._compute_quantity(move.quantity_done, move.product_id.uom_id)
qty = forced_qty or qty_done
new_std_price = ((amount_unit * product_tot_qty_available) + (move._get_price_unit() * qty)) / (product_tot_qty_available + qty_done)
@@ -483,10 +482,8 @@ class StockMove(models.Model):
fifo_valued_products = self.env['product.product']
fifo_valued_categories = self.env['product.category'].search([('property_cost_method', '=', 'fifo')])
fifo_valued_products |= self.env['product.product'].search([('categ_id', 'child_of', fifo_valued_categories.ids)])
moves_to_vacuum = self.env['stock.move']
for product in fifo_valued_products:
moves_to_vacuum |= self.search(
[('product_id', '=', product.id), ('remaining_qty', '<', 0)] + self._get_all_base_domain())
moves_to_vacuum = self.search(
[('product_id', 'in', fifo_valued_products.ids), ('remaining_qty', '<', 0)] + self._get_all_base_domain())
moves_to_vacuum._fifo_vacuum()
@api.multi
@@ -2587,6 +2587,40 @@ class TestStockValuation(TransactionCase):
self.assertAlmostEqual(self.product1.qty_at_date, 0.0)
self.assertAlmostEqual(self.product1.stock_value, 0.0)
def test_average_perpetual_6(self):
self.product1.product_tmpl_id.cost_method = 'average'
move1 = self.env['stock.move'].create({
'name': 'Receive 1 unit at 10',
'location_id': self.supplier_location.id,
'location_dest_id': self.stock_location.id,
'product_id': self.product1.id,
'product_uom': self.uom_unit.id,
'product_uom_qty': 1.0,
'price_unit': 10,
})
move1._action_confirm()
move1._action_assign()
move1.move_line_ids.qty_done = 1.0
move2 = self.env['stock.move'].create({
'name': 'Receive 1 units at 5',
'location_id': self.supplier_location.id,
'location_dest_id': self.stock_location.id,
'product_id': self.product1.id,
'product_uom': self.uom_unit.id,
'product_uom_qty': 1.0,
'price_unit': 5,
})
move2._action_confirm()
move2._action_assign()
move2.move_line_ids.qty_done = 1.0
# Receive both at the same time
(move1 | move2)._action_done()
self.assertAlmostEqual(self.product1.standard_price, 7.5)
def test_average_negative_1(self):
""" Test edit in the past. Receive 10, send 20, edit the second move to only send 10.
"""
@@ -307,6 +307,9 @@ class AdjustmentLines(models.Model):
return False
accounts = self.product_id.product_tmpl_id.get_product_accounts()
debit_account_id = accounts.get('stock_valuation') and accounts['stock_valuation'].id or False
# If the stock move is dropshipped move we need to get the cost account instead the stock valuation account
if self.move_id._is_dropshipped():
debit_account_id = accounts.get('expense') and accounts['expense'].id or False
already_out_account_id = accounts['stock_output'].id
credit_account_id = self.cost_line_id.account_id.id or cost_product.property_account_expense_id.id or cost_product.categ_id.property_account_expense_categ_id.id
+9 -1
View File
@@ -155,7 +155,15 @@ class TestChannelFeatures(common.BaseFunctionalTest, common.MockEmails):
""" Posting a message on a mailing list should send one email to all recipients """
self.env['ir.config_parameter'].set_param('mail.catchall.domain', 'schlouby.fr')
self.test_channel.write({'email_send': True})
self._join_channel(self.test_channel, self.user_employee.partner_id | self.test_partner)
# Subscribe an user without email. We shouldn't try to send email to them.
nomail = self.env['res.users'].create({
"login": "nomail",
"name": "No Mail",
"email": False,
"notification_type": "email",
})
self._join_channel(self.test_channel, self.user_employee.partner_id | self.test_partner | nomail.partner_id)
self.test_channel.message_post(body="Test", message_type='comment', subtype='mt_comment')
self.assertEqual(len(self._mails), 1)

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