[MERGE] forward port branch saas-12.2 up to fcb972db9d
@@ -11,6 +11,7 @@
|
||||
# Marc Tormo i Bochaca <mtbochaca@gmail.com>, 2019
|
||||
# RGB Consulting <odoo@rgbconsulting.com>, 2019
|
||||
# Quim - eccit <quim@eccit.com>, 2019
|
||||
# Sabina Batlle <sbb@odoo.com>, 2019
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@@ -18,7 +19,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: Quim - eccit <quim@eccit.com>, 2019\n"
|
||||
"Last-Translator: Sabina Batlle <sbb@odoo.com>, 2019\n"
|
||||
"Language-Team: Catalan (https://www.transifex.com/odoo/teams/41243/ca/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -216,7 +217,7 @@ msgstr ""
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.portal_my_home_menu_invoice
|
||||
msgid "<em>Draft Invoice</em>"
|
||||
msgstr ""
|
||||
msgstr "Factura esborrany"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.account_move_line_view_kanban
|
||||
@@ -12070,13 +12071,6 @@ msgstr ""
|
||||
msgid "have been reconciled automatically."
|
||||
msgstr ""
|
||||
|
||||
#. module: account
|
||||
#. openerp-web
|
||||
#: code:addons/account/static/src/xml/account_reconciliation.xml:44
|
||||
#, python-format
|
||||
msgid "o_manual_statement"
|
||||
msgstr ""
|
||||
|
||||
#. module: account
|
||||
#: selection:account.payment.term.line,option:0
|
||||
msgid "of the current month"
|
||||
|
||||
@@ -70,9 +70,9 @@
|
||||
# Miquel Torner <miq@odoo.com>, 2019
|
||||
# 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
|
||||
# Gabriel Umana <gabriel.umana@delfixcr.com>, 2019
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@@ -80,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: Edilianny Sánchez <esanchez@vauxoo.com>, 2019\n"
|
||||
"Last-Translator: Gabriel Umana <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"
|
||||
@@ -5107,7 +5107,7 @@ msgstr "Agrupar líneas de factura"
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_register_payments__group_invoices
|
||||
msgid "Group Invoices"
|
||||
msgstr ""
|
||||
msgstr "Grupo de facturas"
|
||||
|
||||
#. module: account
|
||||
#: selection:account.tax,amount_type:0
|
||||
@@ -12358,13 +12358,6 @@ msgstr ""
|
||||
msgid "have been reconciled automatically."
|
||||
msgstr ""
|
||||
|
||||
#. module: account
|
||||
#. openerp-web
|
||||
#: code:addons/account/static/src/xml/account_reconciliation.xml:44
|
||||
#, python-format
|
||||
msgid "o_manual_statement"
|
||||
msgstr "o_manual_statement"
|
||||
|
||||
#. module: account
|
||||
#: selection:account.payment.term.line,option:0
|
||||
msgid "of the current month"
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
# Fabri Yohann <psn@fabri.pw>, 2017
|
||||
# FR R4 <e2ffr1111@hotmail.com>, 2017
|
||||
# Matillon Samantha <smatillon@gmail.com>, 2017
|
||||
# Moka Tourisme <hello@mokatourisme.fr>, 2018
|
||||
# Olivier Dony <odo@odoo.com>, 2018
|
||||
# Hubert TETARD <htetard@apik-conseils.com>, 2018
|
||||
# Nancy Bolognesi <nb@microcom.ca>, 2018
|
||||
@@ -79,6 +78,8 @@
|
||||
# Alain van de Werve <avw@openerp.com>, 2019
|
||||
# Khalyomede Khalyomede <khalyomede@gmail.com>, 2019
|
||||
# Fabien Bourgeois <fabien@yaltik.com>, 2019
|
||||
# Phil Anth <thep3r3x@gmail.com>, 2019
|
||||
# Moka Tourisme <hello@mokatourisme.fr>, 2019
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@@ -86,7 +87,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: Fabien Bourgeois <fabien@yaltik.com>, 2019\n"
|
||||
"Last-Translator: Moka Tourisme <hello@mokatourisme.fr>, 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"
|
||||
@@ -681,7 +682,7 @@ msgstr ""
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.portal_invoice_page
|
||||
msgid "<strong class=\"text-muted\">Your Contact</strong>"
|
||||
msgstr ""
|
||||
msgstr "<strong class=\"text-muted\">Votre contact</strong>"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.report_invoice_document_with_payments
|
||||
@@ -4909,6 +4910,8 @@ msgstr "Pour être en pourcentage, saisir une valeur entre 0 et 100."
|
||||
msgid ""
|
||||
"Forbidden unit price, account and quantity on non-accountable invoice line"
|
||||
msgstr ""
|
||||
"Prix unitaire, compte et quantité interdits sur la ligne de facture non "
|
||||
"comptable"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_reconcile_model__force_second_tax_included
|
||||
@@ -12422,13 +12425,6 @@ msgstr ""
|
||||
msgid "have been reconciled automatically."
|
||||
msgstr ""
|
||||
|
||||
#. module: account
|
||||
#. openerp-web
|
||||
#: code:addons/account/static/src/xml/account_reconciliation.xml:44
|
||||
#, python-format
|
||||
msgid "o_manual_statement"
|
||||
msgstr "o_manual_statement"
|
||||
|
||||
#. module: account
|
||||
#: selection:account.payment.term.line,option:0
|
||||
msgid "of the current month"
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
# picibucor <picibucor@gmail.com>, 2017
|
||||
# Tibor Kőnig <konig.tibor@evitalit.hu>, 2019
|
||||
# krnkris, 2019
|
||||
# gezza <geza.nagy@oregional.hu>, 2019
|
||||
# Kovács Tibor <kovika@gmail.com>, 2019
|
||||
# Martin Trigaux, 2019
|
||||
# gezza <geza.nagy@oregional.hu>, 2019
|
||||
# Ákos Nagy <akos.nagy@oregional.hu>, 2019
|
||||
#
|
||||
msgid ""
|
||||
@@ -4044,7 +4044,7 @@ msgstr ""
|
||||
#. module: account
|
||||
#: model:ir.model,name:account.model_digest_digest
|
||||
msgid "Digest"
|
||||
msgstr ""
|
||||
msgstr "Kivonat"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_res_config_settings__group_products_in_bills
|
||||
@@ -4152,7 +4152,7 @@ msgstr "Feltüntetve a számlákon"
|
||||
#: code:addons/account/models/digest.py:16
|
||||
#, python-format
|
||||
msgid "Do not have access, skip this data for user's digest email"
|
||||
msgstr ""
|
||||
msgstr "Nincs hozzáférés, adat kihagyása a felhasználó kivonat e-mailjéből"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_invoice_send__auto_delete_message
|
||||
@@ -6332,7 +6332,7 @@ msgstr ""
|
||||
#: model_terms:ir.ui.view,arch_db:account.view_account_invoice_filter
|
||||
#: model_terms:ir.ui.view,arch_db:account.view_account_payment_search
|
||||
msgid "Late Activities"
|
||||
msgstr ""
|
||||
msgstr "Késő tevékenységek"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_account__last_time_entries_checked
|
||||
@@ -6344,7 +6344,7 @@ msgstr "Legutóbbi Számlák & Fizetések könyvelői párosításának dátuma"
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice_send__layout
|
||||
msgid "Layout"
|
||||
msgstr ""
|
||||
msgstr "Elrendezés"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.view_account_position_form
|
||||
@@ -9344,6 +9344,10 @@ msgid ""
|
||||
"Today: Activity date is today\n"
|
||||
"Planned: Future activities."
|
||||
msgstr ""
|
||||
"Tevékenységeken alapuló állapot\n"
|
||||
"Lejárt: Az esedékesség dátuma már elmúlt\n"
|
||||
"Ma: A tevékenység dátuma ma van\n"
|
||||
"Tervezett: Jövőbeli tevékenységek."
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.onboarding_bank_account_step
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
# Batzul B <batzul.active@gmail.com>, 2018
|
||||
# nasaaskii <nasaaskii@gmail.com>, 2019
|
||||
# Chinzorita <chinzorig.o@asterisk-tech.mn>, 2019
|
||||
# Baskhuu Lodoikhuu <baskhuujacara@gmail.com>, 2019
|
||||
# Otgonbayar.A <gobi.mn@gmail.com>, 2019
|
||||
# Ganbaatar Buriad <Ganbaatar@asterisk-tech.mn>, 2019
|
||||
# Onii Onii <onii0223@yahoo.com>, 2019
|
||||
# Martin Trigaux, 2019
|
||||
# Khishigbat Ganbold <khishigbat@asterisk-tech.mn>, 2019
|
||||
# Baskhuu Lodoikhuu <baskhuujacara@gmail.com>, 2019
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@@ -25,7 +25,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: Khishigbat Ganbold <khishigbat@asterisk-tech.mn>, 2019\n"
|
||||
"Last-Translator: Baskhuu Lodoikhuu <baskhuujacara@gmail.com>, 2019\n"
|
||||
"Language-Team: Mongolian (https://www.transifex.com/odoo/teams/41243/mn/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -1116,7 +1116,7 @@ msgstr "Санхүү"
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.view_account_journal_form
|
||||
msgid "Accounting App Options"
|
||||
msgstr ""
|
||||
msgstr "Санхүү бүртгэлийн сонголтууд"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_bank_statement__accounting_date
|
||||
@@ -1139,7 +1139,7 @@ msgstr "Санхүүгийн бичилт"
|
||||
#. module: account
|
||||
#: model:ir.actions.act_window,name:account.open_account_journal_dashboard_kanban
|
||||
msgid "Accounting Overview"
|
||||
msgstr ""
|
||||
msgstr "Санхүүгийн тойм"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.view_partner_property_form
|
||||
@@ -2318,7 +2318,7 @@ msgstr "Төлөх тооцоо"
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.account_journal_dashboard_kanban_view
|
||||
msgid "Bills to Validate"
|
||||
msgstr ""
|
||||
msgstr "Батлах нэхэмжлэлүүд"
|
||||
|
||||
#. module: account
|
||||
#: code:addons/account/models/account_journal_dashboard.py:178
|
||||
@@ -2344,7 +2344,7 @@ msgstr "Байгаа улсуудыг харах"
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_res_config_settings__module_account_budget
|
||||
msgid "Budget Management"
|
||||
msgstr ""
|
||||
msgstr "Төсвийн удирдлага"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_reconcile_model_template__name
|
||||
@@ -2359,7 +2359,7 @@ msgstr "Улсаар"
|
||||
#. module: account
|
||||
#: model:ir.filters,name:account.filter_invoice_refund
|
||||
msgid "By Credit Note"
|
||||
msgstr ""
|
||||
msgstr "Буцаалтын нэхэмжлэлээр"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.filters,name:account.filter_invoice_product
|
||||
@@ -2900,6 +2900,8 @@ msgstr "Мөнгө/Тооцооны Утга"
|
||||
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
|
||||
msgid "Collect customer payments in one-click using Euro SEPA Service"
|
||||
msgstr ""
|
||||
"Euro SEPA үйлчилгээ ашиглан ганцхан товч даралтаар харилцагчдын төлбөр "
|
||||
"төлөлтүүдийг цуглуулна"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
|
||||
@@ -3466,7 +3468,7 @@ msgstr "Зээлийн карт"
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_move_line__credit_cash_basis
|
||||
msgid "Credit Cash Basis"
|
||||
msgstr ""
|
||||
msgstr "Мөнгөн суурьт кредит"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice_refund__filter_refund
|
||||
@@ -3476,7 +3478,7 @@ msgstr ""
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_partial_reconcile__credit_move_id
|
||||
msgid "Credit Move"
|
||||
msgstr ""
|
||||
msgstr "Кредит гүйлгээ"
|
||||
|
||||
#. module: account
|
||||
#: code:addons/account/models/account_invoice.py:498
|
||||
@@ -3494,22 +3496,22 @@ msgstr "Буцаалт"
|
||||
#: code:addons/account/models/account_invoice.py:499
|
||||
#, python-format
|
||||
msgid "Credit Note - %s"
|
||||
msgstr ""
|
||||
msgstr "Буцаалт - %s"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.account_journal_dashboard_kanban_view
|
||||
msgid "Credit Note Bill"
|
||||
msgstr ""
|
||||
msgstr "Өглөгийн буцаалт"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice_refund__date_invoice
|
||||
msgid "Credit Note Date"
|
||||
msgstr ""
|
||||
msgstr "Буцаалтын огноо"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_journal__refund_sequence_id
|
||||
msgid "Credit Note Entry Sequence"
|
||||
msgstr ""
|
||||
msgstr "Буцаалтын гүйлгээний дугаарлалт"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.actions.act_window,name:account.action_invoice_out_refund
|
||||
@@ -3517,12 +3519,12 @@ msgstr ""
|
||||
#: model_terms:ir.ui.view,arch_db:account.account_journal_dashboard_kanban_view
|
||||
#: model_terms:ir.ui.view,arch_db:account.view_account_invoice_report_search
|
||||
msgid "Credit Notes"
|
||||
msgstr ""
|
||||
msgstr "Буцаалтууд"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_journal__refund_sequence_number_next
|
||||
msgid "Credit Notes: Next Number"
|
||||
msgstr ""
|
||||
msgstr "Буцаалт: Дараагийн дугаар"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_tax_adjustments_wizard__credit_account_id
|
||||
@@ -3645,7 +3647,7 @@ msgstr "Захиалагчийн Төлбөрийн нөхцлүүд"
|
||||
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
|
||||
#: model_terms:ir.ui.view,arch_db:account.view_account_payment_search
|
||||
msgid "Customer Payments"
|
||||
msgstr ""
|
||||
msgstr "Харилцагчийн төлөлт"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_invoice__access_url
|
||||
@@ -4460,7 +4462,7 @@ msgstr "Гадаад Код"
|
||||
#: code:addons/account/static/src/xml/account_reconciliation.xml:335
|
||||
#, python-format
|
||||
msgid "External link"
|
||||
msgstr ""
|
||||
msgstr "Гадаад холбоос"
|
||||
|
||||
#. module: account
|
||||
#: model:account.incoterms,name:account.incoterm_FAS
|
||||
@@ -4501,7 +4503,7 @@ msgstr "Холбооны Мужууд"
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice_import_wizard__attachment_ids
|
||||
msgid "Files"
|
||||
msgstr ""
|
||||
msgstr "Файл"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.cash_box_in_form
|
||||
@@ -4529,7 +4531,7 @@ msgstr "Санхүүгийн жилийн мэдээлэл"
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
|
||||
msgid "Fiscal Localization"
|
||||
msgstr ""
|
||||
msgstr "Байршилаас хамаарах санхүү бүртгэл "
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_fiscal_position_account_template__position_id
|
||||
@@ -4539,7 +4541,7 @@ msgstr "Санхүүгийн зурагжуулалт"
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
|
||||
msgid "Fiscal Periods"
|
||||
msgstr ""
|
||||
msgstr "Тайлант мөчлөг"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.actions.act_window,name:account.action_account_fiscal_position_template_form
|
||||
@@ -4583,35 +4585,35 @@ msgstr "Санхүүгийн жил"
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.action_account_fiscal_year_form
|
||||
msgid "Fiscal Year 2018"
|
||||
msgstr ""
|
||||
msgstr "Санхүүгийн жил 2018"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.actions.act_window,name:account.actions_account_fiscal_year
|
||||
#: model:ir.model.fields,field_description:account.field_res_config_settings__group_fiscal_year
|
||||
msgid "Fiscal Years"
|
||||
msgstr ""
|
||||
msgstr "Санхүүгийн жил"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_financial_year_op__fiscalyear_last_day
|
||||
msgid "Fiscal year last day."
|
||||
msgstr ""
|
||||
msgstr "Санхүүгийн жилийн сүүлийн өдөр."
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_financial_year_op__fiscalyear_last_month
|
||||
msgid "Fiscal year last month."
|
||||
msgstr ""
|
||||
msgstr "Санхүүгийн жилийн сүүлийн сар."
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_financial_year_op__fiscalyear_last_day
|
||||
#: model:ir.model.fields,field_description:account.field_res_company__fiscalyear_last_day
|
||||
msgid "Fiscalyear Last Day"
|
||||
msgstr ""
|
||||
msgstr "Санхүүгийн жилийн сүүлийн өдөр"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_financial_year_op__fiscalyear_last_month
|
||||
#: model:ir.model.fields,field_description:account.field_res_company__fiscalyear_last_month
|
||||
msgid "Fiscalyear Last Month"
|
||||
msgstr ""
|
||||
msgstr "Санхүүгийн жилийн сүүлийн сар"
|
||||
|
||||
#. module: account
|
||||
#: selection:account.reconcile.model,amount_type:0
|
||||
@@ -4672,12 +4674,12 @@ msgstr "Дагагчид (Харилцагчид)"
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_journal__inbound_payment_method_ids
|
||||
msgid "For Incoming Payments"
|
||||
msgstr ""
|
||||
msgstr "Ирж буй төлбөрийн хувьд"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_journal__outbound_payment_method_ids
|
||||
msgid "For Outgoing Payments"
|
||||
msgstr ""
|
||||
msgstr "Гарч буй төлбөрийн хувьд"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_payment_term_line__value_amount
|
||||
@@ -4689,18 +4691,20 @@ msgstr "Хувь оруулахдаа 0-100 хооронд харьцааг ор
|
||||
msgid ""
|
||||
"Forbidden unit price, account and quantity on non-accountable invoice line"
|
||||
msgstr ""
|
||||
"Санхүүгийн гүйлгээнд хамаарахгүй мөр дээр ямар нэг нэгж үнэ, данс, тоо "
|
||||
"хэмжээний утга оруулахыг хориглоно"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_reconcile_model__force_second_tax_included
|
||||
#: model:ir.model.fields,help:account.field_account_reconcile_model_template__force_second_tax_included
|
||||
msgid "Force the second tax to be managed as a price included tax."
|
||||
msgstr ""
|
||||
msgstr "Хоёрдогч нэмэлт татвар нь үнийн дүнд шингэсэн байдлаар хэрэгжинэ."
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_reconcile_model__force_tax_included
|
||||
#: model:ir.model.fields,help:account.field_account_reconcile_model_template__force_tax_included
|
||||
msgid "Force the tax to be managed as a price included tax."
|
||||
msgstr ""
|
||||
msgstr "Хоёрдогч нэмэлт татвар нь үнийн дүнд шингэсэн байдлаар хэрэгжинэ."
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_account__currency_id
|
||||
@@ -4760,7 +4764,7 @@ msgstr ""
|
||||
#: code:addons/account/models/account_invoice.py:387
|
||||
#, python-format
|
||||
msgid "From: "
|
||||
msgstr ""
|
||||
msgstr "Хаанаас:"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model,name:account.model_account_full_reconcile
|
||||
@@ -4794,12 +4798,12 @@ msgstr "Бичилтүүдийг үүсгэх"
|
||||
#. module: account
|
||||
#: model:ir.ui.menu,name:account.account_reports_legal_statements_menu
|
||||
msgid "Generic Statements"
|
||||
msgstr ""
|
||||
msgstr "Нийтлэг тайлангууд"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
|
||||
msgid "Get warnings when invoicing specific customers"
|
||||
msgstr ""
|
||||
msgstr "Зарим онцгой харилцагчид нэхэмжлэл бүртгэх үед анхааруулга өгөх"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
|
||||
@@ -4809,6 +4813,11 @@ msgid ""
|
||||
"“Bank Synchronization” in bank account settings. Then, click “Configure” on "
|
||||
"the online account to enter your bank credentials."
|
||||
msgstr ""
|
||||
"Yodlee ба Plaid үйлчилгээг ашигласнаар Таны банкны гүйлгээ 4 цаг тутамд "
|
||||
"автоматаар бүртгэгдэх эсвэл товч дарж тэр даруй бүртгэгдэх боломжтой. Энэ "
|
||||
"нэмэлтийг суулгасны дараа банкны дансны тохиргооны хэсэг дэх \"Банк "
|
||||
"ижилсүүлэлт\" талбарт \"Банкнаас татах\" утгыг сонгоно. Дараа нь "
|
||||
"\"Тохируулах\" товч дээр дарж онлайн дансны бүртгэлээ оруулна. "
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_invoice_line__sequence
|
||||
@@ -4830,14 +4839,14 @@ msgstr "Нэхэмжлэлийн татварын харагдах эрэмбэ
|
||||
#: model:ir.model.fields,help:account.field_account_payment_term_line__sequence
|
||||
msgid ""
|
||||
"Gives the sequence order when displaying a list of payment terms lines."
|
||||
msgstr ""
|
||||
msgstr "Төлбөрийн нөхцөлийн мөрүүдийн хэрэгжих эрэмбэ, дарааллыг тодорхойлно."
|
||||
|
||||
#. module: account
|
||||
#. openerp-web
|
||||
#: code:addons/account/static/src/xml/account_reconciliation.xml:63
|
||||
#, python-format
|
||||
msgid "Go to bank statement(s)"
|
||||
msgstr ""
|
||||
msgstr "Банкны бүртгэл рүү очно уу"
|
||||
|
||||
#. module: account
|
||||
#: code:addons/account/models/account_invoice.py:773
|
||||
@@ -4850,7 +4859,7 @@ msgstr "Тохиргооны самбар руу очно уу"
|
||||
#: code:addons/account/models/company.py:482
|
||||
#, python-format
|
||||
msgid "Go to the journal configuration"
|
||||
msgstr ""
|
||||
msgstr "Журнал тохируулга руу очих"
|
||||
|
||||
#. module: account
|
||||
#: selection:res.partner,trust:0
|
||||
@@ -4891,7 +4900,7 @@ msgstr "Нэхэмжлэлийн мөрүүдийг бүлэглэх"
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_register_payments__group_invoices
|
||||
msgid "Group Invoices"
|
||||
msgstr ""
|
||||
msgstr "Нэхэмжлэл бүлэглэлт"
|
||||
|
||||
#. module: account
|
||||
#: selection:account.tax,amount_type:0
|
||||
@@ -4902,22 +4911,22 @@ msgstr "Татварын бүлэг"
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
|
||||
msgid "Group payments into a single batch to ease the reconciliation process"
|
||||
msgstr ""
|
||||
msgstr "Хялбар тулгалт хийх зорилгоор төлөлтүүдийг бүлэглэж багц болгох"
|
||||
|
||||
#. module: account
|
||||
#: selection:account.cash.rounding,rounding_method:0
|
||||
msgid "HALF-UP"
|
||||
msgstr ""
|
||||
msgstr "HALF-UP"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_res_config_settings__has_accounting_entries
|
||||
msgid "Has Accounting Entries"
|
||||
msgstr ""
|
||||
msgstr "Ажил гүйлгээтэй"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_payment__has_invoices
|
||||
msgid "Has Invoices"
|
||||
msgstr ""
|
||||
msgstr "Нэхэмжлэлтэй"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice__has_outstanding
|
||||
@@ -4928,30 +4937,30 @@ msgstr ""
|
||||
#: model:ir.model.fields,field_description:account.field_res_partner__has_unreconciled_entries
|
||||
#: model:ir.model.fields,field_description:account.field_res_users__has_unreconciled_entries
|
||||
msgid "Has Unreconciled Entries"
|
||||
msgstr ""
|
||||
msgstr "Тулгагдаагүй гүйлгээтэй"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_setup_bank_manual_config__qr_code_valid
|
||||
msgid "Has all required arguments"
|
||||
msgstr ""
|
||||
msgstr "QR код шаардлагатай"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice_send__has_error
|
||||
#: model:ir.model.fields,help:account.field_account_invoice_send__has_error
|
||||
msgid "Has error"
|
||||
msgstr ""
|
||||
msgstr "Алдаатай"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_abstract_payment__hide_payment_method
|
||||
#: model:ir.model.fields,field_description:account.field_account_payment__hide_payment_method
|
||||
#: model:ir.model.fields,field_description:account.field_account_register_payments__hide_payment_method
|
||||
msgid "Hide Payment Method"
|
||||
msgstr ""
|
||||
msgstr "Төлбөрийн аргыг нуух"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_tax__hide_tax_exigibility
|
||||
msgid "Hide Use Cash Basis Option"
|
||||
msgstr ""
|
||||
msgstr "Мөнгөн суурьт сонголтын нуух"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.portal_invoice_chatter
|
||||
@@ -4962,6 +4971,7 @@ msgstr "Түүх"
|
||||
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
|
||||
msgid "How total tax amount is computed in orders and invoices"
|
||||
msgstr ""
|
||||
"Захиалга болон нэхэмжлэл дээрх татварын нийлбэр дүн хэрхэн тооцоглогдох"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_abstract_payment__id
|
||||
@@ -5059,7 +5069,7 @@ msgstr "Хэрэв сонгосон бол, шинэ зурвасуудад ан
|
||||
#: model:ir.model.fields,help:account.field_account_invoice__message_has_error
|
||||
#: model:ir.model.fields,help:account.field_account_payment__message_has_error
|
||||
msgid "If checked, some messages have a delivery error."
|
||||
msgstr ""
|
||||
msgstr "Үүнийг сонговол алдаа үүсэх үед зурвасууд ирнэ."
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_account_template__nocreate
|
||||
@@ -5081,6 +5091,9 @@ msgid ""
|
||||
" type and recipient bank account in the generated payments. If disabled,\n"
|
||||
" a distinct payment will be generated for each invoice."
|
||||
msgstr ""
|
||||
"Үүнийг сонговол, нэхэмжлэлүүдээс үүсэх төлбөрийн бүртгэлүүд харилцагч, нэхэмжлэлийн данс,\n"
|
||||
" нэхэмжлэлийн төрөл болон хүлээн авагчийн банкны данс зэрэг мэдээллээр ижилсэж бүлэглэгдэнэ.\n"
|
||||
" Хэрэв сонгохгүй орхивол, нэхэмжлэл бүрээс харгалзан төлбөрийн бүртгэл үүснэ."
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_tax__include_base_amount
|
||||
@@ -5098,6 +5111,8 @@ msgid ""
|
||||
"If set, the accounting entries created during the bank statement reconciliation process will be created at this date.\n"
|
||||
"This is useful if the accounting period in which the entries should normally be booked is already closed."
|
||||
msgstr ""
|
||||
"Үүнийг сонговол, банкны хуулга тулгалтаас үүсч буй ажил гүйлгээнүүд энэ огноогоор бүртгэгдэх болно.\n"
|
||||
" Хэрэв хаагдсан тайлант хугацааны гүйлгээнд тулгалт хийж байгаа бол энэ огноог зөв сонгох хэрэгтэй."
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_tax__analytic
|
||||
@@ -5115,6 +5130,8 @@ msgid ""
|
||||
"If the active field is set to False, it will allow you to hide the payment "
|
||||
"terms without removing it."
|
||||
msgstr ""
|
||||
"Хэрэв Идэвхитэй талбарыг Үгүй утгатай болговол энэ нь танд төлбөрийн "
|
||||
"нөхцөлийг устгалгүйгээр нуух боломжыг олгоно."
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_journal__group_invoice_lines
|
||||
@@ -5131,6 +5148,8 @@ msgid ""
|
||||
"If this checkbox is ticked, this entry will be automatically reversed at the"
|
||||
" reversal date you defined."
|
||||
msgstr ""
|
||||
"Үүнийг сонгосон бол, энэ ажил гүйлгээнээс таны тохируулсан өдөр автоматаар "
|
||||
"эсрэг бичилт үүсэх болно."
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
|
||||
@@ -5421,7 +5440,7 @@ msgstr "Нөөцийн үнэлгээний оролтын данс"
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
|
||||
msgid "Install More Packages"
|
||||
msgstr ""
|
||||
msgstr "Нэмэлт сангууд суулгах"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_res_company__transfer_account_id
|
||||
@@ -5440,7 +5459,7 @@ msgstr ""
|
||||
#: model:ir.model.fields,field_description:account.field_account_account__internal_group
|
||||
#: model:ir.model.fields,field_description:account.field_account_account_type__internal_group
|
||||
msgid "Internal Group"
|
||||
msgstr ""
|
||||
msgstr "Дотоод бүлэг"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_move__narration
|
||||
@@ -5529,7 +5548,7 @@ msgstr "Нэхэмжлэл #"
|
||||
#: code:addons/account/models/account_invoice.py:497
|
||||
#, python-format
|
||||
msgid "Invoice - %s"
|
||||
msgstr ""
|
||||
msgstr "Нэхэмжлэл - %s"
|
||||
|
||||
#. module: account
|
||||
#: model:mail.message.subtype,description:account.mt_invoice_created
|
||||
@@ -5550,12 +5569,12 @@ msgstr "Нэхэмжлэх огноо"
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice__invoice_icon
|
||||
msgid "Invoice Icon"
|
||||
msgstr ""
|
||||
msgstr "Нэхэмжлэлийн дүрс"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.onboarding_invoice_layout_step
|
||||
msgid "Invoice Layout"
|
||||
msgstr ""
|
||||
msgstr "Нэхэмжлэлийн загвар"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model,name:account.model_account_invoice_line
|
||||
@@ -5577,12 +5596,12 @@ msgstr "Компанийн хэмжээнд нэхэмжлэлийн дугаа
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.invoice_form
|
||||
msgid "Invoice Number:"
|
||||
msgstr ""
|
||||
msgstr "Нэхэмжлэлийн дугаар:"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_res_config_settings__module_account_payment
|
||||
msgid "Invoice Online Payment"
|
||||
msgstr ""
|
||||
msgstr "Нэхэмжлэл онлайнаар төлөх"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice_line__invoice_id
|
||||
@@ -5636,7 +5655,7 @@ msgstr "Нэхэмжлэл төлөгдсөн"
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.account_invoice_send_wizard_form
|
||||
msgid "Invoice send & Print"
|
||||
msgstr ""
|
||||
msgstr "Нэхэмжлэлийг илгээсэн & Хэвлэсэн"
|
||||
|
||||
#. module: account
|
||||
#: model:mail.message.subtype,description:account.mt_invoice_validated
|
||||
@@ -5708,17 +5727,17 @@ msgstr "Танд өртэй нэхэмжлэлүүд"
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.account_journal_dashboard_kanban_view
|
||||
msgid "Invoices to Validate"
|
||||
msgstr ""
|
||||
msgstr "Батлах нэхэмжлэлүүд"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_payment__reconciled_invoice_ids
|
||||
msgid "Invoices whose journal items have been reconciled with this payment's."
|
||||
msgstr ""
|
||||
msgstr "Энэ төлбөрөөр тулгагдсан нэхэмжлэлийн журналын бичилтүүд."
|
||||
|
||||
#. module: account
|
||||
#: model:ir.actions.report,name:account.account_invoices_without_payment
|
||||
msgid "Invoices without Payment"
|
||||
msgstr ""
|
||||
msgstr "Төлөлтгүй нэхэмжлэл"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.ui.menu,name:account.menu_finance
|
||||
@@ -5732,7 +5751,7 @@ msgstr "Нэхэмжлэх"
|
||||
#: selection:account.reconcile.model,match_amount:0
|
||||
#: selection:account.reconcile.model.template,match_amount:0
|
||||
msgid "Is Between"
|
||||
msgstr ""
|
||||
msgstr "Хооронд"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_bank_statement__message_is_follower
|
||||
@@ -5745,28 +5764,28 @@ msgstr "Дагагч эсэх"
|
||||
#: selection:account.reconcile.model,match_amount:0
|
||||
#: selection:account.reconcile.model.template,match_amount:0
|
||||
msgid "Is Greater Than"
|
||||
msgstr ""
|
||||
msgstr "Их эсэх"
|
||||
|
||||
#. module: account
|
||||
#: selection:account.reconcile.model,match_amount:0
|
||||
#: selection:account.reconcile.model.template,match_amount:0
|
||||
msgid "Is Lower Than"
|
||||
msgstr ""
|
||||
msgstr "Бага эсэх"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice_send__printed
|
||||
msgid "Is Printed"
|
||||
msgstr ""
|
||||
msgstr "Хэвлэгдсэн эсэх"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_reconcile_model__is_second_tax_price_included
|
||||
msgid "Is Second Tax Included in Price"
|
||||
msgstr ""
|
||||
msgstr "Үнийн дүнд хоёрдогч татвар шингэсэн эсэх"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_reconcile_model__is_tax_price_included
|
||||
msgid "Is Tax Included in Price"
|
||||
msgstr ""
|
||||
msgstr "Татвар үнийн дүнд шингэсэн эсэх"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_invoice_line__is_rounding_line
|
||||
@@ -5883,7 +5902,7 @@ msgstr "Журналийн бичилт"
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.view_account_move_filter
|
||||
msgid "Journal Entries by Date"
|
||||
msgstr ""
|
||||
msgstr "Ажил гүйлгээ огноогоор"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice__move_id
|
||||
@@ -5953,7 +5972,7 @@ msgstr "Журналын мөрүүд"
|
||||
#. module: account
|
||||
#: model:ir.actions.act_window,name:account.action_move_line_select_tax_audit
|
||||
msgid "Journal Items for Tax Audit"
|
||||
msgstr ""
|
||||
msgstr "Ажил гүйлгээ татвар аудитаар"
|
||||
|
||||
#. module: account
|
||||
#. openerp-web
|
||||
@@ -5988,7 +6007,7 @@ msgstr ""
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_bank_statement_line__journal_currency_id
|
||||
msgid "Journal's Currency"
|
||||
msgstr ""
|
||||
msgstr "Журналын валют"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.actions.act_window,name:account.action_account_journal_form
|
||||
@@ -6026,7 +6045,7 @@ msgstr "6-р сар"
|
||||
#: selection:res.company,account_setup_coa_state:0
|
||||
#: selection:res.company,account_setup_fy_data_state:0
|
||||
msgid "Just done"
|
||||
msgstr ""
|
||||
msgstr "Дөнгөж дууссан"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_tax_adjustments_wizard__reason
|
||||
@@ -6036,12 +6055,12 @@ msgstr "Баталгаажуулалт"
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_journal__kanban_dashboard
|
||||
msgid "Kanban Dashboard"
|
||||
msgstr ""
|
||||
msgstr "Канбан самбар"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_journal__kanban_dashboard_graph
|
||||
msgid "Kanban Dashboard Graph"
|
||||
msgstr ""
|
||||
msgstr "Канбан самбар граффик"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.view_account_journal_form
|
||||
@@ -6336,7 +6355,7 @@ msgstr "Хамгийн сүүлийн Нэхэмжлэл, Төлбөрийг т
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice_send__layout
|
||||
msgid "Layout"
|
||||
msgstr ""
|
||||
msgstr "Хэв загвар"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.view_account_position_form
|
||||
@@ -6400,7 +6419,7 @@ msgstr "Хөрвөх чадвар"
|
||||
#: code:addons/account/models/chart_template.py:154
|
||||
#, python-format
|
||||
msgid "Liquidity Transfer"
|
||||
msgstr ""
|
||||
msgstr "Мөнгөн хөрөнгө шилжүүлгэ"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_chart_template__tax_template_ids
|
||||
@@ -6412,7 +6431,7 @@ msgstr "Шинээр үүсгэх татваруудын жагсаалт"
|
||||
#: code:addons/account/static/src/xml/account_reconciliation.xml:33
|
||||
#, python-format
|
||||
msgid "Load more"
|
||||
msgstr ""
|
||||
msgstr "Илүү унших"
|
||||
|
||||
#. module: account
|
||||
#. openerp-web
|
||||
@@ -6439,7 +6458,7 @@ msgstr "Дотоод тэмдэглэл хөтлөх"
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.onboarding_invoice_layout_step
|
||||
msgid "Looks great!"
|
||||
msgstr ""
|
||||
msgstr "Дажгүй харагдаж байна!"
|
||||
|
||||
#. module: account
|
||||
#: code:addons/account/models/account_bank_statement.py:175
|
||||
@@ -6479,7 +6498,7 @@ msgstr "Мэйлийн жагсаалт"
|
||||
#: 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 "Үндсэн хавсралт"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_res_config_settings__currency_id
|
||||
@@ -6489,7 +6508,7 @@ msgstr "Компанийн үндсэн валют."
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
|
||||
msgid "Main currency of your company"
|
||||
msgstr ""
|
||||
msgstr "Компаны үндсэн валют"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.ui.menu,name:account.account_management_menu
|
||||
@@ -6556,7 +6575,7 @@ msgstr "3-р сар"
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
|
||||
msgid "Margin Analysis"
|
||||
msgstr ""
|
||||
msgstr "Ашгийн шинжилгээ"
|
||||
|
||||
#. module: account
|
||||
#: selection:account.abstract.payment,payment_difference_handling:0
|
||||
@@ -6578,7 +6597,7 @@ msgstr "Масс мэйлийн шинжилгээ"
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice_send__mass_mailing_name
|
||||
msgid "Mass Mailing Name"
|
||||
msgstr ""
|
||||
msgstr "Масс мэйлийн нэр"
|
||||
|
||||
#. module: account
|
||||
#: selection:account.reconcile.model,match_label:0
|
||||
@@ -6593,17 +6612,17 @@ msgstr ""
|
||||
#: code:addons/account/models/chart_template.py:983
|
||||
#, python-format
|
||||
msgid "Match existing invoices/bills."
|
||||
msgstr ""
|
||||
msgstr "Байгаа нэхэмжлэлтэй харгалзуулах"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_move_line__matched_credit_ids
|
||||
msgid "Matched Credit"
|
||||
msgstr ""
|
||||
msgstr "Харгалзсан кредит"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_move_line__matched_debit_ids
|
||||
msgid "Matched Debit"
|
||||
msgstr ""
|
||||
msgstr "Харгалзсан дебет"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_full_reconcile__reconciled_line_ids
|
||||
@@ -6625,7 +6644,7 @@ msgstr "Тохирох дугаар"
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_partial_reconcile__max_date
|
||||
msgid "Max Date of Matched Lines"
|
||||
msgstr ""
|
||||
msgstr "Харгалзсан мөрүүдийн хамгийн өндөр огноо"
|
||||
|
||||
#. module: account
|
||||
#: selection:res.company,fiscalyear_last_month:0
|
||||
@@ -6642,7 +6661,7 @@ msgstr "Санамж"
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.view_account_payment_from_invoices
|
||||
msgid "Memo will be computed from invoices"
|
||||
msgstr ""
|
||||
msgstr "Санамж нэхэмжлэлээс тооцоологдоно"
|
||||
|
||||
#. module: account
|
||||
#. openerp-web
|
||||
@@ -6858,7 +6877,7 @@ msgstr "Шинэ"
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_setup_bank_manual_config__new_journal_name
|
||||
msgid "New Journal Name"
|
||||
msgstr ""
|
||||
msgstr "Шинэ журналын нэр"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.account_journal_dashboard_kanban_view
|
||||
@@ -6875,7 +6894,7 @@ msgstr "Төлбөрийн шинэ таамагласан огноо: "
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice__activity_date_deadline
|
||||
#: model:ir.model.fields,field_description:account.field_account_payment__activity_date_deadline
|
||||
msgid "Next Activity Deadline"
|
||||
msgstr ""
|
||||
msgstr "Дараагийн ажилбарын эцсийн огноо"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice__activity_summary
|
||||
@@ -6887,7 +6906,7 @@ msgstr "Дараагийн Ажилбарын Хураангуй"
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice__activity_type_id
|
||||
#: model:ir.model.fields,field_description:account.field_account_payment__activity_type_id
|
||||
msgid "Next Activity Type"
|
||||
msgstr ""
|
||||
msgstr "Дараагийн ажилбарын төрөл"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice__sequence_number_next
|
||||
@@ -6898,7 +6917,7 @@ msgstr "Дараагийн дугаар"
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice__sequence_number_next_prefix
|
||||
msgid "Next Number Prefix"
|
||||
msgstr ""
|
||||
msgstr "Дараагийн дугаарын Угтвар"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_move_line__blocked
|
||||
@@ -6916,7 +6935,7 @@ msgstr "Мессеж байхгүй"
|
||||
#: code:addons/account/static/src/js/reconciliation/reconciliation_renderer.js:143
|
||||
#, python-format
|
||||
msgid "No Title"
|
||||
msgstr ""
|
||||
msgstr "Гарчиг байхгүй"
|
||||
|
||||
#. module: account
|
||||
#: code:addons/account/models/account_invoice.py:912
|
||||
@@ -6970,7 +6989,7 @@ msgstr "Энгийн Зээлдэгч"
|
||||
#: selection:account.reconcile.model,match_label:0
|
||||
#: selection:account.reconcile.model.template,match_label:0
|
||||
msgid "Not Contains"
|
||||
msgstr ""
|
||||
msgstr "Агуулаагүй"
|
||||
|
||||
#. module: account
|
||||
#: selection:res.company,account_dashboard_onboarding_state:0
|
||||
@@ -6982,7 +7001,7 @@ msgstr ""
|
||||
#: selection:res.company,account_setup_coa_state:0
|
||||
#: selection:res.company,account_setup_fy_data_state:0
|
||||
msgid "Not done"
|
||||
msgstr ""
|
||||
msgstr "Дуусаагүй"
|
||||
|
||||
#. module: account
|
||||
#. openerp-web
|
||||
@@ -7021,7 +7040,7 @@ msgstr "Тэмдэглэл"
|
||||
#: code:addons/account/static/src/xml/account_reconciliation.xml:37
|
||||
#, python-format
|
||||
msgid "Nothing to do!"
|
||||
msgstr ""
|
||||
msgstr "Хийх зүйл алга!"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice_send__notification_ids
|
||||
@@ -7077,7 +7096,7 @@ msgstr "Хоногийн Тоо"
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice__message_has_error_counter
|
||||
#: model:ir.model.fields,field_description:account.field_account_payment__message_has_error_counter
|
||||
msgid "Number of error"
|
||||
msgstr ""
|
||||
msgstr "Алдааны тоо"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_bank_statement__message_needaction_counter
|
||||
@@ -7091,7 +7110,7 @@ msgstr "Үйлдэл шаардлагатай зурвасын тоо"
|
||||
#: model:ir.model.fields,help:account.field_account_invoice__message_has_error_counter
|
||||
#: model:ir.model.fields,help:account.field_account_payment__message_has_error_counter
|
||||
msgid "Number of messages with delivery error"
|
||||
msgstr ""
|
||||
msgstr "Алдааны мэдэгдэл бүхий зурвасын тоо"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_bank_statement__message_unread_counter
|
||||
@@ -7131,7 +7150,7 @@ msgstr ""
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_move_line__tax_line_grouping_key
|
||||
msgid "Old Taxes"
|
||||
msgstr ""
|
||||
msgstr "Хуучин татвар"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.view_payment_term_line_form
|
||||
@@ -7191,7 +7210,7 @@ msgstr "Нээх"
|
||||
#: code:addons/account/models/account_reconcile_model.py:276
|
||||
#, python-format
|
||||
msgid "Open Balance"
|
||||
msgstr ""
|
||||
msgstr "Нээлтийн баланс"
|
||||
|
||||
#. module: account
|
||||
#. openerp-web
|
||||
@@ -7204,7 +7223,7 @@ msgstr "Нээлттэй баланс"
|
||||
#. module: account
|
||||
#: model:ir.model,name:account.model_account_financial_year_op
|
||||
msgid "Opening Balance of Financial Year"
|
||||
msgstr ""
|
||||
msgstr "Санхүүгийн жилийн нээлтийн баланс"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_financial_year_op__opening_date
|
||||
@@ -7215,19 +7234,19 @@ msgstr "Нээх өдөр"
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_res_company__account_opening_journal_id
|
||||
msgid "Opening Journal"
|
||||
msgstr ""
|
||||
msgstr "Нээлтийн журналт"
|
||||
|
||||
#. module: account
|
||||
#: code:addons/account/models/company.py:365
|
||||
#: model:ir.model.fields,field_description:account.field_res_company__account_opening_move_id
|
||||
#, python-format
|
||||
msgid "Opening Journal Entry"
|
||||
msgstr ""
|
||||
msgstr "Нээлтийн журналын бичилт"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_financial_year_op__opening_move_posted
|
||||
msgid "Opening Move Posted"
|
||||
msgstr ""
|
||||
msgstr "Нээлтийн батлагдсан гүйлгээ"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_cashbox_line__number
|
||||
@@ -7238,27 +7257,27 @@ msgstr "Нээлтийн нэгжийн тоонууд"
|
||||
#: code:addons/account/models/account.py:169
|
||||
#, python-format
|
||||
msgid "Opening balance"
|
||||
msgstr ""
|
||||
msgstr "Нээлтийн баланс"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_account__opening_credit
|
||||
msgid "Opening credit"
|
||||
msgstr ""
|
||||
msgstr "Нээлтийн кредит"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_account__opening_credit
|
||||
msgid "Opening credit value for this account."
|
||||
msgstr ""
|
||||
msgstr "Энэ дансны нээлтийн кредит утга."
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_account__opening_debit
|
||||
msgid "Opening debit"
|
||||
msgstr ""
|
||||
msgstr "Нээлтийн дебет"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_account__opening_debit
|
||||
msgid "Opening debit value for this account."
|
||||
msgstr ""
|
||||
msgstr "Энэ дансны нээлтийн дебет утга."
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.view_account_reconcile_model_form
|
||||
@@ -7399,7 +7418,7 @@ msgstr "Хугацаа хэтэрсэн нэхэмжлэлүүд, төлөх о
|
||||
#. module: account
|
||||
#: model:ir.ui.menu,name:account.menu_board_journal_1
|
||||
msgid "Overview"
|
||||
msgstr ""
|
||||
msgstr "Тойм"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
|
||||
@@ -11143,7 +11162,7 @@ msgstr ""
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
|
||||
msgid "Use follow-up levels and schedule actions"
|
||||
msgstr ""
|
||||
msgstr "Мөшгөлтийн төвшин болон төлөвлөгөөт арга хэмжээ ашиглах"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_res_config_settings__group_products_in_bills
|
||||
@@ -11302,7 +11321,7 @@ msgstr "Нийлүүлэгчийн тооцоо"
|
||||
#: code:addons/account/models/account_invoice.py:501
|
||||
#, python-format
|
||||
msgid "Vendor Bill - %s"
|
||||
msgstr ""
|
||||
msgstr "Нийлүүлэгчийн нэхэмжлэл - %s"
|
||||
|
||||
#. module: account
|
||||
#: code:addons/account/models/chart_template.py:419
|
||||
@@ -11318,24 +11337,24 @@ msgstr "Нийлүүлэгчийн Нэхэмжлэлүүд"
|
||||
#: model_terms:ir.ui.view,arch_db:account.report_invoice_document
|
||||
#, python-format
|
||||
msgid "Vendor Credit Note"
|
||||
msgstr ""
|
||||
msgstr "Нийлүүлэгчийн буцаалт"
|
||||
|
||||
#. module: account
|
||||
#: code:addons/account/models/account_invoice.py:503
|
||||
#, python-format
|
||||
msgid "Vendor Credit Note - %s"
|
||||
msgstr ""
|
||||
msgstr "Нийлүүлэгчийн буцаалт - %s"
|
||||
|
||||
#. module: account
|
||||
#: code:addons/account/models/account_invoice.py:1359
|
||||
#, python-format
|
||||
msgid "Vendor Credit note"
|
||||
msgstr ""
|
||||
msgstr "Нийлүүлэгчийн буцаалт"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice__vendor_display_name
|
||||
msgid "Vendor Display Name"
|
||||
msgstr ""
|
||||
msgstr "Нийлүүлэгчийн дэлгэцийн нэр"
|
||||
|
||||
#. module: account
|
||||
#: code:addons/account/models/account_payment.py:774
|
||||
@@ -11354,7 +11373,7 @@ msgstr "Нийлүүлэгчийн Төлбөрийн Нөхцлүүд"
|
||||
#: model_terms:ir.ui.view,arch_db:account.account_journal_dashboard_kanban_view
|
||||
#: model_terms:ir.ui.view,arch_db:account.view_account_payment_search
|
||||
msgid "Vendor Payments"
|
||||
msgstr ""
|
||||
msgstr "Нийлүүлэгчийн төлбөр"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.invoice_supplier_form
|
||||
@@ -11412,7 +11431,7 @@ msgstr "Анхааруулга"
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_res_config_settings__group_warning_account
|
||||
msgid "Warnings in Invoices"
|
||||
msgstr ""
|
||||
msgstr "Нэхэмжлэлийн анхааруулга"
|
||||
|
||||
#. module: account
|
||||
#: code:addons/account/models/company.py:541
|
||||
@@ -11496,7 +11515,7 @@ msgstr "Хасалт"
|
||||
#: model:ir.model.fields,field_description:account.field_account_reconcile_model__amount
|
||||
#: model:ir.model.fields,field_description:account.field_account_reconcile_model_template__amount
|
||||
msgid "Write-off Amount"
|
||||
msgstr ""
|
||||
msgstr "Зөрүү дүн"
|
||||
|
||||
#. module: account
|
||||
#: sql_constraint:account.move.line:0
|
||||
@@ -11830,7 +11849,7 @@ msgstr ""
|
||||
#: code:addons/account/static/src/xml/account_reconciliation.xml:55
|
||||
#, python-format
|
||||
msgid "You reconciled"
|
||||
msgstr ""
|
||||
msgstr "Та тулгалт хийсэн"
|
||||
|
||||
#. module: account
|
||||
#: code:addons/account/models/account_move.py:1617
|
||||
@@ -11962,13 +11981,6 @@ msgstr "байна. Энэ баримтыг хуваарилж төлсөнөө
|
||||
msgid "have been reconciled automatically."
|
||||
msgstr ""
|
||||
|
||||
#. module: account
|
||||
#. openerp-web
|
||||
#: code:addons/account/static/src/xml/account_reconciliation.xml:44
|
||||
#, python-format
|
||||
msgid "o_manual_statement"
|
||||
msgstr ""
|
||||
|
||||
#. module: account
|
||||
#: selection:account.payment.term.line,option:0
|
||||
msgid "of the current month"
|
||||
|
||||
@@ -10005,8 +10005,8 @@ msgid ""
|
||||
" as it can have been generated first, and reconciled later"
|
||||
msgstr ""
|
||||
"Technisch veld bevat de facturen voor welke reeds betalingen werden gegenereerd.\n"
|
||||
"Dit komt niet noodzakelijk overeen met de facturen reconsilieerd met de betalingen,\n"
|
||||
"als deze eerst werden gegenereerd en pas later gereconsilieerd."
|
||||
"Dit komt niet noodzakelijk overeen met de facturen afgeletterd met de betalingen,\n"
|
||||
"als deze eerst werden gegenereerd en pas later afgeletterd."
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_invoice_line__display_type
|
||||
@@ -10222,9 +10222,9 @@ msgid ""
|
||||
" the moment the ORM reflects that change in its one2many"
|
||||
msgstr ""
|
||||
"Technische veld gebruikt om de oude veld waarde te bewaren is gebruikt om de"
|
||||
" belasting regels te berekenen (in account.move from view) op het ogenblik "
|
||||
"dat de gebruiker het wijzigde en het ogenblik dat de ORM deze wijziging "
|
||||
"aangeeft in zijn one2many "
|
||||
" belasting regels te berekenen (in account.move formulier weergave) op het "
|
||||
"ogenblik dat de gebruiker het wijzigde en het ogenblik dat de ORM deze "
|
||||
"wijziging aangeeft in zijn one2many "
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_res_config_settings__chart_template_id
|
||||
@@ -12027,8 +12027,8 @@ msgid ""
|
||||
"You can not have an overlap between two fiscal years, please correct the "
|
||||
"start and/or end dates of your fiscal years."
|
||||
msgstr ""
|
||||
"U kunt geen overlapping hebben tussen twee belastbare jaren, wijzig aub de "
|
||||
"start en/of eind datum van uw belastbare jaren."
|
||||
"U kunt geen overlapping hebben tussen twee fiscale jaren, wijzig aub de "
|
||||
"start en/of einddatum van uw belastbare jaren."
|
||||
|
||||
#. module: account
|
||||
#: code:addons/account/models/account_invoice.py:1827
|
||||
@@ -12100,8 +12100,8 @@ msgid ""
|
||||
"You cannot create journal items with a secondary currency without filling "
|
||||
"both 'currency' and 'amount currency' fields."
|
||||
msgstr ""
|
||||
"U kunt geen joernaalposten aanmaken met een tweede valuta zonder invulling "
|
||||
"van beide 'valuta' en 'bedrag valuta' velden."
|
||||
"U kunt geen dagboeken aanmaken met een tweede valuta zonder invulling van "
|
||||
"beide 'valuta' en 'bedrag valuta' velden."
|
||||
|
||||
#. module: account
|
||||
#: code:addons/account/models/company.py:187
|
||||
@@ -12290,8 +12290,8 @@ msgid ""
|
||||
"You cannot switch an account to prevent the reconciliation if some partial "
|
||||
"reconciliations are still pending."
|
||||
msgstr ""
|
||||
"You cannot switch an account to prevent the reconciliation if some partial "
|
||||
"reconciliations are still pending."
|
||||
"U kan niet wisselen van account om het afletteren te voorkomen als "
|
||||
"gedeeltelijke afletteringen nog in aanvraag zijn."
|
||||
|
||||
#. module: account
|
||||
#: code:addons/account/models/account_move.py:1102
|
||||
|
||||
@@ -2827,6 +2827,8 @@ msgid ""
|
||||
"Click to <b>validate your invoice.</b> A reference will be assigned to this "
|
||||
"invoice and you will not be able to modify it anymore."
|
||||
msgstr ""
|
||||
"Faceți clic pentru a <b>valida factura</b>. O referință va fi atribuită "
|
||||
"acestei facturi și nu o veți mai putea modifica."
|
||||
|
||||
#. module: account
|
||||
#. openerp-web
|
||||
@@ -11892,13 +11894,6 @@ msgstr ""
|
||||
msgid "have been reconciled automatically."
|
||||
msgstr ""
|
||||
|
||||
#. module: account
|
||||
#. openerp-web
|
||||
#: code:addons/account/static/src/xml/account_reconciliation.xml:44
|
||||
#, python-format
|
||||
msgid "o_manual_statement"
|
||||
msgstr ""
|
||||
|
||||
#. module: account
|
||||
#: selection:account.payment.term.line,option:0
|
||||
msgid "of the current month"
|
||||
|
||||
@@ -2993,7 +2993,7 @@ msgstr ""
|
||||
#. module: account
|
||||
#: model:ir.model,name:account.model_res_config_settings
|
||||
msgid "Config Settings"
|
||||
msgstr ""
|
||||
msgstr "Konfigurations Inställningar"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.ui.menu,name:account.menu_finance_configuration
|
||||
|
||||
@@ -2911,7 +2911,7 @@ msgstr ""
|
||||
#: code:addons/account/static/src/xml/account_reconciliation.xml:17
|
||||
#, python-format
|
||||
msgid "Click to Rename"
|
||||
msgstr ""
|
||||
msgstr "Натисніть, щоб змінити назву"
|
||||
|
||||
#. module: account
|
||||
#. openerp-web
|
||||
@@ -3077,7 +3077,7 @@ msgstr "Повний набір податків"
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice_send__composer_id
|
||||
msgid "Composer"
|
||||
msgstr ""
|
||||
msgstr "Автор"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_invoice_send__composition_mode
|
||||
@@ -3106,7 +3106,7 @@ msgstr "Розрахований баланс"
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.view_account_reconcile_model_form
|
||||
msgid "Conditions on Bank Statement Line"
|
||||
msgstr ""
|
||||
msgstr "Умови на рядку банківського рахунку"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model,name:account.model_res_config_settings
|
||||
@@ -3129,12 +3129,12 @@ msgstr "Налаштування"
|
||||
#: code:addons/account/static/src/js/section_and_note_fields_backend.js:102
|
||||
#, python-format
|
||||
msgid "Configure a product"
|
||||
msgstr ""
|
||||
msgstr "Налаштувати товар"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.actions.act_window,name:account.action_open_account_onboarding_invoice_layout
|
||||
msgid "Configure your document layout"
|
||||
msgstr ""
|
||||
msgstr "Налаштуйте макет документа"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.view_account_bnk_stmt_cashbox
|
||||
@@ -3188,7 +3188,7 @@ msgstr "Вітаємо, ви все завершили!"
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.account_invoice_onboarding_panel
|
||||
msgid "Congratulations! You are all set."
|
||||
msgstr ""
|
||||
msgstr "Вітаємо! Ви все налаштували."
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model,name:account.model_res_partner
|
||||
@@ -3218,7 +3218,7 @@ msgstr "Містить"
|
||||
#: model:ir.model.fields,field_description:account.field_res_partner__contracts_count
|
||||
#: model:ir.model.fields,field_description:account.field_res_users__contracts_count
|
||||
msgid "Contracts Count"
|
||||
msgstr ""
|
||||
msgstr "Підрахунок контрактів"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.view_account_journal_form
|
||||
@@ -5765,6 +5765,8 @@ msgid ""
|
||||
"Invoice_${(object.number or '').replace('/','_')}${object.state == 'draft' "
|
||||
"and '_draft' or ''}"
|
||||
msgstr ""
|
||||
"Invoice_${(object.number or '').replace('/','_')}${object.state == 'draft' "
|
||||
"and '_draft' or ''}"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.view_account_invoice_report_search
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
# Andy Cheng <andy.cheng@richsoda.com>, 2019
|
||||
# sejun huang <sejun.huang@gmail.com>, 2019
|
||||
# 敬雲 林 <chingyun@yuanchih-consult.com>, 2019
|
||||
# Ying Chang <acc.tou@gmail.com>, 2019
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@@ -19,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: 敬雲 林 <chingyun@yuanchih-consult.com>, 2019\n"
|
||||
"Last-Translator: Ying Chang <acc.tou@gmail.com>, 2019\n"
|
||||
"Language-Team: Chinese (Taiwan) (https://www.transifex.com/odoo/teams/41243/zh_TW/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -248,7 +249,7 @@ msgstr ""
|
||||
#. 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=\"Date\" title=\"Date\"/>"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.portal_invoice_page
|
||||
@@ -341,6 +342,9 @@ msgid ""
|
||||
"specific.\" aria-label=\"Values set here are company-specific.\" "
|
||||
"groups=\"base.group_multi_company\" role=\"img\"/>"
|
||||
msgstr ""
|
||||
"<span class=\"fa fa-lg fa-building-o\" title=\"Values set here are company-"
|
||||
"specific.\" aria-label=\"Values set here are company-specific.\" "
|
||||
"groups=\"base.group_multi_company\" role=\"img\"/>"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form
|
||||
@@ -812,7 +816,7 @@ msgstr "選擇的對帳單明細已被會計憑證調節。"
|
||||
#. module: account
|
||||
#: sql_constraint:account.fiscal.position.tax:0
|
||||
msgid "A tax fiscal position could be defined only one time on same taxes."
|
||||
msgstr ""
|
||||
msgstr "稅務財政情況在相同的稅上面只能定義一次。"
|
||||
|
||||
#. module: account
|
||||
#: model:res.groups,name:account.group_warning_account
|
||||
@@ -1689,7 +1693,7 @@ msgstr "金額:"
|
||||
#: sql_constraint:account.fiscal.position.account:0
|
||||
msgid ""
|
||||
"An account fiscal position could be defined only one time on same accounts."
|
||||
msgstr ""
|
||||
msgstr "財政狀況只能在一個科目上定義一次。"
|
||||
|
||||
#. module: account
|
||||
#: model_terms:ir.actions.act_window,help:account.action_account_form
|
||||
@@ -4608,12 +4612,12 @@ msgstr "財務年度"
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_financial_year_op__fiscalyear_last_day
|
||||
msgid "Fiscal year last day."
|
||||
msgstr ""
|
||||
msgstr "會計年度的最後一天"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,help:account.field_account_financial_year_op__fiscalyear_last_month
|
||||
msgid "Fiscal year last month."
|
||||
msgstr ""
|
||||
msgstr "會計年度的最後一個月"
|
||||
|
||||
#. module: account
|
||||
#: model:ir.model.fields,field_description:account.field_account_financial_year_op__fiscalyear_last_day
|
||||
@@ -5489,7 +5493,7 @@ msgstr "無效的\"郵編範圍\",請正確的配置。"
|
||||
#: code:addons/account/models/company.py:106
|
||||
#, python-format
|
||||
msgid "Invalid fiscal year last day"
|
||||
msgstr ""
|
||||
msgstr "無效的會計年度的最後一天"
|
||||
|
||||
#. module: account
|
||||
#: code:addons/account/models/account_invoice.py:1358
|
||||
@@ -11870,13 +11874,6 @@ msgstr "對於此供應商,您可以將它們分配為標記此帳單已付款
|
||||
msgid "have been reconciled automatically."
|
||||
msgstr "已自動調帳"
|
||||
|
||||
#. module: account
|
||||
#. openerp-web
|
||||
#: code:addons/account/static/src/xml/account_reconciliation.xml:44
|
||||
#, python-format
|
||||
msgid "o_manual_statement"
|
||||
msgstr "o_manual_statement"
|
||||
|
||||
#. module: account
|
||||
#: selection:account.payment.term.line,option:0
|
||||
msgid "of the current month"
|
||||
|
||||
@@ -482,7 +482,7 @@ class AccountJournal(models.Model):
|
||||
post_at_bank_rec = fields.Boolean(string="Post At Bank Reconciliation", help="Whether or not the payments made in this journal should be generated in draft state, so that the related journal entries are only posted when performing bank reconciliation.")
|
||||
|
||||
# alias configuration for 'purchase' type journals
|
||||
alias_id = fields.Many2one('mail.alias', string='Alias')
|
||||
alias_id = fields.Many2one('mail.alias', string='Alias', copy=False)
|
||||
alias_domain = fields.Char('Alias domain', compute='_compute_alias_domain', default=lambda self: self.env["ir.config_parameter"].sudo().get_param("mail.catchall.domain"))
|
||||
alias_name = fields.Char('Alias Name for Vendor Bills', related='alias_id.alias_name', help="It creates draft vendor bill by sending an email.", readonly=False)
|
||||
|
||||
|
||||
@@ -604,8 +604,11 @@ class AccountBankStatementLine(models.Model):
|
||||
|
||||
# Fully reconciled moves are just linked to the bank statement
|
||||
total = self.amount
|
||||
currency = self.currency_id or statement_currency
|
||||
for aml_rec in payment_aml_rec:
|
||||
total -= aml_rec.debit - aml_rec.credit
|
||||
balance = aml_rec.amount_currency if aml_rec.currency_id else aml_rec.balance
|
||||
aml_currency = aml_rec.currency_id or aml_rec.company_currency_id
|
||||
total -= aml_currency._convert(balance, currency, aml_rec.company_id, aml_rec.date)
|
||||
aml_rec.with_context(check_move_validity=False).write({'statement_line_id': self.id})
|
||||
counterpart_moves = (counterpart_moves | aml_rec.move_id)
|
||||
if aml_rec.journal_id.post_at_bank_rec and aml_rec.payment_id and aml_rec.move_id.state == 'draft':
|
||||
|
||||
@@ -123,7 +123,7 @@ class AccountInvoice(models.Model):
|
||||
self.residual_signed = abs(residual) * sign
|
||||
self.residual = abs(residual)
|
||||
digits_rounding_precision = self.currency_id.rounding
|
||||
if float_is_zero(self.residual, precision_rounding=digits_rounding_precision):
|
||||
if self.move_id and float_is_zero(self.residual, precision_rounding=digits_rounding_precision):
|
||||
self.reconciled = True
|
||||
else:
|
||||
self.reconciled = False
|
||||
|
||||
@@ -410,18 +410,20 @@ class AccountMove(models.Model):
|
||||
@api.multi
|
||||
def _reverse_move(self, date=None, journal_id=None, auto=False):
|
||||
self.ensure_one()
|
||||
reversed_move = self.copy(default={
|
||||
'date': date,
|
||||
'journal_id': journal_id.id if journal_id else self.journal_id.id,
|
||||
'ref': (_('Automatic reversal of: %s') if auto else _('Reversal of: %s')) % (self.name),
|
||||
'auto_reverse': False})
|
||||
for acm_line in reversed_move.line_ids.with_context(check_move_validity=False):
|
||||
acm_line.write({
|
||||
'debit': acm_line.credit,
|
||||
'credit': acm_line.debit,
|
||||
'amount_currency': -acm_line.amount_currency
|
||||
})
|
||||
self.reverse_entry_id = reversed_move
|
||||
with self.env.norecompute():
|
||||
reversed_move = self.copy(default={
|
||||
'date': date,
|
||||
'journal_id': journal_id.id if journal_id else self.journal_id.id,
|
||||
'ref': (_('Automatic reversal of: %s') if auto else _('Reversal of: %s')) % (self.name),
|
||||
'auto_reverse': False})
|
||||
for acm_line in reversed_move.line_ids.with_context(check_move_validity=False):
|
||||
acm_line.write({
|
||||
'debit': acm_line.credit,
|
||||
'credit': acm_line.debit,
|
||||
'amount_currency': -acm_line.amount_currency
|
||||
})
|
||||
self.reverse_entry_id = reversed_move
|
||||
self.recompute()
|
||||
return reversed_move
|
||||
|
||||
@api.multi
|
||||
@@ -550,7 +552,7 @@ class AccountMoveLine(models.Model):
|
||||
#computing the `reconciled` field.
|
||||
reconciled = False
|
||||
digits_rounding_precision = line.company_id.currency_id.rounding
|
||||
if float_is_zero(amount, precision_rounding=digits_rounding_precision):
|
||||
if (line.matched_debit_ids or line.matched_credit_ids) and float_is_zero(amount, precision_rounding=digits_rounding_precision):
|
||||
if line.currency_id and line.amount_currency:
|
||||
if float_is_zero(amount_residual_currency, precision_rounding=line.currency_id.rounding):
|
||||
reconciled = True
|
||||
@@ -932,7 +934,7 @@ class AccountMoveLine(models.Model):
|
||||
for line in self:
|
||||
company_ids.add(line.company_id.id)
|
||||
all_accounts.append(line.account_id)
|
||||
if line.reconciled:
|
||||
if (line.matched_debit_ids or line.matched_credit_ids) and line.reconciled:
|
||||
raise UserError(_('You are trying to reconcile some entries that are already reconciled.'))
|
||||
if len(company_ids) > 1:
|
||||
raise UserError(_('To reconcile the entries company should be the same for all entries.'))
|
||||
@@ -1081,6 +1083,56 @@ class AccountMoveLine(models.Model):
|
||||
)
|
||||
return rec_move_ids.unlink()
|
||||
|
||||
def _apply_taxes(self, vals, amount):
|
||||
tax_lines_vals = []
|
||||
# Get ids from triplets : https://www.odoo.com/documentation/10.0/reference/orm.html#odoo.models.Model.write
|
||||
tax_ids = [tax['id'] for tax in self.resolve_2many_commands('tax_ids', vals['tax_ids']) if tax.get('id')]
|
||||
# Since create() receives ids instead of recordset, let's just use the old-api bridge
|
||||
taxes = self.env['account.tax'].browse(tax_ids)
|
||||
currency = self.env['res.currency'].browse(vals.get('currency_id'))
|
||||
partner = self.env['res.partner'].browse(vals.get('partner_id'))
|
||||
ctx = dict(self._context)
|
||||
ctx['round'] = ctx.get('round', True)
|
||||
res = taxes.with_context(ctx).compute_all(amount,
|
||||
currency, 1, vals.get('product_id'), partner)
|
||||
# Adjust line amount if any tax is price_include
|
||||
if abs(res['total_excluded']) < abs(amount):
|
||||
if vals['debit'] != 0.0: vals['debit'] = res['total_excluded']
|
||||
if vals['credit'] != 0.0: vals['credit'] = -res['total_excluded']
|
||||
if vals.get('amount_currency'):
|
||||
vals['amount_currency'] = self.env['res.currency'].browse(vals['currency_id']).round(vals['amount_currency'] * (res['total_excluded']/amount))
|
||||
# Create tax lines
|
||||
for tax_vals in res['taxes']:
|
||||
if tax_vals['amount']:
|
||||
tax = self.env['account.tax'].browse([tax_vals['id']])
|
||||
account_id = (amount > 0 and tax_vals['account_id'] or tax_vals['refund_account_id'])
|
||||
if not account_id: account_id = vals['account_id']
|
||||
temp = {
|
||||
'account_id': account_id,
|
||||
'name': vals['name'] + ' ' + tax_vals['name'],
|
||||
'tax_line_id': tax_vals['id'],
|
||||
'move_id': vals['move_id'],
|
||||
'partner_id': vals.get('partner_id'),
|
||||
'statement_id': vals.get('statement_id'),
|
||||
'debit': tax_vals['amount'] > 0 and tax_vals['amount'] or 0.0,
|
||||
'credit': tax_vals['amount'] < 0 and -tax_vals['amount'] or 0.0,
|
||||
'analytic_account_id': vals.get('analytic_account_id') if tax.analytic else False,
|
||||
}
|
||||
bank = self.env["account.bank.statement.line"].browse(vals.get('statement_line_id')).statement_id
|
||||
if bank.currency_id != bank.company_id.currency_id:
|
||||
ctx = {}
|
||||
if 'date' in vals:
|
||||
ctx['date'] = vals['date']
|
||||
elif 'date_maturity' in vals:
|
||||
ctx['date'] = vals['date_maturity']
|
||||
temp['currency_id'] = bank.currency_id.id
|
||||
temp['amount_currency'] = bank.company_id.currency_id.with_context(ctx).compute(tax_vals['amount'], bank.currency_id, round=True)
|
||||
if vals.get('tax_exigible'):
|
||||
temp['tax_exigible'] = True
|
||||
temp['account_id'] = tax.cash_basis_account.id or account_id
|
||||
tax_lines_vals.append(temp)
|
||||
return tax_lines_vals
|
||||
|
||||
####################################################
|
||||
# CRUD methods
|
||||
####################################################
|
||||
@@ -1522,90 +1574,91 @@ class AccountPartialReconcile(models.Model):
|
||||
self.ensure_one()
|
||||
move_date = self.debit_move_id.date
|
||||
newly_created_move = self.env['account.move']
|
||||
for move in (self.debit_move_id.move_id, self.credit_move_id.move_id):
|
||||
#move_date is the max of the 2 reconciled items
|
||||
if move_date < move.date:
|
||||
move_date = move.date
|
||||
percentage_before = percentage_before_rec[move.id]
|
||||
percentage_after = move.line_ids[0]._get_matched_percentage()[move.id]
|
||||
# update the percentage before as the move can be part of
|
||||
# multiple partial reconciliations
|
||||
percentage_before_rec[move.id] = percentage_after
|
||||
with self.env.norecompute():
|
||||
for move in (self.debit_move_id.move_id, self.credit_move_id.move_id):
|
||||
#move_date is the max of the 2 reconciled items
|
||||
if move_date < move.date:
|
||||
move_date = move.date
|
||||
percentage_before = percentage_before_rec[move.id]
|
||||
percentage_after = move.line_ids[0]._get_matched_percentage()[move.id]
|
||||
# update the percentage before as the move can be part of
|
||||
# multiple partial reconciliations
|
||||
percentage_before_rec[move.id] = percentage_after
|
||||
|
||||
for line in move.line_ids:
|
||||
if not line.tax_exigible:
|
||||
#amount is the current cash_basis amount minus the one before the reconciliation
|
||||
amount = line.balance * percentage_after - line.balance * percentage_before
|
||||
rounded_amt = self._get_amount_tax_cash_basis(amount, line)
|
||||
if float_is_zero(rounded_amt, precision_rounding=line.company_id.currency_id.rounding):
|
||||
continue
|
||||
if line.tax_line_id and line.tax_line_id.tax_exigibility == 'on_payment':
|
||||
if not newly_created_move:
|
||||
newly_created_move = self._create_tax_basis_move()
|
||||
#create cash basis entry for the tax line
|
||||
to_clear_aml = self.env['account.move.line'].with_context(check_move_validity=False).create({
|
||||
'name': line.move_id.name,
|
||||
'debit': abs(rounded_amt) if rounded_amt < 0 else 0.0,
|
||||
'credit': rounded_amt if rounded_amt > 0 else 0.0,
|
||||
'account_id': line.account_id.id,
|
||||
'analytic_account_id': line.analytic_account_id.id,
|
||||
'analytic_tag_ids': line.analytic_tag_ids.ids,
|
||||
'tax_exigible': True,
|
||||
'amount_currency': line.amount_currency and line.currency_id.round(-line.amount_currency * amount / line.balance) or 0.0,
|
||||
'currency_id': line.currency_id.id,
|
||||
'move_id': newly_created_move.id,
|
||||
'partner_id': line.partner_id.id,
|
||||
})
|
||||
# Group by cash basis account and tax
|
||||
self.env['account.move.line'].with_context(check_move_validity=False).create({
|
||||
'name': line.name,
|
||||
'debit': rounded_amt if rounded_amt > 0 else 0.0,
|
||||
'credit': abs(rounded_amt) if rounded_amt < 0 else 0.0,
|
||||
'account_id': line.tax_line_id.cash_basis_account_id.id,
|
||||
'analytic_account_id': line.analytic_account_id.id,
|
||||
'analytic_tag_ids': line.analytic_tag_ids.ids,
|
||||
'tax_line_id': line.tax_line_id.id,
|
||||
'tax_exigible': True,
|
||||
'amount_currency': line.amount_currency and line.currency_id.round(line.amount_currency * amount / line.balance) or 0.0,
|
||||
'currency_id': line.currency_id.id,
|
||||
'move_id': newly_created_move.id,
|
||||
'partner_id': line.partner_id.id,
|
||||
})
|
||||
if line.account_id.reconcile:
|
||||
#setting the account to allow reconciliation will help to fix rounding errors
|
||||
to_clear_aml |= line
|
||||
to_clear_aml.reconcile()
|
||||
|
||||
if any([tax.tax_exigibility == 'on_payment' for tax in line.tax_ids]):
|
||||
if not newly_created_move:
|
||||
newly_created_move = self._create_tax_basis_move()
|
||||
#create cash basis entry for the base
|
||||
for tax in line.tax_ids.filtered(lambda t: t.tax_exigibility == 'on_payment'):
|
||||
account_id = self._get_tax_cash_basis_base_account(line, tax)
|
||||
self.env['account.move.line'].with_context(check_move_validity=False).create({
|
||||
'name': line.name,
|
||||
'debit': rounded_amt > 0 and rounded_amt or 0.0,
|
||||
'credit': rounded_amt < 0 and abs(rounded_amt) or 0.0,
|
||||
'account_id': account_id.id,
|
||||
for line in move.line_ids:
|
||||
if not line.tax_exigible:
|
||||
#amount is the current cash_basis amount minus the one before the reconciliation
|
||||
amount = line.balance * percentage_after - line.balance * percentage_before
|
||||
rounded_amt = self._get_amount_tax_cash_basis(amount, line)
|
||||
if float_is_zero(rounded_amt, precision_rounding=line.company_id.currency_id.rounding):
|
||||
continue
|
||||
if line.tax_line_id and line.tax_line_id.tax_exigibility == 'on_payment':
|
||||
if not newly_created_move:
|
||||
newly_created_move = self._create_tax_basis_move()
|
||||
#create cash basis entry for the tax line
|
||||
to_clear_aml = self.env['account.move.line'].with_context(check_move_validity=False).create({
|
||||
'name': line.move_id.name,
|
||||
'debit': abs(rounded_amt) if rounded_amt < 0 else 0.0,
|
||||
'credit': rounded_amt if rounded_amt > 0 else 0.0,
|
||||
'account_id': line.account_id.id,
|
||||
'analytic_account_id': line.analytic_account_id.id,
|
||||
'analytic_tag_ids': line.analytic_tag_ids.ids,
|
||||
'tax_exigible': True,
|
||||
'tax_ids': [(6, 0, [tax.id])],
|
||||
'move_id': newly_created_move.id,
|
||||
'amount_currency': line.amount_currency and line.currency_id.round(-line.amount_currency * amount / line.balance) or 0.0,
|
||||
'currency_id': line.currency_id.id,
|
||||
'amount_currency': self.amount_currency and line.currency_id.round(line.amount_currency * amount / line.balance) or 0.0,
|
||||
'move_id': newly_created_move.id,
|
||||
'partner_id': line.partner_id.id,
|
||||
})
|
||||
# Group by cash basis account and tax
|
||||
self.env['account.move.line'].with_context(check_move_validity=False).create({
|
||||
'name': line.name,
|
||||
'credit': rounded_amt > 0 and rounded_amt or 0.0,
|
||||
'debit': rounded_amt < 0 and abs(rounded_amt) or 0.0,
|
||||
'account_id': account_id.id,
|
||||
'debit': rounded_amt if rounded_amt > 0 else 0.0,
|
||||
'credit': abs(rounded_amt) if rounded_amt < 0 else 0.0,
|
||||
'account_id': line.tax_line_id.cash_basis_account_id.id,
|
||||
'analytic_account_id': line.analytic_account_id.id,
|
||||
'analytic_tag_ids': line.analytic_tag_ids.ids,
|
||||
'tax_line_id': line.tax_line_id.id,
|
||||
'tax_exigible': True,
|
||||
'move_id': newly_created_move.id,
|
||||
'amount_currency': line.amount_currency and line.currency_id.round(line.amount_currency * amount / line.balance) or 0.0,
|
||||
'currency_id': line.currency_id.id,
|
||||
'amount_currency': self.amount_currency and line.currency_id.round(-line.amount_currency * amount / line.balance) or 0.0,
|
||||
'move_id': newly_created_move.id,
|
||||
'partner_id': line.partner_id.id,
|
||||
})
|
||||
if line.account_id.reconcile:
|
||||
#setting the account to allow reconciliation will help to fix rounding errors
|
||||
to_clear_aml |= line
|
||||
to_clear_aml.reconcile()
|
||||
|
||||
if any([tax.tax_exigibility == 'on_payment' for tax in line.tax_ids]):
|
||||
if not newly_created_move:
|
||||
newly_created_move = self._create_tax_basis_move()
|
||||
#create cash basis entry for the base
|
||||
for tax in line.tax_ids.filtered(lambda t: t.tax_exigibility == 'on_payment'):
|
||||
account_id = self._get_tax_cash_basis_base_account(line, tax)
|
||||
self.env['account.move.line'].with_context(check_move_validity=False).create({
|
||||
'name': line.name,
|
||||
'debit': rounded_amt > 0 and rounded_amt or 0.0,
|
||||
'credit': rounded_amt < 0 and abs(rounded_amt) or 0.0,
|
||||
'account_id': account_id.id,
|
||||
'tax_exigible': True,
|
||||
'tax_ids': [(6, 0, [tax.id])],
|
||||
'move_id': newly_created_move.id,
|
||||
'currency_id': line.currency_id.id,
|
||||
'amount_currency': self.amount_currency and line.currency_id.round(line.amount_currency * amount / line.balance) or 0.0,
|
||||
'partner_id': line.partner_id.id,
|
||||
})
|
||||
self.env['account.move.line'].with_context(check_move_validity=False).create({
|
||||
'name': line.name,
|
||||
'credit': rounded_amt > 0 and rounded_amt or 0.0,
|
||||
'debit': rounded_amt < 0 and abs(rounded_amt) or 0.0,
|
||||
'account_id': account_id.id,
|
||||
'tax_exigible': True,
|
||||
'move_id': newly_created_move.id,
|
||||
'currency_id': line.currency_id.id,
|
||||
'amount_currency': self.amount_currency and line.currency_id.round(-line.amount_currency * amount / line.balance) or 0.0,
|
||||
'partner_id': line.partner_id.id,
|
||||
})
|
||||
self.recompute()
|
||||
if newly_created_move:
|
||||
if move_date > (self.company_id.period_lock_date or date.min) and newly_created_move.date != move_date:
|
||||
# The move date should be the maximum date between payment and invoice (in case
|
||||
|
||||
@@ -211,6 +211,8 @@ class account_abstract_payment(models.AbstractModel):
|
||||
self.amount = abs(self._compute_payment_amount())
|
||||
|
||||
# Set by default the first liquidity journal having this currency if exists.
|
||||
if self.journal_id:
|
||||
return
|
||||
journal = self.env['account.journal'].search(
|
||||
[('type', 'in', ('bank', 'cash')), ('currency_id', '=', self.currency_id.id)], limit=1)
|
||||
if journal:
|
||||
|
||||
@@ -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 self.existing_accounting(company):
|
||||
if not company.chart_template_id and 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):
|
||||
|
||||
@@ -68,7 +68,7 @@ access_account_account_type_invoice,account.account.type invoice,model_account_a
|
||||
access_account_account_tax,account.account.tag,model_account_account_tag,account.group_account_user,1,1,1,1
|
||||
access_account_account_tax_user,account.account.tag,model_account_account_tag,account.group_account_invoice,1,0,0,0
|
||||
access_account_reconcile_model,account.reconcile.model,model_account_reconcile_model,account.group_account_user,1,1,1,1
|
||||
access_account_reconcile_model_template,account.reconcile.model.template,model_account_reconcile_model_template,account.group_account_user,1,1,1,1
|
||||
access_account_reconcile_model_template,account.reconcile.model.template,model_account_reconcile_model_template,account.group_account_invoice,1,1,1,1
|
||||
access_account_partial_reconcile_group_invoice,account.partial.reconcile.group.invoice,model_account_partial_reconcile,account.group_account_invoice,1,1,1,0
|
||||
access_account_partial_reconcile,account.partial.reconcile,model_account_partial_reconcile,account.group_account_user,1,1,1,1
|
||||
access_account_full_reconcile_group_invoice,account.full.reconcile.group.invoice,model_account_full_reconcile,account.group_account_invoice,1,1,1,0
|
||||
|
||||
|
@@ -925,11 +925,11 @@ var StatementModel = BasicModel.extend({
|
||||
});
|
||||
var company_currency = session.get_currency(line.st_line.currency_id);
|
||||
var company_precision = company_currency && company_currency.digits[1] || 2;
|
||||
total = utils.round_decimals(total*1000, company_precision)/1000 || 0;
|
||||
total = utils.round_decimals(total, company_precision) || 0;
|
||||
if(isOtherCurrencyId){
|
||||
var other_currency = session.get_currency(isOtherCurrencyId);
|
||||
var other_precision = other_currency && other_currency.digits[1] || 2;
|
||||
amount_currency = utils.round_decimals(amount_currency, other_precision)
|
||||
amount_currency = utils.round_decimals(amount_currency, other_precision);
|
||||
}
|
||||
line.balance = {
|
||||
amount: total,
|
||||
|
||||
@@ -262,7 +262,7 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
|
||||
'click .accounting_view thead td': '_onTogglePanel',
|
||||
'click .accounting_view tfoot td:not(.cell_left,.cell_right)': '_onShowPanel',
|
||||
'click .cell': '_onEditAmount',
|
||||
'input input.filter': '_onFilterChange',
|
||||
'change input.filter': '_onFilterChange',
|
||||
'click .match .load-more a': '_onLoadMore',
|
||||
'click .match .mv_line td': '_onSelectMoveLine',
|
||||
'click .accounting_view tbody .mv_line td': '_onSelectProposition',
|
||||
|
||||
@@ -149,7 +149,8 @@
|
||||
|
||||
<div t-name="reconciliation.line.match">
|
||||
<div class="match_controls">
|
||||
<input class="filter" placeholder="Filter..." type="text" value=""/>
|
||||
<input class="filter o_input" placeholder="Filter..." type="text" value=""/>
|
||||
<button class="btn btn-secondary btn-sm fa fa-search" type="button"></button>
|
||||
</div>
|
||||
<table>
|
||||
<tbody>
|
||||
|
||||
@@ -46,6 +46,12 @@ class TestAccountNoChartCommon(SavepointCase):
|
||||
'property_account_receivable_id': cls.account_receivable.id,
|
||||
})
|
||||
|
||||
cls.sale_journal0 = cls.env['account.journal'].create({
|
||||
'name': 'Sale Journal',
|
||||
'type': 'sale',
|
||||
'code': 'SJT0',
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def setUpAdditionalAccounts(cls):
|
||||
""" Set up some addionnal accounts: expenses, revenue, ... """
|
||||
|
||||
@@ -530,6 +530,14 @@ class TestReconciliation(AccountingTestCase):
|
||||
self.diff_income_account.id: {'debit': 0.0, 'credit': 3.27, 'amount_currency': -5, 'currency_id': self.currency_usd_id},
|
||||
self.account_rcv.id: {'debit': 0.0, 'credit': 52.33, 'amount_currency': -80, 'currency_id': self.currency_usd_id},
|
||||
}
|
||||
|
||||
payments = bank_stmt_aml.mapped('payment_id')
|
||||
# creation and reconciliation of the over-amount statement
|
||||
# has created an another payment
|
||||
self.assertEqual(len(payments), 2)
|
||||
# Check amount of second, automatically created payment
|
||||
self.assertEqual((payments - payment).amount, 5)
|
||||
|
||||
for aml in bank_stmt_aml:
|
||||
line = lines[aml.account_id.id]
|
||||
if type(line) == list:
|
||||
|
||||
@@ -96,7 +96,6 @@
|
||||
</div>
|
||||
<div class="o_setting_right_pane" name="account_taxcloud_right_pane">
|
||||
<label for="module_account_taxcloud" string="TaxCloud"/>
|
||||
<span class="fa fa-lg fa-building-o" title="Values set here are company-specific." aria-label="Values set here are company-specific." groups="base.group_multi_company" role="img"/>
|
||||
<div class="text-muted">
|
||||
Compute tax rates based on U.S. ZIP codes
|
||||
</div>
|
||||
|
||||
@@ -3,14 +3,21 @@
|
||||
# * account_bank_statement_import
|
||||
#
|
||||
# Translators:
|
||||
# Bayarkhuu Bataa, 2018
|
||||
# Martin Trigaux, 2018
|
||||
# Otgonbayar.A <gobi.mn@gmail.com>, 2018
|
||||
# nasaaskii <nasaaskii@gmail.com>, 2018
|
||||
# Onii Onii <onii0223@yahoo.com>, 2018
|
||||
# Khishigbat Ganbold <khishigbat@asterisk-tech.mn>, 2018
|
||||
# Baskhuu Lodoikhuu <baskhuujacara@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: Martin Trigaux, 2018\n"
|
||||
"PO-Revision-Date: 2016-08-05 12:55+0000\n"
|
||||
"Last-Translator: Baskhuu Lodoikhuu <baskhuujacara@gmail.com>, 2019\n"
|
||||
"Language-Team: Mongolian (https://www.transifex.com/odoo/teams/41243/mn/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -222,12 +229,12 @@ msgstr "Үүсгэсэн"
|
||||
#. module: account_bank_statement_import
|
||||
#: model:ir.model.fields,field_description:account_bank_statement_import.field_account_bank_statement_import_journal_creation__refund_sequence_id
|
||||
msgid "Credit Note Entry Sequence"
|
||||
msgstr ""
|
||||
msgstr "Буцаалтын гүйлгээний дугаарлалт"
|
||||
|
||||
#. module: account_bank_statement_import
|
||||
#: model:ir.model.fields,field_description:account_bank_statement_import.field_account_bank_statement_import_journal_creation__refund_sequence_number_next
|
||||
msgid "Credit Notes: Next Number"
|
||||
msgstr ""
|
||||
msgstr "Буцаалт: Дараагийн дугаар"
|
||||
|
||||
#. module: account_bank_statement_import
|
||||
#: model:ir.model.fields,field_description:account_bank_statement_import.field_account_bank_statement_import_journal_creation__currency_id
|
||||
@@ -281,12 +288,12 @@ msgstr "Файлын нэр"
|
||||
#. 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 "Ирж буй төлбөрийн хувьд"
|
||||
|
||||
#. 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 "Гарч буй төлбөрийн хувьд"
|
||||
|
||||
#. module: account_bank_statement_import
|
||||
#: model:ir.model.fields,help:account_bank_statement_import.field_account_bank_statement_import__data_file
|
||||
@@ -398,12 +405,12 @@ msgstr ""
|
||||
#. module: account_bank_statement_import
|
||||
#: model:ir.model.fields,field_description:account_bank_statement_import.field_account_bank_statement_import_journal_creation__kanban_dashboard
|
||||
msgid "Kanban Dashboard"
|
||||
msgstr ""
|
||||
msgstr "Канбан самбар"
|
||||
|
||||
#. module: account_bank_statement_import
|
||||
#: model:ir.model.fields,field_description:account_bank_statement_import.field_account_bank_statement_import_journal_creation__kanban_dashboard_graph
|
||||
msgid "Kanban Dashboard Graph"
|
||||
msgstr ""
|
||||
msgstr "Канбан самбар граффик"
|
||||
|
||||
#. module: account_bank_statement_import
|
||||
#: model:ir.model.fields,field_description:account_bank_statement_import.field_account_bank_statement_import____last_update
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
# Haojun Zou <apollo_zhj@msn.com>, 2018
|
||||
# Martin Wilderoth <martin.wilderoth@linserv.se>, 2018
|
||||
# Patrik Lermon <patrik.lermon@gmail.com>, 2018
|
||||
# Kim Asplund <kim.asplund@gmail.com>, 2018
|
||||
# Kristoffer Grundström <hamnisdude@gmail.com>, 2019
|
||||
# Kim Asplund <kim.asplund@gmail.com>, 2019
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@@ -17,7 +17,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: Kristoffer Grundström <hamnisdude@gmail.com>, 2019\n"
|
||||
"Last-Translator: Kim Asplund <kim.asplund@gmail.com>, 2019\n"
|
||||
"Language-Team: Swedish (https://www.transifex.com/odoo/teams/41243/sv/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -161,7 +161,7 @@ msgstr ""
|
||||
#. module: account_check_printing
|
||||
#: model:ir.model,name:account_check_printing.model_res_config_settings
|
||||
msgid "Config Settings"
|
||||
msgstr ""
|
||||
msgstr "Konfigurations Inställningar"
|
||||
|
||||
#. module: account_check_printing
|
||||
#: model:ir.model.fields,field_description:account_check_printing.field_print_prenumbered_checks__create_uid
|
||||
|
||||
@@ -64,12 +64,11 @@ class AccountInvoice(models.Model):
|
||||
with Form(self_ctx, view='account.invoice_supplier_form') as invoice_form:
|
||||
|
||||
# Partner (first step to avoid warning 'Warning! You must first select a partner.').
|
||||
elements = tree.xpath('//ram:SellerTradeParty/ram:Name', namespaces=tree.nsmap)
|
||||
partner_name = elements and elements[0].text
|
||||
|
||||
elements = tree.xpath('//ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID', namespaces=tree.nsmap)
|
||||
partner = elements and self.env['res.partner'].search([('vat', '=', elements[0].text)], limit=1)
|
||||
if not partner:
|
||||
elements = tree.xpath('//ram:SellerTradeParty/ram:Name', namespaces=tree.nsmap)
|
||||
partner_name = elements and elements[0].text
|
||||
partner = elements and self.env['res.partner'].search([('name', 'ilike', partner_name)], limit=1)
|
||||
if not partner:
|
||||
elements = tree.xpath('//ram:SellerTradeParty//ram:URIID[@schemeID=\'SMTP\']', namespaces=tree.nsmap)
|
||||
@@ -161,20 +160,20 @@ class AccountInvoice(models.Model):
|
||||
if product:
|
||||
invoice_line_form.product_id = product
|
||||
|
||||
# Price Unit.
|
||||
line_elements = element.xpath('.//ram:GrossPriceProductTradePrice/ram:ChargeAmount', namespaces=tree.nsmap)
|
||||
if line_elements:
|
||||
invoice_line_form.price_unit = float(line_elements[0].text)
|
||||
else:
|
||||
line_elements = element.xpath('.//ram:NetPriceProductTradePrice/ram:ChargeAmount', namespaces=tree.nsmap)
|
||||
if line_elements:
|
||||
invoice_line_form.price_unit = float(line_elements[0].text)
|
||||
|
||||
# Quantity.
|
||||
line_elements = element.xpath('.//ram:SpecifiedLineTradeDelivery/ram:BilledQuantity', namespaces=tree.nsmap)
|
||||
if line_elements:
|
||||
invoice_line_form.quantity = float(line_elements[0].text) * refund_sign
|
||||
|
||||
# Price Unit.
|
||||
line_elements = element.xpath('.//ram:GrossPriceProductTradePrice/ram:ChargeAmount', namespaces=tree.nsmap)
|
||||
if line_elements:
|
||||
invoice_line_form.price_unit = float(line_elements[0].text) / invoice_line_form.quantity
|
||||
else:
|
||||
line_elements = element.xpath('.//ram:NetPriceProductTradePrice/ram:ChargeAmount', namespaces=tree.nsmap)
|
||||
if line_elements:
|
||||
invoice_line_form.price_unit = float(line_elements[0].text) / invoice_line_form.quantity
|
||||
|
||||
# Discount.
|
||||
line_elements = element.xpath('.//ram:AppliedTradeAllowanceCharge/ram:CalculationPercent', namespaces=tree.nsmap)
|
||||
if line_elements:
|
||||
|
||||
@@ -3,15 +3,21 @@
|
||||
# * account_voucher
|
||||
#
|
||||
# Translators:
|
||||
# Bayarkhuu Bataa, 2018
|
||||
# Martin Trigaux, 2018
|
||||
# nasaaskii <nasaaskii@gmail.com>, 2018
|
||||
# Otgonbayar.A <gobi.mn@gmail.com>, 2018
|
||||
# Khishigbat Ganbold <khishigbat@asterisk-tech.mn>, 2018
|
||||
# Onii Onii <onii0223@yahoo.com>, 2018
|
||||
# nasaaskii <nasaaskii@gmail.com>, 2019
|
||||
# Baskhuu Lodoikhuu <baskhuujacara@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: nasaaskii <nasaaskii@gmail.com>, 2018\n"
|
||||
"PO-Revision-Date: 2016-08-05 12:55+0000\n"
|
||||
"Last-Translator: Baskhuu Lodoikhuu <baskhuujacara@gmail.com>, 2019\n"
|
||||
"Language-Team: Mongolian (https://www.transifex.com/odoo/teams/41243/mn/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -258,7 +264,7 @@ msgstr "Хэрэв сонгосон бол, шинэ зурвасуудад ан
|
||||
#. module: account_voucher
|
||||
#: model:ir.model.fields,help:account_voucher.field_account_voucher__message_has_error
|
||||
msgid "If checked, some messages have a delivery error."
|
||||
msgstr ""
|
||||
msgstr "Үүнийг сонговол алдаа үүсэх үед зурвасууд ирнэ."
|
||||
|
||||
#. module: account_voucher
|
||||
#: model:ir.model.fields,help:account_voucher.field_account_voucher__tax_correction
|
||||
@@ -317,7 +323,7 @@ msgstr "Сүүлийн засвар хийсэн огноо"
|
||||
#. module: account_voucher
|
||||
#: model:ir.model.fields,field_description:account_voucher.field_account_voucher__message_main_attachment_id
|
||||
msgid "Main Attachment"
|
||||
msgstr ""
|
||||
msgstr "Үндсэн хавсралт"
|
||||
|
||||
#. module: account_voucher
|
||||
#: model:ir.model.fields,field_description:account_voucher.field_account_voucher__message_has_error
|
||||
@@ -359,7 +365,7 @@ msgstr "Үйлдлийн тоо"
|
||||
#. module: account_voucher
|
||||
#: model:ir.model.fields,field_description:account_voucher.field_account_voucher__message_has_error_counter
|
||||
msgid "Number of error"
|
||||
msgstr ""
|
||||
msgstr "Алдааны тоо"
|
||||
|
||||
#. module: account_voucher
|
||||
#: model:ir.model.fields,help:account_voucher.field_account_voucher__message_needaction_counter
|
||||
@@ -369,7 +375,7 @@ msgstr "Үйлдэл шаардлагатай зурвасын тоо"
|
||||
#. module: account_voucher
|
||||
#: model:ir.model.fields,help:account_voucher.field_account_voucher__message_has_error_counter
|
||||
msgid "Number of messages with delivery error"
|
||||
msgstr ""
|
||||
msgstr "Алдааны мэдэгдэл бүхий зурвасын тоо"
|
||||
|
||||
#. module: account_voucher
|
||||
#: model:ir.model.fields,help:account_voucher.field_account_voucher__message_unread_counter
|
||||
|
||||
@@ -274,6 +274,8 @@ class AccountVoucher(models.Model):
|
||||
:return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
|
||||
:rtype: tuple(float, list of int)
|
||||
'''
|
||||
tax_calculation_rounding_method = self.env.user.company_id.tax_calculation_rounding_method
|
||||
tax_lines_vals = []
|
||||
for line in self.line_ids:
|
||||
#create one move line per voucher line where amount is not 0.0
|
||||
if not line.price_subtotal:
|
||||
@@ -331,6 +333,33 @@ class AccountVoucher(models.Model):
|
||||
self.env['account.move.line'].create(temp)
|
||||
|
||||
self.env['account.move.line'].create(move_line)
|
||||
# When global rounding is activated, we must wait until all tax lines are computed to
|
||||
# merge them.
|
||||
if tax_calculation_rounding_method == 'round_globally':
|
||||
tax_lines_vals += self.env['account.move.line'].with_context(round=False)._apply_taxes(
|
||||
move_line,
|
||||
move_line.get('debit', 0.0) - move_line.get('credit', 0.0)
|
||||
)
|
||||
|
||||
# When round globally is set, we merge the tax lines
|
||||
if tax_calculation_rounding_method == 'round_globally':
|
||||
tax_lines_vals_merged = {}
|
||||
for tax_line_vals in tax_lines_vals:
|
||||
key = (
|
||||
tax_line_vals['tax_line_id'],
|
||||
tax_line_vals['account_id'],
|
||||
tax_line_vals['analytic_account_id'],
|
||||
)
|
||||
if key not in tax_lines_vals_merged:
|
||||
tax_lines_vals_merged[key] = tax_line_vals
|
||||
else:
|
||||
tax_lines_vals_merged[key]['debit'] += tax_line_vals['debit']
|
||||
tax_lines_vals_merged[key]['credit'] += tax_line_vals['credit']
|
||||
currency = self.env['res.currency'].browse(company_currency)
|
||||
for vals in tax_lines_vals_merged.values():
|
||||
vals['debit'] = currency.round(vals['debit'])
|
||||
vals['credit'] = currency.round(vals['credit'])
|
||||
self.env['account.move.line'].create(vals)
|
||||
return line_total
|
||||
|
||||
@api.multi
|
||||
@@ -363,12 +392,12 @@ class AccountVoucher(models.Model):
|
||||
line_total = voucher.with_context(ctx).voucher_move_line_create(line_total, move.id, company_currency, current_currency)
|
||||
|
||||
# Create a payment to allow the reconciliation when pay_now = 'pay_now'.
|
||||
if self.pay_now == 'pay_now':
|
||||
payment_id = self.env['account.payment'].create(self.voucher_pay_now_payment_create())
|
||||
if voucher.pay_now == 'pay_now':
|
||||
payment_id = self.env['account.payment'].create(voucher.voucher_pay_now_payment_create())
|
||||
payment_id.post()
|
||||
|
||||
# Reconcile the receipt with the payment
|
||||
lines_to_reconcile = (payment_id.move_line_ids + move.line_ids).filtered(lambda l: l.account_id == self.account_id)
|
||||
lines_to_reconcile = (payment_id.move_line_ids + move.line_ids).filtered(lambda l: l.account_id == voucher.account_id)
|
||||
lines_to_reconcile.reconcile()
|
||||
|
||||
# Add tax correction to move line if any tax correction specified
|
||||
|
||||
@@ -3,14 +3,20 @@
|
||||
# * analytic
|
||||
#
|
||||
# Translators:
|
||||
# Martin Trigaux, 2018
|
||||
# Otgonbayar.A <gobi.mn@gmail.com>, 2018
|
||||
# Khishigbat Ganbold <khishigbat@asterisk-tech.mn>, 2018
|
||||
# Tsogjav <Tsogjav2007@yahoo.com>, 2018
|
||||
# Onii Onii <onii0223@yahoo.com>, 2018
|
||||
# Martin Trigaux, 2019
|
||||
# Baskhuu Lodoikhuu <baskhuujacara@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: Martin Trigaux, 2018\n"
|
||||
"PO-Revision-Date: 2016-08-05 12:56+0000\n"
|
||||
"Last-Translator: Baskhuu Lodoikhuu <baskhuujacara@gmail.com>, 2019\n"
|
||||
"Language-Team: Mongolian (https://www.transifex.com/odoo/teams/41243/mn/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -328,7 +334,7 @@ msgstr "Хэрэв сонгосон бол, шинэ зурвасуудад ан
|
||||
#. module: analytic
|
||||
#: model:ir.model.fields,help:analytic.field_account_analytic_account__message_has_error
|
||||
msgid "If checked, some messages have a delivery error."
|
||||
msgstr ""
|
||||
msgstr "Үүнийг сонговол алдаа үүсэх үед зурвасууд ирнэ."
|
||||
|
||||
#. module: analytic
|
||||
#: model:ir.model.fields,help:analytic.field_account_analytic_account__active
|
||||
@@ -382,7 +388,7 @@ msgstr "Сүүлийн засвар хийсэн огноо"
|
||||
#. module: analytic
|
||||
#: model:ir.model.fields,field_description:analytic.field_account_analytic_account__message_main_attachment_id
|
||||
msgid "Main Attachment"
|
||||
msgstr ""
|
||||
msgstr "Үндсэн хавсралт"
|
||||
|
||||
#. module: analytic
|
||||
#: model:ir.model.fields,field_description:analytic.field_account_analytic_account__message_has_error
|
||||
@@ -419,7 +425,7 @@ msgstr "Үйлдлийн тоо"
|
||||
#. module: analytic
|
||||
#: model:ir.model.fields,field_description:analytic.field_account_analytic_account__message_has_error_counter
|
||||
msgid "Number of error"
|
||||
msgstr ""
|
||||
msgstr "Алдааны тоо"
|
||||
|
||||
#. module: analytic
|
||||
#: model:ir.model.fields,help:analytic.field_account_analytic_account__message_needaction_counter
|
||||
@@ -429,7 +435,7 @@ msgstr "Үйлдэл шаардлагатай зурвасын тоо"
|
||||
#. module: analytic
|
||||
#: model:ir.model.fields,help:analytic.field_account_analytic_account__message_has_error_counter
|
||||
msgid "Number of messages with delivery error"
|
||||
msgstr ""
|
||||
msgstr "Алдааны мэдэгдэл бүхий зурвасын тоо"
|
||||
|
||||
#. module: analytic
|
||||
#: model:ir.model.fields,help:analytic.field_account_analytic_account__message_unread_counter
|
||||
|
||||
@@ -53,8 +53,8 @@ class ResPartner(models.Model):
|
||||
the url state components (menu_id, id, view_type) """
|
||||
|
||||
res = dict.fromkeys(self.ids, False)
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||
for partner in self:
|
||||
base_url = partner.get_base_url()
|
||||
# when required, make sure the partner has a valid signup token
|
||||
if self.env.context.get('signup_valid') and not partner.user_ids:
|
||||
partner.sudo().signup_prepare()
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
<xpath expr="//button[@name='create_action']" position="replace">
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='unlink_action']" position="replace">
|
||||
<button name="unlink_action_for_menu" string="Remove Action" type="object"
|
||||
attrs="{'invisible':[('binding_model_id','=',False)]}"
|
||||
help="Remove the contextual action related to this server action"/>
|
||||
</xpath>
|
||||
<div class="oe_title" position="before">
|
||||
<div class="oe_button_box" name="button_box">
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<form string="Import module">
|
||||
<field name="state" invisible="1"/>
|
||||
<separator string="Import Module" colspan="4"/>
|
||||
<p class="alert alert-warning" role="alert">Note: you can only import data modules (.xml files and static assets)</p>
|
||||
<group states="init" col="4">
|
||||
<label for="module_file" string="Select module package to import (.zip file):" colspan="4"/>
|
||||
<field name="module_file" colspan="4"/>
|
||||
|
||||
@@ -56,6 +56,7 @@ var LongpollingBus = Bus.extend(ServicesMixin, {
|
||||
$(window).off("click." + this._id);
|
||||
$(window).off("keydown." + this._id);
|
||||
$(window).off("keyup." + this._id);
|
||||
this._super();
|
||||
},
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
|
||||
@@ -75,7 +75,7 @@ class Lead(models.Model):
|
||||
kanban_state = fields.Selection([('grey', 'No next activity planned'), ('red', 'Next activity late'), ('green', 'Next activity is planned')],
|
||||
string='Kanban State', compute='_compute_kanban_state')
|
||||
email_cc = fields.Text('Global CC', help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma")
|
||||
description = fields.Text('Notes', tracking=60)
|
||||
description = fields.Text('Notes')
|
||||
tag_ids = fields.Many2many('crm.lead.tag', 'crm_lead_tag_rel', 'lead_id', 'tag_id', string='Tags', help="Classify and analyze your lead/opportunity categories like: Training, Service")
|
||||
contact_name = fields.Char('Contact Name', tracking=30)
|
||||
partner_name = fields.Char("Company Name", tracking=20, index=True, help='The name of the future partner company that will be created while converting the lead into opportunity')
|
||||
|
||||
@@ -980,7 +980,6 @@ for rec in records:
|
||||
<field name="domain">[('type','=','opportunity')]</field>
|
||||
<field name="context">{
|
||||
'default_type': 'opportunity',
|
||||
'default_user_id': uid
|
||||
}
|
||||
</field>
|
||||
<field name="search_view_id" ref="crm.view_crm_case_opportunities_filter"/>
|
||||
@@ -1042,7 +1041,6 @@ for rec in records:
|
||||
<field name="domain">[('type','=','opportunity')]</field>
|
||||
<field name="context">{
|
||||
'default_type': 'opportunity',
|
||||
'default_user_id': uid,
|
||||
'search_default_assigned_to_me': 1
|
||||
}</field>
|
||||
<field name="search_view_id" ref="crm.view_crm_case_opportunities_filter"/>
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
<table style="width: 100%; border-spacing: 0; font-family: Helvetica,Arial,Verdana,sans-serif;">
|
||||
<tr>
|
||||
<td align="center" valign="top" style="border-collapse: collapse; padding: 0">
|
||||
% set company, user = ctx['company'], ctx['user']
|
||||
% set user = ctx.get('user', user)
|
||||
% set company = user.company_id
|
||||
% set data = object.compute_kpis(company, user)
|
||||
% set tips = object.compute_tips(company, user)
|
||||
% set kpi_actions = object.compute_kpis_actions(company, user)
|
||||
@@ -136,7 +137,7 @@
|
||||
<table style="margin-top: 5px; border: 1px solid #e7e7e7; font-size: 15px; width: 100%; max-width: 600px;">
|
||||
<tr>
|
||||
<td style="border-collapse: collapse; margin: 0; padding: 10px 20px;">
|
||||
% if ctx['user'].has_group('base.group_system'):
|
||||
% if user.has_group('base.group_system'):
|
||||
<div style="margin-top: 20px;">
|
||||
Want to customize the email?
|
||||
<a href="/web#view_type=form&model=digest.digest&id=${object.id}" target="_blank" style="color: #875A7B;">Choose the metrics you care about</a>
|
||||
|
||||
@@ -101,7 +101,7 @@ class Digest(models.Model):
|
||||
for digest in self:
|
||||
for user in digest.user_ids:
|
||||
subject = '%s: %s' % (user.company_id.name, digest.name)
|
||||
digest.template_id.with_context(user=user, company=user.company_id).send_mail(digest.id, force_send=True, raise_exception=True, email_values={'email_to': user.email, 'subject': subject})
|
||||
digest.template_id.with_context(user=user).send_mail(digest.id, force_send=True, raise_exception=True, email_values={'email_to': user.email, 'subject': subject})
|
||||
digest.next_run_date = digest._get_next_run_date()
|
||||
|
||||
def compute_kpis(self, company, user):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo><data noupdate="1">
|
||||
<odoo><data noupdate="0">
|
||||
<!-- Set demo karma for test tour as demo user-->
|
||||
<record id="base.user_demo" model="res.users">
|
||||
<field name="karma">20</field>
|
||||
@@ -15,7 +15,7 @@
|
||||
<field name="description_motivational" type="html">
|
||||
<div class="media align-items-center">
|
||||
<div class="media-body">Reach the next rank and gain a very nice mug !</div>
|
||||
<img class="ml-3 img img-fluid" style="max-height: 72px;" src="/gamification/static/img/rank_misc_mug.jpg"/>
|
||||
<img class="ml-3 img img-fluid" style="max-height: 72px;" src="/gamification/static/img/rank_misc_mug.png"/>
|
||||
</div></field>
|
||||
<field name="karma_min">100</field>
|
||||
<field name="image" type="base64" file="gamification/static/img/rank_student_badge.svg"/>
|
||||
@@ -27,7 +27,7 @@
|
||||
<field name="description_motivational" type="html">
|
||||
<div class="media align-items-center">
|
||||
<div class="media-body">Reach the next rank and gain a very magic wand !</div>
|
||||
<img class="ml-3 img img-fluid" style="max-height: 72px;" src="/gamification/static/img/rank_misc_wand.jpg"/>
|
||||
<img class="ml-3 img img-fluid" style="max-height: 72px;" src="/gamification/static/img/rank_misc_wand.png"/>
|
||||
</div></field>
|
||||
<field name="karma_min">500</field>
|
||||
<field name="image" type="base64" file="gamification/static/img/rank_bachelor_badge.svg"/>
|
||||
@@ -39,7 +39,7 @@
|
||||
<field name="description_motivational" type="html">
|
||||
<div class="media align-items-center">
|
||||
<div class="media-body">Reach the next rank and gain a very nice hat !</div>
|
||||
<img class="ml-3 img img-fluid" style="max-height: 72px;" src="/gamification/static/img/rank_misc_hat.jpg"/>
|
||||
<img class="ml-3 img img-fluid" style="max-height: 72px;" src="/gamification/static/img/rank_misc_hat.png"/>
|
||||
</div></field>
|
||||
<field name="karma_min">2000</field>
|
||||
<field name="image" type="base64" file="gamification/static/img/rank_master_badge.svg"/>
|
||||
@@ -51,7 +51,7 @@
|
||||
<field name="description_motivational" type="html">
|
||||
<div class="media align-items-center">
|
||||
<div class="media-body">Reach the next rank and gain a week-end with a unicorn !</div>
|
||||
<img class="ml-3 img img-fluid" style="max-height: 72px;" src="/gamification/static/img/rank_misc_unicorn.jpg"/>
|
||||
<img class="ml-3 img img-fluid" style="max-height: 72px;" src="/gamification/static/img/rank_misc_unicorn.png"/>
|
||||
</div></field>
|
||||
<field name="karma_min">10000</field>
|
||||
<field name="image" type="base64" file="gamification/static/img/rank_doctor_badge.svg"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 38 KiB |
@@ -442,7 +442,9 @@ class GoogleCalendar(models.AbstractModel):
|
||||
if google_attendee.get('found'):
|
||||
continue
|
||||
|
||||
attendee = ResPartner.search([('email', '=', google_attendee['email'])], limit=1)
|
||||
attendee = ResPartner.search([('email', '=ilike', google_attendee['email']), ('user_ids', '!=', False)], limit=1)
|
||||
if not attendee:
|
||||
attendee = ResPartner.search([('email', '=ilike', google_attendee['email'])], limit=1)
|
||||
if not attendee:
|
||||
data = {
|
||||
'email': partner_email,
|
||||
|
||||
@@ -21,6 +21,13 @@
|
||||
<field name="groups_id" eval="[(4,ref('group_hr_manager'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_employee_comp_rule" model="ir.rule">
|
||||
<field name="name">Employee multi company rule</field>
|
||||
<field name="model_id" ref="model_hr_employee"/>
|
||||
<field name="global" eval="True"/>
|
||||
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_dept_comp_rule" model="ir.rule">
|
||||
<field name="name">Department multi company rule</field>
|
||||
<field name="model_id" ref="model_hr_department"/>
|
||||
|
||||
@@ -773,6 +773,17 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_users_form_simple_modif_resource" model="ir.ui.view">
|
||||
<field name="name">res.users.preferences.form.resource</field>
|
||||
<field name="model">res.users</field>
|
||||
<field name="inherit_id" ref="base.view_users_form_simple_modif" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="tz" position="attributes">
|
||||
<attribute name="required">1</attribute>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
action="open_module_tree_department"
|
||||
id="menu_hr_department_tree"
|
||||
|
||||
@@ -9,9 +9,8 @@ var QWeb = core.qweb;
|
||||
var KioskConfirm = AbstractAction.extend({
|
||||
events: {
|
||||
"click .o_hr_attendance_back_button": function () { this.do_action(this.next_action, {clear_breadcrumbs: true}); },
|
||||
"click .o_hr_attendance_sign_in_out_icon": function () {
|
||||
"click .o_hr_attendance_sign_in_out_icon": _.debounce(function () {
|
||||
var self = this;
|
||||
this.$('.o_hr_attendance_sign_in_out_icon').attr("disabled", "disabled");
|
||||
this._rpc({
|
||||
model: 'hr.employee',
|
||||
method: 'attendance_manual',
|
||||
@@ -22,10 +21,9 @@ var KioskConfirm = AbstractAction.extend({
|
||||
self.do_action(result.action);
|
||||
} else if (result.warning) {
|
||||
self.do_warn(result.warning);
|
||||
self.$('.o_hr_attendance_sign_in_out_icon').removeAttr("disabled");
|
||||
}
|
||||
});
|
||||
},
|
||||
}, 200, true),
|
||||
'click .o_hr_attendance_pin_pad_button_0': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 0); },
|
||||
'click .o_hr_attendance_pin_pad_button_1': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 1); },
|
||||
'click .o_hr_attendance_pin_pad_button_2': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 2); },
|
||||
@@ -37,7 +35,7 @@ var KioskConfirm = AbstractAction.extend({
|
||||
'click .o_hr_attendance_pin_pad_button_8': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 8); },
|
||||
'click .o_hr_attendance_pin_pad_button_9': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 9); },
|
||||
'click .o_hr_attendance_pin_pad_button_C': function() { this.$('.o_hr_attendance_PINbox').val(''); },
|
||||
'click .o_hr_attendance_pin_pad_button_ok': function() {
|
||||
'click .o_hr_attendance_pin_pad_button_ok': _.debounce(function() {
|
||||
var self = this;
|
||||
this.$('.o_hr_attendance_pin_pad_button_ok').attr("disabled", "disabled");
|
||||
this._rpc({
|
||||
@@ -54,7 +52,7 @@ var KioskConfirm = AbstractAction.extend({
|
||||
setTimeout( function() { self.$('.o_hr_attendance_pin_pad_button_ok').removeAttr("disabled"); }, 500);
|
||||
}
|
||||
});
|
||||
},
|
||||
}, 200, true),
|
||||
},
|
||||
|
||||
init: function (parent, action) {
|
||||
|
||||
@@ -8,10 +8,9 @@ var core = require('web.core');
|
||||
var MyAttendances = AbstractAction.extend({
|
||||
contentTemplate: 'HrAttendanceMyMainMenu',
|
||||
events: {
|
||||
"click .o_hr_attendance_sign_in_out_icon": function() {
|
||||
this.$('.o_hr_attendance_sign_in_out_icon').attr("disabled", "disabled");
|
||||
"click .o_hr_attendance_sign_in_out_icon": _.debounce(function() {
|
||||
this.update_attendance();
|
||||
},
|
||||
}, 200, true),
|
||||
},
|
||||
|
||||
willStart: function () {
|
||||
|
||||
@@ -276,7 +276,7 @@ class HrExpense(models.Model):
|
||||
account_date = expense.sheet_id.accounting_date or expense.date or fields.Date.context_today(expense)
|
||||
|
||||
company_currency = expense.company_id.currency_id
|
||||
different_currency = expense.currency_id != company_currency
|
||||
different_currency = expense.currency_id and expense.currency_id != company_currency
|
||||
|
||||
move_line_values = []
|
||||
taxes = expense.tax_ids.with_context(round=True).compute_all(expense.unit_amount, expense.currency_id, expense.quantity, expense.product_id)
|
||||
@@ -285,13 +285,17 @@ class HrExpense(models.Model):
|
||||
partner_id = expense.employee_id.address_home_id.commercial_partner_id.id
|
||||
|
||||
# source move line
|
||||
amount_currency = expense.total_amount if different_currency else False
|
||||
amount = taxes['total_excluded']
|
||||
amount_currency = False
|
||||
if different_currency:
|
||||
amount = expense.currency_id._convert(amount, company_currency, expense.company_id, account_date)
|
||||
amount_currency = taxes['total_excluded']
|
||||
move_line_src = {
|
||||
'name': move_line_name,
|
||||
'quantity': expense.quantity or 1,
|
||||
'debit': taxes['total_excluded'] > 0 and taxes['total_excluded'],
|
||||
'credit': taxes['total_excluded'] < 0 and -taxes['total_excluded'],
|
||||
'amount_currency': taxes['total_excluded'] > 0 and abs(amount_currency) or -abs(amount_currency),
|
||||
'debit': amount if amount > 0 else 0,
|
||||
'credit': -amount if amount < 0 else 0,
|
||||
'amount_currency': amount_currency if different_currency else 0.0,
|
||||
'account_id': account_src.id,
|
||||
'product_id': expense.product_id.id,
|
||||
'product_uom_id': expense.product_uom_id.id,
|
||||
@@ -308,23 +312,25 @@ class HrExpense(models.Model):
|
||||
|
||||
# taxes move lines
|
||||
for tax in taxes['taxes']:
|
||||
price = expense.currency_id._convert(
|
||||
tax['amount'], company_currency, expense.company_id, account_date)
|
||||
amount_currency = price if different_currency else False
|
||||
amount = tax['amount']
|
||||
amount_currency = False
|
||||
if different_currency:
|
||||
amount = expense.currency_id._convert(amount, company_currency, expense.company_id, account_date)
|
||||
amount_currency = tax['amount']
|
||||
move_line_tax_values = {
|
||||
'name': tax['name'],
|
||||
'quantity': 1,
|
||||
'debit': price > 0 and price,
|
||||
'credit': price < 0 and -price,
|
||||
'amount_currency': price > 0 and abs(amount_currency) or -abs(amount_currency),
|
||||
'debit': amount if amount > 0 else 0,
|
||||
'credit': -amount if amount < 0 else 0,
|
||||
'amount_currency': amount_currency if different_currency else 0.0,
|
||||
'account_id': tax['account_id'] or move_line_src['account_id'],
|
||||
'tax_line_id': tax['id'],
|
||||
'expense_id': expense.id,
|
||||
'partner_id': partner_id,
|
||||
'currency_id': expense.currency_id if different_currency else False,
|
||||
'currency_id': expense.currency_id.id if different_currency else False,
|
||||
}
|
||||
total_amount -= price
|
||||
total_amount_currency -= move_line_tax_values['amount_currency'] or price
|
||||
total_amount -= amount
|
||||
total_amount_currency -= move_line_tax_values['amount_currency'] or amount
|
||||
move_line_values.append(move_line_tax_values)
|
||||
|
||||
# destination move line
|
||||
@@ -334,7 +340,7 @@ class HrExpense(models.Model):
|
||||
'credit': total_amount < 0 and -total_amount,
|
||||
'account_id': account_dst,
|
||||
'date_maturity': account_date,
|
||||
'amount_currency': total_amount > 0 and abs(amount_currency) or -abs(amount_currency),
|
||||
'amount_currency': total_amount_currency if different_currency else 0.0,
|
||||
'currency_id': expense.currency_id.id if different_currency else False,
|
||||
'expense_id': expense.id,
|
||||
'partner_id': partner_id,
|
||||
|
||||
@@ -78,6 +78,73 @@ class TestAccountEntry(TestExpenseCommon):
|
||||
self.assertAlmostEquals(self.analytic_account.line_ids[0].amount, -636.36, "Amount on the only AAL is wrong")
|
||||
self.assertEquals(self.analytic_account.line_ids[0].product_id, self.product_expense, "Product of AAL should be the one from the expense")
|
||||
|
||||
def test_account_entry_multi_currency(self):
|
||||
""" Checking accounting move entries and analytic entries when submitting expense. With
|
||||
multi-currency. And taxes. """
|
||||
|
||||
# Clean-up the rates
|
||||
self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", [self.env.ref('base.USD').id, self.env.user.company_id.id])
|
||||
self.env['res.currency.rate'].search([]).unlink()
|
||||
self.env['res.currency.rate'].create({
|
||||
'currency_id': self.env.ref('base.EUR').id,
|
||||
'company_id': self.env.user.company_id.id,
|
||||
'rate': 2.0,
|
||||
'name': '2010-01-01',
|
||||
})
|
||||
|
||||
expense = self.env['hr.expense.sheet'].create({
|
||||
'name': 'Expense for Dick Tracy',
|
||||
'employee_id': self.employee.id,
|
||||
})
|
||||
expense_line = self.env['hr.expense'].create({
|
||||
'name': 'Choucroute Saucisse',
|
||||
'employee_id': self.employee.id,
|
||||
'product_id': self.product_expense.id,
|
||||
'unit_amount': 700.00,
|
||||
'tax_ids': [(6, 0, [self.tax.id])],
|
||||
'sheet_id': expense.id,
|
||||
'analytic_account_id': self.analytic_account.id,
|
||||
'currency_id': self.env.ref('base.EUR').id,
|
||||
})
|
||||
expense_line._onchange_product_id()
|
||||
# State should default to draft
|
||||
self.assertEquals(expense.state, 'draft', 'Expense should be created in Draft state')
|
||||
# Submitted to Manager
|
||||
expense.action_submit_sheet()
|
||||
self.assertEquals(expense.state, 'submit', 'Expense is not in Reported state')
|
||||
# Approve
|
||||
expense.approve_expense_sheets()
|
||||
self.assertEquals(expense.state, 'approve', 'Expense is not in Approved state')
|
||||
# Create Expense Entries
|
||||
expense.action_sheet_move_create()
|
||||
self.assertEquals(expense.state, 'post', 'Expense is not in Waiting Payment state')
|
||||
self.assertTrue(expense.account_move_id.id, 'Expense Journal Entry is not created')
|
||||
|
||||
# Should get this result [(0.0, 350.0, -700.0), (318.18, 0.0, 636.36), (31.82, 0.0, 63.64)]
|
||||
for line in expense.account_move_id.line_ids:
|
||||
if line.credit:
|
||||
self.assertAlmostEquals(line.credit, 350.0)
|
||||
self.assertAlmostEquals(line.amount_currency, -700.0)
|
||||
self.assertEquals(len(line.analytic_line_ids), 0, "The credit move line should not have analytic lines")
|
||||
self.assertFalse(line.product_id, "Product of credit move line should be false")
|
||||
else:
|
||||
if not line.tax_line_id == self.tax:
|
||||
self.assertAlmostEquals(line.debit, 318.18)
|
||||
self.assertAlmostEquals(line.amount_currency, 636.36)
|
||||
self.assertEquals(len(line.analytic_line_ids), 1, "The debit move line should have 1 analytic lines")
|
||||
self.assertEquals(line.product_id, self.product_expense, "Product of debit move line should be the one from the expense")
|
||||
else:
|
||||
self.assertAlmostEquals(line.debit, 31.82)
|
||||
self.assertAlmostEquals(line.amount_currency, 63.64)
|
||||
self.assertEquals(len(line.analytic_line_ids), 0, "The tax move line should not have analytic lines")
|
||||
self.assertFalse(line.product_id, "Product of tax move line should be false")
|
||||
|
||||
self.assertEquals(self.analytic_account.line_ids, expense.account_move_id.mapped('line_ids.analytic_line_ids'))
|
||||
self.assertEquals(len(self.analytic_account.line_ids), 1, "Analytic Account should have only one line")
|
||||
self.assertAlmostEquals(self.analytic_account.line_ids[0].amount, -318.18, "Amount on the only AAL is wrong")
|
||||
self.assertAlmostEquals(self.analytic_account.line_ids[0].currency_id, self.env.user.company_id.currency_id, "Currency on the only AAL is wrong")
|
||||
self.assertEquals(self.analytic_account.line_ids[0].product_id, self.product_expense, "Product of AAL should be the one from the expense")
|
||||
|
||||
def test_expense_from_email(self):
|
||||
user_demo = self.env.ref('base.user_demo')
|
||||
self.tax.price_include = False
|
||||
|
||||
@@ -437,7 +437,7 @@ class HolidaysRequest(models.Model):
|
||||
""" Returns a float equals to the timedelta between two dates given as string."""
|
||||
if employee_id:
|
||||
employee = self.env['hr.employee'].browse(employee_id)
|
||||
return employee.get_work_days_data(date_from, date_to)['days']
|
||||
return employee._get_work_days_data(date_from, date_to)['days']
|
||||
|
||||
today_hours = self.env.user.company_id.resource_calendar_id.get_work_hours_count(
|
||||
datetime.combine(date_from.date(), time.min),
|
||||
@@ -568,12 +568,15 @@ class HolidaysRequest(models.Model):
|
||||
|
||||
@api.multi
|
||||
def _create_resource_leave(self):
|
||||
""" This method will create entry in resource calendar time off object at the time of holidays validated """
|
||||
""" This method will create entry in resource calendar time off object at the time of holidays validated
|
||||
:returns: created `resource.calendar.leaves`
|
||||
"""
|
||||
vals_list = []
|
||||
for leave in self:
|
||||
date_from = fields.Datetime.from_string(leave.date_from)
|
||||
date_to = fields.Datetime.from_string(leave.date_to)
|
||||
|
||||
self.env['resource.calendar.leaves'].create({
|
||||
vals_list.append({
|
||||
'name': leave.name,
|
||||
'date_from': fields.Datetime.to_string(date_from),
|
||||
'holiday_id': leave.id,
|
||||
@@ -582,7 +585,7 @@ class HolidaysRequest(models.Model):
|
||||
'calendar_id': leave.employee_id.resource_calendar_id.id,
|
||||
'time_type': leave.holiday_status_id.time_type,
|
||||
})
|
||||
return True
|
||||
return self.env['resource.calendar.leaves'].create(vals_list)
|
||||
|
||||
@api.multi
|
||||
def _remove_resource_leave(self):
|
||||
@@ -634,7 +637,7 @@ class HolidaysRequest(models.Model):
|
||||
'request_date_from': self.date_from,
|
||||
'request_date_to': self.date_to,
|
||||
'notes': self.notes,
|
||||
'number_of_days': employee.get_work_days_data(self.date_from, self.date_to)['days'],
|
||||
'number_of_days': employee._get_work_days_data(self.date_from, self.date_to)['days'],
|
||||
'parent_id': self.id,
|
||||
'employee_id': employee.id
|
||||
}
|
||||
|
||||
@@ -178,8 +178,8 @@ class HolidaysAllocation(models.Model):
|
||||
if period_start <= creation_date:
|
||||
period_start = creation_date
|
||||
|
||||
worked = holiday.employee_id.get_work_days_data(period_start, period_end, domain=[('holiday_id.holiday_status_id.unpaid', '=', True), ('time_type', '=', 'leave')])['days']
|
||||
left = holiday.employee_id.get_leave_days_data(period_start, period_end, domain=[('holiday_id.holiday_status_id.unpaid', '=', True), ('time_type', '=', 'leave')])['days']
|
||||
worked = holiday.employee_id._get_work_days_data(period_start, period_end, domain=[('holiday_id.holiday_status_id.unpaid', '=', True), ('time_type', '=', 'leave')])['days']
|
||||
left = holiday.employee_id._get_leave_days_data(period_start, period_end, domain=[('holiday_id.holiday_status_id.unpaid', '=', True), ('time_type', '=', 'leave')])['days']
|
||||
prorata = worked / (left + worked) if worked else 0
|
||||
|
||||
days_to_give = holiday.number_per_interval
|
||||
|
||||
@@ -294,7 +294,10 @@ class HolidaysType(models.Model):
|
||||
if record.allocation_type != 'no':
|
||||
name = "%(name)s (%(count)s)" % {
|
||||
'name': name,
|
||||
'count': _('%g remaining out of %g') % (float_round(record.virtual_remaining_leaves, precision_digits=2) or 0.0, float_round(record.max_leaves, precision_digits=2) or 0.0)
|
||||
'count': _('%g remaining out of %g') % (
|
||||
float_round(record.virtual_remaining_leaves, precision_digits=2) or 0.0,
|
||||
float_round(record.max_leaves, precision_digits=2) or 0.0,
|
||||
)
|
||||
}
|
||||
res.append((record.id, name))
|
||||
return res
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
'views/report_contributionregister_templates.xml',
|
||||
'views/report_payslip_templates.xml',
|
||||
'views/hr_benefit_views.xml',
|
||||
'views/hr_benefit_views.xml',
|
||||
'views/hr_leave_views.xml',
|
||||
'views/resource_views.xml',
|
||||
'views/hr_benefit_template.xml',
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import pytz
|
||||
import itertools
|
||||
import pytz
|
||||
|
||||
from psycopg2 import IntegrityError
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from odoo import api, fields, models, exceptions, _
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.addons.resource.models.resource import Intervals
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
class HrBenefit(models.Model):
|
||||
_name = 'hr.benefit'
|
||||
@@ -17,8 +20,7 @@ class HrBenefit(models.Model):
|
||||
|
||||
name = fields.Char(required=True)
|
||||
active = fields.Boolean(default=True)
|
||||
employee_id = fields.Many2one('hr.employee', required=True,
|
||||
domain=lambda self: [('contract_ids.state', 'in', ('open', 'pending')), ('company_id', '=', self.env.user.company_id.id)])
|
||||
employee_id = fields.Many2one('hr.employee', required=True, domain=[('contract_ids.state', 'in', ('open', 'pending'))])
|
||||
date_start = fields.Datetime(required=True, string='From')
|
||||
date_stop = fields.Datetime(string='To')
|
||||
duration = fields.Float(compute='_compute_duration', inverse='_inverse_duration', store=True, string="Period")
|
||||
@@ -37,34 +39,25 @@ class HrBenefit(models.Model):
|
||||
default=lambda self: self.env['res.company']._company_default_get())
|
||||
|
||||
_sql_constraints = [
|
||||
('_unique', 'unique (employee_id, date_start, date_stop, benefit_type_id)', "Benefit already exists for this attendence"),
|
||||
('_unique', 'unique (date_start, date_stop, employee_id, benefit_type_id)', "Benefit already exists for this attendance"),
|
||||
('_benefit_has_end', 'check (date_stop IS NOT NULL OR duration <> 0)', 'Benefit must end. Please define an end date or a duration.'),
|
||||
('_benefit_start_before_end', 'check (date_stop is null OR (date_stop > date_start))', 'Starting time should be before end time.')
|
||||
]
|
||||
|
||||
@api.constrains('date_stop', 'duration')
|
||||
def _check_validity_benefit_ends(self):
|
||||
""" verifies if benefit has an end. """
|
||||
for benefit in self:
|
||||
if not (benefit.date_stop or benefit.duration):
|
||||
raise exceptions.ValidationError(_('Benefit must end. Please define an end date or a duration.'))
|
||||
|
||||
@api.constrains('date_start', 'date_stop')
|
||||
def _check_validity_start_before_end(self):
|
||||
""" verifies if benefit has an end. """
|
||||
for benefit in self:
|
||||
if benefit.date_stop < benefit.date_start:
|
||||
raise exceptions.ValidationError(_('Starting time should be before end time.'))
|
||||
|
||||
|
||||
@api.onchange('duration')
|
||||
def _onchange_duration(self):
|
||||
self._inverse_duration()
|
||||
|
||||
def _get_duration(self, date_start, date_stop):
|
||||
if not date_start or not date_stop:
|
||||
return 0
|
||||
dt = date_stop - date_start
|
||||
return dt.days * 24 + dt.seconds / 3600 # Number of hours
|
||||
|
||||
@api.depends('date_stop', 'date_start')
|
||||
def _compute_duration(self):
|
||||
for benefit in self:
|
||||
if benefit.date_start and benefit.date_stop:
|
||||
dt = benefit.date_stop - benefit.date_start
|
||||
benefit.duration = dt.days * 24 + dt.seconds / 3600 # Number of hours
|
||||
benefit.duration = benefit._get_duration(benefit.date_start, benefit.date_stop)
|
||||
|
||||
def _inverse_duration(self):
|
||||
for benefit in self:
|
||||
@@ -81,25 +74,22 @@ class HrBenefit(models.Model):
|
||||
return super(HrBenefit, self).write(vals)
|
||||
|
||||
@api.multi
|
||||
def check_if_error(self):
|
||||
def _check_if_error(self):
|
||||
if not self:
|
||||
return False
|
||||
undefined_type = self.filtered(lambda b: not b.benefit_type_id)
|
||||
undefined_type.write({'display_warning': True})
|
||||
conflict = self._compute_schedule_conflicts()
|
||||
conflict_with_leaves = self.compute_conflicts_leaves_to_approve()
|
||||
conflict = self._mark_conflicting_benefits(min(self.mapped('date_start')), max(self.mapped('date_stop')))
|
||||
conflict_with_leaves = self._compute_conflicts_leaves_to_approve()
|
||||
return undefined_type or conflict or conflict_with_leaves
|
||||
|
||||
@api.multi
|
||||
def _compute_schedule_conflicts(self):
|
||||
@api.model
|
||||
def _mark_conflicting_benefits(self, start, stop):
|
||||
conflict = False
|
||||
date_start_benefits = min(self.mapped('date_start'))
|
||||
date_stop_benefits = max(self.mapped('date_stop'))
|
||||
domain = [
|
||||
('date_start', '<', date_stop_benefits),
|
||||
('date_stop', '>', date_start_benefits),
|
||||
('date_start', '<', stop),
|
||||
('date_stop', '>', start),
|
||||
]
|
||||
|
||||
benefs = self.search(domain)
|
||||
benefits_by_employee = itertools.groupby(benefs, lambda b: b.employee_id)
|
||||
for employee, benefs in benefits_by_employee:
|
||||
@@ -111,8 +101,7 @@ class HrBenefit(models.Model):
|
||||
return conflict
|
||||
|
||||
@api.multi
|
||||
def compute_conflicts_leaves_to_approve(self):
|
||||
|
||||
def _compute_conflicts_leaves_to_approve(self):
|
||||
if not self:
|
||||
return False
|
||||
|
||||
@@ -137,26 +126,35 @@ class HrBenefit(models.Model):
|
||||
})
|
||||
return bool(conflicts)
|
||||
|
||||
def safe_duplicate_create(self, vals):
|
||||
def _safe_duplicate_create(self, vals_list, date_start, date_stop):
|
||||
"""
|
||||
Create benefit but silently abord if it already exists in the database (according to the _unique constraints).
|
||||
@return: new record if it didn't exist, empty recordset otherwise.
|
||||
Create benefits between date_start and date_stop according to vals_list.
|
||||
Skip the values in vals_list if a benefit already exists for the given
|
||||
date_start, date_stop, employee_id, benefit_type_id
|
||||
:return: new record id if it didn't exist.
|
||||
"""
|
||||
try:
|
||||
with mute_logger('odoo.sql_db'), self.env.cr.savepoint():
|
||||
# Any database call will be run inside a postgresql savepoint which is
|
||||
# rolledback if an exception occurs allowing to make subsequent calls in the transaction.
|
||||
# (Otherwise InternalError is raised by any subsequent database operations)
|
||||
return self.create(vals)
|
||||
except IntegrityError as err:
|
||||
if 'hr_benefit__unique' not in err.pgerror:
|
||||
raise err
|
||||
return self.env['hr.benefit']
|
||||
# The search_read should be fast as date_start and date_stop are indexed from the
|
||||
# unique sql constraint
|
||||
month_recs = self.search_read([('date_start', '>=', date_start), ('date_stop', '<=', date_stop)],
|
||||
['employee_id', 'date_start', 'date_stop', 'benefit_type_id'])
|
||||
existing_entries = {(
|
||||
r['date_start'],
|
||||
r['date_stop'],
|
||||
r['employee_id'][0],
|
||||
r['benefit_type_id'][0] if r['benefit_type_id'] else False,
|
||||
) for r in month_recs}
|
||||
new_vals = [v for v in vals_list if (v['date_start'].replace(tzinfo=None), v['date_stop'].replace(tzinfo=None), v['employee_id'], v['benefit_type_id']) not in existing_entries]
|
||||
# Remove duplicates from vals_list, shouldn't be necessary from saas-12.2
|
||||
unique_new_vals = set()
|
||||
for values in new_vals:
|
||||
unique_new_vals.add(tuple(values.items()))
|
||||
new_vals = [dict(values) for values in unique_new_vals]
|
||||
return self.create(new_vals)
|
||||
|
||||
def action_leave(self):
|
||||
leave = self.leave_id
|
||||
return {
|
||||
'type':'ir.actions.act_window',
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_id': leave.id,
|
||||
@@ -164,7 +162,7 @@ class HrBenefit(models.Model):
|
||||
'views': [[False, 'form']],
|
||||
}
|
||||
|
||||
def split_by_day(self):
|
||||
def _split_by_day(self):
|
||||
"""
|
||||
Split the benefit by days and unlink the original benefit.
|
||||
@return recordset
|
||||
@@ -184,6 +182,8 @@ class HrBenefit(models.Model):
|
||||
return [(start, end) for start, end in days if start != end]
|
||||
|
||||
new_benefits = self.env['hr.benefit']
|
||||
benefits_to_unlink = self.env['hr.benefit']
|
||||
|
||||
for benefit in self:
|
||||
if benefit.date_start.date() == benefit.date_stop.date():
|
||||
new_benefits |= benefit
|
||||
@@ -197,7 +197,7 @@ class HrBenefit(models.Model):
|
||||
'contract_id': benefit.contract_id.id,
|
||||
}
|
||||
benefit_state = benefit.state
|
||||
benefit.unlink()
|
||||
benefits_to_unlink |= benefit
|
||||
for start, stop in _split_range_by_day(benefit_start, benefit_stop):
|
||||
values['date_start'] = start.astimezone(pytz.utc)
|
||||
values['date_stop'] = stop.astimezone(pytz.utc)
|
||||
@@ -206,6 +206,7 @@ class HrBenefit(models.Model):
|
||||
new_benefit.state = benefit_state
|
||||
new_benefits |= new_benefit
|
||||
|
||||
benefits_to_unlink.unlink()
|
||||
return new_benefits
|
||||
|
||||
@api.multi
|
||||
@@ -213,31 +214,36 @@ class HrBenefit(models.Model):
|
||||
"""
|
||||
Duplicate data to keep the complexity in benefit and not mess up payroll, etc.
|
||||
"""
|
||||
attendance_type = self.env.ref('hr_payroll.benefit_type_attendance')
|
||||
attendance_benefits = self.filtered(lambda b:
|
||||
not b.benefit_type_id.is_leave and
|
||||
# Normal benefit are global to all employees -> avoid duplicating it
|
||||
not b.benefit_type_id == self.env.ref('hr_payroll.benefit_type_attendance'))
|
||||
not b.benefit_type_id == attendance_type)
|
||||
leave_benefits = self.filtered(lambda b: b.benefit_type_id.is_leave)
|
||||
|
||||
benefits_to_duplicate = self.env['hr.benefit']
|
||||
for benefit in attendance_benefits:
|
||||
benefit = benefit.split_by_day()
|
||||
benefit._duplicate_to_calendar_attendance()
|
||||
benefit = benefit._split_by_day()
|
||||
benefits_to_duplicate |= benefit
|
||||
|
||||
benefits_to_duplicate._duplicate_to_calendar_attendance()
|
||||
leave_benefits._duplicate_to_calendar_leave()
|
||||
|
||||
@api.multi
|
||||
def _duplicate_to_calendar_leave(self):
|
||||
|
||||
vals_list = []
|
||||
for benefit in self:
|
||||
if not benefit.leave_id:
|
||||
tz = pytz.timezone(benefit.employee_id.tz)
|
||||
self.env['resource.calendar.leaves'].create({
|
||||
vals_list += [{
|
||||
'name': benefit.name,
|
||||
'date_from': benefit.date_start,
|
||||
'date_to': benefit.date_stop,
|
||||
'calendar_id': benefit.employee_id.resource_calendar_id.id,
|
||||
'resource_id': benefit.employee_id.resource_id.id,
|
||||
'benefit_type_id': benefit.benefit_type_id.id,
|
||||
})
|
||||
}]
|
||||
if vals_list:
|
||||
self.env['resource.calendar.leaves'].create(vals_list)
|
||||
|
||||
@api.multi
|
||||
def _duplicate_to_calendar_attendance(self):
|
||||
@@ -251,32 +257,35 @@ class HrBenefit(models.Model):
|
||||
if any(data[0].date() != data[1].date() for data in mapped_data.values()):
|
||||
raise ValidationError(_("You can't validate a benefit that covers several days."))
|
||||
|
||||
vals_list = []
|
||||
for benefit in self:
|
||||
start, end = mapped_data.get(benefit)
|
||||
|
||||
self.env['resource.calendar.attendance'].create({
|
||||
vals_list += [{
|
||||
'name': benefit.name,
|
||||
'dayofweek': str(start.weekday()),
|
||||
'date_from': start.date(),
|
||||
'date_to': end.date(),
|
||||
'hour_from':start.hour + start.minute/60,
|
||||
'hour_to': end.hour + end.minute/60,
|
||||
'hour_from': start.hour + start.minute / 60,
|
||||
'hour_to': end.hour + end.minute / 60,
|
||||
'calendar_id': benefit.contract_id.resource_calendar_id.id,
|
||||
'day_period': 'morning' if end.hour <= 12 else 'afternoon',
|
||||
'resource_id': benefit.employee_id.resource_id.id,
|
||||
'benefit_type_id': benefit.benefit_type_id.id,
|
||||
})
|
||||
}]
|
||||
self.env['resource.calendar.attendance'].create(vals_list)
|
||||
|
||||
@api.model
|
||||
def action_validate(self, ids):
|
||||
benefits = self.env['hr.benefit'].search([('id', 'in', ids), ('state', '!=', 'validated')])
|
||||
benefits.write({'display_warning': False})
|
||||
if not benefits.check_if_error():
|
||||
if not benefits._check_if_error():
|
||||
benefits.write({'state': 'validated'})
|
||||
benefits._duplicate_to_calendar()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class HrBenefitType(models.Model):
|
||||
_name = 'hr.benefit.type'
|
||||
_description = 'hr.benefit.type'
|
||||
@@ -285,8 +294,9 @@ class HrBenefitType(models.Model):
|
||||
code = fields.Char()
|
||||
color = fields.Integer(default=1) # Will be used with the new calendar/kanban view
|
||||
sequence = fields.Integer(default=25)
|
||||
active = fields.Boolean('Active', default=True,
|
||||
help="If the active field is set to false, it will allow you to hide the benefit type without removing it.")
|
||||
active = fields.Boolean(
|
||||
'Active', default=True,
|
||||
help="If the active field is set to false, it will allow you to hide the benefit type without removing it.")
|
||||
is_leave = fields.Boolean(default=False, string="Leave")
|
||||
|
||||
class Contacts(models.Model):
|
||||
@@ -301,4 +311,4 @@ class Contacts(models.Model):
|
||||
|
||||
_sql_constraints = [
|
||||
('user_id_employee_id_unique', 'UNIQUE(user_id,employee_id)', 'You cannot have twice the same employee.')
|
||||
]
|
||||
]
|
||||
@@ -44,12 +44,15 @@ class HrEmployee(models.Model):
|
||||
|
||||
@api.model
|
||||
def generate_benefit(self, date_start, date_stop):
|
||||
|
||||
def _format_datetime(date):
|
||||
fmt = '%Y-%m-%d %H:%M:%S'
|
||||
date = datetime.strptime(date, fmt) if isinstance(date, str) else date
|
||||
return date.replace(tzinfo=pytz.utc) if not date.tzinfo else date
|
||||
|
||||
attendance_type = self.env.ref('hr_payroll.benefit_type_attendance')
|
||||
vals_list = []
|
||||
leaves = self.env['hr.leave']
|
||||
|
||||
date_start = _format_datetime(date_start)
|
||||
date_stop = _format_datetime(date_stop)
|
||||
|
||||
@@ -70,9 +73,10 @@ class HrEmployee(models.Model):
|
||||
r.date_to.replace(tzinfo=pytz.utc) >= date_start
|
||||
)
|
||||
global_leaves = employee.resource_calendar_id.global_leave_ids
|
||||
(emp_leaves | global_leaves).mapped('holiday_id').copy_to_benefits()
|
||||
|
||||
new_benefits = self.env['hr.benefit']
|
||||
employee_leaves = (emp_leaves | global_leaves).mapped('holiday_id')
|
||||
vals_list.extend(employee_leaves._get_benefits_values())
|
||||
|
||||
for contract in contracts:
|
||||
|
||||
date_start_benefits = max(date_start, datetime.combine(contract.date_start, datetime.min.time()).replace(tzinfo=pytz.utc))
|
||||
@@ -83,8 +87,8 @@ class HrEmployee(models.Model):
|
||||
attendances = calendar._work_intervals(date_start_benefits, date_stop_benefits, resource=resource)
|
||||
# Attendances
|
||||
for interval in attendances:
|
||||
benefit_type_id = interval[2].mapped('benefit_type_id')[:1] or self.env.ref('hr_payroll.benefit_type_attendance')
|
||||
new_benefits |= self.env['hr.benefit'].safe_duplicate_create({
|
||||
benefit_type_id = interval[2].mapped('benefit_type_id')[:1] or attendance_type
|
||||
vals_list += [{
|
||||
'name': "%s: %s" % (benefit_type_id.name, employee.name),
|
||||
'date_start': interval[0].astimezone(pytz.utc),
|
||||
'date_stop': interval[1].astimezone(pytz.utc),
|
||||
@@ -92,6 +96,7 @@ class HrEmployee(models.Model):
|
||||
'employee_id': employee.id,
|
||||
'contract_id': contract.id,
|
||||
'state': 'confirmed',
|
||||
})
|
||||
}]
|
||||
|
||||
new_benefits.compute_conflicts_leaves_to_approve()
|
||||
new_benefits = self.env['hr.benefit']._safe_duplicate_create(vals_list, date_start, date_stop)
|
||||
new_benefits._compute_conflicts_leaves_to_approve()
|
||||
|
||||
@@ -18,13 +18,14 @@ class HrLeave(models.Model):
|
||||
_inherit = 'hr.leave'
|
||||
|
||||
@api.multi
|
||||
def copy_to_benefits(self):
|
||||
def _get_benefits_values(self):
|
||||
vals_list = []
|
||||
for leave in self:
|
||||
contract = leave.employee_id._get_contracts(leave.date_from, leave.date_to, states=['open', 'pending', 'close'])
|
||||
start = max(leave.date_from, datetime.combine(contract.date_start, datetime.min.time()))
|
||||
end = min(leave.date_to, datetime.combine(contract.date_end or date.max, datetime.max.time()))
|
||||
benefit_type = leave.holiday_status_id.benefit_type_id
|
||||
self.env['hr.benefit'].safe_duplicate_create({
|
||||
vals_list += [{
|
||||
'name': "%s%s" % (benefit_type.name + ": " if benefit_type else "", leave.employee_id.name),
|
||||
'date_start': start,
|
||||
'date_stop': end,
|
||||
@@ -34,7 +35,8 @@ class HrLeave(models.Model):
|
||||
'leave_id': leave.id,
|
||||
'state': 'confirmed',
|
||||
'contract_id': contract.id,
|
||||
})
|
||||
}]
|
||||
return vals_list
|
||||
|
||||
@api.multi
|
||||
def _create_resource_leave(self):
|
||||
@@ -43,7 +45,10 @@ class HrLeave(models.Model):
|
||||
This is needed in order to compute the correct number of hours/days of the leave
|
||||
according to the contract's calender.
|
||||
"""
|
||||
super(HrLeave, self)._create_resource_leave()
|
||||
resource_leaves = super(HrLeave, self)._create_resource_leave()
|
||||
for resource_leave in resource_leaves:
|
||||
resource_leave.benefit_type_id = resource_leave.holiday_id.holiday_status_id.benefit_type_id.id
|
||||
|
||||
resource_leave_values = []
|
||||
|
||||
for leave in self.filtered(lambda l: l.employee_id):
|
||||
@@ -61,8 +66,7 @@ class HrLeave(models.Model):
|
||||
'calendar_id': contract.resource_calendar_id.id,
|
||||
}]
|
||||
|
||||
self.env['resource.calendar.leaves'].create(resource_leave_values)
|
||||
return True
|
||||
return resource_leaves | self.env['resource.calendar.leaves'].create(resource_leave_values)
|
||||
|
||||
@api.constrains('date_from', 'date_to')
|
||||
def _check_contracts(self):
|
||||
@@ -101,7 +105,7 @@ class HrLeave(models.Model):
|
||||
"""
|
||||
benefits = self.env['hr.benefit'].search([('leave_id', 'in', self.ids)])
|
||||
if benefits:
|
||||
self.copy_to_benefits()
|
||||
vals_list = self._get_benefits_values()
|
||||
# create new benefits where the leave does not cover the full benefit
|
||||
benefits_intervals = Intervals(intervals=[(b.date_start, b.date_stop, b) for b in benefits])
|
||||
leave_intervals = Intervals(intervals=[(l.date_from, l.date_to, l) for l in self])
|
||||
@@ -116,7 +120,7 @@ class HrLeave(models.Model):
|
||||
benefit_start = interval[0] + relativedelta(seconds=1) if leave.date_to == interval[0] else interval[0]
|
||||
benefit_stop = interval[1] - relativedelta(seconds=1) if leave.date_from == interval[1] else interval[1]
|
||||
|
||||
self.env['hr.benefit'].safe_duplicate_create({
|
||||
vals_list += [{
|
||||
'name': "%s: %s" % (benefit_type.name, employee.name),
|
||||
'date_start': benefit_start,
|
||||
'date_stop': benefit_stop,
|
||||
@@ -124,7 +128,11 @@ class HrLeave(models.Model):
|
||||
'contract_id': benefit.contract_id.id,
|
||||
'employee_id': employee.id,
|
||||
'state': 'confirmed',
|
||||
})
|
||||
}]
|
||||
|
||||
date_start = min(benefits.mapped('date_start'))
|
||||
date_stop = max(benefits.mapped('date_stop'))
|
||||
self.env['hr.benefit']._safe_duplicate_create(vals_list, date_start, date_stop)
|
||||
benefits.unlink()
|
||||
return True
|
||||
return False
|
||||
@@ -132,13 +140,13 @@ class HrLeave(models.Model):
|
||||
@api.multi
|
||||
def action_validate(self):
|
||||
super(HrLeave, self).action_validate()
|
||||
self._cancel_benefit_conflict() # delete preexisting conflicting benefits
|
||||
self.sudo()._cancel_benefit_conflict() # delete preexisting conflicting benefits
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def action_refuse(self):
|
||||
super(HrLeave, self).action_refuse()
|
||||
benefits = self.env['hr.benefit'].search([('leave_id', 'in', self.ids)])
|
||||
benefits = self.env['hr.benefit'].sudo().search([('leave_id', 'in', self.ids)])
|
||||
benefits.write({'display_warning': False, 'active': False})
|
||||
return True
|
||||
|
||||
@@ -155,7 +163,7 @@ class HrLeave(models.Model):
|
||||
# Use sudo otherwise base users can't compute number of days
|
||||
contracts = employee.sudo()._get_contracts(date_from, date_to, states=['incoming', 'open', 'pending'])
|
||||
calendar = contracts[:1].resource_calendar_id if contracts else None # Note: if len(contracts)>1, the leave creation will crash because of unicity constaint
|
||||
return employee.get_work_days_data(date_from, date_to, calendar=calendar)['days']
|
||||
return employee._get_work_days_data(date_from, date_to, calendar=calendar)['days']
|
||||
|
||||
return days
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ class HrPayslip(models.Model):
|
||||
|
||||
benefit_types = self.env['hr.benefit.type'].search([('code', '!=', False)])
|
||||
for benefit_type in benefit_types:
|
||||
hours = contract.employee_id.get_benefit_days_data(benefit_type, day_from, day_to, calendar=calendar)['hours']
|
||||
hours = contract.employee_id._get_benefit_days_data(benefit_type, day_from, day_to, calendar=calendar)['hours']
|
||||
if hours:
|
||||
line = {
|
||||
'name': benefit_type.name,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import models, fields, api
|
||||
from odoo.osv.expression import AND
|
||||
from odoo.tools import float_compare
|
||||
from datetime import datetime
|
||||
|
||||
from odoo import models, fields, api
|
||||
from odoo.addons.resource.models.resource_mixin import timezone_datetime
|
||||
|
||||
class ResourceCalendar(models.Model):
|
||||
_inherit = 'resource.calendar'
|
||||
@@ -78,7 +79,7 @@ class ResourceCalendarLeave(models.Model):
|
||||
class ResourceMixin(models.AbstractModel):
|
||||
_inherit = "resource.mixin"
|
||||
|
||||
def get_benefit_days_data(self, benefit_type, from_datetime, to_datetime, calendar=None):
|
||||
def _get_benefit_days_data(self, benefit_type, from_datetime, to_datetime, calendar=None):
|
||||
"""
|
||||
By default the resource calendar is used, but it can be
|
||||
changed using the `calendar` argument.
|
||||
@@ -94,8 +95,8 @@ class ResourceMixin(models.AbstractModel):
|
||||
domain = [('benefit_type_id', 'in', benefit_type_ids)]
|
||||
|
||||
# naive datetimes are made explicit in UTC
|
||||
from_datetime = self._timezone_datetime(from_datetime)
|
||||
to_datetime = self._timezone_datetime(to_datetime)
|
||||
from_datetime = timezone_datetime(from_datetime)
|
||||
to_datetime = timezone_datetime(to_datetime)
|
||||
|
||||
day_total = self._get_day_total(from_datetime, to_datetime, calendar, resource)
|
||||
# actual hours per day
|
||||
|
||||
@@ -10,5 +10,6 @@ access_hr_payslip_worked_days_officer,hr.payslip.worked_days.officer,model_hr_pa
|
||||
access_hr_payslip_run,hr.payslip.run,model_hr_payslip_run,hr_payroll.group_hr_payroll_manager,1,1,1,1
|
||||
access_hr_salary_rule_user,hr.salary.rule.user,model_hr_salary_rule,hr_payroll.group_hr_payroll_user,1,1,1,1
|
||||
access_hr_benefit,access_hr_benefit,model_hr_benefit,group_hr_payroll_user,1,1,1,1
|
||||
access_hr_benefit_type,access_hr_benefit_type,model_hr_benefit_type,group_hr_payroll_user,1,1,1,1
|
||||
access_hr_benefit_type_officer,access_hr_benefit_type_officer,model_hr_benefit_type,group_hr_payroll_user,1,0,0,0
|
||||
access_hr_benefit_employee,hr.benefit.employee,model_hr_user_benefit_employee,group_hr_payroll_user,1,1,1,1
|
||||
access_hr_benefit_type_manager,access_hr_benefit_type_manager,model_hr_benefit_type,group_hr_payroll_manager,1,1,1,1
|
||||
|
||||
|
@@ -3,11 +3,11 @@ odoo.define('hr_payroll.benefit.view_custo', function(require) {
|
||||
|
||||
var core = require('web.core');
|
||||
var CalendarController = require("web.CalendarController");
|
||||
var time = require('web.time');
|
||||
var CalendarModel = require('web.CalendarModel');
|
||||
var CalendarRenderer = require('web.CalendarRenderer');
|
||||
var CalendarView = require('web.CalendarView');
|
||||
var viewRegistry = require('web.view_registry');
|
||||
|
||||
var _t = core._t;
|
||||
var BenefitCalendarController = CalendarController.extend({
|
||||
|
||||
@@ -35,8 +35,8 @@ odoo.define('hr_payroll.benefit.view_custo', function(require) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.firstDay = this.model.data.target_date.clone().startOf('month');
|
||||
this.lastDay = this.model.data.target_date.clone().endOf('month');
|
||||
this.firstDay = this.model.data.target_date.clone().startOf('month').toDate();
|
||||
this.lastDay = this.model.data.target_date.clone().endOf('month').toDate();
|
||||
this.events = this._checkDataInRange(this.firstDay, this.lastDay, this.model.data.data);
|
||||
var is_validated = this._checkValidation(this.events);
|
||||
this.$buttons.find('.btn-benefit').remove();
|
||||
@@ -54,11 +54,10 @@ odoo.define('hr_payroll.benefit.view_custo', function(require) {
|
||||
_onGeneratePayslips: function (e) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
var date_fmt = 'YYYY-MM-DD';
|
||||
this.do_action('hr_payroll.action_generate_payslips_from_benefits', {
|
||||
additional_context: {
|
||||
default_date_start: this.firstDay.format(date_fmt),
|
||||
default_date_end: this.lastDay.format(date_fmt),
|
||||
default_date_start: time.datetime_to_str(this.firstDay),
|
||||
default_date_end: time.datetime_to_str(this.lastDay),
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -79,12 +78,11 @@ odoo.define('hr_payroll.benefit.view_custo', function(require) {
|
||||
_onGenerateBenefits: function (e) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
var date_fmt = 'YYYY-MM-DD HH:mm:ss';
|
||||
var self = this;
|
||||
this._rpc({
|
||||
model: 'hr.employee',
|
||||
method: 'generate_benefit',
|
||||
args: [this.firstDay.format(date_fmt), this.lastDay.format(date_fmt)],
|
||||
args: [time.datetime_to_str(this.firstDay), time.datetime_to_str(this.lastDay)],
|
||||
}).then(function () {
|
||||
self.reload();
|
||||
});
|
||||
|
||||
@@ -5,3 +5,4 @@ from . import test_payslip_flow
|
||||
from . import test_benefit
|
||||
from . import test_multi_contract
|
||||
from . import test_calendar_sync
|
||||
from . import test_leave
|
||||
|
||||
@@ -5,6 +5,7 @@ from datetime import datetime
|
||||
import pytz
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from odoo import exceptions
|
||||
from odoo.fields import Datetime, Date
|
||||
from odoo.tests.common import tagged
|
||||
from odoo.addons.hr_payroll.tests.common import TestPayslipBase
|
||||
|
||||
@@ -15,8 +16,8 @@ class TestBenefit(TestPayslipBase):
|
||||
def setUp(self):
|
||||
super(TestBenefit, self).setUp()
|
||||
self.tz = pytz.timezone(self.richard_emp.tz)
|
||||
self.start = datetime.strptime('2015-11-01 01:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
self.end = datetime.strptime('2015-11-30 23:59:59', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
self.start = self.to_datetime_tz('2015-11-01 01:00:00')
|
||||
self.end = self.to_datetime_tz('2015-11-30 23:59:59')
|
||||
self.resource_calendar_id = self.env['resource.calendar'].create({'name': 'Zboub'})
|
||||
contract = self.env['hr.contract'].create({
|
||||
'date_start': self.start - relativedelta(days=5),
|
||||
@@ -35,6 +36,24 @@ class TestBenefit(TestPayslipBase):
|
||||
'code': 'WORK200'
|
||||
})
|
||||
|
||||
def to_datetime_tz(self, datetime_str, tz=None):
|
||||
tz = tz or self.tz
|
||||
return tz.localize(Datetime.to_datetime(datetime_str))
|
||||
|
||||
def assertDatetimeTzEqual(self, value, target, tz=None):
|
||||
"""
|
||||
Assert equality between two dates.
|
||||
:param value: timezone naive datetime
|
||||
:param target: datetime string
|
||||
:param tz: timezone to interpret the tartget string
|
||||
:raises AssertionError: raises exception if the two dates are not equal
|
||||
"""
|
||||
tz = tz or self.tz
|
||||
self.assertEqual(
|
||||
pytz.utc.localize(value).astimezone(tz),
|
||||
self.to_datetime_tz(target, tz=tz)
|
||||
)
|
||||
|
||||
def test_no_duplicate(self):
|
||||
self.richard_emp.generate_benefit(self.start, self.end)
|
||||
pou1 = self.env['hr.benefit'].search_count([])
|
||||
@@ -51,8 +70,8 @@ class TestBenefit(TestPayslipBase):
|
||||
self.assertEqual(attendance_nb, benefit_nb, "One benefit should be generated for each calendar attendance")
|
||||
|
||||
def test_split_benefit_by_day(self):
|
||||
start = datetime.strptime('2015-11-01 09:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
end = datetime.strptime('2015-11-03 18:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
start = self.to_datetime_tz('2015-11-01 09:00:00')
|
||||
end = self.to_datetime_tz('2015-11-03 18:00:00')
|
||||
|
||||
# Benefit of type attendance should be split in three
|
||||
benefit = self.env['hr.benefit'].create({
|
||||
@@ -63,20 +82,21 @@ class TestBenefit(TestPayslipBase):
|
||||
'date_stop': end,
|
||||
})
|
||||
|
||||
benefits = benefit.split_by_day()
|
||||
benefits = benefit._split_by_day()
|
||||
self.assertEqual(len(benefits), 3, "Benefit should be split in three")
|
||||
self.assertEqual(pytz.utc.localize(benefits[0].date_start), self.tz.localize(datetime.strptime('2015-11-01 09:00:00', '%Y-%m-%d %H:%M:%S')))
|
||||
self.assertEqual(pytz.utc.localize(benefits[0].date_stop), self.tz.localize(datetime.strptime('2015-11-01 23:59:59', '%Y-%m-%d %H:%M:%S')))
|
||||
|
||||
self.assertEqual(pytz.utc.localize(benefits[1].date_start), self.tz.localize(datetime.strptime('2015-11-02 00:00:00', '%Y-%m-%d %H:%M:%S')))
|
||||
self.assertEqual(pytz.utc.localize(benefits[1].date_stop), self.tz.localize(datetime.strptime('2015-11-02 23:59:59', '%Y-%m-%d %H:%M:%S')))
|
||||
self.assertDatetimeTzEqual(benefits[0].date_start, '2015-11-01 09:00:00')
|
||||
self.assertDatetimeTzEqual(benefits[0].date_stop, '2015-11-01 23:59:59')
|
||||
|
||||
self.assertEqual(pytz.utc.localize(benefits[2].date_start), self.tz.localize(datetime.strptime('2015-11-03 00:00:00', '%Y-%m-%d %H:%M:%S')))
|
||||
self.assertEqual(pytz.utc.localize(benefits[2].date_stop), self.tz.localize(datetime.strptime('2015-11-03 18:00:00', '%Y-%m-%d %H:%M:%S')))
|
||||
self.assertDatetimeTzEqual(benefits[1].date_start, '2015-11-02 00:00:00')
|
||||
self.assertDatetimeTzEqual(benefits[1].date_stop, '2015-11-02 23:59:59')
|
||||
|
||||
self.assertDatetimeTzEqual(benefits[2].date_start, '2015-11-03 00:00:00')
|
||||
self.assertDatetimeTzEqual(benefits[2].date_stop, '2015-11-03 18:00:00')
|
||||
|
||||
# Test with end at mid-night -> should not create benefit starting and ending at the same time (at 00:00)
|
||||
start = datetime.strptime('2013-11-01 00:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
end = datetime.strptime('2013-11-04 00:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
start = self.to_datetime_tz('2013-11-01 00:00:00')
|
||||
end = self.to_datetime_tz('2013-11-04 00:00:00')
|
||||
|
||||
benefit = self.env['hr.benefit'].create({
|
||||
'name': '1',
|
||||
@@ -85,21 +105,21 @@ class TestBenefit(TestPayslipBase):
|
||||
'date_start': start,
|
||||
'date_stop': end,
|
||||
})
|
||||
benefits = benefit.split_by_day()
|
||||
benefits = benefit._split_by_day()
|
||||
self.assertEqual(len(benefits), 3, "Benefit should be split in three")
|
||||
self.assertEqual(pytz.utc.localize(benefits[0].date_start), self.tz.localize(datetime.strptime('2013-11-01 00:00:00', '%Y-%m-%d %H:%M:%S')))
|
||||
self.assertEqual(pytz.utc.localize(benefits[0].date_stop), self.tz.localize(datetime.strptime('2013-11-01 23:59:59', '%Y-%m-%d %H:%M:%S')))
|
||||
self.assertDatetimeTzEqual(benefits[0].date_start, '2013-11-01 00:00:00')
|
||||
self.assertDatetimeTzEqual(benefits[0].date_stop, '2013-11-01 23:59:59')
|
||||
|
||||
self.assertEqual(pytz.utc.localize(benefits[1].date_start), self.tz.localize(datetime.strptime('2013-11-02 00:00:00', '%Y-%m-%d %H:%M:%S')))
|
||||
self.assertEqual(pytz.utc.localize(benefits[1].date_stop), self.tz.localize(datetime.strptime('2013-11-02 23:59:59', '%Y-%m-%d %H:%M:%S')))
|
||||
self.assertDatetimeTzEqual(benefits[1].date_start, '2013-11-02 00:00:00')
|
||||
self.assertDatetimeTzEqual(benefits[1].date_stop, '2013-11-02 23:59:59')
|
||||
|
||||
self.assertEqual(pytz.utc.localize(benefits[2].date_start), self.tz.localize(datetime.strptime('2013-11-03 00:00:00', '%Y-%m-%d %H:%M:%S')))
|
||||
self.assertEqual(pytz.utc.localize(benefits[2].date_stop), self.tz.localize(datetime.strptime('2013-11-03 23:59:59', '%Y-%m-%d %H:%M:%S')))
|
||||
self.assertDatetimeTzEqual(benefits[2].date_start, '2013-11-03 00:00:00')
|
||||
self.assertDatetimeTzEqual(benefits[2].date_stop, '2013-11-03 23:59:59')
|
||||
|
||||
def test_approve_multiple_day_benefit(self):
|
||||
|
||||
start = datetime.strptime('2015-11-01 09:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
end = datetime.strptime('2015-11-03 18:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
start = self.to_datetime_tz('2015-11-01 09:00:00')
|
||||
end = self.to_datetime_tz('2015-11-03 18:00:00')
|
||||
|
||||
# Benefit of type attendance should be split in three
|
||||
benefit = self.env['hr.benefit'].create({
|
||||
@@ -116,8 +136,8 @@ class TestBenefit(TestPayslipBase):
|
||||
self.assertEqual(len(benefits), 3, "Benefit should be split in three")
|
||||
|
||||
def test_duplicate_global_benefit_to_attendance(self):
|
||||
start = datetime.strptime('2015-11-01 09:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
end = datetime.strptime('2015-11-03 18:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
start = self.to_datetime_tz('2015-11-01 09:00:00')
|
||||
end = self.to_datetime_tz('2015-11-03 18:00:00')
|
||||
|
||||
benef = self.env['hr.benefit'].create({
|
||||
'name': '1',
|
||||
@@ -135,8 +155,8 @@ class TestBenefit(TestPayslipBase):
|
||||
self.assertEqual(attendance_nb, 0, "It should not duplicate the 'normal/global' benefit type")
|
||||
|
||||
def test_duplicate_benefit_to_attendance(self):
|
||||
start = datetime.strptime('2015-11-01 09:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
end = datetime.strptime('2015-11-03 18:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
start = self.to_datetime_tz('2015-11-01 09:00:00')
|
||||
end = self.to_datetime_tz('2015-11-03 18:00:00')
|
||||
|
||||
# Benefit (not leave) should be split in three attendance
|
||||
benef = self.env['hr.benefit'].create({
|
||||
@@ -154,27 +174,27 @@ class TestBenefit(TestPayslipBase):
|
||||
])
|
||||
self.assertEqual(attendance_nb, 3, "It should create one calendar attendance per day")
|
||||
self.assertTrue(self.env['resource.calendar.attendance'].search([
|
||||
('date_from', '=', datetime.strptime('2015-11-01', '%Y-%m-%d').date()),
|
||||
('date_to', '=', datetime.strptime('2015-11-01', '%Y-%m-%d').date()),
|
||||
('date_from', '=', Date.to_date('2015-11-01')),
|
||||
('date_to', '=', Date.to_date('2015-11-01')),
|
||||
('hour_from', '=', 9.0),
|
||||
('hour_to', '>=', 23.9)
|
||||
]))
|
||||
self.assertTrue(self.env['resource.calendar.attendance'].search([
|
||||
('date_from', '=', datetime.strptime('2015-11-02', '%Y-%m-%d').date()),
|
||||
('date_to', '=', datetime.strptime('2015-11-02', '%Y-%m-%d').date()),
|
||||
('date_from', '=', Date.to_date('2015-11-02')),
|
||||
('date_to', '=', Date.to_date('2015-11-02')),
|
||||
('hour_from', '=', 0.0),
|
||||
('hour_to', '>=', 23.9)
|
||||
]))
|
||||
self.assertTrue(self.env['resource.calendar.attendance'].search([
|
||||
('date_from', '=', datetime.strptime('2015-11-03', '%Y-%m-%d').date()),
|
||||
('date_to', '=', datetime.strptime('2015-11-03', '%Y-%m-%d').date()),
|
||||
('date_from', '=', Date.to_date('2015-11-03')),
|
||||
('date_to', '=', Date.to_date('2015-11-03')),
|
||||
('hour_from', '=', 0.0),
|
||||
('hour_to', '=', 18.0)
|
||||
]))
|
||||
|
||||
def test_create_benefit_leave(self):
|
||||
start = datetime.strptime('2015-11-01 09:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
end = datetime.strptime('2015-11-03 18:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
start = self.to_datetime_tz('2015-11-01 09:00:00')
|
||||
end = self.to_datetime_tz('2015-11-03 18:00:00')
|
||||
|
||||
benef = self.env['hr.benefit'].create({
|
||||
'name': 'Richard leave from benef',
|
||||
@@ -190,8 +210,8 @@ class TestBenefit(TestPayslipBase):
|
||||
self.assertEqual(calendar_leave.benefit_type_id, benef.benefit_type_id, "It should have the same benefit type")
|
||||
|
||||
def test_validate_conflict_benefit(self):
|
||||
start = datetime.strptime('2015-11-01 09:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
end = datetime.strptime('2015-11-01 13:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
start = self.to_datetime_tz('2015-11-01 09:00:00')
|
||||
end = self.to_datetime_tz('2015-11-01 13:00:00')
|
||||
benef1 = self.env['hr.benefit'].create({
|
||||
'name': '1',
|
||||
'employee_id': self.richard_emp.id,
|
||||
@@ -243,8 +263,8 @@ class TestBenefit(TestPayslipBase):
|
||||
self.assertFalse(benef1.action_validate(benef1.ids),"It should not validate benefits without a type")
|
||||
|
||||
def test_approve_leave_benefit(self):
|
||||
start = datetime.strptime('2015-11-01 09:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
end = datetime.strptime('2015-11-03 13:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
start = self.to_datetime_tz('2015-11-01 09:00:00')
|
||||
end = self.to_datetime_tz('2015-11-03 13:00:00')
|
||||
leave = self.env['hr.leave'].create({
|
||||
'name': 'Doctor Appointment',
|
||||
'employee_id': self.richard_emp.id,
|
||||
@@ -265,13 +285,13 @@ class TestBenefit(TestPayslipBase):
|
||||
leave.action_approve()
|
||||
|
||||
new_leave_benef = self.env['hr.benefit'].search([
|
||||
('date_start', '=', datetime.strptime('2015-11-01 09:00:00', '%Y-%m-%d %H:%M:%S')),
|
||||
('date_stop', '=', datetime.strptime('2015-11-02 09:00:00', '%Y-%m-%d %H:%M:%S')),
|
||||
('date_start', '=', Datetime.to_datetime('2015-11-01 09:00:00')),
|
||||
('date_stop', '=', Datetime.to_datetime('2015-11-02 09:00:00')),
|
||||
('benefit_type_id.is_leave', '=', True)
|
||||
])
|
||||
|
||||
new_benef = self.env['hr.benefit'].search([
|
||||
('date_start', '=', datetime.strptime('2015-11-02 09:00:01', '%Y-%m-%d %H:%M:%S')),
|
||||
('date_start', '=', Datetime.to_datetime('2015-11-02 09:00:01')),
|
||||
('date_stop', '=', end),
|
||||
('benefit_type_id.is_leave', '=', False)
|
||||
])
|
||||
@@ -279,11 +299,11 @@ class TestBenefit(TestPayslipBase):
|
||||
self.assertTrue(new_benef, "It should have created a benefit for the last two days")
|
||||
self.assertTrue(new_leave_benef, "It should have created a leave benefit for the first day")
|
||||
|
||||
self.assertTrue(benef.action_validate((new_benef|new_leave_benef).ids), "It should be able to validate the benefits")
|
||||
self.assertTrue(benef.action_validate((new_benef | new_leave_benef).ids), "It should be able to validate the benefits")
|
||||
|
||||
def test_refuse_leave_benefit(self):
|
||||
start = datetime.strptime('2015-11-01 09:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
end = datetime.strptime('2015-11-03 13:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
start = self.to_datetime_tz('2015-11-01 09:00:00')
|
||||
end = self.to_datetime_tz('2015-11-03 13:00:00')
|
||||
leave = self.env['hr.leave'].create({
|
||||
'name': 'Doctor Appointment',
|
||||
'employee_id': self.richard_emp.id,
|
||||
@@ -308,12 +328,12 @@ class TestBenefit(TestPayslipBase):
|
||||
|
||||
def test_time_normal_benefit(self):
|
||||
# Normal attendances (global to all employees)
|
||||
data = self.richard_emp.get_benefit_days_data(self.env.ref('hr_payroll.benefit_type_attendance'), self.start, self.end)
|
||||
data = self.richard_emp._get_benefit_days_data(self.env.ref('hr_payroll.benefit_type_attendance'), self.start, self.end)
|
||||
self.assertEqual(data['hours'], 168.0)
|
||||
|
||||
def test_time_extra_benefit(self):
|
||||
start = datetime.strptime('2015-11-01 10:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
end = datetime.strptime('2015-11-01 17:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=self.tz)
|
||||
start = self.to_datetime_tz('2015-11-01 10:00:00')
|
||||
end = self.to_datetime_tz('2015-11-01 17:00:00')
|
||||
benef = self.env['hr.benefit'].create({
|
||||
'name': '1',
|
||||
'employee_id': self.richard_emp.id,
|
||||
@@ -323,13 +343,13 @@ class TestBenefit(TestPayslipBase):
|
||||
'date_stop': end,
|
||||
})
|
||||
benef.action_validate(benef.ids)
|
||||
data = self.richard_emp.get_benefit_days_data(self.benefit_type, self.start, self.end)
|
||||
data = self.richard_emp._get_benefit_days_data(self.benefit_type, self.start, self.end)
|
||||
self.assertEqual(data['hours'], 7.0)
|
||||
|
||||
def test_time_week_leave_benefit(self):
|
||||
# /!\ this is a week day => it exists an calendar attendance at this time
|
||||
start = datetime.strptime('2015-11-02 10:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.utc) # UTC
|
||||
end = datetime.strptime('2015-11-02 17:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.utc)
|
||||
start = self.to_datetime_tz('2015-11-02 10:00:00', tz=pytz.utc)
|
||||
end = self.to_datetime_tz('2015-11-02 17:00:00', tz=pytz.utc)
|
||||
leave_benef = self.env['hr.benefit'].create({
|
||||
'name': '1leave',
|
||||
'employee_id': self.richard_emp.id,
|
||||
@@ -339,13 +359,13 @@ class TestBenefit(TestPayslipBase):
|
||||
'date_stop': end,
|
||||
})
|
||||
leave_benef.action_validate(leave_benef.ids)
|
||||
data = self.richard_emp.get_benefit_days_data(self.benefit_type_leave, self.start, self.end)
|
||||
data = self.richard_emp._get_benefit_days_data(self.benefit_type_leave, self.start, self.end)
|
||||
self.assertEqual(data['hours'], 5.0, "It should equal the number of hours richard should have worked")
|
||||
|
||||
def test_time_weekend_leave_benefit(self):
|
||||
# /!\ this is in the weekend => no calendar attendance at this time
|
||||
start = datetime.strptime('2015-11-01 10:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.utc) # UTC
|
||||
end = datetime.strptime('2015-11-01 17:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.utc)
|
||||
start = self.to_datetime_tz('2015-11-01 10:00:00', tz=pytz.utc)
|
||||
end = self.to_datetime_tz('2015-11-01 17:00:00', tz=pytz.utc)
|
||||
leave_benef = self.env['hr.benefit'].create({
|
||||
'name': '1leave',
|
||||
'employee_id': self.richard_emp.id,
|
||||
@@ -355,13 +375,13 @@ class TestBenefit(TestPayslipBase):
|
||||
'date_stop': end,
|
||||
})
|
||||
leave_benef.action_validate(leave_benef.ids)
|
||||
data = self.richard_emp.get_benefit_days_data(self.benefit_type_leave, self.start, self.end)
|
||||
data = self.richard_emp._get_benefit_days_data(self.benefit_type_leave, self.start, self.end)
|
||||
self.assertEqual(data['hours'], 0.0, "It should equal the number of hours richard should have worked")
|
||||
|
||||
def test_payslip_generation_with_leave(self):
|
||||
# /!\ this is a week day => it exists an calendar attendance at this time
|
||||
start = datetime.strptime('2015-11-02 10:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.utc) # UTC
|
||||
end = datetime.strptime('2015-11-02 17:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.utc)
|
||||
start = self.to_datetime_tz('2015-11-02 10:00:00', tz=pytz.utc)
|
||||
end = self.to_datetime_tz('2015-11-02 17:00:00', tz=pytz.utc)
|
||||
leave_benef = self.env['hr.benefit'].create({
|
||||
'name': '1leave',
|
||||
'employee_id': self.richard_emp.id,
|
||||
@@ -372,9 +392,9 @@ class TestBenefit(TestPayslipBase):
|
||||
})
|
||||
leave_benef.action_validate(leave_benef.ids)
|
||||
payslip_wizard = self.env['hr.payslip.employees'].create({'employee_ids': [(4, self.richard_emp.id)]})
|
||||
payslip_wizard.with_context({'default_date_start': start.strftime('%Y-%m-%d'),'default_date_end': end.strftime('%Y-%m-%d')}).compute_sheet()
|
||||
payslip_wizard.with_context({'default_date_start': Date.to_string(start), 'default_date_end': Date.to_string(end)}).compute_sheet()
|
||||
payslip = self.env['hr.payslip'].search([('employee_id', '=', self.richard_emp.id)])
|
||||
work_line = payslip.worked_days_line_ids.filtered(lambda l: l.code == 'WORK100') # From default calendar.attendance
|
||||
work_line = payslip.worked_days_line_ids.filtered(lambda l: l.code == 'WORK100') # From default calendar.attendance
|
||||
leave_line = payslip.worked_days_line_ids.filtered(lambda l: l.code == 'LEAVE100')
|
||||
|
||||
self.assertTrue(work_line, "It should have a work line in the payslip")
|
||||
@@ -384,8 +404,8 @@ class TestBenefit(TestPayslipBase):
|
||||
|
||||
def test_payslip_generation_with_extra_work(self):
|
||||
# /!\ this is in the weekend (Sunday) => no calendar attendance at this time
|
||||
start = datetime.strptime('2015-11-01 10:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.utc) # UTC
|
||||
end = datetime.strptime('2015-11-01 17:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.utc)
|
||||
start = self.to_datetime_tz('2015-11-01 10:00:00', tz=pytz.utc)
|
||||
end = self.to_datetime_tz('2015-11-01 17:00:00', tz=pytz.utc)
|
||||
benef = self.env['hr.benefit'].create({
|
||||
'name': 'Extra',
|
||||
'employee_id': self.richard_emp.id,
|
||||
@@ -397,8 +417,8 @@ class TestBenefit(TestPayslipBase):
|
||||
benef.action_validate(benef.ids)
|
||||
payslip_wizard = self.env['hr.payslip.employees'].create({'employee_ids': [(4, self.richard_emp.id)]})
|
||||
payslip_wizard.with_context({
|
||||
'default_date_start': start.strftime('%Y-%m-%d'),
|
||||
'default_date_end': (end + relativedelta(days=1)).strftime('%Y-%m-%d')
|
||||
'default_date_start': Date.to_string(start),
|
||||
'default_date_end': Date.to_string(end + relativedelta(days=1))
|
||||
}).compute_sheet()
|
||||
payslip = self.env['hr.payslip'].search([('employee_id', '=', self.richard_emp.id)])
|
||||
work_line = payslip.worked_days_line_ids.filtered(lambda l: l.code == 'WORK100') # From default calendar.attendance
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# # -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
import pytz
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from odoo import exceptions
|
||||
from odoo.tests.common import tagged
|
||||
from odoo.addons.hr_payroll.tests.common import TestPayslipBase
|
||||
|
||||
|
||||
class TestPayrollLeave(TestPayslipBase):
|
||||
|
||||
def create_leave(self):
|
||||
return self.env['hr.leave'].create({
|
||||
'name': 'Holiday !!!',
|
||||
'employee_id': self.richard_emp.id,
|
||||
'holiday_status_id': self.leave_type.id,
|
||||
'date_to': datetime.today() + relativedelta(days=1),
|
||||
'date_from': datetime.today(),
|
||||
'number_of_days': 1,
|
||||
})
|
||||
|
||||
def test_resource_leave_has_benefit_type(self):
|
||||
leave = self.create_leave()
|
||||
|
||||
resource_leave = leave._create_resource_leave()
|
||||
self.assertEqual(resource_leave.benefit_type_id, self.leave_type.benefit_type_id, "it should have the corresponding benefit type")
|
||||
|
||||
def test_resource_leave_in_contract_calendar(self):
|
||||
other_calendar = self.env['resource.calendar'].create({'name': 'New calendar'})
|
||||
contract = self.richard_emp.contract_ids[0]
|
||||
contract.resource_calendar_id = other_calendar
|
||||
contract.state = 'open' # this set richard's calendar to New calendar
|
||||
leave = self.create_leave()
|
||||
|
||||
resource_leave = leave._create_resource_leave()
|
||||
self.assertEqual(len(resource_leave), 1, "it should have created only one resource leave")
|
||||
self.assertEqual(resource_leave.benefit_type_id, self.leave_type.benefit_type_id, "it should have the corresponding benefit type")
|
||||
|
||||
def test_resource_leave_different_calendars(self):
|
||||
other_calendar = self.env['resource.calendar'].create({'name': 'New calendar'})
|
||||
contract = self.richard_emp.contract_ids[0]
|
||||
contract.resource_calendar_id = other_calendar
|
||||
contract.state = 'open' # this set richard's calendar to New calendar
|
||||
|
||||
# set another calendar
|
||||
self.richard_emp.resource_calendar_id = self.env['resource.calendar'].create({'name': 'Other calendar'})
|
||||
|
||||
leave = self.create_leave()
|
||||
resource_leave = leave._create_resource_leave()
|
||||
self.assertEqual(len(resource_leave), 2, "it should have created one resource leave per calendar")
|
||||
self.assertEqual(resource_leave.mapped('benefit_type_id'), self.leave_type.benefit_type_id, "they should have the corresponding benefit type")
|
||||
@@ -53,8 +53,7 @@
|
||||
<span class="fa fa-exclamation-triangle mr-2" aria-label="Conflict" title="error"/>
|
||||
</t>
|
||||
<t t-if="record.state == 'validated'">
|
||||
<span class="fa fa-check-circle-o" aria-label="Validated" title="validated"/>
|
||||
<t t-esc="' '"/>
|
||||
<span class="fa fa-check-circle-o mr-2" aria-label="Validated" title="validated"/>
|
||||
</t>
|
||||
<t t-foreach="widget.displayFields" t-as="name">
|
||||
<t t-esc="format(record, name)"/>
|
||||
@@ -75,7 +74,7 @@
|
||||
<header>
|
||||
<field name="display_warning" invisible="1"/>
|
||||
<field name="leave_id" invisible="1"/>
|
||||
<button string="Go To Conflicting Leave" name="action_leave" type="object" attrs="{'invisible': ['|', '|', '!',('display_warning', '=', True), '!', ('leave_id', '!=', False), ('benefit_type_id', '=', False)]}"/>
|
||||
<button string="Go To Conflicting Leave" name="action_leave" type="object" attrs="{'invisible': ['|', '|', '!',('display_warning', '=', True), ('leave_id', '=', False), ('benefit_type_id', '=', False)]}"/>
|
||||
<field name="state" widget="statusbar" options="{'clickable': '1'}"/>
|
||||
</header>
|
||||
<sheet>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
from dateutil import relativedelta
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.fields import Date
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class PayslipLinesContributionRegister(models.TransientModel):
|
||||
_name = 'payslip.lines.contribution.register'
|
||||
_description = 'Payslip Lines by Contribution Registers'
|
||||
|
||||
date_from = fields.Date(string='Date From', required=True,
|
||||
default=datetime.now().strftime('%Y-%m-01'))
|
||||
date_to = fields.Date(string='Date To', required=True,
|
||||
default=str(datetime.now() + relativedelta.relativedelta(months=+1, day=1, days=-1))[:10])
|
||||
date_from = Date(string='Date From', required=True,
|
||||
default=Date.today().replace(day=1))
|
||||
date_to = Date(string='Date To', required=True,
|
||||
default=Date.today() + relativedelta.relativedelta(months=+1, day=1, days=-1))
|
||||
|
||||
@api.multi
|
||||
def print_report(self):
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from datetime import datetime, date
|
||||
|
||||
|
||||
class HrPayslipEmployees(models.TransientModel):
|
||||
@@ -24,8 +23,8 @@ class HrPayslipEmployees(models.TransientModel):
|
||||
def compute_sheet(self):
|
||||
self.ensure_one()
|
||||
if not self.env.context.get('active_id'):
|
||||
from_date = datetime.strptime(self.env.context.get('default_date_start'), '%Y-%m-%d')
|
||||
end_date = datetime.strptime(self.env.context.get('default_date_end'), '%Y-%m-%d')
|
||||
from_date = fields.Date.to_date(self.env.context.get('default_date_start'))
|
||||
end_date = fields.Date.to_date(self.env.context.get('default_date_end'))
|
||||
payslip_run = self.env['hr.payslip.run'].create({
|
||||
'name': from_date.strftime('%B %Y'),
|
||||
'date_start': from_date,
|
||||
|
||||
@@ -54,13 +54,13 @@ class HrPayslip(models.Model):
|
||||
@api.multi
|
||||
def action_payslip_done(self):
|
||||
res = super(HrPayslip, self).action_payslip_done()
|
||||
precision = self.env['decimal.precision'].precision_get('Payroll')
|
||||
|
||||
for slip in self:
|
||||
line_ids = []
|
||||
debit_sum = 0.0
|
||||
credit_sum = 0.0
|
||||
date = slip.date or slip.date_to
|
||||
currency = slip.company_id.currency_id
|
||||
|
||||
name = _('Payslip of %s') % (slip.employee_id.name)
|
||||
move_dict = {
|
||||
@@ -70,8 +70,8 @@ class HrPayslip(models.Model):
|
||||
'date': date,
|
||||
}
|
||||
for line in slip.line_ids.filtered(lambda line: line.category_id):
|
||||
amount = slip.credit_note and -line.total or line.total
|
||||
if float_is_zero(amount, precision_digits=precision):
|
||||
amount = currency.round(slip.credit_note and -line.total or line.total)
|
||||
if currency.is_zero(amount):
|
||||
continue
|
||||
debit_account_id = line.salary_rule_id.account_debit.id
|
||||
credit_account_id = line.salary_rule_id.account_credit.id
|
||||
@@ -106,7 +106,7 @@ class HrPayslip(models.Model):
|
||||
line_ids.append(credit_line)
|
||||
credit_sum += credit_line[2]['credit'] - credit_line[2]['debit']
|
||||
|
||||
if float_compare(credit_sum, debit_sum, precision_digits=precision) == -1:
|
||||
if currency.compare_amounts(credit_sum, debit_sum) == -1:
|
||||
acc_id = slip.journal_id.default_credit_account_id.id
|
||||
if not acc_id:
|
||||
raise UserError(_('The Expense Journal "%s" has not properly configured the Credit Account!') % (slip.journal_id.name))
|
||||
@@ -117,11 +117,11 @@ class HrPayslip(models.Model):
|
||||
'journal_id': slip.journal_id.id,
|
||||
'date': date,
|
||||
'debit': 0.0,
|
||||
'credit': debit_sum - credit_sum,
|
||||
'credit': currency.round(debit_sum - credit_sum),
|
||||
})
|
||||
line_ids.append(adjust_credit)
|
||||
|
||||
elif float_compare(debit_sum, credit_sum, precision_digits=precision) == -1:
|
||||
elif currency.compare_amounts(debit_sum, credit_sum) == -1:
|
||||
acc_id = slip.journal_id.default_debit_account_id.id
|
||||
if not acc_id:
|
||||
raise UserError(_('The Expense Journal "%s" has not properly configured the Debit Account!') % (slip.journal_id.name))
|
||||
@@ -131,7 +131,7 @@ class HrPayslip(models.Model):
|
||||
'account_id': acc_id,
|
||||
'journal_id': slip.journal_id.id,
|
||||
'date': date,
|
||||
'debit': credit_sum - debit_sum,
|
||||
'debit': currency.round(credit_sum - debit_sum),
|
||||
'credit': 0.0,
|
||||
})
|
||||
line_ids.append(adjust_debit)
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
td.o_skill_cell {
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
.o_group_resume tbody tr.o_resume_group_header th {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
<t t-name="hr_skill_data_row">
|
||||
|
||||
<tr class="o_data_row" t-att-data-id="id">
|
||||
<td class="o_data_cell">
|
||||
<td class="o_data_cell o_skill_cell">
|
||||
<t t-esc="data.skill_id.data.display_name"/>
|
||||
</td>
|
||||
<td class="o_data_cell">
|
||||
<td class="o_data_cell o_skill_cell">
|
||||
<t t-esc="data.skill_level_id.data.display_name"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<!-- This field uses a custom tree view rendered by the 'hr_resume' widget.
|
||||
Adding fields in the tree arch below makes them accessible to the widget
|
||||
-->
|
||||
<field class="" nolabel="1" name="resume_line_ids" widget="hr_resume">
|
||||
<field mode="tree" nolabel="1" name="resume_line_ids" widget="hr_resume">
|
||||
<tree>
|
||||
<field name="line_type_id"/>
|
||||
<field name="name"/>
|
||||
@@ -62,7 +62,7 @@
|
||||
</div>
|
||||
<div class="col-lg-4 o_group_skills">
|
||||
<separator string="Skills"/>
|
||||
<field nolabel="1" name="employee_skill_ids" widget="hr_skills">
|
||||
<field mode="tree" nolabel="1" name="employee_skill_ids" widget="hr_skills">
|
||||
<tree>
|
||||
<field name="skill_type_id" invisible="1"/>
|
||||
<field name="skill_id"/>
|
||||
@@ -89,7 +89,7 @@
|
||||
<!-- This field uses a custom tree view rendered by the 'hr_resume' widget.
|
||||
Adding fields in the tree arch below makes them accessible to the widget
|
||||
-->
|
||||
<field class="" nolabel="1" name="resume_line_ids" widget="hr_resume">
|
||||
<field mode="tree" nolabel="1" name="resume_line_ids" widget="hr_resume">
|
||||
<tree>
|
||||
<field name="line_type_id"/>
|
||||
<field name="name"/>
|
||||
@@ -102,7 +102,7 @@
|
||||
</div>
|
||||
<div class="col-lg-4 o_group_skills">
|
||||
<separator string="Skills"/>
|
||||
<field nolabel="1" name="employee_skill_ids" widget="hr_skills">
|
||||
<field mode="tree" nolabel="1" name="employee_skill_ids" widget="hr_skills">
|
||||
<tree>
|
||||
<field name="skill_type_id" invisible="1"/>
|
||||
<field name="skill_id"/>
|
||||
|
||||
@@ -166,6 +166,7 @@ class ImLivechatChannel(models.Model):
|
||||
'public': 'private',
|
||||
'email_send': False,
|
||||
})
|
||||
mail_channel._broadcast([operator_partner_id])
|
||||
return mail_channel.sudo().channel_info()[0]
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -4,26 +4,25 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 9.0c\n"
|
||||
"Project-Id-Version: Odoo Server 12.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-03-14 15:52+0000\n"
|
||||
"PO-Revision-Date: 2016-12-15 14:35+0100\n"
|
||||
"Last-Translator: <didier.donze@camptocamp.com>\n"
|
||||
"POT-Creation-Date: 2019-02-12 05:39+0000\n"
|
||||
"PO-Revision-Date: 2019-02-12 12:06+0530\n"
|
||||
"Last-Translator: didier.donze@camptocamp.com\n"
|
||||
"Language-Team: \n"
|
||||
"Language: de\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: \n"
|
||||
"X-Generator: Poedit 2.2.1\n"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_O_exclude
|
||||
#: model:account.tax.template,description:l10n_ch.vat_O_exclude
|
||||
msgid "0% excl."
|
||||
msgstr "0% Exkl."
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_O_import
|
||||
#: model:account.tax.template,description:l10n_ch.vat_O_import
|
||||
msgid "0% import."
|
||||
msgstr "0% Import."
|
||||
@@ -34,118 +33,105 @@ msgid "100% dédouanement TVA"
|
||||
msgstr "100% Verzollung MwSt."
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_100_import
|
||||
#: model:account.tax.template,description:l10n_ch.vat_100_import
|
||||
msgid "100% imp."
|
||||
msgstr "100% Imp."
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_25
|
||||
#: model:account.tax.template,description:l10n_ch.vat_25
|
||||
msgid "2.5%"
|
||||
msgstr "2.5%"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_25_incl
|
||||
#: model:account.tax.template,description:l10n_ch.vat_25_incl
|
||||
msgid "2.5% Incl."
|
||||
msgstr "2.5% Inkl."
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_25_purchase
|
||||
#: model:account.tax.template,description:l10n_ch.vat_25_purchase
|
||||
msgid "2.5% achat"
|
||||
msgstr "2.5% Einkauf"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_25_purchase_incl
|
||||
#: model:account.tax.template,description:l10n_ch.vat_25_purchase_incl
|
||||
msgid "2.5% achat Incl."
|
||||
msgstr "2.5% Einkauf Inkl."
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_25_invest
|
||||
#: model:account.tax.template,description:l10n_ch.vat_25_invest
|
||||
msgid "2.5% invest."
|
||||
msgstr "2.5% Invest."
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_25_invest_incl
|
||||
#: model:account.tax.template,description:l10n_ch.vat_25_invest_incl
|
||||
msgid "2.5% invest. Incl."
|
||||
msgstr "2.5% Invest. Inkl."
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_38
|
||||
#: model:account.tax.template,description:l10n_ch.vat_38
|
||||
msgid "3.8%"
|
||||
msgstr "3.8%"
|
||||
#: model:account.tax.template,description:l10n_ch.vat_37
|
||||
msgid "3.7%"
|
||||
msgstr "3.7%"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_38_incl
|
||||
#: model:account.tax.template,description:l10n_ch.vat_38_incl
|
||||
msgid "3.8% Incl."
|
||||
msgstr "3.8% Inkl."
|
||||
#: model:account.tax.template,description:l10n_ch.vat_37_incl
|
||||
msgid "3.7% Incl."
|
||||
msgstr "3.7% Inkl."
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_38_purchase
|
||||
#: model:account.tax.template,description:l10n_ch.vat_38_purchase
|
||||
msgid "3.8% achat"
|
||||
msgstr "3.8% Einkauf"
|
||||
#: model:account.tax.template,description:l10n_ch.vat_37_purchase
|
||||
msgid "3.7% achat"
|
||||
msgstr "3.7% Einkauf"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_38_purchase_incl
|
||||
#: model:account.tax.template,description:l10n_ch.vat_38_purchase_incl
|
||||
msgid "3.8% achat Incl."
|
||||
msgstr "3.8% Einkauf Inkl."
|
||||
#: model:account.tax.template,description:l10n_ch.vat_37_purchase_incl
|
||||
msgid "3.7% achat Incl."
|
||||
msgstr "3.7% Einkauf Inkl."
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_38_invest
|
||||
#: model:account.tax.template,description:l10n_ch.vat_38_invest
|
||||
msgid "3.8% invest"
|
||||
msgstr "3.8% Invest."
|
||||
#: model:account.tax.template,description:l10n_ch.vat_37_invest
|
||||
msgid "3.7% invest"
|
||||
msgstr "3.7% Invest."
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_38_invest_incl
|
||||
#: model:account.tax.template,description:l10n_ch.vat_38_invest_incl
|
||||
msgid "3.8% invest Incl."
|
||||
msgstr "3.8% Invest. Inkl."
|
||||
#: model:account.tax.template,description:l10n_ch.vat_37_invest_incl
|
||||
msgid "3.7% invest Incl."
|
||||
msgstr "3.7% Invest. Inkl."
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_80
|
||||
#: model:account.tax.template,description:l10n_ch.vat_80
|
||||
msgid "8.0%"
|
||||
msgstr "8.0%"
|
||||
#: model:account.tax.template,description:l10n_ch.vat_77
|
||||
msgid "7.7%"
|
||||
msgstr "7.7%"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_80_incl
|
||||
#: model:account.tax.template,description:l10n_ch.vat_80_incl
|
||||
msgid "8.0% Incl."
|
||||
msgstr "8.0% Inkl."
|
||||
#: model:account.tax.template,description:l10n_ch.vat_77_incl
|
||||
msgid "7.7% Incl."
|
||||
msgstr "7.7% Inkl."
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_80_purchase
|
||||
#: model:account.tax.template,description:l10n_ch.vat_80_purchase
|
||||
msgid "8.0% achat"
|
||||
msgstr "8.0% Einkauf"
|
||||
#: model:account.tax.template,description:l10n_ch.vat_77_purchase
|
||||
#: model:account.tax.template,description:l10n_ch.vat_77_purchase_reverse
|
||||
msgid "7.7% achat"
|
||||
msgstr "7.7% Einkauf"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_80_purchase_incl
|
||||
#: model:account.tax.template,description:l10n_ch.vat_80_purchase_incl
|
||||
msgid "8.0% achat Incl."
|
||||
msgstr "8.0% Einkauf Inkl."
|
||||
#: model:account.tax.template,description:l10n_ch.vat_77_purchase_return
|
||||
msgid "7.7% achat (return)"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_80_invest
|
||||
#: model:account.tax.template,description:l10n_ch.vat_80_invest
|
||||
msgid "8.0% invest."
|
||||
msgstr "8.0% Invest."
|
||||
#: model:account.tax.template,description:l10n_ch.vat_77_purchase_incl
|
||||
msgid "7.7% achat Incl."
|
||||
msgstr "7.7% Einkauf Inkl."
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax,description:l10n_ch.1_vat_80_invest_incl
|
||||
#: model:account.tax.template,description:l10n_ch.vat_80_invest_incl
|
||||
msgid "8.0% invest. Incl."
|
||||
msgstr "8.0% Invest. Inkl."
|
||||
#: model:account.tax.template,description:l10n_ch.vat_77_invest
|
||||
msgid "7.7% invest."
|
||||
msgstr "7.7% Invest."
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,description:l10n_ch.vat_77_invest_incl
|
||||
msgid "7.7% invest. Incl."
|
||||
msgstr "7.7% Invest. Inkl."
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.template,name:l10n_ch.ch_coa_4200
|
||||
@@ -255,7 +241,8 @@ msgstr "Abschreibungen auf Fahrzeuge"
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.template,name:l10n_ch.ch_coa_1579
|
||||
msgid "Amortissements sur les équipements et installations"
|
||||
msgstr "Abschreibungen und Wertberichtigungen feste Einrichtungen und Installationen"
|
||||
msgstr ""
|
||||
"Abschreibungen und Wertberichtigungen feste Einrichtungen und Installationen"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.template,name:l10n_ch.ch_coa_2270
|
||||
@@ -312,37 +299,6 @@ msgstr "Übrige Erlöse aus Lieferungen und Leistungen"
|
||||
msgid "Avances et prêts"
|
||||
msgstr "Vorschüsse und Darlehen"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model,name:l10n_ch.model_res_bank
|
||||
msgid "Bank"
|
||||
msgstr "Bank"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model,name:l10n_ch.model_res_partner_bank
|
||||
msgid "Bank Accounts"
|
||||
msgstr "Bankkonten"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,help:l10n_ch.field_account_config_settings_l10n_ch_isr_print_bank_location
|
||||
#: model:ir.model.fields,help:l10n_ch.field_res_company_l10n_ch_isr_print_bank_location
|
||||
msgid ""
|
||||
"Boolean option field indicating whether or not the alternate layout (the one "
|
||||
"printing bank name and address) must be used when generating an ISR."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,help:l10n_ch.field_account_invoice_l10n_ch_isr_sent
|
||||
msgid ""
|
||||
"Boolean value telling whether or not the ISR corresponding to this invoice "
|
||||
"has already been printed or sent by mail."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,help:l10n_ch.field_account_invoice_l10n_ch_isr_valid
|
||||
msgid ""
|
||||
"Boolean value. True iff all the data required to generate the ISR are present"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.template,name:l10n_ch.ch_coa_1700
|
||||
msgid "Brevets, know-how, licences, droits, développement"
|
||||
@@ -358,11 +314,6 @@ msgstr "Jahresgewinn oder Jahresverlust"
|
||||
msgid "Bénéfice / perte reporté"
|
||||
msgstr "Gewinnvortrag oder Verlustvortrag"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,field_description:l10n_ch.field_res_bank_l10n_ch_postal_chf
|
||||
msgid "CHF ISR reference"
|
||||
msgstr "CHF ESR Referenznummer"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.template,name:l10n_ch.ch_coa_1850
|
||||
msgid ""
|
||||
@@ -520,11 +471,6 @@ msgstr "Forderungen gegenüber Sozialversicherungen und Vorsorgeeinrichtungen"
|
||||
msgid "Créanciers"
|
||||
msgstr "Verbindlichkeiten aus Lieferungen und Leistungen (Kreditoren)"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,field_description:l10n_ch.field_account_invoice_l10n_ch_currency_name
|
||||
msgid "Currency"
|
||||
msgstr "Währung"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.template,name:l10n_ch.ch_coa_2100
|
||||
#: model:account.account.template,name:l10n_ch.ch_coa_2400
|
||||
@@ -577,11 +523,6 @@ msgstr "Einkaufsrabatte"
|
||||
msgid "Déductions sur ventes"
|
||||
msgstr "Erlösminderungen"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,field_description:l10n_ch.field_res_bank_l10n_ch_postal_eur
|
||||
msgid "EUR ISR reference"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.template,name:l10n_ch.ch_coa_4540
|
||||
msgid "Eau"
|
||||
@@ -597,11 +538,6 @@ msgstr "Strom"
|
||||
msgid "Email Templates"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model,name:l10n_ch.model_mail_compose_message
|
||||
msgid "Email composition wizard"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.template,name:l10n_ch.ch_coa_2450
|
||||
msgid "Emprunts"
|
||||
@@ -676,34 +612,6 @@ msgstr "Goodwill"
|
||||
msgid "Hypothèques"
|
||||
msgstr "Hypotheken"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.actions.report.xml,name:l10n_ch.l10n_ch_isr_report
|
||||
msgid "ISR"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_ch.l10n_ch_isr_report_template
|
||||
msgid "ISR for invoice"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,help:l10n_ch.field_account_invoice_l10n_ch_isr_number_spaced
|
||||
msgid ""
|
||||
"ISR number split in blocks of 5 characters (right-justified), to generate "
|
||||
"ISR report."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_ch.isr_invoice_search_view
|
||||
msgid "ISR reference number"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: code:addons/l10n_ch/models/mail_compose_message.py:23
|
||||
#, python-format
|
||||
msgid "ISR sent"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.template,name:l10n_ch.ch_coa_1600
|
||||
msgid "Immeubles d’exploitation"
|
||||
@@ -751,51 +659,6 @@ msgstr "Direkte Steuern"
|
||||
msgid "Installations de stockage"
|
||||
msgstr "Installations de stockage"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model,name:l10n_ch.model_account_invoice
|
||||
msgid "Invoice"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,field_description:l10n_ch.field_account_invoice_l10n_ch_isr_number
|
||||
msgid "L10N Ch Isr Number"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,field_description:l10n_ch.field_account_invoice_l10n_ch_isr_number_spaced
|
||||
msgid "L10N Ch Isr Number Spaced"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,field_description:l10n_ch.field_account_invoice_l10n_ch_isr_optical_line
|
||||
msgid "L10N Ch Isr Optical Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,field_description:l10n_ch.field_account_invoice_l10n_ch_isr_postal
|
||||
msgid "L10N Ch Isr Postal"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,field_description:l10n_ch.field_account_invoice_l10n_ch_isr_postal_formatted
|
||||
msgid "L10N Ch Isr Postal Formatted"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,field_description:l10n_ch.field_account_invoice_l10n_ch_isr_sent
|
||||
msgid "L10N Ch Isr Sent"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,field_description:l10n_ch.field_account_invoice_l10n_ch_isr_valid
|
||||
msgid "L10N Ch Isr Valid"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,field_description:l10n_ch.field_res_partner_bank_l10n_ch_postal
|
||||
msgid "L10N Ch Postal"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.template,name:l10n_ch.ch_coa_6260
|
||||
msgid "Leasing et location de véhicules"
|
||||
@@ -806,6 +669,11 @@ msgstr "Fahrzeugleasing und -mieten"
|
||||
msgid "Leasing immobilisations corporelles meubles"
|
||||
msgstr "Leasingaufwand mobile Sachanlagen"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.template,name:l10n_ch.l10nch_chart_template_liquidity_transfer
|
||||
msgid "Liquidity Transfer"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.template,name:l10n_ch.ch_coa_1520
|
||||
msgid "Machines de bureau, informatique, systèmes de communication"
|
||||
@@ -851,11 +719,6 @@ msgstr "Heizöl"
|
||||
msgid "Mobilier et installations"
|
||||
msgstr "Mobiliar und Einrichtungen"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,help:l10n_ch.field_account_invoice_l10n_ch_isr_optical_line
|
||||
msgid "Optical reading line, as it will be printed on ISR"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.template,name:l10n_ch.ch_coa_1540
|
||||
msgid "Outillages et appareils"
|
||||
@@ -881,13 +744,6 @@ msgstr "Verluste Forderungen (Debitoren), Veränderung Delkredere"
|
||||
msgid "Plan comptable 2015"
|
||||
msgstr "Kontenrahmen 2015"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,help:l10n_ch.field_account_invoice_l10n_ch_isr_postal_formatted
|
||||
msgid ""
|
||||
"Postal reference of the bank, formated with '-' and without the padding "
|
||||
"zeros, to generate ISR report."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.template,name:l10n_ch.ch_coa_4400
|
||||
msgid "Prestations / travaux de tiers"
|
||||
@@ -898,30 +754,6 @@ msgstr "Aufwand für bezogene Dienstleistungen"
|
||||
msgid "Prestations propres"
|
||||
msgstr "Eigenleistungen"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_ch.isr_invoice_form
|
||||
msgid "Print ISR"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,field_description:l10n_ch.field_res_company_l10n_ch_isr_print_bank_location
|
||||
msgid "Print bank location"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,field_description:l10n_ch.field_account_config_settings_l10n_ch_isr_print_bank_location
|
||||
msgid "Print bank on ISR"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_ch.l10n_ch-isr_account_config_settings
|
||||
msgid ""
|
||||
"Print the coordinates of your bank under the 'Payment for' title of the "
|
||||
"ISR.\n"
|
||||
" Your address will be moved to the 'in favour "
|
||||
"of' section."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.template,name:l10n_ch.ch_coa_7000
|
||||
msgid "Produits accessoires"
|
||||
@@ -1034,11 +866,6 @@ msgstr "Fertige Erzeugnisse"
|
||||
msgid "Stocks de produits semi-ouvrés"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.ui.menu,name:l10n_ch.account_reports_ch_statements_menu
|
||||
msgid "Swiss Statements"
|
||||
msgstr "CH Bilanz- und Erfolgsrechnungen"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.tag,name:l10n_ch.vat_tag_dedouanement
|
||||
msgid "Switzerland VAT Form: 100% dédouanement (base in grid 400)"
|
||||
@@ -1080,13 +907,13 @@ msgid "Switzerland VAT Form: grid 280"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.tag,name:l10n_ch.vat_tag_301_a
|
||||
msgid "Switzerland VAT Form: grid 301 base"
|
||||
#: model:account.account.tag,name:l10n_ch.vat_tag_302_a
|
||||
msgid "Switzerland VAT Form: grid 302 base"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.tag,name:l10n_ch.vat_tag_301_b
|
||||
msgid "Switzerland VAT Form: grid 301 tax"
|
||||
#: model:account.account.tag,name:l10n_ch.vat_tag_302_b
|
||||
msgid "Switzerland VAT Form: grid 302 tax"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
@@ -1100,13 +927,13 @@ msgid "Switzerland VAT Form: grid 311 tax"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.tag,name:l10n_ch.vat_tag_341_a
|
||||
msgid "Switzerland VAT Form: grid 341 base"
|
||||
#: model:account.account.tag,name:l10n_ch.vat_tag_342_a
|
||||
msgid "Switzerland VAT Form: grid 342 base"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.tag,name:l10n_ch.vat_tag_341_b
|
||||
msgid "Switzerland VAT Form: grid 341 tax"
|
||||
#: model:account.account.tag,name:l10n_ch.vat_tag_342_b
|
||||
msgid "Switzerland VAT Form: grid 342 tax"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
@@ -1119,6 +946,16 @@ msgstr ""
|
||||
msgid "Switzerland VAT Form: grid 381 tax"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.tag,name:l10n_ch.vat_tag_382_a
|
||||
msgid "Switzerland VAT Form: grid 382 base"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.tag,name:l10n_ch.vat_tag_382_b
|
||||
msgid "Switzerland VAT Form: grid 382 tax"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.tag,name:l10n_ch.vat_tag_400
|
||||
msgid "Switzerland VAT Form: grid 400"
|
||||
@@ -1144,6 +981,11 @@ msgstr ""
|
||||
msgid "Switzerland VAT Form: grid 420"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.group,name:l10n_ch.tax_group_tva_0
|
||||
msgid "TVA 0%"
|
||||
msgstr "MwSt. 0%"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,name:l10n_ch.vat_O_import
|
||||
msgid "TVA 0% Importations de biens et services"
|
||||
@@ -1154,6 +996,11 @@ msgstr "MwSt. 0% auf Importe von Gütern und Dienstleistungen"
|
||||
msgid "TVA 0% exclue"
|
||||
msgstr "MwSt. 0% ausgeschlossen"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.group,name:l10n_ch.tax_group_tva_25
|
||||
msgid "TVA 2.5%"
|
||||
msgstr "MwSt. 2.5%"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,name:l10n_ch.vat_25_purchase_incl
|
||||
msgid "TVA 2.5% sur achat B&S (Incl. TR)"
|
||||
@@ -1175,44 +1022,59 @@ msgid "TVA 2.5% sur invest. et autres ch. (TR)"
|
||||
msgstr "MwSt. 2,5% auf Invest. und sonst. Aufw. (USt)"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,name:l10n_ch.vat_38_purchase_incl
|
||||
msgid "TVA 3.8% sur achat B&S (Incl. TS)"
|
||||
msgstr "MwSt. 3,8% auf Einkäufe B&S (inkl. LSt)"
|
||||
#: model:account.tax.group,name:l10n_ch.tax_group_tva_37
|
||||
msgid "TVA 3.7%"
|
||||
msgstr "MwSt. 3,7%"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,name:l10n_ch.vat_38_purchase
|
||||
msgid "TVA 3.8% sur achat B&S (TS)"
|
||||
msgstr "MwSt. 3,8% auf Einkäufe B&S (LSt)"
|
||||
#: model:account.tax.template,name:l10n_ch.vat_37_purchase_incl
|
||||
msgid "TVA 3.7% sur achat B&S (Incl. TS)"
|
||||
msgstr "MwSt. 3,7% auf Einkäufe B&S (inkl. LSt)"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,name:l10n_ch.vat_38_invest_incl
|
||||
msgid "TVA 3.8% sur invest. et autres ch. (Incl. TS)"
|
||||
msgstr "MwSt. 3,8% auf Invest. und sonst. Aufw. (inkl. LSt)"
|
||||
#: model:account.tax.template,name:l10n_ch.vat_37_purchase
|
||||
msgid "TVA 3.7% sur achat B&S (TS)"
|
||||
msgstr "MwSt. 3,7% auf Einkäufe B&S (LSt)"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,name:l10n_ch.vat_38_invest
|
||||
msgid "TVA 3.8% sur invest. et autres ch. (TS)"
|
||||
msgstr "MwSt. 3,8% auf Invest. und sonst. Aufw. (LSt)"
|
||||
#: model:account.tax.template,name:l10n_ch.vat_37_invest_incl
|
||||
msgid "TVA 3.7% sur invest. et autres ch. (Incl. TS)"
|
||||
msgstr "MwSt. 3,7% auf Invest. und sonst. Aufw. (inkl. LSt)"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,name:l10n_ch.vat_80_purchase_incl
|
||||
msgid "TVA 8.0% sur achat B&S (Incl. TN)"
|
||||
msgstr "MwSt. 8.0% auf Einkäufe B&S (inkl. NRZ)"
|
||||
#: model:account.tax.template,name:l10n_ch.vat_37_invest
|
||||
msgid "TVA 3.7% sur invest. et autres ch. (TS)"
|
||||
msgstr "MwSt. 3,7% auf Invest. und sonst. Aufw. (LSt)"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,name:l10n_ch.vat_80_purchase
|
||||
msgid "TVA 8.0% sur achat B&S (TN)"
|
||||
msgstr "MwSt. 8,0% auf Einkäufe B&S (NRZ)"
|
||||
#: model:account.tax.group,name:l10n_ch.tax_group_tva_77
|
||||
msgid "TVA 7.7%"
|
||||
msgstr "MwSt. 7,7%"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,name:l10n_ch.vat_80_invest_incl
|
||||
msgid "TVA 8.0% sur invest. et autres ch. (Incl. TN)"
|
||||
msgstr "MwSt. 8,0% auf Invest. und sonst. Aufw. (inkl. NRZ)"
|
||||
#: model:account.tax.template,name:l10n_ch.vat_77_purchase_incl
|
||||
msgid "TVA 7.7% sur achat B&S (Incl. TN)"
|
||||
msgstr "MwSt. 7,7% auf Einkäufe B&S (inkl. NRZ)"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,name:l10n_ch.vat_80_invest
|
||||
msgid "TVA 8.0% sur invest. et autres ch. (TN)"
|
||||
msgstr "MwSt. 8,0% auf Invest. und sonst. Aufw. (NRZ)"
|
||||
#: model:account.tax.template,name:l10n_ch.vat_77_purchase
|
||||
msgid "TVA 7.7% sur achat B&S (TN)"
|
||||
msgstr "MwSt. 7,7% auf Einkäufe B&S (NRZ)"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,name:l10n_ch.vat_77_purchase_reverse
|
||||
msgid "TVA 7.7% sur achat service a l'etranger (reverse charge)"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,name:l10n_ch.vat_77_invest_incl
|
||||
msgid "TVA 7.7% sur invest. et autres ch. (Incl. TN)"
|
||||
msgstr "MwSt. 7,7% auf Invest. und sonst. Aufw. (inkl. NRZ)"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,name:l10n_ch.vat_77_invest
|
||||
msgid "TVA 7.7% sur invest. et autres ch. (TN)"
|
||||
msgstr "MwSt. 7,7% auf Invest. und sonst. Aufw. (NRZ)"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.template,name:l10n_ch.ch_coa_2200
|
||||
@@ -1230,61 +1092,34 @@ msgid "TVA due a 2.5% (TR)"
|
||||
msgstr "Zu zahlende MwSt. bei 2,5% (USt)"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,name:l10n_ch.vat_38
|
||||
msgid "TVA due a 3.8% (TS)"
|
||||
msgstr "Zu zahlende MwSt. bei 3,8% (LSt)"
|
||||
#: model:account.tax.template,name:l10n_ch.vat_37
|
||||
msgid "TVA due a 3.7% (TS)"
|
||||
msgstr "Zu zahlende MwSt. bei 3,7% (LSt)"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,name:l10n_ch.vat_80
|
||||
msgid "TVA due a 8.0% (TN)"
|
||||
#: model:account.tax.template,name:l10n_ch.vat_77
|
||||
msgid "TVA due a 7.7% (TN)"
|
||||
msgstr "Zu zahlende MwSt. bei 8,0% (NRZ)"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,name:l10n_ch.vat_77_purchase_return
|
||||
msgid "TVA due a 7.7% (TN) (return)"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,name:l10n_ch.vat_25_incl
|
||||
msgid "TVA due à 2.5% (Incl. TR)"
|
||||
msgstr "Zu zahlende MwSt. bei 2,5% (inkl. USt)"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,name:l10n_ch.vat_38_incl
|
||||
msgid "TVA due à 3.8% (Incl. TS)"
|
||||
msgstr "Zu zahlende MwSt. bei 3,8% (inkl. LSt)"
|
||||
#: model:account.tax.template,name:l10n_ch.vat_37_incl
|
||||
msgid "TVA due à 3.7% (Incl. TS)"
|
||||
msgstr "Zu zahlende MwSt. bei 3,7% (inkl. LSt)"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.tax.template,name:l10n_ch.vat_80_incl
|
||||
msgid "TVA due à 8.0% (Incl. TN)"
|
||||
msgstr "Zu zahlende MwSt. bei 8,0% (inkl. NRZ)"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,help:l10n_ch.field_res_partner_bank_l10n_ch_postal
|
||||
msgid "The ISR number of the company within the bank"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,help:l10n_ch.field_account_invoice_l10n_ch_currency_name
|
||||
msgid "The name of this invoice's currency"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,help:l10n_ch.field_account_invoice_l10n_ch_isr_postal
|
||||
msgid "The postal reference identifying the bank managing this ISR."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,help:l10n_ch.field_res_bank_l10n_ch_postal_chf
|
||||
msgid ""
|
||||
"The postal reference of the bank, used to generate ISR payment slips in CHF."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,help:l10n_ch.field_res_bank_l10n_ch_postal_eur
|
||||
msgid ""
|
||||
"The postal reference of the bank, used to generate ISR payment slips in EUR."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model.fields,help:l10n_ch.field_account_invoice_l10n_ch_isr_number
|
||||
msgid "The reference number associated with this invoice"
|
||||
msgstr ""
|
||||
#: model:account.tax.template,name:l10n_ch.vat_77_incl
|
||||
msgid "TVA due à 7.7% (Incl. TN)"
|
||||
msgstr "Zu zahlende MwSt. bei 7,7% (inkl. NRZ)"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.template,name:l10n_ch.ch_coa_1060
|
||||
@@ -1296,11 +1131,6 @@ msgstr "Wertschriften"
|
||||
msgid "Titres à long terme"
|
||||
msgstr "Wertschriften"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.template,name:l10n_ch.transfer_account_id
|
||||
msgid "Transferts internes"
|
||||
msgstr "Interne Verrechnungen"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:account.account.template,name:l10n_ch.ch_coa_1280
|
||||
msgid "Travaux en cours"
|
||||
@@ -1377,30 +1207,3 @@ msgstr "Produktionserlöse"
|
||||
#: model:account.account.template,name:l10n_ch.ch_coa_1530
|
||||
msgid "Véhicules"
|
||||
msgstr "Fahrzeuge"
|
||||
|
||||
#. module: l10n_ch
|
||||
#: code:addons/l10n_ch/models/account_invoice.py:153
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You cannot generate an ISR yet.\n"
|
||||
"\n"
|
||||
" For this, you need to :\n"
|
||||
"\n"
|
||||
" - set a valid postal account number (or "
|
||||
"an IBAN referencing one) for your company\n"
|
||||
"\n"
|
||||
" - define its bank\n"
|
||||
"\n"
|
||||
" - associate this bank with a postal "
|
||||
"reference for the currency used in this invoice\n"
|
||||
"\n"
|
||||
" - fill the 'bank account' field of the "
|
||||
"invoice with the postal to be used to receive the related payment. A default "
|
||||
"account will be automatically set for all invoices created after you defined "
|
||||
"a postal account for your company."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_ch
|
||||
#: model:ir.model,name:l10n_ch.model_account_config_settings
|
||||
msgid "account.config.settings"
|
||||
msgstr ""
|
||||
|
||||
@@ -17,7 +17,7 @@ Piano dei conti italiano di un'impresa generica.
|
||||
Italian accounting chart and localization.
|
||||
""",
|
||||
'category': 'Localization',
|
||||
'website': 'http://www.openerp-italia.org/',
|
||||
'website': 'http://www.odoo.com/',
|
||||
'data': [
|
||||
'data/l10n_it_chart_data.xml',
|
||||
'data/account.account.template.csv',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import controllers
|
||||
from . import models
|
||||
from odoo.addons.payment.models.payment_acquirer import create_missing_journal_for_acquirers
|
||||
from . import tests
|
||||
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Italy - E-invoicing',
|
||||
'version': '0.3',
|
||||
'depends': ['l10n_it'],
|
||||
'author': 'Odoo',
|
||||
'description': """
|
||||
E-invoice implementation
|
||||
""",
|
||||
'category': 'Localization',
|
||||
'website': 'http://www.odoo.com/',
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'data/invoice_it_template.xml',
|
||||
'views/l10n_it_view.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/account_invoice_demo.xml',
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!-- add VAT, codice fiscal and tax system for main company -->
|
||||
<record id="base.main_company" model="res.company">
|
||||
<field name="vat">IT00410123456</field>
|
||||
<field name="l10n_it_codice_fiscale">0123456789987654</field>
|
||||
<field name="l10n_it_tax_system">RF01</field>
|
||||
<field name="zip">12345</field>
|
||||
</record>
|
||||
|
||||
<record id="base.res_partner_1" model="res.partner">
|
||||
<field name="vat">IT11111111123</field>
|
||||
</record>
|
||||
<record id="base.res_partner_2" model="res.partner">
|
||||
<field name="vat">IT11111111234</field>
|
||||
</record>
|
||||
<record id="base.res_partner_12" model="res.partner">
|
||||
<field name="vat">IT11111112345</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,175 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<template id="account_invoice_line_it_FatturaPA">
|
||||
<t t-set="taxes" t-value="line.invoice_line_tax_ids.compute_all(line.price_unit)"/>
|
||||
<DettaglioLinee>
|
||||
<NumeroLinea t-esc="line_counter"/>
|
||||
<CodiceArticolo t-if="line.product_id.barcode">
|
||||
<!--2.2.1.3-->
|
||||
<CodiceTipo>EAN</CodiceTipo>
|
||||
<CodiceValore t-esc="line.product_id.barcode"/>
|
||||
</CodiceArticolo>
|
||||
<CodiceArticolo t-if="line.product_id.default_code">
|
||||
<CodiceTipo>INTERNAL</CodiceTipo>
|
||||
<CodiceValore t-esc="line.product_id.default_code"/>
|
||||
</CodiceArticolo>
|
||||
<Descrizione>
|
||||
<t t-esc="line.name[:1000]"/>
|
||||
<t t-if="not line.name" t-esc="'NO NAME'"/>
|
||||
</Descrizione>
|
||||
<Quantita t-esc="format_numbers(line.quantity)"/>
|
||||
<UnitaMisura t-if="line.uom_id.category_id.measure_type != 'unit'" t-esc="line.uom_id.name"/>
|
||||
<PrezzoUnitario t-esc="format_monetary(taxes['total_excluded'], currency)"/>
|
||||
<ScontoMaggiorazione t-if="line.discount != 0">
|
||||
<!-- [2.2.1.10] -->
|
||||
<Tipo t-esc="discount_type(line.discount)"/>
|
||||
<Percentuale t-esc="format_numbers(abs(line.discount))"/>
|
||||
</ScontoMaggiorazione>
|
||||
<PrezzoTotale t-esc="format_monetary(line.price_subtotal, currency)"/>
|
||||
<!-- without tax, must include any discounts and any extra charge-->
|
||||
<AliquotaIVA t-if="line.invoice_line_tax_ids.amount_type == 'percent'" t-esc="format_numbers(line.invoice_line_tax_ids.amount)"/>
|
||||
<AliquotaIVA t-if="line.invoice_line_tax_ids.amount_type != 'percent'" t-esc="'0.00'"/>
|
||||
<Natura t-if="line.invoice_line_tax_ids.l10n_it_has_exoneration" t-esc="line.invoice_line_tax_ids.l10n_it_kind_exoneration"/>
|
||||
</DettaglioLinee>
|
||||
</template>
|
||||
|
||||
<template id="account_invoice_it_FatturaPA_sede">
|
||||
<Sede>
|
||||
<Indirizzo><t t-if="partner.street" t-esc="partner.street"/> <t t-if="partner.street2" t-esc="partner.street2"/></Indirizzo>
|
||||
<CAP><t t-if="partner.country_id.code != 'IT'" t-esc="'00000'"/><t t-else="" t-esc="partner.zip"/></CAP>
|
||||
<Comune t-esc="partner.city"/>
|
||||
<Provincia t-if="partner.country_id.code == 'IT'" t-esc="partner.state_id.code"/>
|
||||
<Nazione t-esc="partner.country_id.code"/>
|
||||
</Sede>
|
||||
</template>
|
||||
|
||||
<template id="account_invoice_it_FatturaPA_export">
|
||||
<t t-set="currency" t-value="record.currency_id or record.company_currency_id"/>
|
||||
<t t-set="bank" t-value="record.partner_bank_id"/>
|
||||
<p:FatturaElettronica t-att-versione="formato_trasmissione" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:p="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.2 http://www.fatturapa.gov.it/export/fatturazione/sdi/fatturapa/v1.2/Schema_del_file_xml_FatturaPA_versione_1.2.xsd">
|
||||
<FatturaElettronicaHeader>
|
||||
<DatiTrasmissione>
|
||||
<IdTrasmittente>
|
||||
<IdPaese t-esc="get_vat_country(record.company_id.vat)"/>
|
||||
<IdCodice t-esc="record.company_id.l10n_it_codice_fiscale"/>
|
||||
</IdTrasmittente>
|
||||
<ProgressivoInvio t-esc="record.number.replace('/','')[-10:]"/>
|
||||
<FormatoTrasmissione t-esc="formato_trasmissione"/>
|
||||
<CodiceDestinatario t-if="record.commercial_partner_id.l10n_it_pa_index and record.commercial_partner_id.country_id.code == 'IT'" t-esc="record.commercial_partner_id.l10n_it_pa_index.upper()"/>
|
||||
<CodiceDestinatario t-if="not record.commercial_partner_id.l10n_it_pa_index and record.commercial_partner_id.country_id.code == 'IT'" t-esc="'0000000'"/>
|
||||
<CodiceDestinatario t-if="record.commercial_partner_id.country_id.code != 'IT'" t-esc="'XXXXXXX'"/>
|
||||
<ContattiTrasmittente>
|
||||
<Telefono t-if="format_phone(record.company_id.partner_id.phone)" t-esc="format_phone(record.company_id.partner_id.phone)"/>
|
||||
<Telefono t-if="not format_phone(record.company_id.partner_id.phone) and format_phone(record.company_id.partner_id.mobile)" t-esc="format_phone(record.company_id.partner_id.mobile)"/>
|
||||
<Email t-if="record.company_id.email" t-esc="record.company_id.email"/>
|
||||
</ContattiTrasmittente>
|
||||
<PECDestinatario t-if="record.commercial_partner_id.l10n_it_pec_email" t-esc="record.commercial_partner_id.l10n_it_pec_email"/>
|
||||
</DatiTrasmissione>
|
||||
<CedentePrestatore>
|
||||
<DatiAnagrafici>
|
||||
<IdFiscaleIVA>
|
||||
<IdPaese t-esc="get_vat_country(record.company_id.vat)"/>
|
||||
<IdCodice t-esc="get_vat_number(record.company_id.vat)"/>
|
||||
</IdFiscaleIVA>
|
||||
<CodiceFiscale t-if="record.company_id.l10n_it_codice_fiscale" t-esc="record.company_id.l10n_it_codice_fiscale"/>
|
||||
<Anagrafica>
|
||||
<Denominazione t-esc="record.company_id.partner_id.display_name"/>
|
||||
</Anagrafica>
|
||||
<RegimeFiscale t-esc="record.company_id.l10n_it_tax_system"/>
|
||||
</DatiAnagrafici>
|
||||
<t t-call="l10n_it_edi.account_invoice_it_FatturaPA_sede">
|
||||
<t t-set="partner" t-value="record.company_id.partner_id"/>
|
||||
</t>
|
||||
<IscrizioneREA t-if="record.company_id.l10n_it_has_eco_index">
|
||||
<!--1.2.4-->
|
||||
<Ufficio t-esc="record.company_id.l10n_it_eco_index_office.code"/>
|
||||
<NumeroREA t-esc="record.company_id.l10n_it_eco_index_number"/>
|
||||
<CapitaleSociale t-if="record.company_id.l10n_it_eco_index_share_capital != 0" t-esc="format_numbers_two(record.company_id.l10n_it_eco_index_share_capital)"/>
|
||||
<SocioUnico t-if="record.company_id.l10n_it_eco_index_sole_shareholder != 'NO'" t-esc="record.company_id.l10n_it_eco_index_sole_shareholder"/>
|
||||
<StatoLiquidazione t-esc="record.company_id.l10n_it_eco_index_liquidation_state"/>
|
||||
</IscrizioneREA>
|
||||
</CedentePrestatore>
|
||||
<RappresentanteFiscale t-if="record.company_id.l10n_it_has_tax_representative">
|
||||
<!--1.3-->
|
||||
<DatiAnagrafici>
|
||||
<IdFiscaleIVA>
|
||||
<IdPaese t-esc="get_vat_country(record.company_id.l10n_it_tax_representative_partner_id.vat)"/>
|
||||
<IdCodice t-esc="get_vat_number(record.company_id.l10n_it_tax_representative_partner_id.vat)"/>
|
||||
</IdFiscaleIVA>
|
||||
<CodiceFiscale t-if="record.company_id.l10n_it_tax_representative_partner_id.l10n_it_codice_fiscale" t-esc="record.company_id.l10n_it_tax_representative_partner_id.l10n_it_codice_fiscale"/>
|
||||
<Anagrafica>
|
||||
<Denominazione t-if="record.company_id.l10n_it_tax_representative_partner_id.is_company" t-esc="record.company_id.l10n_it_tax_representative_partner_id.display_name"/>
|
||||
<Nome t-if="not record.company_id.l10n_it_tax_representative_partner_id.is_company" t-esc="record.company_id.l10n_it_tax_representative_partner_id.name"/>
|
||||
<Cognome t-if="not record.company_id.l10n_it_tax_representative_partner_id.is_company" t-esc="record.company_id.l10n_it_tax_representative_partner_id.name"/>
|
||||
</Anagrafica>
|
||||
</DatiAnagrafici>
|
||||
</RappresentanteFiscale>
|
||||
<CessionarioCommittente>
|
||||
<DatiAnagrafici>
|
||||
<IdFiscaleIVA t-if="record.commercial_partner_id.vat">
|
||||
<IdPaese t-esc="get_vat_country(record.commercial_partner_id.vat)"/>
|
||||
<IdCodice t-esc="get_vat_number(record.commercial_partner_id.vat)"/>
|
||||
</IdFiscaleIVA>
|
||||
<CodiceFiscale t-if="not record.commercial_partner_id.vat" t-esc="record.commercial_partner_id.l10n_it_codice_fiscale"/>
|
||||
<Anagrafica>
|
||||
<Denominazione t-if="record.commercial_partner_id.is_company" t-esc="record.commercial_partner_id.display_name"/>
|
||||
<Nome t-if="not record.commercial_partner_id.is_company" t-esc="record.commercial_partner_id.name"/>
|
||||
<Cognome t-if="not record.commercial_partner_id.is_company" t-esc="record.commercial_partner_id.name"/>
|
||||
</Anagrafica>
|
||||
</DatiAnagrafici>
|
||||
<t t-call="l10n_it_edi.account_invoice_it_FatturaPA_sede">
|
||||
<t t-set="partner" t-value="record.commercial_partner_id"/>
|
||||
</t>
|
||||
</CessionarioCommittente>
|
||||
</FatturaElettronicaHeader>
|
||||
<FatturaElettronicaBody>
|
||||
<DatiGenerali>
|
||||
<DatiGeneraliDocumento>
|
||||
<!--2.1.1-->
|
||||
<TipoDocumento t-esc="document_type"/>
|
||||
<Divisa t-esc="currency.name"/>
|
||||
<Data t-esc="format_date(record.date_invoice)"/>
|
||||
<Numero t-esc="record.number[-20:]"/>
|
||||
<DatiBollo t-if="record.l10n_it_stamp_duty">
|
||||
<!--2.1.1.6-->
|
||||
<BolloVirtuale>SI</BolloVirtuale>
|
||||
<ImportoBollo t-esc="format_numbers(record.l10n_it_stamp_duty)"/>
|
||||
</DatiBollo>
|
||||
</DatiGeneraliDocumento>
|
||||
<DatiDDT t-if="record.l10n_it_ddt_id">
|
||||
<!--2.1.8-->
|
||||
<NumeroDDT t-esc="record.l10n_it_ddt_id.name"/>
|
||||
<DataDDT t-esc="format_date(record.l10n_it_ddt_id.date)"/>
|
||||
</DatiDDT>
|
||||
</DatiGenerali>
|
||||
<DatiBeniServizi>
|
||||
<!-- Invoice lines. -->
|
||||
<t t-set="line_counter" t-value="0"/>
|
||||
<t t-foreach="record.invoice_line_ids.filtered(lambda l: not l.display_type)" t-as="line">
|
||||
<t t-set="line_counter" t-value="line_counter + 1"/>
|
||||
<t t-call="l10n_it_edi.account_invoice_line_it_FatturaPA"/>
|
||||
</t>
|
||||
<t t-foreach="record.tax_line_ids" t-as="tax_line">
|
||||
<DatiRiepilogo>
|
||||
<!--2.2.2-->
|
||||
<AliquotaIVA t-esc="format_numbers(tax_line.tax_id.amount)"/>
|
||||
<Natura t-if="tax_line.tax_id.l10n_it_has_exoneration" t-esc="tax_line.tax_id.l10n_it_kind_exoneration"/>
|
||||
<ImponibileImporto t-esc="format_monetary(tax_line.base, currency)"/>
|
||||
<Imposta t-esc="format_monetary(tax_line.amount, currency)"/>
|
||||
<EsigibilitaIVA t-if="not tax_line.tax_id.l10n_it_has_exoneration or tax_line.tax_id.l10n_it_kind_exoneration=='N6'" t-esc="tax_line.tax_id.l10n_it_vat_due_date"/>
|
||||
<RiferimentoNormativo t-if="tax_line.tax_id.l10n_it_has_exoneration" t-esc="tax_line.tax_id.l10n_it_law_reference"/>
|
||||
</DatiRiepilogo>
|
||||
</t>
|
||||
|
||||
</DatiBeniServizi>
|
||||
<Allegati t-if="pdf">
|
||||
<NomeAttachment t-esc="pdf_name"/>
|
||||
<FormatoAttachment>PDF</FormatoAttachment>
|
||||
<Attachment t-esc="pdf"/>
|
||||
</Allegati>
|
||||
</FatturaElettronicaBody>
|
||||
</p:FatturaElettronica>
|
||||
</template>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import res_partner
|
||||
from . import res_company
|
||||
from . import account_invoice
|
||||
from . import ir_mail_server
|
||||
from . import ddt
|
||||
@@ -0,0 +1,758 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import base64
|
||||
import zipfile
|
||||
import io
|
||||
import logging
|
||||
import re
|
||||
|
||||
from datetime import date, datetime
|
||||
from lxml import etree
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tools import float_repr
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.addons.base.models.ir_mail_server import MailDeliveryException
|
||||
from odoo.tests.common import Form
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_FACTUR_ITALIAN_DATE_FORMAT = '%Y-%m-%d'
|
||||
|
||||
class AccountInvoice(models.Model):
|
||||
_inherit = 'account.invoice'
|
||||
_name = 'account.invoice'
|
||||
|
||||
l10n_it_send_state = fields.Selection([
|
||||
('new', 'New'),
|
||||
('other', 'Other'),
|
||||
('to_send', 'Not yet send'),
|
||||
('sent', 'Sent, waiting for response'),
|
||||
('invalid', 'Sent, but invalid'),
|
||||
('delivered', 'This invoice is delivered'),
|
||||
('delivered_accepted', 'This invoice is delivered and accepted by destinatory'),
|
||||
('delivered_refused', 'This invoice is delivered and refused by destinatory'),
|
||||
('delivered_expired', 'This invoice is delivered and expired (expiry of the maximum term for communication of acceptance/refusal)'),
|
||||
('failed_delivery', 'Delivery impossible, ES certify that it has received the invoice and that the file \
|
||||
could not be delivered to the addressee') # ok we must do nothing
|
||||
], default='to_send', copy=False)
|
||||
|
||||
l10n_it_stamp_duty = fields.Float(default=0, string="Dati Bollo", size=15, readonly=True, states={'draft': [('readonly', False)]})
|
||||
|
||||
l10n_it_ddt_id = fields.Many2one('l10n_it.ddt', string='DDT', readonly=True, states={'draft': [('readonly', False)]}, copy=False)
|
||||
|
||||
l10n_it_einvoice_name = fields.Char(readonly=True, copy=False)
|
||||
|
||||
l10n_it_einvoice_id = fields.Many2one('ir.attachment', string="Electronic invoice", copy=False)
|
||||
|
||||
@api.multi
|
||||
def invoice_validate(self):
|
||||
# Clean context from default_type to avoid making attachment
|
||||
# with wrong values in subsequent operations
|
||||
cleaned_ctx = dict(self.env.context)
|
||||
cleaned_ctx.pop('default_type', None)
|
||||
self = self.with_context(cleaned_ctx)
|
||||
|
||||
super(AccountInvoice, self).invoice_validate()
|
||||
for invoice in self:
|
||||
if invoice.company_id.country_id != self.env.ref('base.it'):
|
||||
continue
|
||||
if invoice.type == 'in_invoice' or invoice.type == 'in_refund':
|
||||
invoice.l10n_it_send_state = "other"
|
||||
continue
|
||||
|
||||
invoice._check_before_xml_exporting()
|
||||
|
||||
invoice.invoice_generate_xml()
|
||||
if len(invoice.commercial_partner_id.l10n_it_pa_index or '') == 6:
|
||||
invoice.message_post(
|
||||
body=(_("Invoices for PA are not managed by Odoo, you can download the document and send it on your own."))
|
||||
)
|
||||
invoice.l10n_it_send_state = "other"
|
||||
continue
|
||||
invoice.l10n_it_send_state = "to_send"
|
||||
invoice.send_pec_mail()
|
||||
|
||||
def _check_before_xml_exporting(self):
|
||||
seller = self.company_id
|
||||
buyer = self.commercial_partner_id
|
||||
|
||||
# <1.1.1.1>
|
||||
if not seller.country_id:
|
||||
raise UserError(_("%s must have a country") % (seller.display_name))
|
||||
|
||||
# <1.1.1.2>
|
||||
if not seller.vat:
|
||||
raise UserError(_("%s must have a VAT number") % (seller.display_name))
|
||||
elif len(seller.vat) > 30:
|
||||
raise UserError(_("The maximum length for VAT number is 30. %s have a VAT number too long: %s.") % (seller.display_name, seller.vat))
|
||||
|
||||
# <1.2.1.2>
|
||||
if not seller.l10n_it_codice_fiscale:
|
||||
raise UserError(_("%s must have a codice fiscale number") % (seller.display_name))
|
||||
|
||||
# <1.2.1.8>
|
||||
if not seller.l10n_it_tax_system:
|
||||
raise UserError("The seller's company must have a tax system.")
|
||||
|
||||
# <1.2.2>
|
||||
if not seller.street and not seller.street2:
|
||||
raise UserError(_("%s must have a street.") % (seller.display_name))
|
||||
if not seller.zip:
|
||||
raise UserError(_("%s must have a post code.") % (seller.display_name))
|
||||
if len(seller.zip) != 5 and seller.country_id.code == 'IT':
|
||||
raise UserError(_("%s must have a post code of length 5.") % (seller.display_name))
|
||||
if not seller.city:
|
||||
raise UserError(_("%s must have a city.") % (seller.display_name))
|
||||
if not seller.country_id:
|
||||
raise UserError(_("%s must have a country.") % (seller.display_name))
|
||||
|
||||
# <1.4.1>
|
||||
if not buyer.vat and not buyer.l10n_it_codice_fiscale:
|
||||
raise UserError(_("The buyer, %s, or his company must have either a VAT number either a tax code (Codice Fiscale).") % (buyer.display_name))
|
||||
|
||||
# <1.4.2>
|
||||
if not buyer.street and not buyer.street2:
|
||||
raise UserError(_("%s must have a street.") % (buyer.display_name))
|
||||
if not buyer.zip:
|
||||
raise UserError(_("%s must have a post code.") % (buyer.display_name))
|
||||
if len(buyer.zip) != 5 and buyer.country_id.code == 'IT':
|
||||
raise UserError(_("%s must have a post code of length 5.") % (buyer.display_name))
|
||||
if not buyer.city:
|
||||
raise UserError(_("%s must have a city.") % (buyer.display_name))
|
||||
if not buyer.country_id:
|
||||
raise UserError(_("%s must have a country.") % (buyer.display_name))
|
||||
|
||||
# <2.2.1>
|
||||
for invoice_line in self.invoice_line_ids:
|
||||
if len(invoice_line.invoice_line_tax_ids) != 1:
|
||||
raise UserError(_("You must select one and only one tax by line."))
|
||||
|
||||
for tax_line in self.tax_line_ids:
|
||||
if not tax_line.tax_id.l10n_it_has_exoneration and tax_line.tax_id.amount == 0:
|
||||
raise ValidationError(_("%s has an amount of 0.0, you must indicate the kind of exoneration." % tax_line.name))
|
||||
|
||||
@api.multi
|
||||
def invoice_generate_xml(self):
|
||||
for invoice in self:
|
||||
if invoice.l10n_it_einvoice_id and invoice.l10n_it_send_state not in ['invalid', 'to_send']:
|
||||
raise UserError(_("You can't regenerate an E-Invoice when the first one is sent and there are no errors"))
|
||||
if invoice.l10n_it_einvoice_id:
|
||||
invoice.l10n_it_einvoice_id.unlink()
|
||||
|
||||
a = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
n = invoice.id
|
||||
progressive_number = ""
|
||||
while n:
|
||||
(n,m) = divmod(n,len(a))
|
||||
progressive_number = a[m] + progressive_number
|
||||
|
||||
report_name = '%(country_code)s%(codice)s_%(progressive_number)s.xml' % {
|
||||
'country_code': invoice.company_id.country_id.code,
|
||||
'codice': invoice.company_id.l10n_it_codice_fiscale,
|
||||
'progressive_number': progressive_number.zfill(5),
|
||||
}
|
||||
invoice.l10n_it_einvoice_name = report_name
|
||||
|
||||
data = b"<?xml version='1.0' encoding='UTF-8'?>" + invoice._export_as_xml()
|
||||
description = _('Italian invoice: %s') % invoice.type
|
||||
invoice.l10n_it_einvoice_id = self.env['ir.attachment'].create({
|
||||
'name': report_name,
|
||||
'res_id': invoice.id,
|
||||
'res_model': invoice._name,
|
||||
'datas': base64.encodestring(data),
|
||||
'datas_fname': report_name,
|
||||
'description': description,
|
||||
'type': 'binary',
|
||||
})
|
||||
|
||||
invoice.message_post(
|
||||
body=(_("E-Invoice is generated on %s by %s") % (fields.Datetime.now(), self.env.user.display_name))
|
||||
)
|
||||
|
||||
def _export_as_xml(self):
|
||||
''' Create the xml file content.
|
||||
:return: The XML content as str.
|
||||
'''
|
||||
self.ensure_one()
|
||||
|
||||
def format_date(dt):
|
||||
# Format the date in the italian standard.
|
||||
dt = dt or datetime.now()
|
||||
return dt.strftime(DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
|
||||
|
||||
def format_monetary(number, currency):
|
||||
# Format the monetary values to avoid trailing decimals (e.g. 90.85000000000001).
|
||||
return float_repr(number, min(2, currency.decimal_places))
|
||||
|
||||
def format_numbers(number):
|
||||
#format number to str with between 2 and 8 decimals (event if it's .00)
|
||||
number_splited = str(number).split('.')
|
||||
if len(number_splited) == 1:
|
||||
return "%.02f" % number
|
||||
|
||||
cents = number_splited[1]
|
||||
if len(cents) > 8:
|
||||
return "%.08f" % number
|
||||
return float_repr(number, max(2, len(cents)))
|
||||
|
||||
def format_numbers_two(number):
|
||||
#format number to str with 2 (event if it's .00)
|
||||
return "%.02f" % number
|
||||
|
||||
def discount_type(discount):
|
||||
return 'SC' if discount > 0 else 'MG'
|
||||
|
||||
def format_phone(number):
|
||||
if not number:
|
||||
return False
|
||||
number = number.replace(' ', '').replace('/', '').replace('.', '')
|
||||
if len(number) > 4 and len(number) < 13:
|
||||
return number
|
||||
return False
|
||||
|
||||
def get_vat_number(vat):
|
||||
return vat[2:].replace(' ', '')
|
||||
|
||||
def get_vat_country(vat):
|
||||
return vat[:2].upper()
|
||||
|
||||
formato_trasmissione = "FPR12"
|
||||
if len(self.commercial_partner_id.l10n_it_pa_index or '1') == 6:
|
||||
formato_trasmissione = "FPA12"
|
||||
|
||||
if self.type == 'out_invoice':
|
||||
document_type = 'TD01'
|
||||
elif self.type == 'out_refund':
|
||||
document_type = 'TD04'
|
||||
else:
|
||||
document_type = 'TD0X'
|
||||
|
||||
pdf = self.env.ref('account.account_invoices').render_qweb_pdf(self.id)[0]
|
||||
pdf = base64.b64encode(pdf)
|
||||
pdf_name = re.sub(r'\W+', '', self.number) + '.pdf'
|
||||
|
||||
# Create file content.
|
||||
template_values = {
|
||||
'record': self,
|
||||
'format_date': format_date,
|
||||
'format_monetary': format_monetary,
|
||||
'format_numbers': format_numbers,
|
||||
'format_numbers_two': format_numbers_two,
|
||||
'format_phone': format_phone,
|
||||
'discount_type': discount_type,
|
||||
'get_vat_number': get_vat_number,
|
||||
'get_vat_country': get_vat_country,
|
||||
'abs': abs,
|
||||
'formato_trasmissione': formato_trasmissione,
|
||||
'document_type': document_type,
|
||||
'pdf': pdf,
|
||||
'pdf_name': pdf_name,
|
||||
}
|
||||
content = self.env.ref('l10n_it_edi.account_invoice_it_FatturaPA_export').render(template_values)
|
||||
return content
|
||||
|
||||
@api.multi
|
||||
def send_pec_mail(self):
|
||||
self.ensure_one()
|
||||
allowed_state = ['to_send', 'invalid']
|
||||
|
||||
if not self.company_id.l10n_it_mail_pec_server_id or not self.company_id.l10n_it_address_send_fatturapa:
|
||||
self.message_post(
|
||||
body=(_("Error when sending mail with E-Invoice: Your company must have a mail PEC server and must indicate the mail PEC that will send electronic invoice."))
|
||||
)
|
||||
self.l10n_it_send_state = 'invalid'
|
||||
return
|
||||
|
||||
if self.l10n_it_send_state not in allowed_state:
|
||||
raise UserError(_("%s isn't in a right state. It must be in a 'Not yet send' or 'Invalid' state.") % (self.display_name))
|
||||
|
||||
message = self.env['mail.message'].create({
|
||||
'subject': _('Sending file: %s') % (self.l10n_it_einvoice_id.name),
|
||||
'body': _('Sending file: %s to ES: %s') % (self.l10n_it_einvoice_id.name, self.env.user.company_id.l10n_it_address_recipient_fatturapa),
|
||||
'email_from': self.env.user.company_id.l10n_it_address_send_fatturapa,
|
||||
'mail_server_id': self.env.user.company_id.l10n_it_mail_pec_server_id.id,
|
||||
'attachment_ids': [(6, 0, self.l10n_it_einvoice_id.ids)],
|
||||
})
|
||||
|
||||
mail_fattura = self.env['mail.mail'].create({
|
||||
'mail_message_id': message.id,
|
||||
'email_to': self.env.user.company_id.l10n_it_address_recipient_fatturapa,
|
||||
})
|
||||
try:
|
||||
mail_fattura.send(raise_exception=True)
|
||||
self.message_post(
|
||||
body=(_("Mail sent on %s by %s") % (fields.Datetime.now(), self.env.user.display_name))
|
||||
)
|
||||
self.l10n_it_send_state = 'sent'
|
||||
except MailDeliveryException as error:
|
||||
self.message_post(
|
||||
body=(_("Error when sending mail with E-Invoice: %s") % (error.args[0]))
|
||||
)
|
||||
self.l10n_it_send_state = 'invalid'
|
||||
|
||||
def _import_xml_invoice(self, content, attachment):
|
||||
''' Extract invoice values from the E-Invoice xml tree passed as parameter.
|
||||
|
||||
:param content: The tree of the xml file.
|
||||
:return: A dictionary containing account.invoice values to create/update it.
|
||||
'''
|
||||
|
||||
try:
|
||||
tree = etree.fromstring(content)
|
||||
except:
|
||||
_logger.info('Error during decoding XML file')
|
||||
return self.env['account.invoice']
|
||||
|
||||
invoices = self.env['account.invoice']
|
||||
|
||||
# possible to have multiple invoices in the case of an invoice batch, the batch itself is repeated for every invoice of the batch
|
||||
for body_tree in tree.xpath('//FatturaElettronicaBody', namespaces=tree.nsmap):
|
||||
|
||||
elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento', namespaces=tree.nsmap)
|
||||
if elements and elements[0].text and elements[0].text == 'TD01':
|
||||
self_ctx = self.with_context(type='in_invoice')
|
||||
elif elements and elements[0].text and elements[0].text == 'TD04':
|
||||
self_ctx = self.with_context(type='in_refund')
|
||||
else:
|
||||
_logger.info(_('Document type not managed: %s.') % (elements[0].text))
|
||||
|
||||
# type must be present in the context to get the right behavior of the _default_journal method (account.invoice).
|
||||
# journal_id must be present in the context to get the right behavior of the _default_account method (account.invoice.line).
|
||||
|
||||
elements = tree.xpath('//CessionarioCommittente//IdCodice', namespaces=tree.nsmap)
|
||||
company = elements and self.env['res.company'].search([('vat', 'ilike', elements[0].text)], limit=1)
|
||||
if not company:
|
||||
elements = tree.xpath('//CessionarioCommittente//CodiceFiscale', namespaces=tree.nsmap)
|
||||
company = elements and self.env['res.company'].search([('l10n_it_codice_fiscale', 'ilike', elements[0].text)], limit=1)
|
||||
|
||||
if company:
|
||||
self_ctx = self_ctx.with_context(company_id=company.id)
|
||||
else:
|
||||
company = self.env.user.company_id
|
||||
if elements:
|
||||
_logger.info(_('Company not found with codice fiscale: %s. The company\'s user is set by default.') % elements[0].text)
|
||||
else:
|
||||
_logger.info(_('Company not found. The company\'s user is set by default.'))
|
||||
|
||||
if not self.env.user._is_superuser():
|
||||
if self.env.user.company_id != company:
|
||||
raise UserError(_("You can only import invoice concern your current company: %s") % self.env.user.company_id.display_name)
|
||||
|
||||
journal_id = self_ctx._default_journal().id
|
||||
self_ctx = self_ctx.with_context(journal_id=journal_id)
|
||||
|
||||
# self could be a single record (editing) or be empty (new).
|
||||
with Form(self_ctx, view='account.invoice_supplier_form') as invoice_form:
|
||||
message_to_log = []
|
||||
|
||||
invoice_form.company_id = company
|
||||
|
||||
# Refund type.
|
||||
# TD01 == invoice
|
||||
# TD02 == advance/down payment on invoice
|
||||
# TD03 == advance/down payment on fee
|
||||
# TD04 == credit note
|
||||
# TD05 == debit note
|
||||
# TD06 == fee
|
||||
elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento', namespaces=tree.nsmap)
|
||||
if elements and elements[0].text and elements[0].text == 'TD01':
|
||||
invoice_form.type = 'in_invoice'
|
||||
elif elements and elements[0].text and elements[0].text == 'TD04':
|
||||
invoice_form.type = 'in_refund'
|
||||
|
||||
# Partner (first step to avoid warning 'Warning! You must first select a partner.'). <1.2>
|
||||
elements = tree.xpath('//CedentePrestatore//IdCodice', namespaces=tree.nsmap)
|
||||
partner = elements and self.env['res.partner'].search(['&', ('vat', 'ilike', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False)], limit=1)
|
||||
if not partner:
|
||||
elements = tree.xpath('//CedentePrestatore//CodiceFiscale', namespaces=tree.nsmap)
|
||||
partner = elements and self.env['res.partner'].search(['&', ('l10n_it_codice_fiscale', '=', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False)], limit=1)
|
||||
if not partner:
|
||||
elements = tree.xpath('//DatiTrasmissione//Email', namespaces=tree.nsmap)
|
||||
partner = elements and self.env['res.partner'].search(['&', '|', ('email', '=', elements[0].text), ('l10n_it_pec_email', '=', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False)], limit=1)
|
||||
if partner:
|
||||
invoice_form.partner_id = partner
|
||||
else:
|
||||
message_to_log.append("%s<br/>%s" % (
|
||||
_("Vendor not found, useful informations from XML file:"),
|
||||
self._compose_info_message(
|
||||
tree, './/CedentePrestatore')))
|
||||
|
||||
# Numbering attributed by the transmitter. <1.1.2>
|
||||
elements = tree.xpath('//ProgressivoInvio', namespaces=tree.nsmap)
|
||||
if elements:
|
||||
invoice_form.name = elements[0].text
|
||||
|
||||
elements = body_tree.xpath('.//DatiGeneraliDocumento//Numero', namespaces=body_tree.nsmap)
|
||||
if elements:
|
||||
invoice_form.reference = elements[0].text
|
||||
|
||||
# Currency. <2.1.1.2>
|
||||
elements = body_tree.xpath('.//DatiGeneraliDocumento/Divisa', namespaces=body_tree.nsmap)
|
||||
if elements:
|
||||
currency_str = elements[0].text
|
||||
currency = self.env.ref('base.%s' % currency_str.upper(), raise_if_not_found=False)
|
||||
if currency != self.env.user.company_id.currency_id and currency.active:
|
||||
invoice_form.currency_id = currency
|
||||
|
||||
# Date. <2.1.1.3>
|
||||
elements = body_tree.xpath('.//DatiGeneraliDocumento/Data', namespaces=body_tree.nsmap)
|
||||
if elements:
|
||||
date_str = elements[0].text
|
||||
date_obj = datetime.strptime(date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
|
||||
invoice_form.date_invoice = date_obj.strftime(DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
|
||||
|
||||
# Dati Bollo. <2.1.1.6>
|
||||
elements = body_tree.xpath('.//DatiGeneraliDocumento/DatiBollo/ImportoBollo', namespaces=body_tree.nsmap)
|
||||
if elements:
|
||||
invoice_form.l10n_it_stamp_duty = float(elements[0].text)
|
||||
|
||||
# List of all amount discount (will be add after all article to avoid to have a negative sum)
|
||||
discount_list = []
|
||||
percentage_global_discount = 1.0
|
||||
|
||||
# Global discount. <2.1.1.8>
|
||||
discount_elements = body_tree.xpath('.//DatiGeneraliDocumento/ScontoMaggiorazione', namespaces=body_tree.nsmap)
|
||||
total_discount_amount = 0.0
|
||||
if discount_elements:
|
||||
for discount_element in discount_elements:
|
||||
discount_line = discount_element.xpath('.//Tipo', namespaces=body_tree.nsmap)
|
||||
discount_sign = -1
|
||||
if discount_line and discount_line[0].text == 'SC':
|
||||
discount_sign = 1
|
||||
discount_percentage = discount_element.xpath('.//Percentuale', namespaces=body_tree.nsmap)
|
||||
if discount_percentage and discount_percentage[0].text:
|
||||
percentage_global_discount *= 1 - float(discount_percentage[0].text)/100 * discount_sign
|
||||
|
||||
discount_amount_text = discount_element.xpath('.//Importo', namespaces=body_tree.nsmap)
|
||||
if discount_amount_text and discount_amount_text[0].text:
|
||||
discount_amount = float(discount_amount_text[0].text) * discount_sign * -1
|
||||
discount = {}
|
||||
discount["seq"] = 0
|
||||
|
||||
if discount_amount < 0:
|
||||
discount["name"] = _('GLOBAL DISCOUNT')
|
||||
else:
|
||||
discount["name"] = _('GLOBAL EXTRA CHARGE')
|
||||
discount["amount"] = discount_amount
|
||||
discount["tax"] = []
|
||||
discount_list.append(discount)
|
||||
|
||||
# Comment. <2.1.1.11>
|
||||
elements = body_tree.xpath('.//DatiGeneraliDocumento//Causale', namespaces=body_tree.nsmap)
|
||||
for element in elements:
|
||||
invoice_form.comment = '%s%s\n' % (invoice_form.comment or '', element.text)
|
||||
|
||||
# Informations relative to the purchase order, the contract, the agreement,
|
||||
# the reception phase or invoices previously transmitted
|
||||
# <2.1.2> - <2.1.6>
|
||||
for document_type in ['DatiOrdineAcquisto', 'DatiContratto', 'DatiConvenzione', 'DatiRicezione', 'DatiFattureCollegate']:
|
||||
elements = body_tree.xpath('.//DatiGenerali/' + document_type, namespaces=body_tree.nsmap)
|
||||
if elements:
|
||||
for element in elements:
|
||||
message_to_log.append("%s %s<br/>%s" % (document_type, _("from XML file:"),
|
||||
self._compose_info_message(element, '.')))
|
||||
|
||||
# Dati DDT. <2.1.8>
|
||||
elements = body_tree.xpath('.//DatiGenerali/DatiDDT', namespaces=body_tree.nsmap)
|
||||
if elements:
|
||||
message_to_log.append("%s<br/>%s" % (
|
||||
_("Transport informations from XML file:"),
|
||||
self._compose_info_message(body_tree, './/DatiGenerali/DatiDDT')))
|
||||
|
||||
# Due date. <2.4.2.5>
|
||||
elements = body_tree.xpath('.//DatiPagamento/DettaglioPagamento/DataScadenzaPagamento', namespaces=body_tree.nsmap)
|
||||
if elements:
|
||||
date_str = elements[0].text
|
||||
date_obj = datetime.strptime(date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
|
||||
invoice_form.date_due = fields.Date.to_string(date_obj)
|
||||
|
||||
# Total amount. <2.4.2.6>
|
||||
elements = body_tree.xpath('.//ImportoPagamento', namespaces=body_tree.nsmap)
|
||||
amount_total_import = 0
|
||||
for element in elements:
|
||||
amount_total_import += float(element.text)
|
||||
if amount_total_import:
|
||||
message_to_log.append(_("Total amount from the XML File: %s") % (
|
||||
amount_total_import))
|
||||
|
||||
# Bank account. <2.4.2.13>
|
||||
elements = body_tree.xpath('.//DatiPagamento/DettaglioPagamento/IBAN', namespaces=body_tree.nsmap)
|
||||
if elements:
|
||||
if invoice_form.partner_id and invoice_form.partner_id.commercial_partner_id:
|
||||
bank = self.env['res.partner.bank'].search([
|
||||
('acc_number', '=', elements[0].text),
|
||||
('partner_id.id', '=', invoice_form.partner_id.commercial_partner_id.id)
|
||||
])
|
||||
else:
|
||||
bank = self.env['res.partner.bank'].search([('acc_number', '=', elements[0].text)])
|
||||
if bank:
|
||||
invoice_form.partner_bank_id = bank
|
||||
else:
|
||||
message_to_log.append("%s<br/>%s" % (
|
||||
_("Bank account not found, useful informations from XML file:"),
|
||||
self._compose_multi_info_message(
|
||||
body_tree, ['.//DatiPagamento//Beneficiario',
|
||||
'.//DatiPagamento//IstitutoFinanziario',
|
||||
'.//DatiPagamento//IBAN',
|
||||
'.//DatiPagamento//ABI',
|
||||
'.//DatiPagamento//CAB',
|
||||
'.//DatiPagamento//BIC',
|
||||
'.//DatiPagamento//ModalitaPagamento'])))
|
||||
else:
|
||||
elements = body_tree.xpath('.//DatiPagamento/DettaglioPagamento', namespaces=body_tree.nsmap)
|
||||
if elements:
|
||||
message_to_log.append("%s<br/>%s" % (
|
||||
_("Bank account not found, useful informations from XML file:"),
|
||||
self._compose_info_message(body_tree, './/DatiPagamento')))
|
||||
|
||||
# Invoice lines. <2.2.1>
|
||||
elements = body_tree.xpath('.//DettaglioLinee', namespaces=body_tree.nsmap)
|
||||
if elements:
|
||||
for element in elements:
|
||||
with invoice_form.invoice_line_ids.new() as invoice_line_form:
|
||||
|
||||
# Sequence.
|
||||
line_elements = element.xpath('.//NumeroLinea', namespaces=body_tree.nsmap)
|
||||
if line_elements:
|
||||
invoice_line_form.sequence = int(line_elements[0].text) * 2
|
||||
|
||||
# Product.
|
||||
line_elements = element.xpath('.//Descrizione', namespaces=body_tree.nsmap)
|
||||
if line_elements:
|
||||
invoice_line_form.name = " ".join(line_elements[0].text.split())
|
||||
|
||||
elements_code = element.xpath('.//CodiceArticolo', namespaces=body_tree.nsmap)
|
||||
if elements_code:
|
||||
for element_code in elements_code:
|
||||
type_code = element_code.xpath('.//CodiceTipo', namespaces=body_tree.nsmap)[0]
|
||||
code = element_code.xpath('.//CodiceValore', namespaces=body_tree.nsmap)[0]
|
||||
if type_code.text == 'EAN':
|
||||
product = self.env['product.product'].search([('barcode', '=', code.text)])
|
||||
if product:
|
||||
invoice_line_form.product_id = product
|
||||
break
|
||||
if partner:
|
||||
product_supplier = self.env['product.supplierinfo'].search([('name', '=', partner.id), ('product_code', '=', code.text)])
|
||||
if product_supplier and product_supplier.product_id:
|
||||
invoice_line_form.product_id = product_supplier.product_id
|
||||
break
|
||||
if not invoice_line_form.product_id:
|
||||
for element_code in elements_code:
|
||||
code = element_code.xpath('.//CodiceValore', namespaces=body_tree.nsmap)[0]
|
||||
product = self.env['product.product'].search([('default_code', '=', code.text)])
|
||||
if product:
|
||||
invoice_line_form.product_id = product
|
||||
break
|
||||
|
||||
# Price Unit.
|
||||
line_elements = element.xpath('.//PrezzoUnitario', namespaces=body_tree.nsmap)
|
||||
if line_elements:
|
||||
invoice_line_form.price_unit = float(line_elements[0].text)
|
||||
|
||||
# Quantity.
|
||||
line_elements = element.xpath('.//Quantita', namespaces=body_tree.nsmap)
|
||||
if line_elements:
|
||||
invoice_line_form.quantity = float(line_elements[0].text)
|
||||
else:
|
||||
invoice_line_form.quantity = 1
|
||||
|
||||
# Taxes
|
||||
tax_element = element.xpath('.//AliquotaIVA', namespaces=body_tree.nsmap)
|
||||
natura_element = element.xpath('.//Natura', namespaces=body_tree.nsmap)
|
||||
invoice_line_form.invoice_line_tax_ids.clear()
|
||||
if tax_element and tax_element[0].text:
|
||||
percentage = float(tax_element[0].text)
|
||||
if natura_element and natura_element[0].text:
|
||||
l10n_it_kind_exoneration = natura_element[0].text
|
||||
tax = self.env['account.tax'].search([
|
||||
('company_id', '=', invoice_form.company_id.id),
|
||||
('amount_type', '=', 'percent'),
|
||||
('type_tax_use', '=', 'purchase'),
|
||||
('amount', '=', percentage),
|
||||
('l10n_it_kind_exoneration', '=', l10n_it_kind_exoneration),
|
||||
], limit=1)
|
||||
else:
|
||||
tax = self.env['account.tax'].search([
|
||||
('company_id', '=', invoice_form.company_id.id),
|
||||
('amount_type', '=', 'percent'),
|
||||
('type_tax_use', '=', 'purchase'),
|
||||
('amount', '=', percentage),
|
||||
], limit=1)
|
||||
l10n_it_kind_exoneration = ''
|
||||
|
||||
if tax:
|
||||
invoice_line_form.invoice_line_tax_ids.add(tax)
|
||||
else:
|
||||
if l10n_it_kind_exoneration:
|
||||
message_to_log.append(_("Tax not found with percentage: %s and exoneration %s for the article: %s") % (
|
||||
percentage,
|
||||
l10n_it_kind_exoneration,
|
||||
invoice_line_form.name))
|
||||
else:
|
||||
message_to_log.append(_("Tax not found with percentage: %s for the article: %s") % (
|
||||
percentage,
|
||||
invoice_line_form.name))
|
||||
|
||||
# Discount in cascade mode.
|
||||
# if 3 discounts : -10% -50€ -20%
|
||||
# the result must be : (((price -10%)-50€) -20%)
|
||||
# Generic form : (((price -P1%)-A1€) -P2%)
|
||||
# It will be split in two parts: fix amount and pourcent amount
|
||||
# example: (((((price - A1€) -P2%) -A3€) -A4€) -P5€)
|
||||
# pourcent: 1-(1-P2)*(1-P5)
|
||||
# fix amount: A1*(1-P2)*(1-P5)+A3*(1-P5)+A4*(1-P5) (we must take account of all
|
||||
# percentage present after the fix amount)
|
||||
line_elements = element.xpath('.//ScontoMaggiorazione', namespaces=body_tree.nsmap)
|
||||
total_discount_amount = 0.0
|
||||
total_discount_percentage = percentage_global_discount
|
||||
if line_elements:
|
||||
for line_element in line_elements:
|
||||
discount_line = line_element.xpath('.//Tipo', namespaces=body_tree.nsmap)
|
||||
discount_sign = -1
|
||||
if discount_line and discount_line[0].text == 'SC':
|
||||
discount_sign = 1
|
||||
discount_percentage = line_element.xpath('.//Percentuale', namespaces=body_tree.nsmap)
|
||||
if discount_percentage and discount_percentage[0].text:
|
||||
pourcentage_actual = 1 - float(discount_percentage[0].text)/100 * discount_sign
|
||||
total_discount_percentage *= pourcentage_actual
|
||||
total_discount_amount *= pourcentage_actual
|
||||
|
||||
discount_amount = line_element.xpath('.//Importo', namespaces=body_tree.nsmap)
|
||||
if discount_amount and discount_amount[0].text:
|
||||
total_discount_amount += float(discount_amount[0].text) * discount_sign * -1
|
||||
|
||||
# Save amount discount.
|
||||
if total_discount_amount != 0:
|
||||
discount = {}
|
||||
discount["seq"] = invoice_line_form.sequence + 1
|
||||
|
||||
if total_discount_amount < 0:
|
||||
discount["name"] = _('DISCOUNT: ') + invoice_line_form.name
|
||||
else:
|
||||
discount["name"] = _('EXTRA CHARGE: ') + invoice_line_form.name
|
||||
discount["amount"] = total_discount_amount
|
||||
discount["tax"] = []
|
||||
for tax in invoice_line_form.invoice_line_tax_ids:
|
||||
discount["tax"].append(tax)
|
||||
discount_list.append(discount)
|
||||
invoice_line_form.discount = (1 - total_discount_percentage) * 100
|
||||
|
||||
# Apply amount discount.
|
||||
for discount in discount_list:
|
||||
with invoice_form.invoice_line_ids.new() as invoice_line_form_discount:
|
||||
invoice_line_form_discount.invoice_line_tax_ids.clear()
|
||||
invoice_line_form_discount.sequence = discount["seq"]
|
||||
invoice_line_form_discount.name = discount["name"]
|
||||
invoice_line_form_discount.price_unit = discount["amount"]
|
||||
|
||||
new_invoice = invoice_form.save()
|
||||
new_invoice.l10n_it_send_state = "other"
|
||||
|
||||
elements = body_tree.xpath('.//Allegati', namespaces=body_tree.nsmap)
|
||||
if elements:
|
||||
for element in elements:
|
||||
name_attachment = element.xpath('.//NomeAttachment', namespaces=body_tree.nsmap)[0].text
|
||||
attachment_64 = str.encode(element.xpath('.//Attachment', namespaces=body_tree.nsmap)[0].text)
|
||||
attachment_64 = self.env['ir.attachment'].create({
|
||||
'name': name_attachment,
|
||||
'datas': attachment_64,
|
||||
'datas_fname': name_attachment,
|
||||
'type': 'binary',
|
||||
})
|
||||
|
||||
# default_res_id is had to context to avoid facturx to import his content
|
||||
new_invoice.with_context(default_res_id=new_invoice.id).message_post(
|
||||
body=(_("Attachment from XML")),
|
||||
attachment_ids=[attachment_64.id]
|
||||
)
|
||||
|
||||
for message in message_to_log:
|
||||
new_invoice.message_post(body=message)
|
||||
|
||||
if attachment:
|
||||
new_invoice.l10n_it_einvoice_name = attachment.name
|
||||
attachment.write({'res_model': 'account.invoice', 'res_id': new_invoice.id})
|
||||
new_invoice.message_post(attachment_ids=[attachment.id])
|
||||
invoices += new_invoice
|
||||
return invoices
|
||||
|
||||
def _compose_info_message(self, tree, element_tags):
|
||||
output_str = ""
|
||||
elements = tree.xpath(element_tags, namespaces=tree.nsmap)
|
||||
for element in elements:
|
||||
output_str += "<ul>"
|
||||
for line in element.iter():
|
||||
if line.text:
|
||||
text = " ".join(line.text.split())
|
||||
if text:
|
||||
output_str += "<li>%s: %s</li>" % (line.tag, text)
|
||||
output_str += "</ul>"
|
||||
return output_str
|
||||
|
||||
def _compose_multi_info_message(self, tree, element_tags):
|
||||
output_str = "<ul>"
|
||||
|
||||
for element_tag in element_tags:
|
||||
elements = tree.xpath(element_tag, namespaces=tree.nsmap)
|
||||
if not elements:
|
||||
continue
|
||||
for element in elements:
|
||||
text = " ".join(element.text.split())
|
||||
if text:
|
||||
output_str += "<li>%s: %s</li>" % (element.tag, text)
|
||||
return output_str + "</ul>"
|
||||
|
||||
class AccountTax(models.Model):
|
||||
_name = "account.tax"
|
||||
_inherit = "account.tax"
|
||||
|
||||
l10n_it_vat_due_date = fields.Selection([
|
||||
("I", "[I] IVA ad esigibilità immediata"),
|
||||
("D", "[D] IVA ad esigibilità differita"),
|
||||
("S", "[S] Scissione dei pagamenti")], default="I", string="VAT due date")
|
||||
|
||||
l10n_it_has_exoneration = fields.Boolean(string="Has exoneration of tax (Italy)", help="Tax has a tax exoneration.")
|
||||
l10n_it_kind_exoneration = fields.Selection(selection=[
|
||||
("N1", "[N1] Escluse ex art. 15"),
|
||||
("N2", "[N2] Non soggette"),
|
||||
("N3", "[N3] Non imponibili"),
|
||||
("N4", "[N4] Esenti"),
|
||||
("N5", "[N5] Regime del margine / IVA non esposta in fattura"),
|
||||
("N6", "[N6] Inversione contabile (per le operazioni in reverse charge ovvero nei casi di autofatturazione per acquisti extra UE di servizi ovvero per importazioni di beni nei soli casi previsti)"),
|
||||
("N7", "[N7] IVA assolta in altro stato UE (vendite a distanza ex art. 40 c. 3 e 4 e art. 41 c. 1 lett. b, DL 331/93; prestazione di servizi di telecomunicazioni, tele-radiodiffusione ed elettronici ex art. 7-sexies lett. f, g, art. 74-sexies DPR 633/72)")],
|
||||
string="Exoneration",
|
||||
help="Exoneration type",
|
||||
default="N1")
|
||||
l10n_it_law_reference = fields.Char(string="Law Reference", size=100)
|
||||
|
||||
@api.constrains('l10n_it_has_exoneration',
|
||||
'l10n_it_kind_exoneration',
|
||||
'l10n_it_law_reference',
|
||||
'amount',
|
||||
'l10n_it_vat_due_date')
|
||||
def _check_exoneration_with_no_tax(self):
|
||||
for tax in self:
|
||||
if tax.l10n_it_has_exoneration:
|
||||
if not tax.l10n_it_kind_exoneration or not tax.l10n_it_law_reference or tax.amount != 0:
|
||||
raise ValidationError("If the tax has exoneration, you must enter a kind of exoneration, a law reference and the amount of the tax must be 0.0.")
|
||||
if tax.l10n_it_kind_exoneration == 'N6' and tax.l10n_it_vat_due_date == 'S':
|
||||
raise UserError(_("'Scissione dei pagamenti' is not compatible with exoneration of kind 'N6'"))
|
||||
|
||||
class ImportInvoiceImportWizard(models.TransientModel):
|
||||
_name = 'account.invoice.import.wizard'
|
||||
_inherit = 'account.invoice.import.wizard'
|
||||
|
||||
@api.multi
|
||||
def _create_invoice_from_file(self, attachment):
|
||||
if attachment.mimetype == 'application/xml' and re.search("([A-Z]{2}[A-Za-z0-9]{2,28}_[A-Za-z0-9]{0,5}.(xml.p7m|xml))", attachment.name):
|
||||
if self.env['account.invoice'].search([('l10n_it_einvoice_name', '=', attachment.name)], limit=1):
|
||||
# invoice already exist
|
||||
raise UserError(_('E-invoice already exist: %s') % attachment.name)
|
||||
self = self.with_context(default_journal_id= self.journal_id.id)
|
||||
invoice = self.env['account.invoice']._import_xml_invoice(base64.decodestring(attachment.datas), attachment)
|
||||
else:
|
||||
invoice = super(ImportInvoiceImportWizard, self)._create_invoice_from_file(attachment)
|
||||
return invoice
|
||||
@@ -0,0 +1,21 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, api
|
||||
|
||||
class L10nItDdt(models.Model):
|
||||
_name = 'l10n_it.ddt'
|
||||
_description = 'Transport Document'
|
||||
|
||||
invoice_id = fields.One2many('account.invoice', 'l10n_it_ddt_id', string='Invoice Reference',
|
||||
ondelete='cascade')
|
||||
|
||||
name = fields.Char(string="Numero DDT", size=20, help="Transport document number", required=True)
|
||||
date = fields.Date(string="Data DDT", help="Transport document date", required=True)
|
||||
|
||||
@api.multi
|
||||
def name_get(self):
|
||||
res = []
|
||||
for ddt in self:
|
||||
res.append((ddt.id, ("%s (%s)") % (ddt.name, ddt.date)))
|
||||
return res
|
||||
@@ -0,0 +1,358 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import zipfile
|
||||
import io
|
||||
import re
|
||||
import logging
|
||||
import email
|
||||
import dateutil
|
||||
import pytz
|
||||
import base64
|
||||
try:
|
||||
from xmlrpc import client as xmlrpclib
|
||||
except ImportError:
|
||||
import xmlrpclib
|
||||
|
||||
|
||||
from lxml import etree
|
||||
from datetime import datetime
|
||||
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class FetchmailServer(models.Model):
|
||||
_name = 'fetchmail.server'
|
||||
_inherit = 'fetchmail.server'
|
||||
|
||||
l10n_it_is_pec = fields.Boolean('PEC server', help="If PEC Server, only mail from '...@pec.fatturapa.it' will be processed.")
|
||||
l10n_it_last_uid = fields.Integer(string='Last message UID', default=1)
|
||||
|
||||
@api.constrains('l10n_it_is_pec', 'type')
|
||||
def _check_pec(self):
|
||||
for record in self:
|
||||
if record.l10n_it_is_pec and record.type != 'imap':
|
||||
raise ValidationError("PEC mail server must be of type IMAP.")
|
||||
|
||||
@api.multi
|
||||
def fetch_mail(self):
|
||||
""" WARNING: meant for cron usage only - will commit() after each email! """
|
||||
|
||||
MailThread = self.env['mail.thread']
|
||||
for server in self.filtered(lambda s: s.l10n_it_is_pec):
|
||||
_logger.info('start checking for new emails on %s PEC server %s', server.type, server.name)
|
||||
|
||||
count, failed = 0, 0
|
||||
imap_server = None
|
||||
try:
|
||||
imap_server = server.connect()
|
||||
imap_server.select()
|
||||
|
||||
result, data = imap_server.uid('search', None, '(FROM "@pec.fatturapa.it")', '(UID %s:*)' % (server.l10n_it_last_uid))
|
||||
new_max_uid = server.l10n_it_last_uid
|
||||
for uid in data[0].split():
|
||||
if int(uid) <= server.l10n_it_last_uid:
|
||||
# We get always minimum 1 message. If no new message, we receive the newest already managed.
|
||||
continue
|
||||
|
||||
result, data = imap_server.uid('fetch', uid, '(RFC822)')
|
||||
|
||||
if not data[0]:
|
||||
continue
|
||||
message = data[0][1]
|
||||
|
||||
# To leave the mail in the state in which they were.
|
||||
if "Seen" not in data[1].decode("utf-8"):
|
||||
imap_server.uid('STORE', uid, '+FLAGS', '\\Seen')
|
||||
else:
|
||||
imap_server.uid('STORE', uid, '-FLAGS', '\\Seen')
|
||||
|
||||
# See details in message_process() in mail_thread.py
|
||||
if isinstance(message, xmlrpclib.Binary):
|
||||
message = bytes(message.data)
|
||||
if isinstance(message, str):
|
||||
message = message.encode('utf-8')
|
||||
msg_txt = email.message_from_bytes(message)
|
||||
|
||||
try:
|
||||
self._attachment_invoice(msg_txt)
|
||||
new_max_uid = max(new_max_uid, int(uid))
|
||||
except Exception:
|
||||
_logger.info('Failed to process mail from %s server %s.', server.type, server.name, exc_info=True)
|
||||
failed += 1
|
||||
self._cr.commit()
|
||||
count += 1
|
||||
server.write({'l10n_it_last_uid': new_max_uid})
|
||||
_logger.info("Fetched %d email(s) on %s server %s; %d succeeded, %d failed.", count, server.type, server.name, (count - failed), failed)
|
||||
except Exception:
|
||||
_logger.info("General failure when trying to fetch mail from %s server %s.", server.type, server.name, exc_info=True)
|
||||
finally:
|
||||
if imap_server:
|
||||
imap_server.close()
|
||||
imap_server.logout()
|
||||
server.write({'date': fields.Datetime.now()})
|
||||
return super(FetchmailServer, self.filtered(lambda s: not s.l10n_it_is_pec)).fetch_mail()
|
||||
|
||||
def _attachment_invoice(self, msg_txt):
|
||||
body, attachments = self.env['mail.thread']._message_extract_payload(msg_txt)
|
||||
from_address = tools.decode_smtp_header(msg_txt.get('from'))
|
||||
for attachment in attachments:
|
||||
split_attachment = attachment.fname.rpartition('.')
|
||||
if len(split_attachment) < 3:
|
||||
_logger.info('E-invoice filename not compliant: %s', attachment.fname)
|
||||
continue
|
||||
attachment_name = split_attachment[0]
|
||||
attachment_ext = split_attachment[2]
|
||||
split_underscore = attachment_name.rsplit('_', 2)
|
||||
if len(split_underscore) < 2:
|
||||
_logger.info('E-invoice filename not compliant: %s', attachment.fname)
|
||||
continue
|
||||
|
||||
if attachment_ext != 'zip':
|
||||
if split_underscore[1] in ['RC', 'NS', 'MC', 'MT', 'EC', 'SE', 'NE', 'DT']:
|
||||
# we have a receipt
|
||||
self._message_receipt_invoice(split_underscore[1], attachment)
|
||||
elif re.search("([A-Z]{2}[A-Za-z0-9]{2,28}_[A-Za-z0-9]{0,5}.(xml.p7m|xml))", attachment.fname):
|
||||
# we have a new E-invoice
|
||||
self._create_invoice_from_mail(attachment.content, attachment.fname, from_address)
|
||||
else:
|
||||
if split_underscore[1] == 'AT':
|
||||
# Attestazione di avvenuta trasmissione della fattura con impossibilità di recapito
|
||||
self._message_AT_invoice(attachment)
|
||||
else:
|
||||
_logger.info('New E-invoice in zip file: %s', attachment.fname)
|
||||
self._create_invoice_from_mail_with_zip(attachment, from_address)
|
||||
|
||||
def _create_invoice_from_mail(self, att_content, att_name, from_address):
|
||||
if self.env['account.invoice'].search([('l10n_it_einvoice_name', '=', att_name)], limit=1):
|
||||
# invoice already exist
|
||||
_logger.info('E-invoice already exist: %s', att_name)
|
||||
return
|
||||
|
||||
invoice_attachment = self.env['ir.attachment'].create({
|
||||
'name': att_name,
|
||||
'datas': base64.encodestring(att_content),
|
||||
'datas_fname': att_name,
|
||||
'type': 'binary',
|
||||
})
|
||||
|
||||
invoice = self.env['account.invoice']._import_xml_invoice(att_content, invoice_attachment)
|
||||
invoice.l10n_it_send_state = "new"
|
||||
invoice.source_email = from_address
|
||||
self._cr.commit()
|
||||
|
||||
_logger.info('New E-invoice: %s', att_name)
|
||||
|
||||
|
||||
def _create_invoice_from_mail_with_zip(self, attachment_zip, from_address):
|
||||
with zipfile.ZipFile(io.BytesIO(attachment_zip.content)) as z:
|
||||
for att_name in z.namelist():
|
||||
if self.env['account.invoice'].search([('l10n_it_einvoice_name', '=', att_name)], limit=1):
|
||||
# invoice already exist
|
||||
_logger.info('E-invoice in zip file (%s) already exist: %s', attachment_zip.fname, att_name)
|
||||
continue
|
||||
att_content = z.open(att_name).read()
|
||||
|
||||
self._create_invoice_from_mail(att_content, att_name, from_address)
|
||||
|
||||
def _message_AT_invoice(self, attachment_zip):
|
||||
with zipfile.ZipFile(io.BytesIO(attachment_zip.content)) as z:
|
||||
for attachment_name in z.namelist():
|
||||
split_name_attachment = attachment_name.rpartition('.')
|
||||
if len(split_name_attachment) < 3:
|
||||
continue
|
||||
split_underscore = split_name_attachment[0].rsplit('_', 2)
|
||||
if len(split_underscore) < 2:
|
||||
continue
|
||||
if split_underscore[1] == 'AT':
|
||||
attachment = z.open(attachment_name).read()
|
||||
_logger.info('New AT receipt for: %s', split_underscore[0])
|
||||
try:
|
||||
tree = etree.fromstring(attachment)
|
||||
except:
|
||||
_logger.info('Error in decoding new receipt file: %s', attachment_name)
|
||||
return
|
||||
|
||||
elements = tree.xpath('//NomeFile', namespaces=tree.nsmap)
|
||||
if elements and elements[0].text:
|
||||
filename = elements[0].text
|
||||
else:
|
||||
return
|
||||
|
||||
related_invoice = self.env['account.invoice'].search([
|
||||
('l10n_it_einvoice_name', '=', filename)])
|
||||
if not related_invoice:
|
||||
_logger.info('Error: invoice not found for receipt file: %s', filename)
|
||||
return
|
||||
|
||||
related_invoice.l10n_it_send_state = 'failed_delivery'
|
||||
info = self._return_multi_line_xml(tree, ['//IdentificativoSdI', '//DataOraRicezione', '//MessageId', '//PecMessageId', '//Note'])
|
||||
related_invoice.message_post(
|
||||
body=(_("ES certify that it has received the invoice and that the file \
|
||||
could not be delivered to the addressee. <br/>%s") % (info))
|
||||
)
|
||||
|
||||
def _message_receipt_invoice(self, receipt_type, attachment):
|
||||
try:
|
||||
tree = etree.fromstring(attachment.content)
|
||||
except:
|
||||
_logger.info('Error in decoding new receipt file: %s', attachment.fname)
|
||||
return {}
|
||||
|
||||
elements = tree.xpath('//NomeFile', namespaces=tree.nsmap)
|
||||
if elements and elements[0].text:
|
||||
filename = elements[0].text
|
||||
else:
|
||||
return {}
|
||||
|
||||
if receipt_type == 'RC':
|
||||
# Delivery receipt
|
||||
# This is the receipt sent by the ES to the transmitting subject to communicate
|
||||
# delivery of the file to the addressee
|
||||
related_invoice = self.env['account.invoice'].search([
|
||||
('l10n_it_einvoice_name', '=', filename),
|
||||
('l10n_it_send_state', '=', 'sent')])
|
||||
if not related_invoice:
|
||||
_logger.info('Error: invoice not found for receipt file: %s', attachment.fname)
|
||||
return
|
||||
related_invoice.l10n_it_send_state = 'delivered'
|
||||
info = self._return_multi_line_xml(tree, ['//IdentificativoSdI', '//DataOraRicezione', '//DataOraConsegna', '//Note'])
|
||||
related_invoice.message_post(
|
||||
body=(_("E-Invoice is delivery to the destinatory:<br/>%s") % (info))
|
||||
)
|
||||
|
||||
elif receipt_type == 'NS':
|
||||
# Rejection notice
|
||||
# This is the receipt sent by the ES to the transmitting subject if one or more of
|
||||
# the checks carried out by the ES on the file received do not have a successful result.
|
||||
related_invoice = self.env['account.invoice'].search([
|
||||
('l10n_it_einvoice_name', '=', filename),
|
||||
('l10n_it_send_state', '=', 'sent')])
|
||||
if not related_invoice:
|
||||
_logger.info('Error: invoice not found for receipt file: %s', attachment.fname)
|
||||
return
|
||||
related_invoice.l10n_it_send_state = 'invalid'
|
||||
error = self._return_error_xml(tree)
|
||||
related_invoice.message_post(
|
||||
body=(_("Errors in the E-Invoice :<br/>%s") % (error))
|
||||
)
|
||||
activity_vals = {
|
||||
'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id,
|
||||
'user_id': related_invoice.user_id.id if related_invoice.user_id else self.env.user.id
|
||||
}
|
||||
related_invoice.activity_schedule(summary='Rejection notice', **activity_vals)
|
||||
|
||||
elif receipt_type == 'MC':
|
||||
# Failed delivery notice
|
||||
# This is the receipt sent by the ES to the transmitting subject if the file is not
|
||||
# delivered to the addressee.
|
||||
related_invoice = self.env['account.invoice'].search([
|
||||
('l10n_it_einvoice_name', '=', filename),
|
||||
('l10n_it_send_state', '=', 'sent')])
|
||||
if not related_invoice:
|
||||
_logger.info('Error: invoice not found for receipt file: %s', attachment.fname)
|
||||
return
|
||||
info = self._return_multi_line_xml(tree, [
|
||||
'//IdentificativoSdI',
|
||||
'//DataOraRicezione',
|
||||
'//Descrizione',
|
||||
'//MessageId',
|
||||
'//Note'])
|
||||
related_invoice.message_post(
|
||||
body=(_("The E-invoice is not delivered to the addressee. The Exchange System is\
|
||||
unable to deliver the file to the Public Administration. The Exchange System will\
|
||||
contact the PA to report the problem and request that they provide a solution. \
|
||||
During the following 15 days, the Exchange System will try to forward the FatturaPA\
|
||||
file to the Administration in question again. More informations:<br/>%s") % (info))
|
||||
)
|
||||
|
||||
elif receipt_type == 'NE':
|
||||
# Outcome notice
|
||||
# This is the receipt sent by the ES to the invoice sender to communicate the result
|
||||
# (acceptance or refusal of the invoice) of the checks carried out on the document by
|
||||
# the addressee.
|
||||
related_invoice = self.env['account.invoice'].search([
|
||||
('l10n_it_einvoice_name', '=', filename),
|
||||
('l10n_it_send_state', '=', 'delivered')])
|
||||
if not related_invoice:
|
||||
_logger.info('Error: invoice not found for receipt file: %s', attachment.fname)
|
||||
return
|
||||
elements = tree.xpath('//Esito', namespaces=tree.nsmap)
|
||||
if elements and elements[0].text:
|
||||
if elements[0].text == 'EC01':
|
||||
related_invoice.l10n_it_send_state = 'delivered_accepted'
|
||||
elif elements[0].text == 'EC02':
|
||||
related_invoice.l10n_it_send_state = 'delivered_refused'
|
||||
|
||||
info = self._return_multi_line_xml(tree,
|
||||
['//Esito',
|
||||
'//Descrizione',
|
||||
'//IdentificativoSdI',
|
||||
'//DataOraRicezione',
|
||||
'//DataOraConsegna',
|
||||
'//Note'
|
||||
])
|
||||
related_invoice.message_post(
|
||||
body=(_("Outcome notice: %s<br/>%s") % (related_invoice.l10n_it_send_state, info))
|
||||
)
|
||||
if related_invoice.l10n_it_send_state == 'delivered_refused':
|
||||
activity_vals = {
|
||||
'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id,
|
||||
'user_id': related_invoice.user_id.id if related_invoice.user_id else self.env.user.id
|
||||
}
|
||||
related_invoice.activity_schedule(summary='Outcome notice: Refused', **activity_vals)
|
||||
|
||||
# elif receipt_type == 'MT':
|
||||
# Metadata file
|
||||
# This is the file sent by the ES to the addressee together with the invoice file,
|
||||
# containing the main reference data of the file useful for processing, including
|
||||
# the IdentificativoSDI.
|
||||
# Useless for Odoo
|
||||
|
||||
elif receipt_type == 'DT':
|
||||
# Deadline passed notice
|
||||
# This is the receipt sent by the ES to both the invoice sender and the invoice
|
||||
# addressee to communicate the expiry of the maximum term for communication of
|
||||
# acceptance/refusal.
|
||||
related_invoice = self.env['account.invoice'].search([
|
||||
('l10n_it_einvoice_name', '=', filename), ('l10n_it_send_state', '=', 'delivered')])
|
||||
if not related_invoice:
|
||||
_logger.info('Error: invoice not found for receipt file: %s', attachment.fname)
|
||||
return
|
||||
related_invoice.l10n_it_send_state = 'delivered_expired'
|
||||
info = self._return_multi_line_xml(tree, [
|
||||
'//Descrizione',
|
||||
'//IdentificativoSdI',
|
||||
'//Note'])
|
||||
related_invoice.message_post(
|
||||
body=(_("Expiration of the maximum term for communication of acceptance/refusal:\
|
||||
%s<br/>%s") % (filename, info))
|
||||
)
|
||||
|
||||
def _return_multi_line_xml(self, tree, element_tags):
|
||||
output_str = "<ul>"
|
||||
|
||||
for element_tag in element_tags:
|
||||
elements = tree.xpath(element_tag, namespaces=tree.nsmap)
|
||||
if not elements:
|
||||
continue
|
||||
for element in elements:
|
||||
text = " ".join(element.text.split())
|
||||
if text:
|
||||
output_str += "<li>%s: %s</li>" % (element.tag, text)
|
||||
return output_str + "</ul>"
|
||||
|
||||
def _return_error_xml(self, tree):
|
||||
output_str = "<ul>"
|
||||
|
||||
elements = tree.xpath('//Errore', namespaces=tree.nsmap)
|
||||
if not elements:
|
||||
return
|
||||
for element in elements:
|
||||
descrizione = " ".join(element[1].text.split())
|
||||
if descrizione:
|
||||
output_str += "<li>Errore %s: %s</li>" % (element[0].text, descrizione)
|
||||
return output_str + "</ul>"
|
||||
@@ -0,0 +1,110 @@
|
||||
# -*- 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 ValidationError
|
||||
|
||||
TAX_SYSTEM = [
|
||||
("RF01", "[RF01] Ordinario"),
|
||||
("RF02", "[RF02] Contribuenti minimi (art.1, c.96-117, L. 244/07)"),
|
||||
("RF04", "[RF04] Agricoltura e attività connesse e pesca (artt.34 e 34-bis, DPR 633/72)"),
|
||||
("RF05", "[RF05] Vendita sali e tabacchi (art.74, c.1, DPR. 633/72)"),
|
||||
("RF06", "[RF06] Commercio fiammiferi (art.74, c.1, DPR 633/72)"),
|
||||
("RF07", "[RF07] Editoria (art.74, c.1, DPR 633/72)"),
|
||||
("RF08", "[RF08] Gestione servizi telefonia pubblica (art.74, c.1, DPR 633/72)"),
|
||||
("RF09", "[RF09] Rivendita documenti di trasporto pubblico e di sosta (art.74, c.1, DPR 633/72)"),
|
||||
("RF10", "[RF10] Intrattenimenti, giochi e altre attività di cui alla tariffa allegata al DPR 640/72 (art.74, c.6, DPR 633/72)"),
|
||||
("RF11", "[RF11] Agenzie viaggi e turismo (art.74-ter, DPR 633/72)"),
|
||||
("RF12", "[RF12] Agriturismo (art.5, c.2, L. 413/91)"),
|
||||
("RF13", "[RF13] Vendite a domicilio (art.25-bis, c.6, DPR 600/73)"),
|
||||
("RF14", "[RF14] Rivendita beni usati, oggetti d’arte, d’antiquariato o da collezione (art.36, DL 41/95)"),
|
||||
("RF15", "[RF15] Agenzie di vendite all’asta di oggetti d’arte, antiquariato o da collezione (art.40-bis, DL 41/95)"),
|
||||
("RF16", "[RF16] IVA per cassa P.A. (art.6, c.5, DPR 633/72)"),
|
||||
("RF17", "[RF17] IVA per cassa (art. 32-bis, DL 83/2012)"),
|
||||
("RF18", "[RF18] Altro"),
|
||||
("RF19", "[RF19] Regime forfettario (art.1, c.54-89, L. 190/2014)"),
|
||||
]
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_name = 'res.company'
|
||||
_inherit = 'res.company'
|
||||
|
||||
l10n_it_codice_fiscale = fields.Char(string="Codice Fiscale", size=16, related='partner_id.l10n_it_codice_fiscale',
|
||||
store=True, readonly=False, help="Fiscal code of your company")
|
||||
l10n_it_tax_system = fields.Selection(selection=TAX_SYSTEM, string="Tax System",
|
||||
help="Please select the Tax system to which you are subjected.")
|
||||
|
||||
# PEC server
|
||||
l10n_it_mail_pec_server_id = fields.Many2one('ir.mail_server', string="Server PEC",
|
||||
help="Configure your PEC-mail server to send electronic invoices.")
|
||||
l10n_it_address_recipient_fatturapa = fields.Char(string="Government PEC-mail",
|
||||
help="Enter Government PEC-mail address. Ex: sdi01@pec.fatturapa.it")
|
||||
l10n_it_address_send_fatturapa = fields.Char(string="Company PEC-mail",
|
||||
help="Enter your company PEC-mail address. Ex: yourcompany@pec.mail.it")
|
||||
|
||||
|
||||
# Economic and Administrative Index
|
||||
l10n_it_has_eco_index = fields.Boolean(default=False,
|
||||
help="The seller/provider is a company listed on the register of companies and as\
|
||||
such must also indicate the registration data on all documents (art. 2250, Italian\
|
||||
Civil Code)")
|
||||
l10n_it_eco_index_office = fields.Many2one('res.country.state', domain="[('country_id','=','IT')]",
|
||||
string="Province of the register-of-companies office")
|
||||
l10n_it_eco_index_number = fields.Char(string="Number in register of companies", size=20,
|
||||
help="This field must contain the number under which the\
|
||||
seller/provider is listed on the register of companies.")
|
||||
l10n_it_eco_index_share_capital = fields.Float(default="0.0", size=15, string="Share capital actually paid up",
|
||||
help="Mandatory if the seller/provider is a company with share\
|
||||
capital (SpA, SApA, Srl), this field must contain the amount\
|
||||
of share capital actually paid up as resulting from the last\
|
||||
financial statement")
|
||||
l10n_it_eco_index_sole_shareholder = fields.Selection(
|
||||
[
|
||||
("NO", "Not a limited liability company"),
|
||||
("SU", "Socio unico"),
|
||||
("SM", "Più soci")],
|
||||
string="Shareholder")
|
||||
l10n_it_eco_index_liquidation_state = fields.Selection(
|
||||
[
|
||||
("LS", "The company is in a state of liquidation"),
|
||||
("LN", "The company is not in a state of liquidation")],
|
||||
string="Liquidation state")
|
||||
|
||||
|
||||
# Tax representative
|
||||
l10n_it_has_tax_representative = fields.Boolean(default=False,
|
||||
help="The seller/provider is a non-resident subject which\
|
||||
carries out transactions in Italy with relevance for VAT\
|
||||
purposes and which takes avail of a tax representative in\
|
||||
Italy")
|
||||
l10n_it_tax_representative_partner_id = fields.Many2one('res.partner', string='Tax representative partner')
|
||||
|
||||
@api.constrains('l10n_it_has_eco_index',
|
||||
'l10n_it_eco_index_office',
|
||||
'l10n_it_eco_index_number',
|
||||
'l10n_it_eco_index_share_capital',
|
||||
'l10n_it_eco_index_sole_shareholder',
|
||||
'l10n_it_eco_index_liquidation_state')
|
||||
def _check_eco_admin_index(self):
|
||||
for record in self:
|
||||
if not record.l10n_it_has_eco_index:
|
||||
continue
|
||||
if not record.l10n_it_eco_index_office\
|
||||
or not record.l10n_it_eco_index_number\
|
||||
or not record.l10n_it_eco_index_share_capital\
|
||||
or not record.l10n_it_eco_index_sole_shareholder\
|
||||
or not record.l10n_it_eco_index_liquidation_state:
|
||||
raise ValidationError("All fields about the Economic and Administrative Index must be completed.")
|
||||
|
||||
@api.constrains('l10n_it_has_tax_representative',
|
||||
'l10n_it_tax_representative_partner_id')
|
||||
def _check_tax_representative(self):
|
||||
for record in self:
|
||||
if not record.l10n_it_has_tax_representative:
|
||||
continue
|
||||
if not record.l10n_it_tax_representative_partner_id:
|
||||
raise ValidationError("You must select a tax representative.")
|
||||
if not record.l10n_it_tax_representative_partner_id.vat:
|
||||
raise ValidationError("Your tax representative partner must have a tax number.")
|
||||
if not record.l10n_it_tax_representative_partner_id.country_id:
|
||||
raise ValidationError("Your tax representative partner must have a country.")
|
||||
@@ -0,0 +1,28 @@
|
||||
# -*- 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 ValidationError
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_name = 'res.partner'
|
||||
_inherit = 'res.partner'
|
||||
|
||||
l10n_it_pec_email = fields.Char(string="PEC e-mail")
|
||||
l10n_it_codice_fiscale = fields.Char(string="Codice Fiscale", size=16)
|
||||
l10n_it_pa_index = fields.Char(string="PA index",
|
||||
size=7,
|
||||
help="Must contain the 6-character (or 7) code, present in the PA\
|
||||
Index in the information relative to the electronic invoicing service,\
|
||||
associated with the office which, within the addressee administration, deals\
|
||||
with receiving (and processing) the invoice.")
|
||||
|
||||
_sql_constraints = [
|
||||
('l10n_it_codice_fiscale',
|
||||
"CHECK(l10n_it_codice_fiscale IS NULL OR LENGTH(l10n_it_codice_fiscale) >= 11)",
|
||||
"Codice fiscale must have between 11 and 16 characters."),
|
||||
|
||||
('l10n_it_pa_index',
|
||||
"CHECK(l10n_it_pa_index IS NULL OR LENGTH(l10n_it_pa_index) >= 6)",
|
||||
"PA index must have between 6 and 7 characters."),
|
||||
]
|
||||
@@ -0,0 +1,2 @@
|
||||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
|
||||
"access_it_ddt_manager","it_ddt manager","model_l10n_it_ddt","account.group_account_invoice",1,1,1,1
|
||||
|
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import test_generate
|
||||
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
|
||||
from odoo import fields
|
||||
from odoo.tests.common import TransactionCase, Form
|
||||
|
||||
class TestItalianElectronicInvoice(TransactionCase):
|
||||
def test_state(self):
|
||||
f = Form(self.env['account.invoice'])
|
||||
f.partner_id = self.env.ref('base.res_partner_12')
|
||||
with f.invoice_line_ids.new() as l:
|
||||
l.product_id = self.env.ref('product.product_product_3')
|
||||
invoice = f.save()
|
||||
|
||||
# I check that Initially customer invoice state is "Draft"
|
||||
self.assertEqual(invoice.state, 'draft')
|
||||
|
||||
# I called the "Confirm Draft Invoices" wizard
|
||||
w = Form(self.env['account.invoice.confirm']).save()
|
||||
# I clicked on Confirm Invoices Button
|
||||
w.with_context(
|
||||
active_model='account.invoice',
|
||||
active_id=invoice.id,
|
||||
active_ids=invoice.ids,
|
||||
type='out_invoice',
|
||||
).invoice_confirm()
|
||||
|
||||
# I check that customer invoice state is "Open"
|
||||
self.assertEqual(invoice.state, 'open')
|
||||
|
||||
# Electronic invoice must be present and have the same name as l10n_it_einvoice_name
|
||||
self.assertEqual(invoice.l10n_it_einvoice_id.name, invoice.l10n_it_einvoice_name)
|
||||
@@ -0,0 +1,265 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="fetchmail_server_form_l10n_it" model="ir.ui.view">
|
||||
<field name="name">fetchmail.server.form.l10n.it</field>
|
||||
<field name="model">fetchmail.server</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="inherit_id" ref="fetchmail.view_email_server_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//field[@name='date']" position="after">
|
||||
<field name="l10n_it_is_pec"/>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="account_tax_form_l10n_it" model="ir.ui.view">
|
||||
<field name="name">account.tax.form.l10n.it</field>
|
||||
<field name="model">account.tax</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="inherit_id" ref="account.view_tax_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//page" position="inside">
|
||||
<group>
|
||||
<field name="l10n_it_vat_due_date"/>
|
||||
<field name="l10n_it_has_exoneration" readonly="False"/>
|
||||
<field name="l10n_it_kind_exoneration" attrs="{'invisible': [('l10n_it_has_exoneration', '=', False)]}"/>
|
||||
<field name="l10n_it_law_reference" attrs="{'invisible': [('l10n_it_has_exoneration', '=', False)]}"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="res_partner_form_l10n_it" model="ir.ui.view">
|
||||
<field name="name">res.partner.form.l10n.it</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//field[@name='category_id']" position="after">
|
||||
<field name="l10n_it_pec_email" attrs="{'invisible': [('parent_id', '!=', False)]}"/>
|
||||
<field name="l10n_it_codice_fiscale" attrs="{'invisible': [('parent_id', '!=', False)]}"/>
|
||||
<field name="l10n_it_pa_index" attrs="{'invisible': [('parent_id', '!=', False)]}"/>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="res_company_form_l10n_it" model="ir.ui.view">
|
||||
<field name="name">res.company.form.l10n.it</field>
|
||||
<field name="model">res.company</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="inherit_id" ref="base.view_company_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//div[hasclass('o_address_format')]" position="after">
|
||||
<field name="l10n_it_mail_pec_server_id"/>
|
||||
<field name="l10n_it_address_send_fatturapa"/>
|
||||
<field name="l10n_it_address_recipient_fatturapa"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='vat']" position="after">
|
||||
<field name="l10n_it_codice_fiscale"/>
|
||||
<field name="l10n_it_tax_system"/>
|
||||
</xpath>
|
||||
<xpath expr="//page" position="after">
|
||||
<page string="Electronic Invoicing">
|
||||
<group>
|
||||
<separator string="Economic and Administrative Index" colspan="4"/>
|
||||
<div colspan="4">
|
||||
The seller/provider is a company listed on the register of companies and as
|
||||
such must also indicate the registration data on all documents (art. 2250, Italian
|
||||
Civil Code)
|
||||
</div>
|
||||
<group>
|
||||
<field name="l10n_it_has_eco_index" string="Company listed on the register of companies"/>
|
||||
</group>
|
||||
<group attrs="{'invisible': [('l10n_it_has_eco_index', '=', False)]}">
|
||||
<field name="l10n_it_eco_index_office"/>
|
||||
<field name="l10n_it_eco_index_number"/>
|
||||
<field name="l10n_it_eco_index_share_capital"/>
|
||||
<field name="l10n_it_eco_index_sole_shareholder"/>
|
||||
<field name="l10n_it_eco_index_liquidation_state"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<separator string="Tax representative" colspan="4"/>
|
||||
<div colspan="4">
|
||||
The seller/provider is a non-resident subject which carries out transactions in Italy
|
||||
with relevance for VAT purposes and which takes avail of a tax representative in Italy
|
||||
</div>
|
||||
<group>
|
||||
<field name="l10n_it_has_tax_representative" string="Company have a tax representative"/>
|
||||
</group>
|
||||
<group attrs="{'invisible': [('l10n_it_has_tax_representative', '=', False)]}">
|
||||
<field name="l10n_it_tax_representative_partner_id"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="invoice_supplier_tree_l10n_it" model="ir.ui.view">
|
||||
<field name="name">account.invoice.supplier.tree.l10n.it</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="inherit_id" ref="account.invoice_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//field[@name='type']" position="after">
|
||||
<field name="l10n_it_send_state" invisible="1" widget="label_selection" options="{'classes': {'to_send': 'default', 'invalid': 'danger', 'sent': 'warning',
|
||||
'delivered': 'success', 'delivered_accepted': 'success', 'delivered_refused': 'success', 'delivered_expired': 'success', 'failed_delivery': 'success'}}"/>
|
||||
|
||||
<button icon="fa-paper-plane-o" class="btn-outline-warning disabled" aria-label="Sent" title="Sent" attrs="{'invisible': [('l10n_it_send_state', '!=', 'sent')]}"/>
|
||||
<button icon="fa-exclamation-triangle" class="btn-outline-danger disabled" aria-label="Error" title="Error" attrs="{'invisible': [('l10n_it_send_state', '!=', 'invalid')]}"/>
|
||||
<button icon="fa-check" class="btn-outline-success disabled" aria-label="Delivered" title="Delivered" attrs="{'invisible': [('l10n_it_send_state', 'not in', ['delivered', 'delivered_accepted', 'delivered_refused', 'delivered_expired', 'failed_delivery'])]}"/>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="invoice_kanban_l10n_it" model="ir.ui.view">
|
||||
<field name="name">account.invoice.kanban.l10n.it</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="inherit_id" ref="account.invoice_kanban"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//field[@name='currency_id']" position="after">
|
||||
<field name="l10n_it_send_state"/>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//div[hasclass('o_kanban_record_headings')]" position="inside">
|
||||
<i class="text-success fa fa-plus-circle" t-if="record.l10n_it_send_state.raw_value == 'new'"/>
|
||||
<i class="text-warning fa fa-paper-plane-o" t-if="record.l10n_it_send_state.raw_value == 'sent'"/>
|
||||
<i class="text-danger fa fa-exclamation-triangle" t-if="record.l10n_it_send_state.raw_value == 'invalid'"/>
|
||||
<i class="text-success fa fa-check" t-if="['delivered', 'delivered_accepted', 'delivered_refused', 'delivered_expired', 'failed_delivery'].indexOf(record.l10n_it_send_state.raw_value) >= 0" />
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="account_invoice_form_l10n_it" model="ir.ui.view">
|
||||
<field name="name">account.invoice.form.l10n.it</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="inherit_id" ref="account.invoice_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//button[@name='preview_invoice']" position="after">
|
||||
<button name="invoice_validate" type="object" icon="fa-envelope-o" attrs="{'invisible': [('l10n_it_send_state', '!=', 'invalid')]}" class="btn btn-primary" string="Resend"/>
|
||||
<button name="invoice_validate" type="object" icon="fa-envelope-o" attrs="{'invisible': ['|',('state', 'in', ['draft', 'cancel']),('l10n_it_send_state', 'not in', ['to_send', False])]}" class="btn btn-primary" string="Send"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='type']" position="before">
|
||||
<div class="alert alert-success" role="alert" attrs="{'invisible': [('l10n_it_send_state', 'not in', ['delivered', 'delivered_accepted', 'delivered_refused', 'delivered_expired', 'failed_delivery'])]}">
|
||||
<i class="fa fa-check" aria-label="Delivered" title="Delivered"></i> <field name="l10n_it_send_state" readonly="1"/>
|
||||
</div>
|
||||
<div class="alert alert-warning" role="alert" attrs="{'invisible': [('l10n_it_send_state', '!=', 'sent')]}">
|
||||
<i class="fa fa-paper-plane-o"/> E-Invoice sent, waiting for a response
|
||||
</div>
|
||||
<div class="alert alert-danger" role="alert" attrs="{'invisible': [('l10n_it_send_state', '!=', 'invalid')]}">
|
||||
<i class="fa fa-exclamation-triangle"/> E-Invoice check failed. You can modify the invoice, and resend it.
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//page[@name='other_info']" position="after">
|
||||
<page string="Electronic Invoicing">
|
||||
<group>
|
||||
<group>
|
||||
<field name="l10n_it_stamp_duty"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="l10n_it_ddt_id"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="account_invoice_supplier_form_l10n_it" model="ir.ui.view">
|
||||
<field name="name">account.invoice.supplier.form.l10n.it</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="inherit_id" ref="account.invoice_supplier_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//field[@name='date_due']" position="replace">
|
||||
<field name="date_due" attrs="{'readonly': [('state', 'in', ['open', 'in_payment', 'paid'])]}" force_save="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//page[@name='other_info']" position="after">
|
||||
<page string="Electronic Invoicing">
|
||||
<group>
|
||||
<group>
|
||||
<field name="l10n_it_stamp_duty"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_account_invoice_filter_l10n_it" model="ir.ui.view">
|
||||
<field name="name">account.invoice.select.l10n.it</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="inherit_id" ref="account.view_account_invoice_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//filter[@name='late']" position="after">
|
||||
<separator/>
|
||||
<filter name="error" string="E-invoice error" domain="[('l10n_it_send_state', '=', 'invalid')]"/>
|
||||
<filter name="sent" string="E-invoice sent" domain="[('l10n_it_send_state', '=', 'sent')]"/>
|
||||
<filter name="no_sent" string="E-invoice to send" domain="[('l10n_it_send_state', 'in', ['to_send',False])]"/>
|
||||
<filter name="delivered" string="E-invoice delivered" domain="[('l10n_it_send_state', 'in', ['delivered', 'delivered_accepted', 'delivered_refused', 'delivered_expired', 'failed_delivery'])]"/>
|
||||
</xpath>
|
||||
<xpath expr="//filter[@name='status']" position="after">
|
||||
<filter name="send_status" string="Send status" context="{'group_by':'l10n_it_send_state'}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="l10n_it_ddt" model="ir.ui.view">
|
||||
<field name="name">ddt.form.l10n.it</field>
|
||||
<field name="model">l10n_it.ddt</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="date"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="l10n_it_ddt_list_view" model="ir.ui.view">
|
||||
<field name="name">l10n_it.ddt.list.view</field>
|
||||
<field name="model">l10n_it.ddt</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="date"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_ddt_account" model="ir.actions.act_window">
|
||||
<field name="name">Transport Document</field>
|
||||
<field name="res_model">l10n_it.ddt</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="l10n_it_ddt_list_view"/>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
name="DDT"
|
||||
parent="account.account_account_menu"
|
||||
action="action_ddt_account"
|
||||
id="menu_action_ddt_account"
|
||||
sequence="15"
|
||||
groups="base.group_no_one"/>
|
||||
</odoo>
|
||||
@@ -11,6 +11,10 @@
|
||||
<field name="name">IVA 16% </field>
|
||||
</record>
|
||||
|
||||
<record id="tax_group_iva_8" model="account.tax.group">
|
||||
<field name="name">IVA 8%</field>
|
||||
</record>
|
||||
|
||||
<record id="tax_group_iva_ret_4" model="account.tax.group">
|
||||
<field name="name">IVA Retencion 4%</field>
|
||||
</record>
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
<field name="name">DIOT: 0%</field>
|
||||
<field name="applicability">taxes</field>
|
||||
</record>
|
||||
<record id="tag_diot_8" model="account.account.tag">
|
||||
<field name="name">DIOT: 8%</field>
|
||||
<field name="applicability">taxes</field>
|
||||
</record>
|
||||
<record id="tag_diot_ret" model="account.account.tag">
|
||||
<field name="name">DIOT: Retención</field>
|
||||
<field name="applicability">taxes</field>
|
||||
@@ -190,5 +194,35 @@
|
||||
<field name="cash_basis_account_id" ref="cuenta118_01"/>
|
||||
<field name="tag_ids" eval="[(6,0,[ref('tag_diot_16')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="tax16" model="account.tax.template">
|
||||
<field name="chart_template_id" ref="mx_coa"/>
|
||||
<field name="name">IVA(8%) COMPRAS</field>
|
||||
<field name="description">IVA(8%)</field>
|
||||
<field name="amount">8</field>
|
||||
<field name="amount_type">percent</field>
|
||||
<field name="type_tax_use">purchase</field>
|
||||
<field name="account_id" ref="cuenta119_01"/>
|
||||
<field name="refund_account_id" ref="cuenta119_01"/>
|
||||
<field name="tax_group_id" ref="tax_group_iva_8"/>
|
||||
<field name="tax_exigibility">on_payment</field>
|
||||
<field name="cash_basis_account_id" ref="cuenta118_01"/>
|
||||
<field name="tag_ids" eval="[(6,0,[ref('tag_diot_8')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="tax17" model="account.tax.template">
|
||||
<field name="chart_template_id" ref="mx_coa"/>
|
||||
<field name="name">IVA(8%) VENTAS</field>
|
||||
<field name="description">IVA(8%)</field>
|
||||
<field name="amount">8</field>
|
||||
<field name="amount_type">percent</field>
|
||||
<field name="type_tax_use">sale</field>
|
||||
<field name="account_id" ref="cuenta209_01"/>
|
||||
<field name="refund_account_id" ref="cuenta209_01"/>
|
||||
<field name="tag_ids" eval="[(6,0,[ref('tag_iva')])]"/>
|
||||
<field name="tax_group_id" ref="tax_group_iva_8"/>
|
||||
<field name="tax_exigibility">on_payment</field>
|
||||
<field name="cash_basis_account_id" ref="cuenta208_01"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -231,7 +231,9 @@
|
||||
<td align="center" style="min-width: 590px;">
|
||||
<t t-set="access_action" t-value="record.with_context(force_website=True).get_access_action()"/>
|
||||
<t t-set="is_online" t-value="access_action and access_action['type'] == 'ir.actions.act_url'"/>
|
||||
<t t-set="access_url" t-value="is_online and record._get_share_url(redirect=True, signup_partner=True) or ''"/>
|
||||
<t t-set="base_url" t-value="record.get_base_url()"/>
|
||||
<t t-set="share_url" t-value="record._get_share_url(redirect=True, signup_partner=True)"/>
|
||||
<t t-set="access_url" t-value="is_online and share_url and base_url + share_url or ''"/>
|
||||
<t t-set="access_name">
|
||||
View <t t-esc="model_description or 'document'"/>
|
||||
</t>
|
||||
|
||||
@@ -756,7 +756,11 @@ class Channel(models.Model):
|
||||
self.ensure_one()
|
||||
if self.channel_message_ids.ids:
|
||||
last_message_id = self.channel_message_ids.ids[0] # zero is the index of the last message
|
||||
self.env['mail.channel.partner'].search([('channel_id', 'in', self.ids), ('partner_id', '=', self.env.user.partner_id.id)]).write({
|
||||
channel_partner = self.env['mail.channel.partner'].search([('channel_id', 'in', self.ids), ('partner_id', '=', self.env.user.partner_id.id)])
|
||||
if channel_partner.seen_message_id.id == last_message_id:
|
||||
# last message seen by user is already up-to-date
|
||||
return
|
||||
channel_partner.write({
|
||||
'seen_message_id': last_message_id,
|
||||
'fetched_message_id': last_message_id,
|
||||
})
|
||||
@@ -765,26 +769,37 @@ class Channel(models.Model):
|
||||
'last_message_id': last_message_id,
|
||||
'partner_id': self.env.user.partner_id.id,
|
||||
}
|
||||
self.env['bus.bus'].sendmany([[(self._cr.dbname, 'mail.channel', self.id), data]])
|
||||
if self.channel_type == 'chat':
|
||||
self.env['bus.bus'].sendmany([[(self._cr.dbname, 'mail.channel', self.id), data]])
|
||||
else:
|
||||
data['channel_id'] = self.id
|
||||
self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), data)
|
||||
return last_message_id
|
||||
|
||||
@api.multi
|
||||
def channel_fetched(self):
|
||||
""" Broadcast the channel_fetched notification to channel members
|
||||
:param channel_ids : list of channel id that has been fetched by current user
|
||||
"""
|
||||
self.ensure_one()
|
||||
if not self.channel_message_ids.ids:
|
||||
return
|
||||
last_message_id = self.channel_message_ids.ids[0] # zero is the index of the last message
|
||||
self.env['mail.channel.partner'].search([('channel_id', 'in', self.ids), ('partner_id', '=', self.env.user.partner_id.id)]).write({
|
||||
'fetched_message_id': last_message_id,
|
||||
})
|
||||
data = {
|
||||
'info': 'channel_fetched',
|
||||
'last_message_id': last_message_id,
|
||||
'partner_id': self.env.user.partner_id.id,
|
||||
}
|
||||
self.env['bus.bus'].sendmany([[(self._cr.dbname, 'mail.channel', self.id), data]])
|
||||
for channel in self:
|
||||
if not channel.channel_message_ids.ids:
|
||||
return
|
||||
if channel.channel_type != 'chat':
|
||||
return
|
||||
last_message_id = channel.channel_message_ids.ids[0] # zero is the index of the last message
|
||||
channel_partner = self.env['mail.channel.partner'].search([('channel_id', '=', channel.id), ('partner_id', '=', self.env.user.partner_id.id)])
|
||||
if channel_partner.fetched_message_id.id == last_message_id:
|
||||
# last message fetched by user is already up-to-date
|
||||
return
|
||||
channel_partner.write({
|
||||
'fetched_message_id': last_message_id,
|
||||
})
|
||||
data = {
|
||||
'info': 'channel_fetched',
|
||||
'last_message_id': last_message_id,
|
||||
'partner_id': self.env.user.partner_id.id,
|
||||
}
|
||||
self.env['bus.bus'].sendmany([[(self._cr.dbname, 'mail.channel', channel.id), data]])
|
||||
|
||||
@api.multi
|
||||
def channel_invite(self, partner_ids):
|
||||
|
||||
@@ -559,7 +559,7 @@ class Message(models.Model):
|
||||
'model': message.model,
|
||||
'last_message_date': message.date,
|
||||
'module_icon': '/mail/static/src/img/smiley/mailfailure.jpg',
|
||||
'notifications': dict((notif.res_partner_id.id, (notif.email_status, notif.res_partner_id.name)) for notif in message.notification_ids)
|
||||
'notifications': dict((notif.res_partner_id.id, (notif.email_status, notif.res_partner_id.name)) for notif in message.notification_ids.sudo())
|
||||
}
|
||||
failures_infos.append(info)
|
||||
return failures_infos
|
||||
|
||||
@@ -744,7 +744,9 @@ class MailThread(models.AbstractModel):
|
||||
access_link = self._notify_get_action_link('view')
|
||||
|
||||
if message.model:
|
||||
model_name = self.env['ir.model']._get(message.model).display_name
|
||||
model = self.env['ir.model'].with_context(
|
||||
lang=self.env.context.get('lang', self.env.user.lang))
|
||||
model_name = model._get(message.model).display_name
|
||||
view_title = _('View %s') % model_name
|
||||
else:
|
||||
view_title = _('View')
|
||||
|
||||
@@ -90,6 +90,7 @@ var Chatter = Widget.extend({
|
||||
this._$topbar = this.$('.o_chatter_topbar');
|
||||
if(!this._disableAttachmentBox) {
|
||||
this.$('.o_topbar_right_area').append(QWeb.render('mail.chatter.Attachment.Button', {
|
||||
displayCounter: !!this.fields.thread,
|
||||
count: this.record.data.message_attachment_count || 0,
|
||||
}));
|
||||
}
|
||||
@@ -362,7 +363,9 @@ var Chatter = Widget.extend({
|
||||
if (this._isAttachmentBoxOpen) {
|
||||
this._fetchAttachments().then(this._openAttachmentBox.bind(this));
|
||||
}
|
||||
this.trigger_up('reload', { fieldNames: ['message_attachment_count'] });
|
||||
if (this.fields.thread) {
|
||||
this.trigger_up('reload', { fieldNames: ['message_attachment_count'] });
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -490,7 +493,9 @@ var Chatter = Widget.extend({
|
||||
})
|
||||
.then(function () {
|
||||
self._reloadAttachmentBox();
|
||||
self.fields.thread.removeAttachments([ev.data.attachmentId]);
|
||||
if (self.fields.thread) {
|
||||
self.fields.thread.removeAttachments([ev.data.attachmentId]);
|
||||
}
|
||||
self.trigger_up('reload');
|
||||
});
|
||||
}
|
||||
|
||||