[MERGE] forward port branch saas-12.2 up to fcb972db9d

This commit is contained in:
Christophe Simonis
2019-02-26 16:39:30 +01:00
297 changed files with 6878 additions and 4075 deletions
+3 -9
View File
@@ -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"
+3 -10
View File
@@ -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"
+6 -10
View File
@@ -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"
+9 -5
View File
@@ -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
+134 -122
View File
@@ -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"
+11 -11
View File
@@ -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
+2 -7
View File
@@ -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"
+1 -1
View File
@@ -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
+9 -7
View File
@@ -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
+11 -14
View File
@@ -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"
+1 -1
View File
@@ -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':
+1 -1
View File
@@ -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
+139 -86
View File
@@ -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
+2
View File
@@ -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:
+1 -1
View File
@@ -173,7 +173,7 @@ class AccountChartTemplate(models.Model):
# (won't work well for multi-company)
company = self.env.user.company_id
# If we don't have any chart of account on this company, install this chart of account
if not 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):
+1 -1
View File
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
68 access_account_account_tax account.account.tag model_account_account_tag account.group_account_user 1 1 1 1
69 access_account_account_tax_user account.account.tag model_account_account_tag account.group_account_invoice 1 0 0 0
70 access_account_reconcile_model account.reconcile.model model_account_reconcile_model account.group_account_user 1 1 1 1
71 access_account_reconcile_model_template account.reconcile.model.template model_account_reconcile_model_template account.group_account_user account.group_account_invoice 1 1 1 1
72 access_account_partial_reconcile_group_invoice account.partial.reconcile.group.invoice model_account_partial_reconcile account.group_account_invoice 1 1 1 0
73 access_account_partial_reconcile account.partial.reconcile model_account_partial_reconcile account.group_account_user 1 1 1 1
74 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
+3 -3
View File
@@ -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:
+13 -7
View File
@@ -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
+13 -7
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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')
-2
View File
@@ -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"/>
+3 -2
View File
@@ -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&amp;model=digest.digest&amp;id=${object.id}" target="_blank" style="color: #875A7B;">Choose the metrics you care about</a>
+1 -1
View File
@@ -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"/>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

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,
+7
View File
@@ -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"/>
+11
View File
@@ -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 () {
+21 -15
View File
@@ -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,
+67
View File
@@ -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
+8 -5
View File
@@ -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
+4 -1
View File
@@ -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
-1
View File
@@ -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',
+78 -68
View File
@@ -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.')
]
]
+12 -7
View File
@@ -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()
+20 -12
View File
@@ -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
+1 -1
View File
@@ -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,
+5 -4
View File
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
10 access_hr_payslip_run hr.payslip.run model_hr_payslip_run hr_payroll.group_hr_payroll_manager 1 1 1 1
11 access_hr_salary_rule_user hr.salary.rule.user model_hr_salary_rule hr_payroll.group_hr_payroll_user 1 1 1 1
12 access_hr_benefit access_hr_benefit model_hr_benefit group_hr_payroll_user 1 1 1 1
13 access_hr_benefit_type access_hr_benefit_type_officer access_hr_benefit_type access_hr_benefit_type_officer model_hr_benefit_type group_hr_payroll_user 1 1 0 1 0 1 0
14 access_hr_benefit_employee hr.benefit.employee model_hr_user_benefit_employee group_hr_payroll_user 1 1 1 1
15 access_hr_benefit_type_manager access_hr_benefit_type_manager model_hr_benefit_type group_hr_payroll_manager 1 1 1 1
+6 -8
View File
@@ -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();
});
+1
View File
@@ -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
+82 -62
View File
@@ -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
+53
View File
@@ -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")
+2 -3
View File
@@ -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>
+4 -4
View File
@@ -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
+137 -334
View File
@@ -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 ""
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -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
+22
View File
@@ -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>
+8
View File
@@ -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
+21
View File
@@ -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
+358
View File
@@ -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>"
+110
View File
@@ -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.")
+28
View File
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_it_ddt_manager it_ddt manager model_l10n_it_ddt account.group_account_invoice 1 1 1 1
+2
View File
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import test_generate
+32
View File
@@ -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)
+265
View File
@@ -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>
+4
View File
@@ -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>
+34
View File
@@ -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>
+3 -1
View File
@@ -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>
+30 -15
View File
@@ -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):
+1 -1
View File
@@ -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
+3 -1
View File
@@ -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')
+7 -2
View File
@@ -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');
});
}

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