diff --git a/.weblate.json b/.weblate.json index c01b2738cbf05e..a54383c192a604 100644 --- a/.weblate.json +++ b/.weblate.json @@ -2647,6 +2647,12 @@ "new_base": "addons/l10n_lb_account/i18n/l10n_lb_account.pot", "language_regex": "^(ar|fr)$" }, + { + "name": "l10n_lk_invoice", + "filemask": "addons/l10n_lk_invoice/i18n/*.po", + "new_base": "addons/l10n_lk_invoice/i18n/l10n_lk_invoice.pot", + "language_regex": "^(si|ta)$" + }, { "name": "l10n_lt", "filemask": "addons/l10n_lt/i18n/*.po", diff --git a/addons/account/i18n/fa.po b/addons/account/i18n/fa.po index 4c8e184d175945..ddca239a4a21c1 100644 --- a/addons/account/i18n/fa.po +++ b/addons/account/i18n/fa.po @@ -40,8 +40,8 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-17 17:37+0000\n" -"PO-Revision-Date: 2026-08-09 10:15+0000\n" -"Last-Translator: سید سعید محمودزاده حسینی \n" +"PO-Revision-Date: 2026-08-23 06:09+0000\n" +"Last-Translator: Heshmatkhah \n" "Language-Team: Persian \n" "Language: fa\n" @@ -1911,8 +1911,8 @@ msgstr "" " \n" " \n" -" \"سازمان‌یار\"\n" " \n" " \n" @@ -12180,11 +12180,11 @@ msgid "" "U.S. ISO20022: Pay in the US by submitting an ISO20022 file to your bank. " "Module account_iso20022 is necessary.\n" msgstr "" -"دستی: پرداخت یا دریافت وجه با هر روشی خارج از سازمان‌یار.\n" +"دستی: پرداخت یا دریافت وجه با هر روشی خارج از اودو.\n" "ارائه‌دهندگان پرداخت: هر ارائه‌دهنده پرداخت، روش پرداخت مخصوص به خود را دارد. " "با استفاده از توکنی که طرف همکار هنگام خرید یا اشتراک آنلاین ذخیره می‌کند، " "درخواست تراکنش روی/به یک کارت ارسال می‌شود.\n" -"چک: پرداخت صورت‌حساب‌ها با چک و چاپ آن از سازمان‌یار.\n" +"چک: پرداخت صورت‌حساب‌ها با چک و چاپ آن از اودو.\n" "واریز دسته‌ای: جمع‌آوری چند چک مشتری به صورت یکجا، تولید و ارسال واریز دسته‌ای " "به بانک. ماژول account_batch_payment ضروری است.\n" "انتقال اعتباری SEPA: پرداخت در منطقه SEPA با ارسال فایل انتقال اعتباری SEPA " @@ -12246,7 +12246,7 @@ msgstr "همگام سازی کدهای حساب برای هر شرکت" #. module: account #: model_terms:ir.ui.view,arch_db:account.report_statement msgid "Marc Demo" -msgstr "دموی مارچ" +msgstr "ماهان دمو‌پور" #. module: account #: model:ir.model.fields.selection,name:account.selection__res_company__fiscalyear_last_month__3 @@ -16168,7 +16168,7 @@ msgid "" msgstr "" "برای هر فاکتور یک ایمیل جداگانه ارسال کنید.\n" "هر پسوند فایلی پذیرفته می‌شود.\n" -"فقط فایل‌های PDF و XML توسط سازمان‌یار تفسیر خواهند شد" +"فقط فایل‌های PDF و XML توسط اودو تفسیر خواهند شد" #. module: account #: model:ir.actions.act_window,name:account.account_send_payment_receipt_by_email_action diff --git a/addons/account/i18n/fr.po b/addons/account/i18n/fr.po index e33c486bc2ba65..78474b2501d46f 100644 --- a/addons/account/i18n/fr.po +++ b/addons/account/i18n/fr.po @@ -23,13 +23,14 @@ # "Malaz Siddig Elsayed Abuidris (msea)" , 2025, 2026. # Stiers Nathan , 2025, 2026. # Grace Conlon , 2026. +# "Cécile Collart (cco)" , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-17 17:37+0000\n" -"PO-Revision-Date: 2026-08-03 23:38+0000\n" -"Last-Translator: Grace Conlon \n" +"PO-Revision-Date: 2026-08-17 09:58+0000\n" +"Last-Translator: \"Cécile Collart (cco)\" \n" "Language-Team: French \n" "Language: fr\n" @@ -19692,7 +19693,7 @@ msgstr "Pour annuler une écriture, celle-ci doit d’abord être comptabilisée #. odoo-javascript #: code:addons/account/static/src/components/char_with_placeholder_field_to_check/char_with_placeholder_field.xml:0 msgid "To review" -msgstr "À réviser" +msgstr "À vérifier" #. module: account #: model_terms:ir.ui.view,arch_db:account.view_partner_bank_search_inherit diff --git a/addons/account/i18n/ja.po b/addons/account/i18n/ja.po index df88b0129de1dc..4834b87f50fc44 100644 --- a/addons/account/i18n/ja.po +++ b/addons/account/i18n/ja.po @@ -21,7 +21,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-17 17:37+0000\n" -"PO-Revision-Date: 2026-08-14 08:06+0000\n" +"PO-Revision-Date: 2026-08-24 03:12+0000\n" "Last-Translator: \"Junko Augias (juau)\" \n" "Language-Team: Japanese \n" @@ -188,7 +188,7 @@ msgstr "%(label)s : %(currency_amount)s" #: code:addons/account/models/account_move.py:0 #, fuzzy msgid "%(label)s at %(currency_amount)s (Draft)" -msgstr "%(label)s : %(currency_amount)s (ドラフト)" +msgstr "%(label)s : %(currency_amount)s (下書き)" #. module: account #. odoo-python @@ -1379,8 +1379,7 @@ msgstr "中リスク: Iban" msgid "" "Draft" msgstr "" -"ドラフト" +"下書き" #. module: account #: model_terms:ir.ui.view,arch_db:account.autopost_bills_wizard @@ -1531,7 +1530,7 @@ msgstr "値引率%" #. module: account #: model_terms:ir.ui.view,arch_db:account.view_account_payment_form msgid "Draft" -msgstr "ドラフト" +msgstr "下書き" #. module: account #: model_terms:ir.ui.view,arch_db:account.report_payment_receipt_document @@ -3131,8 +3130,8 @@ msgid "" "Account: Post draft entries with auto_post enabled and accounting date up to " "today" msgstr "" -"勘定科目: auto_postを有効にし、記帳日を「今日まで」と設定して、仕訳ドラフトを" -"記入します" +"勘定科目: auto_postを有効にし、記帳日を「今日まで」と設定して、仕訳下書きを記" +"入します" #. module: account #: model:account.account,name:account.2_account_account_us_accounting @@ -7976,26 +7975,26 @@ 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 "Draft" -msgstr "ドラフト" +msgstr "下書き" #. module: account #. odoo-python #: code:addons/account/models/account_move.py:0 msgid "Draft (%(currency_amount)s)" -msgstr "ドラフト (%(currency_amount)s)" +msgstr "下書き (%(currency_amount)s)" #. module: account #. odoo-python #: code:addons/account/models/account_move.py:0 msgid "Draft Bill" -msgstr "ドラフト仕入先請求書" +msgstr "下書き仕入先請求書" #. module: account #. odoo-python #: code:addons/account/models/account_move.py:0 #: model_terms:ir.ui.view,arch_db:account.report_invoice_document msgid "Draft Credit Note" -msgstr "クレジットノート ドラフト" +msgstr "クレジットノート 下書き" #. module: account #. odoo-python @@ -8003,14 +8002,14 @@ msgstr "クレジットノート ドラフト" #: code:addons/account/wizard/account_secure_entries_wizard.py:0 #: model:ir.model.fields,field_description:account.field_account_report__filter_show_draft msgid "Draft Entries" -msgstr "ドラフト仕訳" +msgstr "下書き仕訳" #. module: account #. odoo-python #: code:addons/account/models/account_move.py:0 #: code:addons/account/models/account_move_line.py:0 msgid "Draft Entry" -msgstr "ドラフト仕訳" +msgstr "下書き仕訳" #. module: account #. odoo-python @@ -8029,35 +8028,35 @@ msgstr "請求書(下書き)" #. odoo-python #: code:addons/account/models/account_payment.py:0 msgid "Draft Payment" -msgstr "ドラフトの支払" +msgstr "下書きの支払" #. module: account #: model_terms:ir.ui.view,arch_db:account.report_invoice_document msgid "Draft Proforma Credit Note" -msgstr "ドラフト仮クレジットノート" +msgstr "下書き仮クレジットノート" #. module: account #: model_terms:ir.ui.view,arch_db:account.report_invoice_document msgid "Draft Proforma Invoice" -msgstr "ドラフト仮請求書" +msgstr "下書き仮請求書" #. module: account #. odoo-python #: code:addons/account/models/account_move.py:0 msgid "Draft Purchase Receipt" -msgstr "仕入先領収書(ドラフト)" +msgstr "仕入先領収書(下書き)" #. module: account #. odoo-python #: code:addons/account/models/account_move.py:0 msgid "Draft Sales Receipt" -msgstr "売上領収書(ドラフト)" +msgstr "売上領収書(下書き)" #. module: account #. odoo-python #: code:addons/account/models/account_move.py:0 msgid "Draft Vendor Credit Note" -msgstr "ドラフト仕入先クレジットノート" +msgstr "下書き仕入先クレジットノート" #. module: account #. odoo-javascript @@ -11049,8 +11048,8 @@ msgid "" "Irregularities due to draft, cancelled or deleted bills with a sequence " "number since last lock date." msgstr "" -"最終ロック日以降のシーケンス番号の仕入先請求書のキャンセルまたは削除された" -"ドラフトによる不正。" +"最終ロック日以降のシーケンス番号の仕入先請求書のキャンセルまたは削除された下" +"書きによる不正。" #. module: account #. odoo-python @@ -11059,8 +11058,8 @@ msgid "" "Irregularities due to draft, cancelled or deleted invoices with a sequence " "number since last lock date." msgstr "" -"最終ロック日以降のシーケンス番号の顧客請求書の、キャンセルまたは削除された" -"ドラフトによる不正。" +"最終ロック日以降のシーケンス番号の顧客請求書の、キャンセルまたは削除された下" +"書きによる不正。" #. module: account #: model:ir.model.fields,field_description:account.field_account_bank_statement_line__is_being_sent @@ -11159,7 +11158,7 @@ msgstr "返金です" #. module: account #: model:ir.model.fields,field_description:account.field_account_payment_register__is_register_payment_on_draft msgid "Is Register Payment On Draft" -msgstr "ドラフトでの支払登録か" +msgstr "下書きでの支払登録か" #. module: account #: model:ir.model.fields,field_description:account.field_account_bank_statement_line__is_sale_installed @@ -13318,7 +13317,7 @@ msgstr "管理者のみがチャートのテンプレートをインストール #. odoo-python #: code:addons/account/models/account_move.py:0 msgid "Only draft journal entries can be cancelled." -msgstr "ドラフトの記帳仕訳のみキャンセルできます。" +msgstr "下書きの記帳仕訳のみキャンセルできます。" #. module: account #. odoo-python @@ -13336,7 +13335,7 @@ msgstr "請求ごとではなく、取引先(銀行)ごとに支払が作成 #. odoo-python #: code:addons/account/models/account_move.py:0 msgid "Only posted/cancelled journal entries can be reset to draft." -msgstr "記帳済/キャンセル済の記帳のみドラフトにリセットできます。" +msgstr "記帳済/キャンセル済の記帳のみ下書きにリセットできます。" #. module: account #. odoo-python @@ -15346,7 +15345,7 @@ msgstr "請求書の日付に基づいて為替レートをデフォルトにリ #: model_terms:ir.ui.view,arch_db:account.view_account_payment_form #: model_terms:ir.ui.view,arch_db:account.view_move_form msgid "Reset to Draft" -msgstr "ドラフトにリセット" +msgstr "下書きにリセット" #. module: account #: model:ir.model.fields,field_description:account.field_account_move_reversal__residual @@ -16492,7 +16491,7 @@ msgstr "価格を表示" #: model:ir.model.fields,field_description:account.field_account_bank_statement_line__show_reset_to_draft_button #: model:ir.model.fields,field_description:account.field_account_move__show_reset_to_draft_button msgid "Show Reset To Draft Button" -msgstr "ドラフトボタンにリセットを表示" +msgstr "下書きボタンにリセットを表示" #. module: account #: model:ir.model.fields,field_description:account.field_account_bank_statement_line__show_taxable_supply_date @@ -16527,7 +16526,7 @@ msgstr "次回活動日が過去のすべてのレコードを表示" #. odoo-python #: code:addons/account/models/company.py:0 msgid "Show draft entries" -msgstr "ドラフト仕訳を表示" +msgstr "下書き仕訳を表示" #. module: account #: model_terms:ir.ui.view,arch_db:account.view_account_tax_search @@ -18071,7 +18070,7 @@ msgstr "請求書を生成した文書。" #. odoo-python #: code:addons/account/models/account_move.py:0 msgid "The entry %(name)s (id %(id)s) must be in draft." -msgstr "仕訳%(name)s (id %(id)s) はドラフトである必要があります。" +msgstr "仕訳%(name)s (id %(id)s) は下書きである必要があります。" #. module: account #. odoo-python @@ -18673,7 +18672,7 @@ msgstr "" #: code:addons/account/models/account_move.py:0 #: code:addons/account/wizard/account_validate_account_move.py:0 msgid "There are no journal items in the draft state to post." -msgstr "記入できるドラフトステータスの仕訳明細はありません。" +msgstr "記入できる下書きステータスの仕訳明細はありません。" #. module: account #. odoo-python @@ -18685,7 +18684,7 @@ msgstr "支払手続き中です。二重払いにならないよう注意して #. odoo-python #: code:addons/account/wizard/account_secure_entries_wizard.py:0 msgid "There are still draft entries before the selected date." -msgstr "選択した日付より前のドラフト仕訳がまだ残っています。" +msgstr "選択した日付より前の下書き仕訳がまだ残っています。" #. module: account #. odoo-python @@ -18694,8 +18693,8 @@ msgid "" "There are still draft entries in the period you want to hard lock. You " "should either post or delete them." msgstr "" -"確定ロックしたい期間に、まだドラフト仕訳が残っています。記帳するか、削除して" -"下さい。" +"確定ロックしたい期間に、まだ下書き仕訳が残っています。記帳するか、削除して下" +"さい。" #. module: account #. odoo-python @@ -20120,7 +20119,7 @@ msgstr "" #. module: account #: model:ir.model.fields,field_description:account.field_account_partial_reconcile__draft_caba_move_vals msgid "Values that created the draft cash-basis entry" -msgstr "ドラフトの現金主義仕訳を作成した値" +msgstr "下書きの現金主義仕訳を作成した値" #. module: account #: model:ir.model.fields,field_description:account.field_account_report__variant_report_ids @@ -20250,7 +20249,7 @@ msgid "" msgstr "" "仕入先請求書は、購買オーダまたは入荷伝票に\n" " 基づいて事前に作成することができます。これにより、\n" -" Odoo内のドラフトドキュメントに従って\n" +" Odoo内の下書きドキュメントに従って\n" "仕入先から受け取る請求書を、\n" " 管理することができます。" @@ -20521,12 +20520,12 @@ msgid "" "2/ then filter on 'Draft' entries\n" "3/ select them all and post or delete them through the action menu" msgstr "" -"ドラフトの仕訳を含む仕訳をアーカイブすることはできません。\n" +"下書きの仕訳を含む仕訳をアーカイブすることはできません。\n" "\n" "続けるには:\n" "1 /この仕訳帳フォームの右上のボタン「仕訳」をクリックします\n" -"2 /次に「ドラフト」仕訳でフィルターしてください\n" -"3 /ドラフトの仕訳をすべて選択し、アクションメニューから投稿または削除します" +"2 /次に「下書き」仕訳でフィルターしてください\n" +"3 /下書きの仕訳をすべて選択し、アクションメニューから投稿または削除します" #. module: account #. odoo-python @@ -20665,8 +20664,8 @@ msgid "" "You can't delete a posted journal item. Don’t play games with your " "accounting records; reset the journal entry to draft before deleting it." msgstr "" -"計上した仕訳明細を削除することはできません。仕訳をドラフトに戻してから削除し" -"て下さい。" +"計上した仕訳明細を削除することはできません。仕訳を下書きに戻してから削除して" +"下さい。" #. module: account #. odoo-python @@ -20733,8 +20732,8 @@ msgid "" "You can't reset to draft those journal entries. You need to request a " "cancellation instead." msgstr "" -"仕訳をドラフトにリセットすることはできません。キャンセルを申請する必要があり" -"ます。" +"仕訳を下書きにリセットすることはできません。キャンセルを申請する必要がありま" +"す。" #. module: account #. odoo-python @@ -20806,8 +20805,8 @@ msgid "" "You cannot create a move already in the posted state. Please create a draft " "move and post it after." msgstr "" -"投稿済ステータスの移動を作成することはできません。ドラフトを作成して、後で投" -"稿してください。" +"投稿済ステータスの移動を作成することはできません。下書きを作成して、後で投稿" +"してください。" #. module: account #. odoo-python @@ -20970,8 +20969,8 @@ msgid "" "modify the opening balance of your accounts, reset the move to draft." msgstr "" "開始仕訳(%s)がすでに計上されている場合は、 \"openning_balance\"をインポートす" -"ることはできません。 どうしても勘定科目開始残高を変更したい場合は、仕訳を" -"ドラフトにリセットして下さい。" +"ることはできません。 どうしても勘定科目開始残高を変更したい場合は、仕訳を下書" +"きにリセットして下さい。" #. module: account #. odoo-python @@ -21009,7 +21008,7 @@ msgid "" "reset the journal entry to draft to do so." msgstr "" "転記された仕訳に関連する税金を変更することはできません。変更するには、仕訳を" -"ドラフトにリセットする必要があります。" +"下書きにリセットする必要があります。" #. module: account #. odoo-python @@ -21094,19 +21093,19 @@ msgstr "" #. odoo-python #: code:addons/account/models/account_move.py:0 msgid "You cannot reset to draft a locked journal entry." -msgstr "ロック済の仕訳をドラフトに戻すことはできません。" +msgstr "ロック済の仕訳を下書きに戻すことはできません。" #. module: account #. odoo-python #: code:addons/account/models/account_move.py:0 msgid "You cannot reset to draft a tax cash basis journal entry." -msgstr "現金基準税仕訳帳をドラフトに戻すことはできません。" +msgstr "現金基準税仕訳帳を下書きに戻すことはできません。" #. module: account #. odoo-python #: code:addons/account/models/account_move.py:0 msgid "You cannot reset to draft an exchange difference journal entry." -msgstr "為替差仕訳をドラフトに戻すことはできません。" +msgstr "為替差仕訳を下書きに戻すことはできません。" #. module: account #. odoo-python diff --git a/addons/account/i18n/nl.po b/addons/account/i18n/nl.po index ac7fc2d7564b78..38f38ec857177a 100644 --- a/addons/account/i18n/nl.po +++ b/addons/account/i18n/nl.po @@ -19,7 +19,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-17 17:37+0000\n" -"PO-Revision-Date: 2026-08-14 07:18+0000\n" +"PO-Revision-Date: 2026-08-24 07:57+0000\n" "Last-Translator: Bren Driesen \n" "Language-Team: Dutch \n" @@ -3507,7 +3507,7 @@ msgstr "Voeg een notitie toe" #. module: account #: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form msgid "Add a payment QR-code to your invoices" -msgstr "Voeg een betaal QR-code toe aan je facturen" +msgstr "Voeg een QR-code voor betaling toe aan je facturen" #. module: account #: model:ir.model.fields.selection,name:account.selection__account_cash_rounding__strategy__add_invoice_line @@ -3517,7 +3517,7 @@ msgstr "Voeg een afrondingsregel toe" #. module: account #: model_terms:ir.ui.view,arch_db:account.view_move_form msgid "Add a section" -msgstr "Voeg een sectie toe" +msgstr "Sectie toevoegen" #. module: account #. odoo-javascript @@ -3804,7 +3804,7 @@ msgstr "Sta beheer kassa afronding toe" #. module: account #: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form msgid "Allow to configure taxes using cash basis" -msgstr "Sta toe om btw's te configureren op kasstelsel methode" +msgstr "Sta toe om btw's te configureren met de kasstelsel-methode" #. module: account #: model:ir.model.fields,field_description:account.field_account_move_line__allowed_uom_ids @@ -7906,7 +7906,7 @@ msgstr "Toon QR-code" #: model:ir.model.fields,field_description:account.field_base_document_layout__qr_code #: model:ir.model.fields,field_description:account.field_res_company__qr_code msgid "Display QR-code on invoices" -msgstr "Voeg een betaal QR-code toe aan je facturen" +msgstr "Voeg een QR-code voor betaling toe aan je facturen" #. module: account #: model:ir.model.fields,field_description:account.field_res_currency__display_rounding_warning @@ -7943,7 +7943,7 @@ msgstr "Voorbeeldknop weergeven" #. module: account #: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form msgid "Display the total amount of an invoice in letters" -msgstr "Toon het totaal bedrag van een factuur voluit" +msgstr "Toon het totaalbedrag van een factuur in letters" #. module: account #: model:account.account,name:account.2_account_account_us_disposal_fees @@ -14055,7 +14055,7 @@ msgstr "Kredietwaarschuwing partner" #. module: account #: model:ir.actions.act_window,name:account.action_account_moves_ledger_partner msgid "Partner Ledger" -msgstr "Grootboek partner" +msgstr "Saldilijsten" #. module: account #: model:ir.model.fields,field_description:account.field_res_partner__use_partner_credit_limit @@ -14307,7 +14307,7 @@ msgstr "Details betalingstermijn" #: model_terms:ir.ui.view,arch_db:account.view_payment_term_search #: model_terms:ir.ui.view,arch_db:account.view_payment_term_tree msgid "Payment Terms" -msgstr "Betaalvoorwaarden" +msgstr "Betalingsvoorwaarden" #. module: account #: model:ir.model,name:account.model_account_payment_term_line @@ -14325,7 +14325,7 @@ msgstr "Betaalwijze" #. module: account #: model_terms:ir.ui.view,arch_db:account.view_move_form msgid "Payment terms" -msgstr "Betaalvoorwaarden" +msgstr "Betalingsvoorwaarden" #. module: account #: model_terms:account.payment.term,note:account.account_payment_term_30_days_end_month_the_10 @@ -15093,7 +15093,7 @@ msgstr "QR-code-URL" #. module: account #: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form msgid "QR Codes" -msgstr "QR codes" +msgstr "QR-codes" #. module: account #: model:ir.model.fields,field_description:account.field_account_move_line__quantity @@ -15206,7 +15206,7 @@ msgstr "Ontvang facturen" #: model:ir.model.fields,field_description:account.field_account_bank_statement_line__partner_bank_id #: model:ir.model.fields,field_description:account.field_account_move__partner_bank_id msgid "Recipient Bank" -msgstr "Ontvangende bank" +msgstr "Bankrekening begunstigde" #. module: account #: model:ir.model.fields,field_description:account.field_account_payment__partner_bank_id @@ -16245,8 +16245,8 @@ msgid "" "Select this if the taxes should use cash basis, which will create an entry " "for such taxes on a given account during reconciliation." msgstr "" -"Selecteer dit als de btw-kasstelsel moet gebruiken. Dit maakt een boeking " -"aan bij het afletteren." +"Selecteer dit als btw het kasstelsel moet gebruiken. Dit maakt een boeking " +"voor die belastingen aan bij het afletteren." #. module: account #: model:ir.model.fields,field_description:account.field_account_journal__selected_payment_method_codes @@ -17437,7 +17437,7 @@ msgstr "Btw landcode" #: model:ir.model.fields.selection,name:account.selection__res_company__account_price_include__tax_excluded #: model_terms:ir.ui.view,arch_db:account.view_invoice_tree msgid "Tax Excluded" -msgstr "Exclusief BTW" +msgstr "Exclusief btw" #. module: account #: model:ir.model.fields,field_description:account.field_account_tax__tax_exigibility @@ -19709,7 +19709,7 @@ msgstr "Totaal bedrag voluit:
" #: model:ir.model.fields,field_description:account.field_res_company__display_invoice_amount_total_words #: model:ir.model.fields,field_description:account.field_res_config_settings__display_invoice_amount_total_words msgid "Total amount of invoice in letters" -msgstr "Totaal bedrag van de factuur voluit" +msgstr "Totaalbedrag van factuur voluit schrijven" #. module: account #: model:ir.model.fields,help:account.field_res_partner__credit diff --git a/addons/account/i18n/sv.po b/addons/account/i18n/sv.po index ce5c6a3b080ef0..a9e5c8dcef609f 100644 --- a/addons/account/i18n/sv.po +++ b/addons/account/i18n/sv.po @@ -42,7 +42,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-17 17:37+0000\n" -"PO-Revision-Date: 2026-08-13 13:46+0000\n" +"PO-Revision-Date: 2026-08-24 07:58+0000\n" "Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" @@ -3088,7 +3088,7 @@ msgstr "Guide för att sammanfoga konton" #. module: account #: model:ir.model.fields,field_description:account.field_account_fiscal_position_account__account_src_id msgid "Account on Product" -msgstr "Konto angett för produkten" +msgstr "Konto på produkten" #. module: account #: model:ir.model.fields,help:account.field_account_tax_repartition_line__account_id @@ -4122,7 +4122,7 @@ msgstr "Analytisk precision" #. module: account #: model:ir.ui.menu,name:account.menu_action_analytic_reporting msgid "Analytic Report" -msgstr "Analytisk rapport" +msgstr "Analytiska rapporter" #. module: account #: model:ir.actions.act_window,name:account.action_analytic_reporting @@ -4233,7 +4233,7 @@ msgstr "" #. module: account #: model:ir.model.fields,field_description:account.field_account_account_tag__applicability msgid "Applicability" -msgstr "Tillämplighet" +msgstr "Användningsområde" #. module: account #: model_terms:ir.ui.view,arch_db:account.res_company_form_view_onboarding_sale_tax @@ -4700,7 +4700,7 @@ msgstr "Bankkontonamn" #: model_terms:ir.ui.view,arch_db:account.view_account_journal_form #: model_terms:ir.ui.view,arch_db:account.view_base_document_layout msgid "Bank Account Number" -msgstr "Bankkontonummer" +msgstr "Kontonummer" #. module: account #: model:ir.model.fields,help:account.field_account_bank_statement_line__partner_bank_id @@ -4728,7 +4728,7 @@ msgstr "Bankkonton" #. module: account #: model:ir.model.fields,field_description:account.field_account_journal__bank_statements_source msgid "Bank Feeds" -msgstr "Bankanslutningar" +msgstr "Kontoutdrag" #. module: account #. odoo-python @@ -5097,7 +5097,7 @@ msgstr "Fakturor att ta emot" #: model:ir.model.fields,field_description:account.field_account_report_column__blank_if_zero #: model:ir.model.fields,field_description:account.field_account_report_expression__blank_if_zero msgid "Blank if Zero" -msgstr "Lämnas tom för att anges som Noll" +msgstr "Dölj vid nollvärde" #. module: account #: model:ir.model.fields.selection,name:account.selection__account_invoice_report__payment_state__blocked @@ -6330,7 +6330,7 @@ msgstr "Landskod" #: model:ir.model,name:account.model_res_country_group #: model:ir.model.fields,field_description:account.field_account_fiscal_position__country_group_id msgid "Country Group" -msgstr "Landsgrupp" +msgstr "Landsgrupper" #. module: account #: model:ir.model.fields.selection,name:account.selection__account_report__availability_condition__country @@ -7586,7 +7586,7 @@ msgstr "Mottagarens konto" #. module: account #: model:ir.model.fields,field_description:account.field_account_fiscal_position__auto_apply msgid "Detect Automatically" -msgstr "Upptäck automatiskt" +msgstr "Identifiera automatiskt" #. module: account #: model:ir.model.fields,help:account.field_account_tax__type_tax_use @@ -8850,7 +8850,7 @@ msgstr "Exportera som ZIP-fil" #. module: account #: model:ir.model.fields,field_description:account.field_account_report_column__expression_label msgid "Expression Label" -msgstr "Uttrycksetikett" +msgstr "Etikett" #. module: account #: model:ir.model.fields,field_description:account.field_account_report_line__expression_ids @@ -8965,7 +8965,7 @@ msgstr "Federal inkomstskatt" #. module: account #: model:ir.model.fields,field_description:account.field_account_fiscal_position__state_ids msgid "Federal States" -msgstr "Regioner och län" +msgstr "Regioner" #. module: account #. odoo-javascript @@ -8987,7 +8987,7 @@ msgstr "Fältvärde" #: model:ir.model.fields,field_description:account.field_account_report_column__figure_type #: model:ir.model.fields,field_description:account.field_account_report_expression__figure_type msgid "Figure Type" -msgstr "Figurtyp" +msgstr "Typ" #. module: account #. odoo-javascript @@ -9350,7 +9350,7 @@ msgstr "Förlust på valutaväxling" #. module: account #: model:ir.model.fields,field_description:account.field_account_fiscal_position__foreign_vat msgid "Foreign Tax ID" -msgstr "Utländskt skatteregistreringsnummer" +msgstr "Utländskt skattenummer" #. module: account #: model:account.fiscal.position,name:account.1_template_generic_export_fiscal_position @@ -10520,7 +10520,7 @@ msgstr "Inkomstskatter som ska betalas till den federala regeringen i USA" #. module: account #: model_terms:ir.ui.view,arch_db:account.view_account_journal_form msgid "Incoming Payments" -msgstr "Inkommande betalningar" +msgstr "Inbetalningar" #. module: account #. odoo-python @@ -10845,7 +10845,7 @@ msgstr "Faktura #" #. module: account #: model:ir.ui.menu,name:account.menu_action_account_invoice_report_all msgid "Invoice Analysis" -msgstr "Analyser av fakturor" +msgstr "Fakturering" #. module: account #: model:ir.model.fields,field_description:account.field_account_analytic_account__invoice_count @@ -11122,7 +11122,7 @@ msgstr "Fakturor och leverantörsfakturor" #: model_terms:ir.ui.view,arch_db:account.view_account_invoice_report_pivot #: model_terms:ir.ui.view,arch_db:account.view_account_invoice_report_search msgid "Invoices Analysis" -msgstr "Fakturaanalyser" +msgstr "Fakturering" #. module: account #: model:ir.model,name:account.model_account_invoice_report @@ -11640,7 +11640,7 @@ msgstr "Bokföringsjournal:" #: model:ir.ui.menu,name:account.menu_action_account_journal_form #: model_terms:ir.ui.view,arch_db:account.view_account_move_line_filter msgid "Journals" -msgstr "Bokföringsjournaler" +msgstr "Journaler" #. module: account #: model_terms:ir.ui.view,arch_db:account.view_account_reconcile_model_search @@ -11951,7 +11951,7 @@ msgstr "Juridisk" #. module: account #: model:ir.model.fields,field_description:account.field_account_tax__invoice_legal_notes msgid "Legal Notes" -msgstr "Juridiska noteringar" +msgstr "Juridisk information" #. module: account #: model_terms:ir.ui.view,arch_db:account.view_account_position_form @@ -12081,7 +12081,7 @@ msgstr "" #. module: account #: model:ir.model.fields,field_description:account.field_account_report__load_more_limit msgid "Load More Limit" -msgstr "Gräns för att ladda mer" +msgstr "Laddningsgräns" #. module: account #: model:account.account,name:account.2_account_account_us_loan_interest_expense @@ -12232,7 +12232,7 @@ msgstr "Skapa periodiseringsposter" #. module: account #: model:ir.ui.menu,name:account.account_reports_management_menu msgid "Management" -msgstr "Förvaltning" +msgstr "Analyser" #. module: account #: model:ir.model.fields.selection,name:account.selection__account_reconcile_model__trigger__manual @@ -12756,7 +12756,7 @@ msgstr "Flera företag" #. module: account #: model:ir.ui.menu,name:account.menu_action_account_journal_group_list msgid "Multi-Ledger" -msgstr "Flera huvudböcker" +msgstr "Reskontragrupper" #. module: account #: model:ir.actions.act_window,name:account.action_account_journal_group_list @@ -13805,7 +13805,7 @@ msgstr "Betalningsmetoder för utgående betalningar" #. module: account #: model_terms:ir.ui.view,arch_db:account.view_account_journal_form msgid "Outgoing Payments" -msgstr "Utgående betalningar" +msgstr "Utbetalningar" #. module: account #: model:ir.model.fields,field_description:account.field_account_payment__outstanding_account_id @@ -13823,7 +13823,7 @@ msgstr "Utestående betalningar" #. module: account #: model_terms:ir.ui.view,arch_db:account.view_account_journal_form msgid "Outstanding Payments accounts" -msgstr "Konton för utestående betalningar" +msgstr "Konto för pågående utbetalningar" #. module: account #. odoo-python @@ -13836,7 +13836,7 @@ msgstr "Utestående betalningar för inleveranser" #. module: account #: model_terms:ir.ui.view,arch_db:account.view_account_journal_form msgid "Outstanding Receipts accounts" -msgstr "Konton för pågående inkommande betalningar" +msgstr "Konto för pågående inbetalningar" #. module: account #. odoo-python @@ -14080,7 +14080,7 @@ msgstr "Partnerns namn" #. module: account #: model:ir.ui.menu,name:account.account_reports_partners_reports_menu msgid "Partner Reports" -msgstr "Partnerrapporter" +msgstr "Reskontror" #. module: account #: model:ir.model.fields,field_description:account.field_account_payment__partner_type @@ -15806,7 +15806,7 @@ msgstr "Rot" #. module: account #: model:ir.model.fields,field_description:account.field_account_report__root_report_id msgid "Root Report" -msgstr "Ursprunglig rapport" +msgstr "Ursprungsrapport" #. module: account #: model:ir.model.fields.selection,name:account.selection__res_company__tax_calculation_rounding_method__round_per_line @@ -16503,7 +16503,7 @@ msgstr "Sekvens som går före regex" #: model:ir.model.fields,field_description:account.field_sequence_mixin__sequence_prefix #: model_terms:ir.ui.view,arch_db:account.view_account_invoice_filter msgid "Sequence Prefix" -msgstr "Sekvensprefix" +msgstr "Prefix" #. module: account #: model:ir.model.fields.selection,name:account.selection__account_tax__tax_scope__service @@ -16958,7 +16958,7 @@ msgstr "Namn på kontoutdrag" #. module: account #: model:ir.ui.menu,name:account.account_reports_legal_statements_menu msgid "Statement Reports" -msgstr "Rapporter om kontoutdrag" +msgstr "Resultatrapporter" #. module: account #. odoo-python @@ -17157,7 +17157,7 @@ msgstr "Växla till faktura/kreditfaktura" #. module: account #: model:ir.model.fields,field_description:account.field_account_account_tag__name msgid "Tag Name" -msgstr "Taggrubrik" +msgstr "Namn" #. module: account #: model:ir.model.fields,field_description:account.field_account_account__tag_ids @@ -17659,12 +17659,12 @@ msgstr "Platshållare för datum för beskattningsbar leverans" #: model_terms:ir.ui.view,arch_db:account.view_message_tree_audit_log_search #: model_terms:ir.ui.view,arch_db:account.view_move_line_form msgid "Taxes" -msgstr "Moms" +msgstr "Momssatser" #. module: account #: model:ir.ui.menu,name:account.account_reports_taxes_and_fiscal_menu msgid "Taxes & Fiscal" -msgstr "Skatter och finansiell information" +msgstr "Skatter och ekonomi" #. module: account #: model_terms:ir.ui.view,arch_db:account.view_move_form @@ -19926,7 +19926,7 @@ msgstr "Okategoriserad" #. odoo-python #: code:addons/account/models/account_journal.py:0 msgid "Undefined Yet" -msgstr "Ännu odefinierad" +msgstr "Ej definierat" #. module: account #: model:account.account,name:account.2_account_account_us_unemployment_insurance @@ -20722,7 +20722,8 @@ msgstr "" #. module: account #: model:ir.model.fields,help:account.field_account_report_column__blank_if_zero msgid "When checked, 0 values will not show in this column." -msgstr "Om 0-värden är markerade visas de inte i denna kolumn." +msgstr "" +"Om ikryssad, så kommer eventuella nollvärden att döljas i den här kolumnen." #. module: account #: model:ir.model.fields,help:account.field_account_report_expression__blank_if_zero diff --git a/addons/account/i18n/vi.po b/addons/account/i18n/vi.po index 19d9ee880311e0..fe77c103f04483 100644 --- a/addons/account/i18n/vi.po +++ b/addons/account/i18n/vi.po @@ -2559,7 +2559,7 @@ msgid "" "A journal entry consists of several journal items, each of\n" " which is either a debit or a credit transaction." msgstr "" -"Một bút toán chứa một số hạng mục bút toán, mỗi hạng mục\n" +"Một bút toán chứa một số phát sinh kế toán, mỗi hạng mục\n" " có thể là một giao dịch phát sinh nợ hoặc phát sinh có." #. module: account @@ -3172,7 +3172,7 @@ msgstr "Kế toán" #: model_terms:ir.ui.view,arch_db:account.view_invoice_tree #: model_terms:ir.ui.view,arch_db:account.view_move_form msgid "Accounting Date" -msgstr "Ngày hạch toán" +msgstr "Ngày kế toán" #. module: account #: model_terms:ir.ui.view,arch_db:account.res_config_settings_view_form @@ -4177,7 +4177,7 @@ msgstr "" msgid "" "Any journal item on a payable account must have a due date and vice versa." msgstr "" -"Bất kỳ hạng mục bút toán nào trên khoản phải trả đều phải có ngày đến hạn " +"Bất kỳ phát sinh kế toán nào trên khoản phải trả đều phải có ngày đến hạn " "thanh toán và ngược lại." #. module: account @@ -4186,7 +4186,7 @@ msgstr "" msgid "" "Any journal item on a receivable account must have a due date and vice versa." msgstr "" -"Bất kỳ hạng mục bút toán nào trên khoản phải thu đều phải có ngày đến hạn " +"Bất kỳ phát sinh kế toán nào trên khoản phải thu đều phải có ngày đến hạn " "thanh toán và ngược lại." #. module: account @@ -5681,7 +5681,7 @@ msgid "" "journal items." msgstr "" "Chọn ô này nếu tài khoản này cho phép đối chiếu các hoá đơn và khoản thanh " -"toán với hạng mục bút toán." +"toán với phát sinh kế toán." #. module: account #: model:ir.model.fields,help:account.field_account_journal__refund_sequence @@ -6752,7 +6752,7 @@ msgstr "Thẻ" #. module: account #: model:ir.model.fields,help:account.field_account_move_line__matched_credit_ids msgid "Credit journal items that are matched with this journal item." -msgstr "Bút toán ghi có có khớp với hạng mục bút toán này." +msgstr "Bút toán ghi có có khớp với phát sinh kế toán này." #. module: account #: model:ir.model.fields,help:account.field_res_partner__credit_limit @@ -6845,12 +6845,12 @@ msgstr "Tiền tệ phải luôn được cung cấp để tạo mã QR" #. module: account #: model:ir.model.fields,field_description:account.field_account_partial_reconcile__credit_currency_id msgid "Currency of the credit journal item." -msgstr "Tiền tệ của hạng mục bút toán ghi có." +msgstr "Tiền tệ của phát sinh kế toán ghi có." #. module: account #: model:ir.model.fields,field_description:account.field_account_partial_reconcile__debit_currency_id msgid "Currency of the debit journal item." -msgstr "Tiền tệ của hạng mục bút toán ghi nợ." +msgstr "Tiền tệ của phát sinh kế toán ghi nợ." #. module: account #: model:ir.model.fields,help:account.field_account_bank_statement_line__invoice_currency_rate @@ -7233,7 +7233,7 @@ msgstr "Bút toán ghi nợ" #. module: account #: model:ir.model.fields,help:account.field_account_move_line__matched_debit_ids msgid "Debit journal items that are matched with this journal item." -msgstr "Hạng mục bút toán ghi nợ khớp với hạng mục bút toán này." +msgstr "Phát sinh kế toán ghi nợ khớp với phát sinh kế toán này." #. module: account #: model:ir.model.fields.selection,name:account.selection__res_company__fiscalyear_last_month__12 @@ -8504,7 +8504,7 @@ msgstr "Cho phép sử dụng hạn mức tín dụng đối với đối tác." #: model:ir.model.fields,help:account.field_res_config_settings__restrictive_audit_trail msgid "Enable this option to prevent deletion of journal item related logs" msgstr "" -"Bật tùy chọn này để ngăn việc xóa các nhật ký liên quan đến hạng mục bút toán" +"Bật tùy chọn này để ngăn việc xóa các nhật ký liên quan đến phát sinh kế toán" #. module: account #: model:ir.model.fields.selection,name:account.selection__account_report__filter_hide_0_lines__by_default @@ -9307,7 +9307,7 @@ msgid "" "Forces all journal items in this account to have a specific currency (i.e. " "bank journals). If no currency is set, entries can use any currency." msgstr "" -"Buộc tất cả hạng mục bút toán trong tài khoản này phải có một loại tiền tệ " +"Buộc tất cả phát sinh kế toán trong tài khoản này phải có một loại tiền tệ " "cụ thể (ví dụ: sổ nhật ký ngân hàng). Nếu không có loại tiền tệ nào được cài " "đặt, các bút toán có thể sử dụng bất kỳ loại tiền tệ nào." @@ -10064,7 +10064,7 @@ msgid "" "If activated, user-defined filters on journal items can be selected on this " "report" msgstr "" -"Nếu được kích hoạt, những bộ lọc người dùng tạo trên các hạng mục bút toán " +"Nếu được kích hoạt, những bộ lọc người dùng tạo trên các phát sinh kế toán " "có thể được chọn trên báo cáo này" #. module: account @@ -10334,7 +10334,7 @@ msgstr "Nhập mẫu cho hóa đơn" #. odoo-python #: code:addons/account/models/account_move_line.py:0 msgid "Import Template for Journal Items" -msgstr "Nhập mẫu hạng mục bút toán" +msgstr "Nhập mẫu phát sinh kế toán" #. module: account #. odoo-python @@ -10560,7 +10560,7 @@ msgstr "" #. module: account #: model:ir.model.fields,help:account.field_account_move_line__tax_line_id msgid "Indicates that this journal item is a tax line" -msgstr "Cho biết hạng mục bút toán này là một dòng thuế" +msgstr "Cho biết phát sinh kế toán này là một dòng thuế" #. module: account #. odoo-javascript @@ -11482,30 +11482,30 @@ msgstr "Thông tin bút toán" #: model_terms:ir.ui.view,arch_db:account.view_account_move_line_payment_filter #: model_terms:ir.ui.view,arch_db:account.view_move_line_form msgid "Journal Item" -msgstr "Hạng mục bút toán" +msgstr "Phát sinh kế toán" #. module: account #. odoo-python #: code:addons/account/models/account_move_line.py:0 msgid "Journal Item %s created" -msgstr "Hạng mục bút toán %s đã được tạo" +msgstr "Phát sinh kế toán %s đã được tạo" #. module: account #. odoo-python #: code:addons/account/models/account_move_line.py:0 msgid "Journal Item %s deleted" -msgstr "Hạng mục bút toán %s đã bị xoá" +msgstr "Phát sinh kế toán %s đã bị xoá" #. module: account #. odoo-python #: code:addons/account/models/account_move_line.py:0 msgid "Journal Item %s updated" -msgstr "Hạng mục bút toán %s đã được cập nhật" +msgstr "Phát sinh kế toán %s đã được cập nhật" #. module: account #: model:ir.model.fields,field_description:account.field_account_payment_register__writeoff_label msgid "Journal Item Label" -msgstr "Nhãn hạng mục bút toán" +msgstr "Nhãn phát sinh kế toán" #. module: account #. odoo-python @@ -11523,13 +11523,13 @@ msgstr "Nhãn hạng mục bút toán" #: model_terms:ir.ui.view,arch_db:account.view_move_line_pivot #: model_terms:ir.ui.view,arch_db:account.view_move_line_tree msgid "Journal Items" -msgstr "Hạng mục bút toán" +msgstr "Phát sinh kế toán" #. module: account #: model:ir.model.fields,field_description:account.field_account_bank_statement_line__journal_line_ids #: model:ir.model.fields,field_description:account.field_account_move__journal_line_ids msgid "Journal Items (DEPRECATED)" -msgstr "Hạng mục bút toán (NGỪNG SỬ DỤNG)" +msgstr "Phát sinh kế toán (NGỪNG SỬ DỤNG)" #. module: account #: model:ir.model.fields,field_description:account.field_account_journal__name @@ -11555,12 +11555,12 @@ msgstr "Mã sổ nhật ký trong mỗi công ty phải là duy nhất." #. module: account #: model:ir.model.fields,field_description:account.field_account_payment_register__line_ids msgid "Journal items" -msgstr "Hạng mục bút toán" +msgstr "Phát sinh kế toán" #. module: account #: model_terms:ir.ui.view,arch_db:account.view_account_move_line_filter msgid "Journal items where matching number isn't set" -msgstr "Hạng mục bút toán không có số đối chiếu" +msgstr "Phát sinh kế toán không có số đối chiếu" #. module: account #: model_terms:ir.ui.view,arch_db:account.view_account_move_line_filter @@ -12387,7 +12387,7 @@ msgstr "Khoản nợ đã khớp" #: model:ir.model.fields,field_description:account.field_account_full_reconcile__reconciled_line_ids #: model_terms:ir.ui.view,arch_db:account.view_full_reconcile_form msgid "Matched Journal Items" -msgstr "Hạng mục bút toán đã khớp" +msgstr "Phát sinh kế toán đã khớp" #. module: account #: model:ir.model.fields,field_description:account.field_account_bank_statement_line__matched_payment_ids @@ -14593,7 +14593,7 @@ msgstr "Bút toán đã vào sổ" #: model_terms:ir.ui.view,arch_db:account.view_account_move_line_filter #: model_terms:ir.ui.view,arch_db:account.view_account_move_line_payment_filter msgid "Posted Journal Items" -msgstr "Hạng mục bút toán đã vào sổ" +msgstr "Phát sinh kế toán đã vào sổ" #. module: account #: model:ir.model.fields,field_description:account.field_account_tax_group__preceding_subtotal @@ -15437,7 +15437,7 @@ msgstr "" #. module: account #: model:ir.model.fields.selection,name:account.selection__account_resequence_wizard__ordering__date msgid "Reorder by accounting date" -msgstr "Sắp xếp lại theo ngày hạch toán" +msgstr "Sắp xếp lại theo ngày kế toán" #. module: account #: model:ir.model.fields,field_description:account.field_account_tax__repartition_lines_str @@ -16039,7 +16039,7 @@ msgstr "Tìm kiếm hóa đơn" #: model_terms:ir.ui.view,arch_db:account.view_account_move_line_filter #: model_terms:ir.ui.view,arch_db:account.view_account_move_line_payment_filter msgid "Search Journal Items" -msgstr "Tìm kiếm hạng mục bút toán" +msgstr "Tìm kiếm phát sinh kế toán" #. module: account #: model_terms:ir.ui.view,arch_db:account.view_account_move_filter @@ -17128,7 +17128,7 @@ msgstr "" msgid "" "Tags to be set on the base and tax journal items created for this product." msgstr "" -"Thẻ được thiết lập trên các hạng mục bút toán thuế và cơ sở được tạo cho sản " +"Thẻ được thiết lập trên các phát sinh kế toán thuế và cơ sở được tạo cho sản " "phẩm này." #. module: account @@ -17634,7 +17634,7 @@ msgid "" "item if they share some tag." msgstr "" "Không thể trộn lẫn các khoản thuế có hiệu lực trong thanh toán và hóa đơn " -"trên cùng một hạng mục bút toán nếu chúng có chung thẻ." +"trên cùng một phát sinh kế toán nếu chúng có chung thẻ." #. module: account #: model:ir.model.fields,field_description:account.field_res_company__display_invoice_tax_company_currency @@ -18468,7 +18468,7 @@ msgstr "Sổ để tải hóa đơn lên chưađược chỉ định. " #: code:addons/account/models/account_analytic_line.py:0 msgid "The journal item is not linked to the correct financial account" msgstr "" -"Hạng mục bút toán này không được liên kết với tài khoản tài chính chính xác" +"Phát sinh kế toán này không được liên kết với tài khoản tài chính chính xác" #. module: account #: model:ir.model.fields,help:account.field_account_financial_year_op__fiscalyear_last_day @@ -18551,7 +18551,7 @@ msgstr "Số tiền thanh toán không thể là số âm." #: model:ir.model.fields,help:account.field_account_bank_statement_line__payment_reference #: model:ir.model.fields,help:account.field_account_move__payment_reference msgid "The payment reference to set on journal items." -msgstr "Thông tin thanh toán hiển thị trên hạng mục bút toán." +msgstr "Thông tin thanh toán hiển thị trên phát sinh kế toán." #. module: account #: model:ir.model.fields,help:account.field_account_move_line__payment_id @@ -18654,7 +18654,7 @@ msgid "" "The residual amount on a journal item expressed in its currency (possibly " "not the company currency)." msgstr "" -"Số tiền còn lại trên hạng mục bút toán được biểu thị bằng loại tiền tệ của " +"Số tiền còn lại trên phát sinh kế toán được biểu thị bằng loại tiền tệ của " "nó (có thể không phải là tiền tệ của công ty)." #. module: account @@ -18662,7 +18662,7 @@ msgstr "" msgid "" "The residual amount on a journal item expressed in the company currency." msgstr "" -"Số tiền còn lại trên hạng mục bút toán được biểu thị bằng loại tiền tệ của " +"Số tiền còn lại trên phát sinh kế toán được biểu thị bằng loại tiền tệ của " "công ty." #. module: account @@ -18912,7 +18912,7 @@ msgstr "" #: code:addons/account/models/account_move.py:0 #: code:addons/account/wizard/account_validate_account_move.py:0 msgid "There are no journal items in the draft state to post." -msgstr "Không có hạng mục bút toán nào ở trạng thái nháp để vào sổ." +msgstr "Không có phát sinh kế toán nào ở trạng thái nháp để vào sổ." #. module: account #. odoo-python @@ -19031,7 +19031,7 @@ msgid "" "registration is necessary. You've got your finances under control like a " "boss!" msgstr "" -"Không còn khoản nào cần thanh toán cho các hạng mục bút toán đã chọn, nên " +"Không còn khoản nào cần thanh toán cho các phát sinh kế toán đã chọn, nên " "không cần ghi nhận thanh toán. Bạn đang kiểm soát nguồn tài chính của mình " "như một chuyên gia thực thụ!" @@ -19126,7 +19126,7 @@ msgstr "Chỉ có thể sử dụng trên tài khoản." #. odoo-python #: code:addons/account/wizard/account_automatic_entry_wizard.py:0 msgid "This can only be used on journal items" -msgstr "Chỉ có thể sử dụng cho hạng mục bút toán" +msgstr "Chỉ có thể sử dụng cho phát sinh kế toán" #. module: account #. odoo-python @@ -19361,7 +19361,7 @@ msgid "" "Those can be used to quickly create a journal items when reconciling\n" " a bank statement or an account." msgstr "" -"Tính năng này được dùng để tạo nhanh các hạng mục bút toán khi đối chiếu\n" +"Tính năng này được dùng để tạo nhanh các phát sinh kế toán khi đối chiếu\n" " một sao kê ngân hàng hoặc một tài khoản." #. module: account @@ -19709,7 +19709,7 @@ msgstr "Ngày luân chuyển" #. module: account #: model:ir.actions.act_window,name:account.account_automatic_entry_wizard_action msgid "Transfer Journal Items" -msgstr "Chuyển hạng mục bút toán" +msgstr "Chuyển phát sinh kế toán" #. module: account #. odoo-python @@ -19942,7 +19942,7 @@ msgstr "Bút toán chưa vào sổ" #. module: account #: model_terms:ir.ui.view,arch_db:account.view_account_move_line_filter msgid "Unposted Journal Items" -msgstr "Hạng mục bút toán chưa vào sổ" +msgstr "Phát sinh kế toán chưa vào sổ" #. module: account #. odoo-javascript @@ -20900,7 +20900,7 @@ msgid "" "You can't change the company of your tax since there are some journal items " "linked to it." msgstr "" -"Bạn không thể thay đổi công ty của thuế vì có một số hạng mục bút toán được " +"Bạn không thể thay đổi công ty của thuế vì có một số phát sinh kế toán được " "liên kết với nó." #. module: account @@ -20949,8 +20949,8 @@ msgid "" "You can't delete a posted journal item. Don’t play games with your " "accounting records; reset the journal entry to draft before deleting it." msgstr "" -"Bạn không thể xóa một hạng mục bút toán đã ghi sổ. Hãy cẩn thận với hồ sơ kế " -"toán; đặt lại hạng mục bút toán thành nháp trước khi xóa nó." +"Bạn không thể xóa một phát sinh kế toán đã ghi sổ. Hãy cẩn thận với hồ sơ kế " +"toán; đặt lại phát sinh kế toán thành nháp trước khi xóa nó." #. module: account #. odoo-python @@ -21059,7 +21059,7 @@ msgid "" "You cannot change the currency of the company since some journal items " "already exist" msgstr "" -"Bạn không thể thay đổi tiền tệ của công ty vì một số hạng mục bút toán đã " +"Bạn không thể thay đổi tiền tệ của công ty vì một số phát sinh kế toán đã " "tồn tại" #. module: account @@ -21311,7 +21311,7 @@ msgid "" "You cannot modify the taxes related to a posted journal item, you should " "reset the journal entry to draft to do so." msgstr "" -"Bạn không thể sửa đổi các loại thuế liên quan đến một hạng mục bút toán đã " +"Bạn không thể sửa đổi các loại thuế liên quan đến một phát sinh kế toán đã " "vào sổ, bạn nên cài lại bút toán về nháp rồi chỉnh sửa." #. module: account @@ -21320,7 +21320,7 @@ msgstr "" msgid "" "You cannot perform this action on an account that contains journal items." msgstr "" -"Bạn không thể thực hiện thao tác này trên một tài khoản có hạng mục bút toán." +"Bạn không thể thực hiện thao tác này trên một tài khoản có phát sinh kế toán." #. module: account #. odoo-python diff --git a/addons/account/models/account_account.py b/addons/account/models/account_account.py index 606f1197239acd..e2f92303636494 100644 --- a/addons/account/models/account_account.py +++ b/addons/account/models/account_account.py @@ -311,8 +311,9 @@ def _check_account_type_sales_purchase_journal(self): def _check_account_code(self): for account in self: if account.code and not re.match(ACCOUNT_CODE_REGEX, account.code): - raise ValidationError(_( - "The account code can only contain alphanumeric characters and dots." + raise ValidationError(self.env._( + "The account code can only contain alphanumeric characters and dots. (account code: %s)", + account.code, )) @api.constrains('account_type') @@ -744,10 +745,8 @@ def _get_most_frequent_accounts_for_partner(self, company_id, partner_id, move_t ('account_id.active', '=', True), ('date', '>=', fields.Date.add(fields.Date.today(), days=-365 * 2)), ] - if move_type in self.env['account.move'].get_inbound_types(include_receipts=True): - domain.append(('account_id.internal_group', '=', 'income')) - elif move_type in self.env['account.move'].get_outbound_types(include_receipts=True): - domain.append(('account_id.internal_group', '=', 'expense')) + if allowed_account_types := self._get_name_search_account_types(move_type): + domain.append(('account_id.account_type', 'in', allowed_account_types)) query = self.env['account.move.line']._search(domain, bypass_access=True) if not filter_never_user_accounts: diff --git a/addons/account/models/account_journal.py b/addons/account/models/account_journal.py index 9febd043f572b4..be90f7d0c84757 100644 --- a/addons/account/models/account_journal.py +++ b/addons/account/models/account_journal.py @@ -694,8 +694,8 @@ def _check_auto_post_draft_entries(self): if pending_moves: raise ValidationError(_("You can not archive a journal containing draft journal entries.\n\n" "To proceed:\n" - "1/ click on the top-right button 'Journal Entries' from this journal form\n" - "2/ then filter on 'Draft' entries\n" + "1/ go to Accounting > Accounting > Journal Entries\n" + "2/ filter on this journal and on 'Unposted' entries\n" "3/ select them all and post or delete them through the action menu")) @api.constrains('type', 'incoming_einvoice_notification_email') @@ -1059,6 +1059,38 @@ def set_bank_account(self, acc_number, bank_id=None): } ) + def _assign_outsanding_account_to_payment_method_lines(self, payment_type, payment_method_codes=None, chart_template=None): + """ Link bank journal payment method lines to their corresponding outstanding account for the specified chart template. + + :param payment_type: Payment direction, either ``'inbound'`` or ``'outbound'``. + :param payment_method_codes: Optional list of payment method codes used to restrict + the payment method lines that are updated. + :param chart_template: Chart template used to select the relevant bank journals + and determine the outstanding account. + """ + bank_journals = self.filtered(lambda j: j.type == "bank" and j.company_id.chart_template == chart_template) + if not bank_journals: + return + + lines_to_update = bank_journals[f"{payment_type}_payment_method_line_ids"] + if payment_method_codes: + lines_to_update = lines_to_update.filtered( + lambda l: l.payment_method_id.code in payment_method_codes + ) + if not lines_to_update: + return + + account_xmlid = 'account_journal_payment_debit_account_id' if payment_type == 'inbound' else 'account_journal_payment_credit_account_id' + account_company_map = { + company: self.env['account.chart.template'] + .with_company(company) + .ref(account_xmlid, raise_if_not_found=False) + for company in lines_to_update.mapped('company_id') + } + for company, lines in lines_to_update.grouped('company_id').items(): + if account := account_company_map[company]: + lines.payment_account_id = account + @api.depends('currency_id') def _compute_display_name(self): for journal in self: diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py index 3a04c887bfe4d9..8582d0896973b4 100644 --- a/addons/account/models/account_journal_dashboard.py +++ b/addons/account/models/account_journal_dashboard.py @@ -762,8 +762,8 @@ def _get_to_check_payment_query(self): SQL("company_id"), SQL("currency_id AS currency"), SQL("invoice_date_due < %s AS late", fields.Date.context_today(self)), - SQL("SUM(amount_residual_signed) AS amount_total_company"), - SQL("SUM((CASE WHEN move_type = 'in_invoice' THEN -1 ELSE 1 END) * amount_residual) AS amount_total"), + SQL("SUM(amount_total_signed) AS amount_total_company"), + SQL("SUM((CASE WHEN move_type = 'in_invoice' THEN -1 ELSE 1 END) * amount_total) AS amount_total"), SQL("COUNT(*)"), SQL("TRUE AS to_pay") ] diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 2477df4b145fe2..173ab4dbc1efac 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -473,6 +473,11 @@ def _sequence_year_range_monthly_regex(self): tracking=True, compute='_compute_payment_reference', inverse='_inverse_payment_reference', store=True, readonly=False, ) + sanitize_payment_reference = fields.Char( + string="Label sanitize", + compute='_compute_sanitize_payment_reference', + compute_sudo=False, + ) display_qr_code = fields.Boolean( string="Display QR-code", compute='_compute_display_qr_code', @@ -791,6 +796,7 @@ def _sequence_year_range_monthly_regex(self): # used in ._query_has_sequence_holes _made_gaps = models.Index('(journal_id, state, payment_state, move_type, date) WHERE (made_sequence_gap IS TRUE)') _duplicate_bills_idx = models.Index("(ref) WHERE (move_type IN ('in_invoice', 'in_refund'))") + _account_move_sanitize_payment_ref_idx = models.Index("(regexp_replace(COALESCE(payment_reference, ''), '[^a-zA-Z0-9]', '', 'g'))") def _auto_init(self): super()._auto_init() @@ -842,6 +848,11 @@ def _compute_payment_reference(self): move.payment_reference = move._get_invoice_computed_reference() self._inverse_payment_reference() + @api.depends('payment_reference') + def _compute_sanitize_payment_reference(self): + for move in self: + move.sanitize_payment_reference = re.sub(r'[^a-zA-Z0-9]', '', move.payment_reference or '') + def _get_accounting_date_source(self): self.ensure_one() return self.invoice_date or self.date @@ -1344,6 +1355,8 @@ def _field_to_sql(self, alias: str, fname: str, query=None) -> SQL: f"ELSE 'not_sent' " "END" ) + elif fname == 'sanitize_payment_reference': + return SQL("regexp_replace(COALESCE(%s, ''), '[^a-zA-Z0-9]', '', 'g')", super()._field_to_sql(alias, "payment_reference", query)) return super()._field_to_sql(alias, fname, query=query) @api.depends('reconciled_payment_ids') @@ -1830,7 +1843,7 @@ def _compute_tax_totals(self): base_lines=base_lines, currency=move.currency_id, company=move.company_id, - cash_rounding=move.invoice_cash_rounding_id, + cash_rounding=move.sudo().invoice_cash_rounding_id, ) move.tax_totals['display_in_company_currency'] = ( move.company_id.display_invoice_tax_company_currency @@ -3947,7 +3960,11 @@ def write(self, vals): 'invoice_payment_term_id', 'currency_id', 'fiscal_position_id', 'invoice_cash_rounding_id') readonly_fields = [val for val in vals if val in unmodifiable_fields] if not self.env.context.get('skip_readonly_check') and move_state == "posted" and readonly_fields: - raise UserError(_("You cannot modify the following readonly fields on a posted move: %s", ', '.join(readonly_fields))) + raise UserError(self.env._( + "You cannot modify the following readonly fields on the posted move %(move)s: %(fields)s", + move=move.name or move.ref or move.id, + fields=', '.join(readonly_fields), + )) if move.journal_id.sequence_override_regex and vals.get('name') and vals['name'] != '/' and not re.match(move.journal_id.sequence_override_regex, vals['name']): if not self.env.user.has_group('account.group_account_manager'): diff --git a/addons/account/models/account_move_line.py b/addons/account/models/account_move_line.py index 12a34149bf0f4f..3c3a2a4be008e8 100644 --- a/addons/account/models/account_move_line.py +++ b/addons/account/models/account_move_line.py @@ -193,6 +193,9 @@ class AccountMoveLine(models.Model): # === Tax fields === # tax_ids = fields.Many2many( comodel_name='account.tax', + relation='account_move_line_account_tax_rel', + column1='account_move_line_id', + column2='account_tax_id', string="Taxes", compute='_compute_tax_ids', store=True, readonly=False, precompute=True, context={'active_test': False, 'hide_original_tax_ids': True}, @@ -1000,7 +1003,7 @@ def _compute_discount_allocation_key(self): else: line.discount_allocation_key = False - @api.depends('account_id', 'company_id', 'discount', 'price_unit', 'quantity', 'currency_rate', 'analytic_distribution') + @api.depends('account_id', 'company_id', 'price_unit', 'quantity', 'currency_rate', 'move_id.line_ids.discount', 'move_id.line_ids.analytic_distribution') def _compute_discount_allocation_needed(self): line2discounted_amount = { line: [ @@ -1027,7 +1030,7 @@ def _compute_discount_allocation_needed(self): 'currency_rate': line.currency_rate, })][analytic_account_id] += weighted_amount - for line in self.move_id.line_ids: + for line in self: line.discount_allocation_dirty = True if line not in line2discounted_amount: line.discount_allocation_needed = False @@ -3118,7 +3121,7 @@ def _create_exchange_difference_moves(self, exchange_diff_values_list): )) # ==== Create the moves ==== - exchange_moves = self.env['account.move'].with_context(no_exchange_difference=True).create(exchange_move_values_list) + exchange_moves = self.env['account.move'].with_context(no_exchange_difference=True, move_reverse_cancel=False).create(exchange_move_values_list) # The reconciliation of exchange moves is now dealt thanks to the reconciled_lines_ids field # ==== See if the exchange moves need to be posted or not ==== diff --git a/addons/account/models/account_payment_term.py b/addons/account/models/account_payment_term.py index 91fc23d392978d..dc117b53435b88 100644 --- a/addons/account/models/account_payment_term.py +++ b/addons/account/models/account_payment_term.py @@ -320,7 +320,7 @@ def _get_due_date(self, date_ref): except ValueError: days_next_month = 1 - if not days_next_month: + if days_next_month <= 0: return date_utils.end_of(due_date + relativedelta(days=self.nb_days), 'month') return due_date + relativedelta(days=self.nb_days) + relativedelta(months=1, day=days_next_month) @@ -329,7 +329,7 @@ def _get_due_date(self, date_ref): @api.constrains('days_next_month') def _check_valid_char_value(self): for record in self: - if record.days_next_month and record.days_next_month.isnumeric(): + if record.days_next_month and record.days_next_month.removeprefix('-').isnumeric(): if not (0 <= int(record.days_next_month) <= 31): raise ValidationError(_('The days added must be between 0 and 31.')) else: diff --git a/addons/account/models/account_reconcile_model.py b/addons/account/models/account_reconcile_model.py index 06f3a71ae6780c..63dd087cacc2d7 100644 --- a/addons/account/models/account_reconcile_model.py +++ b/addons/account/models/account_reconcile_model.py @@ -51,6 +51,9 @@ class AccountReconcileModelLine(models.Model): """) tax_ids = fields.Many2many( comodel_name='account.tax', + relation='account_reconcile_model_line_account_tax_rel', + column1='account_reconcile_model_line_id', + column2='account_tax_id', string="Taxes", ondelete='restrict', check_company=True, diff --git a/addons/account/models/account_tax.py b/addons/account/models/account_tax.py index dd2018c3f2a786..a547adc7ee8d76 100644 --- a/addons/account/models/account_tax.py +++ b/addons/account/models/account_tax.py @@ -212,6 +212,22 @@ class AccountTax(models.Model): # Technical field depicting if the tax has at least one repartition line with a percentage below 0. # Used for the taxes computation to manage the reverse charge taxes having a repartition +100 -100. has_negative_factor = fields.Boolean(compute='_compute_has_negative_factor') + account_move_line_ids = fields.Many2many( + comodel_name='account.move.line', + relation='account_move_line_account_tax_rel', + column1='account_tax_id', + column2='account_move_line_id', + copy=False, + readonly=True, + ) + account_reconcile_model_line_ids = fields.Many2many( + comodel_name='account.reconcile.model.line', + relation='account_reconcile_model_line_account_tax_rel', + column1='account_tax_id', + column2='account_reconcile_model_line_id', + copy=False, + readonly=True, + ) @api.constrains('company_id', 'name', 'type_tax_use', 'tax_scope', 'country_id') def _constrains_name(self): @@ -325,6 +341,8 @@ def _search_price_include(self, operator, value): def _hook_compute_is_used(self, tax_to_compute): ''' + -- TO BE REMOVED IN MASTER -- + Override to compute the ids of taxes used in other modules. It takes as parameter a set of tax ids. It should return a set containing the ids of the taxes from that input set that are used in transactions. @@ -347,47 +365,23 @@ def _compute_display_alternative_taxes_field(self): ) ) + @api.depends('account_move_line_ids', 'account_reconcile_model_line_ids') def _compute_is_used(self): - used_taxes = set() - - if self.ids: - # Fetch for taxes used in account moves - self.env['account.move.line'].flush_model(['tax_ids']) - used_taxes.update(id_ for [id_] in self.env.execute_query(SQL( - """ SELECT id - FROM account_tax - WHERE EXISTS( - SELECT 1 - FROM account_move_line_account_tax_rel AS line - WHERE account_tax.id = line.account_tax_id - ) AND id IN %s - """, tuple(self.ids), - ))) - taxes_to_compute = set(self.ids) - used_taxes - - # Fetch for taxes used in reconciliation - if taxes_to_compute: - self.env['account.reconcile.model.line'].flush_model(['tax_ids']) - used_taxes.update(id_ for [id_] in self.env.execute_query(SQL( - """ SELECT id - FROM account_tax - WHERE EXISTS( - SELECT 1 - FROM account_reconcile_model_line_account_tax_rel AS reco - WHERE account_tax.id = reco.account_tax_id - ) AND id IN %s - """, tuple(taxes_to_compute) - ))) - taxes_to_compute -= used_taxes - - # Fetch for tax used in other modules - if taxes_to_compute: - used_taxes.update(self._hook_compute_is_used(taxes_to_compute)) + used_taxes = set(self.sudo().search([ + '|', + ('account_move_line_ids', '!=', False), + ('account_reconcile_model_line_ids', '!=', False), + ]).ids) + taxes_to_compute = set(self.ids) - used_taxes + + # Fetch for tax used in custom modules. To be removed in master. + if taxes_to_compute: + used_taxes.update(self._hook_compute_is_used(taxes_to_compute)) for tax in self: tax.is_used = tax._origin.id in used_taxes - @api.depends('repartition_line_ids.account_id', 'repartition_line_ids.sequence', 'repartition_line_ids.factor_percent', 'repartition_line_ids.use_in_tax_closing', 'repartition_line_ids.tag_ids') + @api.depends('is_used', 'repartition_line_ids.account_id', 'repartition_line_ids.sequence', 'repartition_line_ids.factor_percent', 'repartition_line_ids.use_in_tax_closing', 'repartition_line_ids.tag_ids') def _compute_repartition_lines_str(self): for tax in self: repartition_lines_str = tax.repartition_lines_str or "" @@ -2570,6 +2564,7 @@ def _aggregate_base_line_tax_details(self, base_line, grouping_function): tax_details = base_line['tax_details'] taxes_data = tax_details['taxes_data'] manual_tax_amounts = base_line['manual_tax_amounts'] + tax_amount_to_propagate = defaultdict(lambda: defaultdict(float)) # If there are no taxes, we pass None to the grouping function. for tax_data in (taxes_data or [None]): @@ -2593,6 +2588,9 @@ def _aggregate_base_line_tax_details(self, base_line, grouping_function): excluded_manual_field = f'manual_{excluded_rounded_field}' excluded_rounded_amount = tax_details[excluded_rounded_field] + tax_details[excluded_delta_field] excluded_raw_amount = tax_details[excluded_raw_field] + if tax_data: + excluded_rounded_amount += tax_amount_to_propagate[tax_data['tax']][excluded_rounded_field] + excluded_raw_amount += tax_amount_to_propagate[tax_data['tax']][excluded_raw_field] values[excluded_rounded_field] = excluded_rounded_amount values[excluded_raw_field] = excluded_raw_amount if base_line[excluded_manual_field] is not None: @@ -2629,6 +2627,13 @@ def _aggregate_base_line_tax_details(self, base_line, grouping_function): # Tax amount. if tax_data: + # Compute the amount that should be propagated by tax + for tax in tax_data['taxes']: + tax_amount_to_propagate[tax]['raw_total_excluded'] += tax_data['raw_tax_amount'] + tax_amount_to_propagate[tax]['raw_total_excluded_currency'] += tax_data['raw_tax_amount_currency'] + tax_amount_to_propagate[tax]['total_excluded'] += tax_data['tax_amount'] + tax_amount_to_propagate[tax]['total_excluded_currency'] += tax_data['tax_amount_currency'] + reverse_charge_sign = -1 if tax_data['is_reverse_charge'] else 1 values = values_per_grouping_key[grouping_key] for suffix in ('_currency', ''): diff --git a/addons/account/static/src/helpers/account_tax.js b/addons/account/static/src/helpers/account_tax.js index 22ac9ea46c2939..de3134de8d1350 100644 --- a/addons/account/static/src/helpers/account_tax.js +++ b/addons/account/static/src/helpers/account_tax.js @@ -1439,6 +1439,7 @@ export const accountTaxHelpers = { const tax_details = base_line.tax_details; const taxes_data = tax_details.taxes_data; const manual_tax_amounts = base_line.manual_tax_amounts; + const tax_amount_to_propagate = {} // If there are no taxes, we pass an empty object to the grouping function. for (const tax_data of taxes_data.length !== 0 ? taxes_data : [null]) { @@ -1484,9 +1485,13 @@ export const accountTaxHelpers = { const excluded_target_field = `target_${excluded_rounded_field}`; const excluded_manual_field = `manual_${excluded_rounded_field}`; - const excluded_rounded_amount = + let excluded_rounded_amount = tax_details[excluded_rounded_field] + tax_details[excluded_delta_field]; - const excluded_raw_amount = tax_details[excluded_raw_field]; + let excluded_raw_amount = tax_details[excluded_raw_field]; + if (tax_data && tax_data.tax.id in tax_amount_to_propagate) { + excluded_rounded_amount += tax_amount_to_propagate[tax_data.tax.id][excluded_rounded_field] + excluded_raw_amount += tax_amount_to_propagate[tax_data.tax.id][excluded_raw_field] + } values[excluded_rounded_field] = excluded_rounded_amount; values[excluded_raw_field] = excluded_raw_amount; @@ -1538,6 +1543,22 @@ export const accountTaxHelpers = { // Tax amount. if (tax_data) { + // Compute the amount that should be propagated by tax + for (const tax of tax_data['taxes']) { + const tax_id = tax.id + if (tax_id in tax_amount_to_propagate) { + tax_amount_to_propagate[tax_id]['raw_total_excluded'] += tax_data['raw_tax_amount'] + tax_amount_to_propagate[tax_id]['raw_total_excluded_currency'] += tax_data['raw_tax_amount_currency'] + tax_amount_to_propagate[tax_id]['total_excluded'] += tax_data['tax_amount'] + tax_amount_to_propagate[tax_id]['total_excluded_currency'] += tax_data['tax_amount_currency'] + } else { + tax_amount_to_propagate[tax_id] = {} + tax_amount_to_propagate[tax_id]['raw_total_excluded'] = tax_data['raw_tax_amount'] + tax_amount_to_propagate[tax_id]['raw_total_excluded_currency'] = tax_data['raw_tax_amount_currency'] + tax_amount_to_propagate[tax_id]['total_excluded'] = tax_data['tax_amount'] + tax_amount_to_propagate[tax_id]['total_excluded_currency'] = tax_data['tax_amount_currency'] + } + } const reverse_charge_sign = tax_data.is_reverse_charge ? -1 : 1; const values = values_per_grouping_key[grouping_key]; for (const suffix of ["_currency", ""]) { diff --git a/addons/account/tests/test_account_move_entry.py b/addons/account/tests/test_account_move_entry.py index a20567b78d71cf..0adf2a80c1b2d2 100644 --- a/addons/account/tests/test_account_move_entry.py +++ b/addons/account/tests/test_account_move_entry.py @@ -315,7 +315,7 @@ def test_modify_posted_move_readonly_fields(self): readonly_fields = ('invoice_line_ids', 'line_ids', 'invoice_date', 'date', 'partner_id', 'invoice_payment_term_id', 'currency_id', 'fiscal_position_id', 'invoice_cash_rounding_id') for field in readonly_fields: - with self.assertRaisesRegex(UserError, "You cannot modify the following readonly fields on a posted move"): + with self.assertRaisesRegex(UserError, "You cannot modify the following readonly fields on the posted move %s" % self.test_move.name): self.test_move.write({field: False}) def test_misc_move_onchange(self): diff --git a/addons/account/tests/test_account_move_out_invoice.py b/addons/account/tests/test_account_move_out_invoice.py index 24626816d6cff1..869321f3eced08 100644 --- a/addons/account/tests/test_account_move_out_invoice.py +++ b/addons/account/tests/test_account_move_out_invoice.py @@ -5324,3 +5324,57 @@ def post_next_entry(): {'date': apr, 'state': 'posted'}, {'date': may, 'state': 'draft'}, ]) + + def test_out_invoice_multi_currency_exchange_diff(self): + """ + Test that through the 'Reverse and Create Invoice' on a cash basis and multi-currency invoice, + the exchange difference and cash basis transition moves are correctly generated and posted. + """ + self.env['res.currency.rate'].create([ + {'name': '2026-07-01', 'rate': 20.0, 'currency_id': self.other_currency.id, 'company_id': self.env.company.id}, + {'name': '2026-07-15', 'rate': 15.0, 'currency_id': self.other_currency.id, 'company_id': self.env.company.id}, + ]) + + self.env.company.tax_exigibility = True + tax_waiting_account = self.env['account.account'].create({ + 'name': 'TAX_WAIT', + 'code': 'TWAIT', + 'account_type': 'liability_current', + 'reconcile': True, + }) + + caba_tax = self.env['account.tax'].create({ + 'name': 'Cash Basis 15%', + 'type_tax_use': 'sale', + 'amount': 15, + 'tax_exigibility': 'on_payment', + 'cash_basis_transition_account_id': tax_waiting_account.id, + }) + + invoice = self.init_invoice( + move_type='out_invoice', + partner=self.partner_a, + invoice_date='2026-07-01', + currency=self.other_currency, + amounts=[100.0], + taxes=caba_tax, + post=True + ) + + move_reversal = self.env['account.move.reversal'].with_context( + active_model="account.move", + active_ids=invoice.ids, + ).create({ + 'date': '2026-07-15', + 'reason': 'test reversal exchange', + 'journal_id': invoice.journal_id.id, + }) + + move_reversal.modify_moves() + credit_note = self.env['account.move'].search([('reversed_entry_id', '=', invoice.id)]) + self.assertTrue(credit_note) + + partials = invoice.line_ids.matched_credit_ids | invoice.line_ids.matched_debit_ids + exchange_moves = partials.exchange_move_id + + self.assertTrue(exchange_moves) diff --git a/addons/account/tests/test_account_tax.py b/addons/account/tests/test_account_tax.py index 6b9c5ee4b2c5c4..d8b288966bca72 100644 --- a/addons/account/tests/test_account_tax.py +++ b/addons/account/tests/test_account_tax.py @@ -223,7 +223,6 @@ def test_tax_is_used_when_in_transactions(self): }), ], }) - tax_invoice.invalidate_model(fnames=['is_used']) self.assertTrue(tax_invoice.is_used) # Account.reconcile is another of transaction @@ -238,7 +237,6 @@ def test_tax_is_used_when_in_transactions(self): 'tax_ids': [Command.set(tax_reconciliation.ids)], })], }) - tax_reconciliation.invalidate_model(fnames=['is_used']) self.assertTrue(tax_reconciliation.is_used) def test_tax_no_duplicate_in_repartition_line(self): diff --git a/addons/account/tests/test_payment_term.py b/addons/account/tests/test_payment_term.py index 9669e9fea07162..e7ac82fe9a7869 100644 --- a/addons/account/tests/test_payment_term.py +++ b/addons/account/tests/test_payment_term.py @@ -683,3 +683,22 @@ def test_payment_term_multi_company(self): 'company_id': other_company.id }) self.assertFalse(invoice.invoice_payment_term_id) + + def test_payment_term_rejects_negative_day_of_next_month(self): + payment_term = self.env['account.payment.term'].new({ + 'name': 'Invalid next-month day', + 'line_ids': [Command.create({ + 'value': 'percent', + 'value_amount': 100, + 'nb_days': 0, + 'delay_type': 'days_end_of_month_on_the', + 'days_next_month': -1, + })], + }) + + # Accessing the computed field triggers the due-date computation. + payment_term.example_preview + + vals = payment_term._convert_to_write(payment_term._cache) + with self.assertRaisesRegex(ValidationError, 'The days added must be between 0 and 31.'): + self.env['account.payment.term'].create(vals) diff --git a/addons/account/tests/test_taxes_base_lines_tax_details.py b/addons/account/tests/test_taxes_base_lines_tax_details.py index 1073f711a3ff7e..d1d992f6df9962 100644 --- a/addons/account/tests/test_taxes_base_lines_tax_details.py +++ b/addons/account/tests/test_taxes_base_lines_tax_details.py @@ -202,3 +202,110 @@ def test_global_discount_raw_gross_total_excluded(self): self.assertEqual(tax_details['raw_gross_total_excluded'], 100) self.assertEqual(tax_details['raw_gross_price_unit'], 100) self.assertEqual(tax_details['raw_discount_amount'], 20) + + def test_include_base_amount_in_aggregate_base_line_tax_details(self): + """ Test that the tax amount from a tax that is affecting the base of subsequent taxes + is correctly propagated in '_aggregate_base_line_tax_details' + """ + AccountTax = self.env['account.tax'] + tax_20 = AccountTax.create({ + 'name': 'tax_20', + 'sequence': -1, + 'amount_type': 'percent', + 'amount': 20, + 'include_base_amount': True, + }) + tax_18 = AccountTax.create({ + 'name': 'tax_18', + 'amount_type': 'percent', + 'amount': 18, + }) + document = self.populate_document(self.init_document( + lines=[ + {'price_unit': 2000.0, 'quantity': 5, 'discount': 20.0, 'tax_ids': tax_20 + tax_18}, + ], + )) + + def tax_grouping_function(base_line, tax_data): + if not tax_data: + return None + return { + 'amount': tax_data['tax'].amount, + } + + aggregated_data = AccountTax._aggregate_base_line_tax_details(document['lines'][0], tax_grouping_function) + expected_values = [{ + 'grouping_key': {'amount': 20.0}, + 'taxes_data': [{ + 'base_amount': 8000.0, + 'base_amount_currency': 8000.0, + 'batch': tax_20, + 'group': AccountTax, + 'is_reverse_charge': False, + 'price_include': False, + 'raw_base_amount': 8000.0, + 'raw_base_amount_currency': 8000.0, + 'raw_tax_amount': 1600.0, + 'raw_tax_amount_currency': 1600.0, + 'tax': tax_20, + 'tax_amount': 1600.0, + 'tax_amount_currency': 1600.0, + 'taxes': tax_18, + }], + 'base_amount': 8000.0, + 'base_amount_currency': 8000.0, + 'raw_base_amount': 8000.0, + 'raw_base_amount_currency': 8000.0, + 'raw_tax_amount': 1600.0, + 'raw_tax_amount_currency': 1600.0, + 'raw_total_excluded': 8000.0, + 'raw_total_excluded_currency': 8000.0, + 'target_base_amount': 8000.0, + 'target_base_amount_currency': 8000.0, + 'target_tax_amount': 1600.0, + 'target_tax_amount_currency': 1600.0, + 'target_total_excluded': 8000.0, + 'target_total_excluded_currency': 8000.0, + 'tax_amount': 1600.0, + 'tax_amount_currency': 1600.0, + 'total_excluded': 8000.0, + 'total_excluded_currency': 8000.0, + }, { + 'grouping_key': {'amount': 18.0}, + 'taxes_data': [{ + 'base_amount': 9600.0, + 'base_amount_currency': 9600.0, + 'group': AccountTax, + 'is_reverse_charge': False, + 'batch': tax_18, + 'price_include': False, + 'raw_base_amount': 9600.0, + 'raw_base_amount_currency': 9600.0, + 'raw_tax_amount': 1728.0, + 'raw_tax_amount_currency': 1728.0, + 'tax': tax_18, + 'tax_amount': 1728.0, + 'tax_amount_currency': 1728.0, + 'taxes': AccountTax, + }], + 'base_amount': 9600.0, + 'base_amount_currency': 9600.0, + 'raw_base_amount': 9600.0, + 'raw_base_amount_currency': 9600.0, + 'raw_tax_amount': 1728.0, + 'raw_tax_amount_currency': 1728.0, + 'raw_total_excluded': 9600.0, + 'raw_total_excluded_currency': 9600.0, + 'target_base_amount': 9600.0, + 'target_base_amount_currency': 9600.0, + 'target_tax_amount': 1728.0, + 'target_tax_amount_currency': 1728.0, + 'target_total_excluded': 9600.0, + 'target_total_excluded_currency': 9600.0, + 'tax_amount': 1728.0, + 'tax_amount_currency': 1728.0, + 'total_excluded': 9600.0, + 'total_excluded_currency': 9600.0, + }] + for i, data in enumerate(aggregated_data.values()): + self.assertDictEqual(expected_values[i], data) diff --git a/addons/account/views/account_bank_statement_views.xml b/addons/account/views/account_bank_statement_views.xml index 067008f2315fe9..f3d15c680f7162 100644 --- a/addons/account/views/account_bank_statement_views.xml +++ b/addons/account/views/account_bank_statement_views.xml @@ -71,7 +71,7 @@ Credit Statements account.bank.statement - list,pivot,graph + list,pivot,graph,form [('journal_id.type', '=', 'credit')] {'journal_type': 'credit'} @@ -124,7 +124,7 @@ Cash Registers ir.actions.act_window account.bank.statement - list,pivot,graph + list,pivot,graph,form [('journal_id.type', '=', 'cash')] diff --git a/addons/account/views/account_journal_views.xml b/addons/account/views/account_journal_views.xml index a3454ab78e68d0..647f9e62608c4b 100644 --- a/addons/account/views/account_journal_views.xml +++ b/addons/account/views/account_journal_views.xml @@ -36,7 +36,7 @@ name="%(action_account_moves_all_a)d" icon="fa-book" context="{'search_default_journal_id':id}">
- Journal Entries + Journal Items
diff --git a/addons/account/wizard/accrued_orders.py b/addons/account/wizard/accrued_orders.py index 277f3f2ce30c7c..51b55edee8b34e 100644 --- a/addons/account/wizard/accrued_orders.py +++ b/addons/account/wizard/accrued_orders.py @@ -196,7 +196,7 @@ def _ellipsis(string, size): account = stock_variation_account if stock_variation_account else self._get_computed_account(order, order_line.product_id, is_purchase) if any(tax.price_include for tax in order_line.tax_ids): # As included taxes are not taken into account in the price_unit, we need to compute the price_subtotal - qty_to_invoice = order_line.qty_received_at_date - order_line.qty_invoiced_at_date + qty_to_invoice = order_line._get_qty_to_invoice_at_date() price_subtotal = order_line.tax_ids.compute_all( price_unit, currency=order_line.order_id.currency_id, @@ -204,7 +204,7 @@ def _ellipsis(string, size): product=order_line.product_id, partner=order_line.order_id.partner_id)['total_excluded'] else: - price_subtotal = (order_line.qty_received_at_date - order_line.qty_invoiced_at_date) * price_unit + price_subtotal = order_line._get_qty_to_invoice_at_date() * price_unit amount_currency = order_line.currency_id.round(price_subtotal) amount = order.currency_id._convert(amount_currency, self.company_id.currency_id, self.company_id) label = _( @@ -320,7 +320,7 @@ def _ellipsis(string, size): analytic_distribution = {} total = sum(order.amount_total for order in orders) for line in orders.order_line: - ratio = line.price_total / total + ratio = line.price_total / total if total else 0.0 if not line.analytic_distribution: continue for account_id, distribution in line.analytic_distribution.items(): diff --git a/addons/account_add_gln/i18n/ar.po b/addons/account_add_gln/i18n/ar.po index e50f382717b275..a477b67ac76b87 100644 --- a/addons/account_add_gln/i18n/ar.po +++ b/addons/account_add_gln/i18n/ar.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: Odoo Server 19.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-10 13:14+0000\n" +"PO-Revision-Date: 2026-08-24 05:12+0000\n" "Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" @@ -28,7 +28,7 @@ msgstr "جهة اتصال " #. module: account_add_gln #: model:ir.model,website_form_label:account_add_gln.model_res_partner msgid "Create a Customer" -msgstr "" +msgstr "Create a Customer" #. module: account_add_gln #: model:ir.model.fields,field_description:account_add_gln.field_res_partner__display_name @@ -45,7 +45,7 @@ msgstr "GLN" #: model:ir.model.fields,help:account_add_gln.field_res_partner__global_location_number #: model:ir.model.fields,help:account_add_gln.field_res_users__global_location_number msgid "Global Location Number" -msgstr "" +msgstr "رقم الموقع العالمي" #. module: account_add_gln #: model:ir.model.fields,field_description:account_add_gln.field_res_partner__id diff --git a/addons/account_debit_note/i18n/ar.po b/addons/account_debit_note/i18n/ar.po index 232e172f2508c4..331dc64b9cc1f9 100644 --- a/addons/account_debit_note/i18n/ar.po +++ b/addons/account_debit_note/i18n/ar.po @@ -3,13 +3,14 @@ # * account_debit_note # # "Tiffany Chang (tic)" , 2025, 2026. +# "Malaz Siddig Elsayed Abuidris (msea)" , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server saas~18.2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-06-19 17:35+0000\n" -"PO-Revision-Date: 2026-08-04 10:25+0000\n" -"Last-Translator: Anonymous \n" +"PO-Revision-Date: 2026-08-24 05:09+0000\n" +"Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -28,12 +29,12 @@ msgstr "إشعارات مدينة" #. module: account_debit_note #: model_terms:ir.ui.view,arch_db:account_debit_note.report_invoice_document_inherit_debit_note msgid "Debit Note Date" -msgstr "" +msgstr "تاريخ إشعار الخصم" #. module: account_debit_note #: model_terms:ir.ui.view,arch_db:account_debit_note.report_invoice_document_inherit_debit_note msgid "Invoice Date" -msgstr "" +msgstr "تاريخ فاتورة العميل" #. module: account_debit_note #: model:ir.model,name:account_debit_note.model_account_debit_note @@ -48,22 +49,22 @@ msgstr "إلغاء" #. module: account_debit_note #: model_terms:ir.ui.view,arch_db:account_debit_note.report_invoice_document_inherit_debit_note msgid "Cancelled Debit Note" -msgstr "" +msgstr "إشعار خصم ملغى" #. module: account_debit_note #: model_terms:ir.ui.view,arch_db:account_debit_note.report_invoice_document_inherit_debit_note msgid "Cancelled Invoice" -msgstr "" +msgstr "فاتورة ملغاة" #. module: account_debit_note #: model_terms:ir.ui.view,arch_db:account_debit_note.report_invoice_document_inherit_debit_note msgid "Cancelled Proforma Debit Note" -msgstr "" +msgstr "إشعار خصم شكلي ملغى" #. module: account_debit_note #: model_terms:ir.ui.view,arch_db:account_debit_note.report_invoice_document_inherit_debit_note msgid "Cancelled Proforma Invoice" -msgstr "" +msgstr "فاتورة شكلية ملغية" #. module: account_debit_note #: model:ir.model.fields,help:account_debit_note.field_account_journal__debit_sequence @@ -139,22 +140,22 @@ msgstr "اسم العرض" #. module: account_debit_note #: model_terms:ir.ui.view,arch_db:account_debit_note.report_invoice_document_inherit_debit_note msgid "Draft Debit Note" -msgstr "" +msgstr "مسودة إشعار الخصم" #. module: account_debit_note #: model_terms:ir.ui.view,arch_db:account_debit_note.report_invoice_document_inherit_debit_note msgid "Draft Invoice" -msgstr "" +msgstr "مسودة فاتورة" #. module: account_debit_note #: model_terms:ir.ui.view,arch_db:account_debit_note.report_invoice_document_inherit_debit_note msgid "Draft Proforma Debit Note" -msgstr "" +msgstr "مسودة إشعار الخصم الشكلي" #. module: account_debit_note #: model_terms:ir.ui.view,arch_db:account_debit_note.report_invoice_document_inherit_debit_note msgid "Draft Proforma Invoice" -msgstr "" +msgstr "مسودة فاتورة شكلية" #. module: account_debit_note #: model:ir.model.fields,field_description:account_debit_note.field_account_debit_note__id @@ -181,7 +182,7 @@ msgstr "" #. module: account_debit_note #: model_terms:ir.ui.view,arch_db:account_debit_note.report_invoice_document_inherit_debit_note msgid "Invoice" -msgstr "" +msgstr "الفاتورة" #. module: account_debit_note #: model:ir.model,name:account_debit_note.model_account_journal @@ -233,12 +234,12 @@ msgstr "تم خصم الفاتورة الأصلية" #. module: account_debit_note #: model_terms:ir.ui.view,arch_db:account_debit_note.report_invoice_document_inherit_debit_note msgid "Proforma Debit Note" -msgstr "" +msgstr "إشعار الخصم الشكلي" #. module: account_debit_note #: model_terms:ir.ui.view,arch_db:account_debit_note.report_invoice_document_inherit_debit_note msgid "Proforma Invoice" -msgstr "" +msgstr "فاتورة شكلية" #. module: account_debit_note #: model:ir.model.fields,field_description:account_debit_note.field_account_debit_note__reason diff --git a/addons/account_edi_proxy_client/i18n/ar.po b/addons/account_edi_proxy_client/i18n/ar.po index b9452ea019f530..a09ec1048f3070 100644 --- a/addons/account_edi_proxy_client/i18n/ar.po +++ b/addons/account_edi_proxy_client/i18n/ar.po @@ -10,8 +10,8 @@ msgstr "" "Project-Id-Version: Odoo Server saas~18.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-02 14:02+0000\n" -"Last-Translator: Anonymous \n" +"PO-Revision-Date: 2026-08-24 05:12+0000\n" +"Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -237,7 +237,7 @@ msgstr "Token Out of Sync" #. module: account_edi_proxy_client #: model:ir.model.fields,field_description:account_edi_proxy_client.field_account_edi_proxy_client_user__token_sync_version msgid "Token Sync Version" -msgstr "" +msgstr "إصدار مزامنة الرموز" #~ msgid "" #~ "Invalid signature for request. This might be due to another connection to " diff --git a/addons/account_edi_proxy_client/i18n/sv.po b/addons/account_edi_proxy_client/i18n/sv.po index 79e2af398d2256..6d2489c3e0ecf1 100644 --- a/addons/account_edi_proxy_client/i18n/sv.po +++ b/addons/account_edi_proxy_client/i18n/sv.po @@ -11,7 +11,7 @@ msgstr "" "Project-Id-Version: Odoo Server saas~18.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-11 15:30+0000\n" +"PO-Revision-Date: 2026-08-24 08:01+0000\n" "Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" @@ -97,7 +97,7 @@ msgstr "EDI-proxyanvändare" #. module: account_edi_proxy_client #: model:ir.ui.menu,name:account_edi_proxy_client.menu_account_proxy_client_user msgid "EDI Proxy Users" -msgstr "EDI-överföringar genom en proxy för användare" +msgstr "EDI-proxyanvändare" #. module: account_edi_proxy_client #: model:ir.model.fields,field_description:account_edi_proxy_client.field_account_edi_proxy_client_user__edi_mode diff --git a/addons/account_edi_ubl_cii/i18n/fa.po b/addons/account_edi_ubl_cii/i18n/fa.po index b4161790ec9208..8e79402acdc7ad 100644 --- a/addons/account_edi_ubl_cii/i18n/fa.po +++ b/addons/account_edi_ubl_cii/i18n/fa.po @@ -4,13 +4,14 @@ # # Weblate , 2025, 2026. # Aliraza Habibitanha , 2026. +# Heshmatkhah , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server saas~18.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-17 17:37+0000\n" -"PO-Revision-Date: 2026-08-09 11:29+0000\n" -"Last-Translator: Aliraza Habibitanha \n" +"PO-Revision-Date: 2026-08-23 06:16+0000\n" +"Last-Translator: Heshmatkhah \n" "Language-Team: Persian \n" "Language: fa\n" @@ -24,7 +25,7 @@ msgstr "" #. odoo-python #: code:addons/account_edi_ubl_cii/models/account_edi_ubl.py:0 msgid "%(invoice_name)s - Generated by Odoo" -msgstr "%(invoice_name)s - تولید شده توسط سازمان‌یار" +msgstr "%(invoice_name)s - تولید شده توسط اودو" #. module: account_edi_ubl_cii #. odoo-python diff --git a/addons/account_edi_ubl_cii/models/account_edi_common.py b/addons/account_edi_ubl_cii/models/account_edi_common.py index af63500d19f83f..5dfacbc9899019 100644 --- a/addons/account_edi_ubl_cii/models/account_edi_common.py +++ b/addons/account_edi_ubl_cii/models/account_edi_common.py @@ -48,6 +48,7 @@ # ------------------------------------------------------------------------- # ELECTRONIC ADDRESS SCHEME (EAS), see https://docs.peppol.eu/poacc/billing/3.0/codelist/eas/ # ------------------------------------------------------------------------- +DEPRECATED_PEPPOL_EAS = {'0037', '0213', '9955', '0193'} EAS_MAPPING = { 'AD': {'9922': 'vat'}, 'AE': {'0235': 'vat'}, @@ -103,13 +104,13 @@ # DOM-TOM 'BL': {'0009': 'siret', '9957': 'vat', '0002': None}, # Saint Barthélemy 'GF': {'0009': 'siret', '9957': 'vat', '0002': None}, # French Guiana - 'GP': {'0009': 'siret', '9957': 'vat', '0002': None}, # Guadeloupe + 'GP': {'0225': 'peppol_endpoint', '0009': 'siret', '9957': 'vat', '0002': None}, # Guadeloupe 'MF': {'0009': 'siret', '9957': 'vat', '0002': None}, # Saint Martin - 'MQ': {'0009': 'siret', '9957': 'vat', '0002': None}, # Martinique + 'MQ': {'0225': 'peppol_endpoint', '0009': 'siret', '9957': 'vat', '0002': None}, # Martinique 'NC': {'0009': 'siret', '9957': 'vat', '0002': None}, # New Caledonia 'PF': {'0009': 'siret', '9957': 'vat', '0002': None}, # French Polynesia 'PM': {'0009': 'siret', '9957': 'vat', '0002': None}, # Saint Pierre and Miquelon - 'RE': {'0009': 'siret', '9957': 'vat', '0002': None}, # Réunion + 'RE': {'0225': 'peppol_endpoint', '0009': 'siret', '9957': 'vat', '0002': None}, # Réunion 'TF': {'0009': 'siret', '9957': 'vat', '0002': None}, # French Southern and Antarctic Lands 'WF': {'0009': 'siret', '9957': 'vat', '0002': None}, # Wallis and Futuna 'YT': {'0009': 'siret', '9957': 'vat', '0002': None}, # Mayotte @@ -226,7 +227,7 @@ EUROPEAN_ECONOMIC_AREA_COUNTRY_CODES = { # EU Member States 'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', - 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'CH', + 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', # EFTA Countries in the EEA 'IS', 'LI', 'NO', @@ -420,16 +421,18 @@ def _get_tax_category_code(self, customer, supplier, tax): else: return 'S' # standard VAT - if supplier.country_id.code in EUROPEAN_ECONOMIC_AREA_COUNTRY_CODES and supplier.vat: + supplier_in_eea = supplier.country_id.code in EUROPEAN_ECONOMIC_AREA_COUNTRY_CODES + customer_in_eea = customer.country_id.code in EUROPEAN_ECONOMIC_AREA_COUNTRY_CODES + + if (supplier_in_eea or customer_in_eea) and supplier.vat: if tax.amount != 0 and not tax.has_negative_factor: # Special case: Purchase reverse-charge taxes for self-billed invoices. # See explanation above. # In the XML we put the zero-percent tax with code 'G' or 'K' that the buyer would have used. return 'S' - if customer.country_id.code not in EUROPEAN_ECONOMIC_AREA_COUNTRY_CODES: - return 'G' - if customer.country_id.code in EUROPEAN_ECONOMIC_AREA_COUNTRY_CODES: + if supplier_in_eea and customer_in_eea: return 'K' + return 'G' if tax.amount != 0: return 'S' diff --git a/addons/account_edi_ubl_cii/models/account_edi_ubl.py b/addons/account_edi_ubl_cii/models/account_edi_ubl.py index 6eb84c06ea7ae2..b5e2a5e909fd7e 100644 --- a/addons/account_edi_ubl_cii/models/account_edi_ubl.py +++ b/addons/account_edi_ubl_cii/models/account_edi_ubl.py @@ -3033,11 +3033,16 @@ def _import_ubl_invoice_add_allowances_charges_values(self, collected_values): if not category_code: continue + tax_key = frozendict({ + 'category_code': category_code, + 'percentage': percentage, + }) allowance_charge_values['attempt_tax_values'] = tax_values = { 'amount_type': 'percent', 'type_tax_use': odoo_document_type, 'ubl_cii_tax_category_code': category_code, 'amount': percentage, + '_tax_key': tax_key, } taxes_values.append(tax_values) @@ -3470,6 +3475,11 @@ def _import_ubl_invoice_retrieve_taxes(self, collected_values): tax_values_list=tax_values_list, ) + for tax_values in collected_values['taxes_values']: + tax_key = tax_values.get('_tax_key') + if tax_key and (global_tax_values := collected_values['tax_total_values'].get(tax_key)): + global_tax_values['related_taxes_values'].append(tax_values) + # Taxes at the document line level. for line_collected_values in lines_collected_values: to_write = line_collected_values['to_write'] @@ -3729,11 +3739,18 @@ def _import_ubl_invoice_add_base_lines(self, collected_values): # Fix 'price_unit' if some price-included taxes are involved. for base_line in base_lines: - for tax_data in base_line['tax_details']['taxes_data']: - if tax_data['tax'].price_include: - discount = base_line['discount'] / 100 - raw_tax_amount_currency = tax_data['raw_tax_amount_currency'] / (1 - discount) - base_line['price_unit'] += raw_tax_amount_currency / (base_line['quantity'] if base_line['quantity'] else 1) + if base_line['discount'] != 100: + for tax_data in base_line['tax_details']['taxes_data']: + if tax_data['tax'].price_include: + discount = base_line['discount'] / 100 + raw_tax_amount_currency = tax_data['raw_tax_amount_currency'] / (1 - discount) + base_line['price_unit'] += raw_tax_amount_currency / (base_line['quantity'] if base_line['quantity'] else 1) + else: + new_base_line = AccountTax._prepare_base_line_for_taxes_computation(record=base_line, discount=0.0, special_mode="total_excluded") + AccountTax._add_tax_details_in_base_lines([new_base_line], company) + for tax_data in new_base_line['tax_details']['taxes_data']: + if tax_data['tax'].price_include: + base_line['price_unit'] += tax_data['raw_tax_amount_currency'] / (base_line['quantity'] if base_line['quantity'] else 1) # Remove lines having a zero amount except 100% discounts collected_values['base_lines'] = [ diff --git a/addons/account_edi_ubl_cii/models/account_edi_ubl_pint.py b/addons/account_edi_ubl_cii/models/account_edi_ubl_pint.py index 0bc539c291c2b9..1a0cde9bfa509e 100644 --- a/addons/account_edi_ubl_cii/models/account_edi_ubl_pint.py +++ b/addons/account_edi_ubl_cii/models/account_edi_ubl_pint.py @@ -108,7 +108,7 @@ def _ubl_add_buyer_reference_node(self, vals): super()._ubl_add_buyer_reference_node(vals) customer = vals['customer'] - if customer_ref := customer.commercial_partner_id.ref: + if customer_ref := customer.ref or customer.commercial_partner_id.ref: vals['document_node']['cbc:BuyerReference']['_text'] = customer_ref def _ubl_add_billing_reference_nodes(self, vals): diff --git a/addons/account_edi_ubl_cii/models/res_partner.py b/addons/account_edi_ubl_cii/models/res_partner.py index 188952a2c6b552..f60c57837ff3c9 100644 --- a/addons/account_edi_ubl_cii/models/res_partner.py +++ b/addons/account_edi_ubl_cii/models/res_partner.py @@ -4,7 +4,7 @@ from odoo import models, fields, api, _ from odoo.exceptions import ValidationError -from odoo.addons.account_edi_ubl_cii.models.account_edi_common import EAS_MAPPING +from odoo.addons.account_edi_ubl_cii.models.account_edi_common import EAS_MAPPING, DEPRECATED_PEPPOL_EAS from odoo.addons.account.models.company import PEPPOL_DEFAULT_COUNTRIES from odoo.tools import single_email_re @@ -286,10 +286,15 @@ def _compute_peppol_eas(self): country_code = partner._deduce_country_code() if country_code in EAS_MAPPING: eas_to_field = EAS_MAPPING[country_code] - if partner.peppol_eas not in eas_to_field.keys(): - new_eas = next(iter(EAS_MAPPING[country_code].keys())) + if partner.peppol_eas not in eas_to_field: + candidates = { + eas: field + for eas, field in eas_to_field.items() + if eas not in DEPRECATED_PEPPOL_EAS + } or eas_to_field + new_eas = next(iter(candidates)) # Iterate on the possible EAS until a valid one is found - for eas, field in eas_to_field.items(): + for eas, field in candidates.items(): if field and field in partner._fields: value = partner._get_peppol_endpoint_value(country_code, field, eas) if value and not partner._build_error_peppol_endpoint(eas, value): @@ -298,10 +303,14 @@ def _compute_peppol_eas(self): partner.peppol_eas = new_eas @api.depends_context('company') - @api.depends('company_id') + @api.depends('company_id', 'peppol_eas') def _compute_available_peppol_eas(self): # TO OVERRIDE - self.available_peppol_eas = list(dict(self._fields['peppol_eas'].selection)) + for partner in self: + partner.available_peppol_eas = [ + eas for eas in dict(partner._fields['peppol_eas'].selection) + if eas not in DEPRECATED_PEPPOL_EAS or eas == partner.peppol_eas + ] def _build_error_peppol_endpoint(self, eas, endpoint): """ This function contains all the rules regarding the peppol_endpoint.""" diff --git a/addons/account_edi_ubl_cii/tests/test_files/import/bis3/invoice/be/test_import_document_charge_tax_alignment.xml b/addons/account_edi_ubl_cii/tests/test_files/import/bis3/invoice/be/test_import_document_charge_tax_alignment.xml new file mode 100644 index 00000000000000..d620edf5e54396 --- /dev/null +++ b/addons/account_edi_ubl_cii/tests/test_files/import/bis3/invoice/be/test_import_document_charge_tax_alignment.xml @@ -0,0 +1,136 @@ + + + urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0 + urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 + INV/DOC-CHARGE/0001 + 2026-07-01 + 380 + EUR + + + 0202239951 + + company_1_data + + + Chaussée de Namur 40 + Ramillies + 1367 + + BE + + + + BE0202239951 + + VAT + + + + company_1_data + BE0202239951 + + + + + + 0477472701 + + partner_a + + + Rue des Bourlottes 9 + Ramillies + 1367 + + BE + + + + BE0477472701 + + VAT + + + + partner_a + BE0477472701 + + + + + true + AEL + Charge + 15.00 + + S + 21.00 + + VAT + + + + + 14.07 + + 67.00 + 14.07 + + S + 21.00 + + VAT + + + + + + 52.00 + 67.00 + 81.07 + 15.00 + 81.07 + + + 10 + 1.00 + 34.83 + + test_p1 + test_p1 + + S + 21.00 + + VAT + + + + + 34.83 + + + + 20 + 1.00 + 17.17 + + test_p2 + test_p2 + + S + 21.00 + + VAT + + + + + 17.17 + + + diff --git a/addons/account_edi_ubl_cii/tests/test_files/import/bis3/invoice/be/test_partial_import_invoice_line_with_discount_and_included_tax.xml b/addons/account_edi_ubl_cii/tests/test_files/import/bis3/invoice/be/test_partial_import_invoice_line_with_discount_and_included_tax.xml index b79e9ed0d8cbbd..a0cf1633c699b2 100644 --- a/addons/account_edi_ubl_cii/tests/test_files/import/bis3/invoice/be/test_partial_import_invoice_line_with_discount_and_included_tax.xml +++ b/addons/account_edi_ubl_cii/tests/test_files/import/bis3/invoice/be/test_partial_import_invoice_line_with_discount_and_included_tax.xml @@ -44,4 +44,24 @@ + + 0.0 + 1.0 + + false + Mutually Agreed + 100.0 + + + turlututu2 + + S + 21.00 + + VAT + + + + 100.0 + diff --git a/addons/account_edi_ubl_cii/tests/test_ubl_cii.py b/addons/account_edi_ubl_cii/tests/test_ubl_cii.py index 0f7e85ffcf734b..4129397619bdc1 100644 --- a/addons/account_edi_ubl_cii/tests/test_ubl_cii.py +++ b/addons/account_edi_ubl_cii/tests/test_ubl_cii.py @@ -978,3 +978,43 @@ def test_payment_terms_early_payment_discount_in_cii_xml(self): self.assertEqual(due_date.text, '20251231') self.assertEqual(days.text, '15') self.assertEqual(percent.text, '3.0') + + def test_facturx_export_non_eu_supplier_to_eu_customer(self): + """Test that a non-EU/EEA supplier (e.g. Switzerland) exporting goods to an EU customer + is classified as 'G' / VATEX-EU-G (export outside the EU). + """ + switzerland = self.env.ref("base.ch") + germany = self.env.ref("base.de") + + company = self.env.company + company.country_id = switzerland.id + company.vat = 'CHE-123.456.788 MWST' + + self.partner_a.country_id = germany.id + self.partner_a.invoice_edi_format = 'facturx' + + tax_0_export = self.env['account.tax'].create({ + 'name': 'CH Export 0%', + 'amount': 0.0, + 'amount_type': 'percent', + 'type_tax_use': 'sale', + }) + + invoice = self.env['account.move'].create({ + 'partner_id': self.partner_a.id, + 'move_type': 'out_invoice', + 'invoice_date': fields.Date.from_string('2025-12-22'), + 'invoice_line_ids': [Command.create({ + 'product_id': self.product_a.id, + 'tax_ids': [Command.set(tax_0_export.ids)], + })], + }) + invoice.action_post() + + xml_bytes = self.env["account.edi.xml.cii"]._export_invoice(invoice)[0] + xml_tree = etree.fromstring(xml_bytes) + + category_code = xml_tree.find('.//ram:ApplicableTradeTax/ram:CategoryCode', self.namespaces) + exemption_reason_code = xml_tree.find('.//ram:ApplicableTradeTax/ram:ExemptionReasonCode', self.namespaces) + self.assertEqual(category_code.text, 'G') + self.assertEqual(exemption_reason_code.text, 'VATEX-EU-G') diff --git a/addons/account_edi_ubl_cii/tests/test_ubl_export_bis3_be.py b/addons/account_edi_ubl_cii/tests/test_ubl_export_bis3_be.py index 446e622d382b05..3091b77c40e518 100644 --- a/addons/account_edi_ubl_cii/tests/test_ubl_export_bis3_be.py +++ b/addons/account_edi_ubl_cii/tests/test_ubl_export_bis3_be.py @@ -36,6 +36,27 @@ def test_invoice_item_description_name(self): self._generate_invoice_ubl_file(invoice) self._assert_invoice_ubl_file(invoice, 'test_invoice_item_description_name') + def test_invoice_buyer_reference_uses_partner_ref(self): + tax_21 = self.percent_tax(21.0) + product = self._create_product(lst_price=100.0, taxes_id=tax_21) + + customer_company = self.partner_be + customer_contact = self._create_partner(name='Customer contact 1', parent_id=customer_company.id, country_code='BE', ref='CONTACT-REF') + customer_company.ref = 'PARENT-REF' + + invoice = self._create_invoice_one_line( + product_id=product, + partner_id=customer_contact, + post=True, + ) + + self._generate_invoice_ubl_file(invoice) + + xml_tree = etree.fromstring(invoice.ubl_cii_xml_id.raw) + buyer_reference = xml_tree.find('.//{*}BuyerReference') + self.assertIsNotNone(buyer_reference) + self.assertEqual(buyer_reference.text, customer_contact.ref) + def test_invoice_payee_financial_account(self): bank_kbc = self.env['res.bank'].create({ 'name': 'KBC', diff --git a/addons/account_edi_ubl_cii/tests/test_ubl_import_bis3_invoice_be_decode_invoice_line.py b/addons/account_edi_ubl_cii/tests/test_ubl_import_bis3_invoice_be_decode_invoice_line.py index 0e82b2544c6e5a..687ec02072b8da 100644 --- a/addons/account_edi_ubl_cii/tests/test_ubl_import_bis3_invoice_be_decode_invoice_line.py +++ b/addons/account_edi_ubl_cii/tests/test_ubl_import_bis3_invoice_be_decode_invoice_line.py @@ -190,9 +190,17 @@ def test_partial_import_invoice_line_with_discount_and_included_tax(self): 'company_id': self.company_data['company'].id }) invoice = self._import_invoice_as_attachment_on(test_name='test_partial_import_invoice_line_with_discount_and_included_tax') - self.assertRecordValues(invoice.invoice_line_ids, [{ - 'price_unit': 121.0, - 'quantity': 2.0, - 'discount': 50.0, - 'price_subtotal': 100.0, - }]) + self.assertRecordValues(invoice.invoice_line_ids, [ + { + 'price_unit': 121.0, + 'quantity': 2.0, + 'discount': 50.0, + 'price_subtotal': 100.0, + }, + { + 'price_unit': 121.0, + 'quantity': 1.0, + 'discount': 100.0, + 'price_subtotal': 0.0, + } + ]) diff --git a/addons/account_edi_ubl_cii/tests/test_ubl_import_bis3_invoice_be_retrieve_tax.py b/addons/account_edi_ubl_cii/tests/test_ubl_import_bis3_invoice_be_retrieve_tax.py index c938abbba5458e..15577972485c33 100644 --- a/addons/account_edi_ubl_cii/tests/test_ubl_import_bis3_invoice_be_retrieve_tax.py +++ b/addons/account_edi_ubl_cii/tests/test_ubl_import_bis3_invoice_be_retrieve_tax.py @@ -276,3 +276,30 @@ def test_partial_import_tax_included_invoice(self): }, ], ) + + def test_import_document_charge_with_different_tax(self): + tax_21_m = self.percent_tax(21.0, type_tax_use='purchase', name='VAT 21% M') + tax_21_s = self.percent_tax(21.0, type_tax_use='purchase', name='VAT 21% S') + + def mocked_import_retrieve_tax(self, search_plan, company, tax_values_list): + for tax_values in tax_values_list: + if tax_values.get('invoice_predictive'): + tax_values['tax'] = tax_21_m + elif tax_values.get('_tax_key'): + tax_values['tax'] = tax_21_s + + self.patch(self.env.registry['account.tax'], '_import_retrieve_tax', mocked_import_retrieve_tax) + + invoice = self._import_invoice_as_attachment_on( + test_name='test_import_document_charge_tax_alignment', + journal=self.company_data['default_journal_purchase'], + ) + + self.assertRecordValues( + invoice, + [{ + 'amount_untaxed': 67.0, + 'amount_tax': 14.07, + 'amount_total': 81.07, + }], + ) diff --git a/addons/account_fleet/i18n/vi.po b/addons/account_fleet/i18n/vi.po index 7881ea4d1128f6..08b848a92e40fc 100644 --- a/addons/account_fleet/i18n/vi.po +++ b/addons/account_fleet/i18n/vi.po @@ -101,7 +101,7 @@ msgstr "Bút toán" #. module: account_fleet #: model:ir.model,name:account_fleet.model_account_move_line msgid "Journal Item" -msgstr "Hạng mục bút toán" +msgstr "Phát sinh kế toán" #. module: account_fleet #: model:ir.model.fields,field_description:account_fleet.field_account_move_line__need_vehicle diff --git a/addons/account_payment/i18n/ar.po b/addons/account_payment/i18n/ar.po index 25be8517ec0c97..b0a7613cc8da43 100644 --- a/addons/account_payment/i18n/ar.po +++ b/addons/account_payment/i18n/ar.po @@ -8,13 +8,14 @@ # Niyas Raphy, 2022 # Malaz Abuidris , 2023 # "Tiffany Chang (tic)" , 2025, 2026. +# "Malaz Siddig Elsayed Abuidris (msea)" , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-05-29 17:36+0000\n" -"PO-Revision-Date: 2026-08-03 08:30+0000\n" -"Last-Translator: Anonymous \n" +"PO-Revision-Date: 2026-08-24 05:11+0000\n" +"Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -191,7 +192,7 @@ msgstr "المعاملات المُصرح بها" #. module: account_payment #: model:ir.model,name:account_payment.model_account_bank_statement_line msgid "Bank Statement Line" -msgstr "" +msgstr "بند كشف الحساب البنكي" #. module: account_payment #: model_terms:ir.ui.view,arch_db:account_payment.account_invoice_view_form_inherit_payment @@ -629,7 +630,7 @@ msgstr "دفتر اليومية الذي يتم ترحيل المعاملات ا #. odoo-python #: code:addons/account_payment/models/payment_transaction.py:0 msgid "The payment related to transaction %(ref)s has been posted: %(link)s" -msgstr "" +msgstr "تم ترحيل الدفعة المتعلقة بالمعاملة %(ref)s: %(link)s" #. module: account_payment #. odoo-python diff --git a/addons/account_payment/i18n/sv.po b/addons/account_payment/i18n/sv.po index 8ac67bd385e3e3..d9717b7f438319 100644 --- a/addons/account_payment/i18n/sv.po +++ b/addons/account_payment/i18n/sv.po @@ -21,7 +21,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-05-29 17:36+0000\n" -"PO-Revision-Date: 2026-08-07 19:22+0000\n" +"PO-Revision-Date: 2026-08-24 07:57+0000\n" "Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" @@ -525,7 +525,7 @@ msgstr "Guide till återbetalningar" #. module: account_payment #: model:ir.ui.menu,name:account_payment.payment_token_menu msgid "Payment Tokens" -msgstr "Tokens" +msgstr "Betalningstokens" #. module: account_payment #: model:ir.model,name:account_payment.model_payment_transaction diff --git a/addons/account_peppol/controllers/portal.py b/addons/account_peppol/controllers/portal.py index 42e0f9b50460d7..85ee1b3e19a2fd 100644 --- a/addons/account_peppol/controllers/portal.py +++ b/addons/account_peppol/controllers/portal.py @@ -16,9 +16,14 @@ class PortalAccount(CustomerPortal): def _prepare_my_account_rendering_values(self, *args, **kwargs): rendering_values = super()._prepare_my_account_rendering_values(*args, **kwargs) if request.env.company.peppol_can_send: + partner = request.env.user.partner_id rendering_values['invoice_sending_methods'].update({'peppol': _("by Peppol")}) + peppol_eas_list = { + code: label for code, label in partner._fields['peppol_eas']._description_selection(request.env) + if code in (partner.available_peppol_eas or []) + } rendering_values.update({ - 'peppol_eas_list': dict(request.env['res.partner']._fields['peppol_eas']._description_selection(request.env)), + 'peppol_eas_list': peppol_eas_list, }) return rendering_values diff --git a/addons/account_peppol/i18n/ja.po b/addons/account_peppol/i18n/ja.po index d142ec35105daf..0abd5b84683942 100644 --- a/addons/account_peppol/i18n/ja.po +++ b/addons/account_peppol/i18n/ja.po @@ -10,8 +10,8 @@ msgstr "" "Project-Id-Version: Odoo Server saas~18.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-24 17:38+0000\n" -"PO-Revision-Date: 2026-08-06 02:01+0000\n" -"Last-Translator: Weblate \n" +"PO-Revision-Date: 2026-08-24 03:12+0000\n" +"Last-Translator: \"Junko Augias (juau)\" \n" "Language-Team: Japanese \n" "Language: ja\n" @@ -627,8 +627,8 @@ msgid "" "Any self-billed invoices / credit notes received via Peppol will be created " "in draft in this journal. Defaults to the first sale journal." msgstr "" -"Peppol経由で受信した自己請求書 / クレジットノートは、この仕訳帳にドラフトとし" -"て作成されます。デフォルトでは最初の売上仕訳帳が使用されます。" +"Peppol経由で受信した自己請求書 / クレジットノートは、この仕訳帳に下書きとして" +"作成されます。デフォルトでは最初の売上仕訳帳が使用されます。" #. module: account_peppol #. odoo-javascript diff --git a/addons/account_peppol/models/account_edi_proxy_user.py b/addons/account_peppol/models/account_edi_proxy_user.py index 6ad21802640167..006b085835738c 100644 --- a/addons/account_peppol/models/account_edi_proxy_user.py +++ b/addons/account_peppol/models/account_edi_proxy_user.py @@ -316,23 +316,31 @@ def _peppol_get_new_documents(self, skip_no_journal=False): 'Error while receiving the document from Peppol Proxy: %s', e.message) continue - message_uuids = [ - message['uuid'] - for message in messages.get('messages', []) + received_messages = messages.get('messages', []) + # Edge case: self-addressed messages (sender == receiver), i.e. a company genuinely + # invoicing itself. The outgoing invoice already carries the message UUID, so the + # duplicate check would wrongly discard the incoming document. + # Exclude those messages from the check so the vendor bill can still be created. + uuids_to_check = [ + message['uuid'] for message in received_messages + if message.get('sender') and message['sender'] != message['receiver'] ] - # remove the duplicates - if duplicate_message_uuids := list(edi_user._peppol_get_duplicate_message_uuids(message_uuids)): - message_uuids = list(set(message_uuids) - set(duplicate_message_uuids)) - # acknowledge the duplicates on IAP side. + # Acknowledge the duplicates on IAP side. + if duplicate_message_uuids := edi_user._peppol_get_duplicate_message_uuids(uuids_to_check): edi_user._call_peppol_proxy( endpoint=edi_user._get_peppol_proxy_endpoint('1/ack'), - params={'message_uuids': duplicate_message_uuids}, + params={'message_uuids': list(duplicate_message_uuids)}, ) _logger.info( "Messages with UUID %s could not be imported because they are identified as duplicates", ', '.join(duplicate_message_uuids) ) + # Remove the duplicates + message_uuids = [ + message['uuid'] for message in received_messages + if message['uuid'] not in duplicate_message_uuids + ] if not message_uuids: continue diff --git a/addons/account_peppol/models/res_partner.py b/addons/account_peppol/models/res_partner.py index fa93933d88000b..175423031c7126 100644 --- a/addons/account_peppol/models/res_partner.py +++ b/addons/account_peppol/models/res_partner.py @@ -65,13 +65,14 @@ def _compute_available_peppol_edi_formats(self): else: partner.available_peppol_edi_formats = list(dict(self._fields['invoice_edi_format'].selection)) + @api.depends('peppol_eas') def _compute_available_peppol_eas(self): # EXTENDS 'account_edi_ubl_cii' super()._compute_available_peppol_eas() - eas_codes = set(self[:1].available_peppol_eas) - if self.env.company._get_peppol_edi_mode() != 'demo' and 'odemo' in eas_codes: - eas_codes.remove('odemo') - self.available_peppol_eas = list(eas_codes) + is_demo = self.env.company._get_peppol_edi_mode() == 'demo' + for partner in self: + if not is_demo and partner.available_peppol_eas and 'odemo' in partner.available_peppol_eas: + partner.available_peppol_eas = [eas for eas in partner.available_peppol_eas if eas != 'odemo'] # ------------------------------------------------------------------------- # HELPERS diff --git a/addons/account_peppol/wizard/peppol_registration.py b/addons/account_peppol/wizard/peppol_registration.py index de8ed0fdf27409..05f8f206447610 100644 --- a/addons/account_peppol/wizard/peppol_registration.py +++ b/addons/account_peppol/wizard/peppol_registration.py @@ -11,6 +11,7 @@ from odoo.tools.urls import urljoin from odoo.addons.account_peppol.tools.demo_utils import handle_demo +from odoo.addons.account_edi_ubl_cii.models.account_edi_common import DEPRECATED_PEPPOL_EAS from odoo.addons.account_peppol.tools.peppol_iap_connector import PeppolIAPConnector _logger = logging.getLogger(__name__) @@ -254,7 +255,11 @@ def _inverse_peppol_eas(self): # BUSINESS ACTIONS # ------------------------------------------------------------------------- def _get_peppol_eas_selection(self): - return self.env['res.company']._fields['peppol_eas']._description_selection(self.env) + return [ + (eas, label) + for eas, label in self.env['res.company']._fields['peppol_eas']._description_selection(self.env) + if eas not in DEPRECATED_PEPPOL_EAS or eas == self.env.company.peppol_eas + ] def _branch_with_same_address(self): self.ensure_one() diff --git a/addons/account_qr_code_emv/i18n/ar.po b/addons/account_qr_code_emv/i18n/ar.po index ae0a7ea8c26e69..edddc4aa28e2d2 100644 --- a/addons/account_qr_code_emv/i18n/ar.po +++ b/addons/account_qr_code_emv/i18n/ar.po @@ -4,13 +4,14 @@ # # "Tiffany Chang (tic)" , 2025, 2026. # "Dylan Kiss (dyki)" , 2025, 2026. +# "Malaz Siddig Elsayed Abuidris (msea)" , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server saas~18.2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-06 05:42+0000\n" -"Last-Translator: \"Dylan Kiss (dyki)\" \n" +"PO-Revision-Date: 2026-08-24 05:09+0000\n" +"Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -36,7 +37,7 @@ msgstr "الحسابات البنكية" #: model:ir.model.fields,field_description:account_qr_code_emv.field_account_setup_bank_manual_config__country_proxy_keys #: model:ir.model.fields,field_description:account_qr_code_emv.field_res_partner_bank__country_proxy_keys msgid "Country Proxy Keys" -msgstr "" +msgstr "مفاتيح الوكيل الخاصة بالبلد" #. module: account_qr_code_emv #: model:ir.model.fields,field_description:account_qr_code_emv.field_res_partner_bank__display_name diff --git a/addons/account_tax_python/i18n/ar.po b/addons/account_tax_python/i18n/ar.po index 94b239ff99a3db..caa77b44601655 100644 --- a/addons/account_tax_python/i18n/ar.po +++ b/addons/account_tax_python/i18n/ar.po @@ -6,13 +6,14 @@ # Martin Trigaux, 2022 # # "Dylan Kiss (dyki)" , 2025, 2026. +# "Malaz Siddig Elsayed Abuidris (msea)" , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server saas~14.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-02 13:39+0000\n" -"Last-Translator: Anonymous \n" +"PO-Revision-Date: 2026-08-24 05:09+0000\n" +"Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -130,7 +131,7 @@ msgstr "لا يُسمح إلا بالأنواع البدائية في سياق #. odoo-python #: code:addons/account_tax_python/tools/formula_utils.py:0 msgid "Only product['string'] or uom['string'] read-access is allowed" -msgstr "" +msgstr "يُسمح فقط بالوصول للقراءة إلى product['string'] أو uom['string']" #. module: account_tax_python #. odoo-python diff --git a/addons/account_update_tax_tags/i18n/vi.po b/addons/account_update_tax_tags/i18n/vi.po index 1f9897db3aa2da..ce73cd93e02ed8 100644 --- a/addons/account_update_tax_tags/i18n/vi.po +++ b/addons/account_update_tax_tags/i18n/vi.po @@ -37,7 +37,7 @@ msgstr "Được tạo vào" #. module: account_update_tax_tags #: model:ir.model.fields,help:account_update_tax_tags.field_account_update_tax_tags_wizard__date_from msgid "Date from which journal items will be updated." -msgstr "Ngày mà các hạng mục bút toán sẽ được cập nhật." +msgstr "Ngày mà các phát sinh kế toán sẽ được cập nhật." #. module: account_update_tax_tags #: model_terms:ir.ui.view,arch_db:account_update_tax_tags.view_account_update_tax_tags_wizard_form diff --git a/addons/analytic/i18n/ar.po b/addons/analytic/i18n/ar.po index 400903c11cf3fb..1e4140e89cfa36 100644 --- a/addons/analytic/i18n/ar.po +++ b/addons/analytic/i18n/ar.po @@ -15,8 +15,8 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-07 07:27+0000\n" -"Last-Translator: Weblate \n" +"PO-Revision-Date: 2026-08-24 05:09+0000\n" +"Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -73,7 +73,7 @@ msgstr "الحسابات" #. module: analytic #: model:account.analytic.account,name:analytic.analytic_agrolait msgid "Acme Corporation" -msgstr "" +msgstr "شركة Acme" #. module: analytic #: model:ir.model.fields,field_description:analytic.field_account_analytic_account__message_needaction @@ -919,7 +919,7 @@ msgstr "الخطط الفرعية" #. module: analytic #: model:ir.model,name:analytic.model_ir_config_parameter msgid "System Parameter" -msgstr "" +msgstr "معيار النظام" #. module: analytic #. odoo-python diff --git a/addons/analytic/static/src/components/analytic_distribution/analytic_distribution.js b/addons/analytic/static/src/components/analytic_distribution/analytic_distribution.js index e422277a646f69..6155b369cfa7c7 100644 --- a/addons/analytic/static/src/components/analytic_distribution/analytic_distribution.js +++ b/addons/analytic/static/src/components/analytic_distribution/analytic_distribution.js @@ -500,7 +500,6 @@ export class AnalyticDistribution extends Component { } onSaveNew() { - this.closeAnalyticEditor(); const { record, product_field, account_field } = this.props; this.openTemplate({ resId: false, context: { 'default_analytic_distribution': this.dataToJson(), diff --git a/addons/analytic/static/tests/analytic_test_helpers.js b/addons/analytic/static/tests/analytic_test_helpers.js index cbe09125b94870..e22d93ce793ab6 100644 --- a/addons/analytic/static/tests/analytic_test_helpers.js +++ b/addons/analytic/static/tests/analytic_test_helpers.js @@ -1,4 +1,5 @@ import { AccountAnalyticAccount } from "@analytic/../tests/mock_server/mock_models/account_analytic_account"; +import { AccountAnalyticDistributionModel } from "@analytic/../tests/mock_server/mock_models/account_analytic_distribution_model"; import { AccountAnalyticLine } from "@analytic/../tests/mock_server/mock_models/account_analytic_line"; import { AccountAnalyticPlan } from "@analytic/../tests/mock_server/mock_models/account_analytic_plan"; import { mailModels } from "@mail/../tests/mail_test_helpers"; @@ -6,6 +7,7 @@ import { defineModels } from "@web/../tests/web_test_helpers"; export const analyticModels = { AccountAnalyticAccount, + AccountAnalyticDistributionModel, AccountAnalyticLine, AccountAnalyticPlan, }; diff --git a/addons/analytic/static/tests/analytic_widget.test.js b/addons/analytic/static/tests/analytic_widget.test.js index 8566d99e1ee955..56ecdca1c09906 100644 --- a/addons/analytic/static/tests/analytic_widget.test.js +++ b/addons/analytic/static/tests/analytic_widget.test.js @@ -167,3 +167,42 @@ test("Analytic dynamic multi-edit", async () => { expect(".o_list_table tbody tr:nth-child(2) .o_field_analytic_distribution .o_tag:nth-child(1) .o_tag_badge_text").toHaveText("Brussels"); expect(".o_list_table tbody tr:nth-child(2) .o_field_analytic_distribution .o_tag:nth-child(2) .o_tag_badge_text").toHaveText("Belgium"); }) + + +test.tags("desktop"); +test("Analytic multi-edit: New Model dialog stays open", async () => { + await mountView({ + type: "list", + resModel: "account.analytic.line", + arch: ` + + + + + + `, + }); + + await contains(".o_list_table tbody tr:nth-child(1) .o_list_record_selector input").click(); + await contains(".o_list_table tbody tr:nth-child(2) .o_list_record_selector input").click(); + + // open the widget and set a valid distribution line so the "New Model" button appears + await contains(".o_list_table tbody tr:first .o_field_analytic_distribution").click(); + await animationFrame(); + await contains(".analytic_distribution_popup .o_list_table thead th:first a").click(); + await contains(".analytic_distribution_popup tbody tr:first .o_field_many2one").click(); + await contains(".analytic_distribution_popup tbody tr:first .o_field_many2one input").edit("Brussels", { confirm: false }); + await runAllTimers(); + await contains(".analytic_distribution_popup tbody tr:first .o_field_many2one .o_input_dropdown a").click(); + + // click "New Model": opens the "Create Analytic Distribution Model" dialog + await contains(".analytic_distribution_popup .popover-header .btn-link").click(); + await animationFrame(); + await runAllTimers(); + + // the model dialog is the only dialog: New Model no longer saves the records, so no + // multi-edit confirmation is triggered and the widget is not closed/reloaded away + expect(".o_dialog .o_form_view").toHaveCount(1); + expect(".o_dialog").toHaveCount(1); + expect(".analytic_distribution_popup").toHaveCount(1); +}) diff --git a/addons/analytic/static/tests/mock_server/mock_models/account_analytic_distribution_model.js b/addons/analytic/static/tests/mock_server/mock_models/account_analytic_distribution_model.js new file mode 100644 index 00000000000000..acda480f55cb94 --- /dev/null +++ b/addons/analytic/static/tests/mock_server/mock_models/account_analytic_distribution_model.js @@ -0,0 +1,7 @@ +import { models, fields } from "@web/../tests/web_test_helpers"; + +export class AccountAnalyticDistributionModel extends models.ServerModel { + _name = "account.analytic.distribution.model"; + + analytic_distribution = fields.Json(); +} diff --git a/addons/auth_passkey_portal/i18n/ar.po b/addons/auth_passkey_portal/i18n/ar.po index 6ce2423ee0b815..7c292430c6a9cf 100644 --- a/addons/auth_passkey_portal/i18n/ar.po +++ b/addons/auth_passkey_portal/i18n/ar.po @@ -9,8 +9,8 @@ msgstr "" "Project-Id-Version: Odoo Server 19.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-08 00:41+0000\n" -"Last-Translator: Anonymous \n" +"PO-Revision-Date: 2026-08-24 05:13+0000\n" +"Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -85,4 +85,4 @@ msgstr "إعادة التسمية" #. odoo-javascript #: code:addons/auth_passkey_portal/static/src/xml/passkeys_portal_popups.xml:0 msgid "Rename your key" -msgstr "" +msgstr "أعد تسمية مفتاحك" diff --git a/addons/auth_signup/i18n/ar.po b/addons/auth_signup/i18n/ar.po index a5692917982b21..631c17e616e9fd 100644 --- a/addons/auth_signup/i18n/ar.po +++ b/addons/auth_signup/i18n/ar.po @@ -14,8 +14,8 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0beta\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-03 17:36+0000\n" -"PO-Revision-Date: 2026-08-04 00:23+0000\n" -"Last-Translator: Weblate \n" +"PO-Revision-Date: 2026-08-24 05:11+0000\n" +"Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -1172,7 +1172,7 @@ msgstr "إعادة تعيين كلمة المرور" #. odoo-python #: code:addons/auth_signup/controllers/main.py:0 msgid "Password reset instructions sent to your email address." -msgstr "" +msgstr "تم إرسال إرشادات إعادة تعيين كلمة المرور إلى بريدك الإلكتروني." #. module: auth_signup #. odoo-python @@ -1206,7 +1206,7 @@ msgstr "إعادة تعيين كلمة المرور" #. module: auth_signup #: model_terms:ir.ui.view,arch_db:auth_signup.res_users_view_form msgid "Send Password Reset" -msgstr "" +msgstr "إرسال إعادة تعيين كلمة المرور" #. module: auth_signup #: model:ir.actions.server,name:auth_signup.action_send_password_reset_instructions @@ -1227,7 +1227,7 @@ msgstr "يتم إرساله تلقائياً إلى المدير إذا لم ي #. module: auth_signup #: model:mail.template,description:auth_signup.portal_set_password_email msgid "Sent to new portal user after you invited them" -msgstr "" +msgstr "يتم إرساله إلى مستخدم البوابة الجديد بعد أن قد قمت بدعوته" #. module: auth_signup #: model:mail.template,description:auth_signup.set_password_email @@ -1247,7 +1247,7 @@ msgstr "الإعدادات: تسجيل الدخول إلى البوابة الج #. module: auth_signup #: model:mail.template,name:auth_signup.portal_set_password_email msgid "Settings: New Portal User Invite" -msgstr "" +msgstr "الإعدادات: دعوة مستخدم جديد للبوابة" #. module: auth_signup #: model:mail.template,name:auth_signup.set_password_email @@ -1373,7 +1373,7 @@ msgstr "حسابك في {{ object.company_id.name }}" #. odoo-python #: code:addons/auth_signup/controllers/main.py:0 msgid "Your password has been reset successfully." -msgstr "" +msgstr "تمت إعادة تعيين كلمة مرورك بنجاح." #. module: auth_signup #: model_terms:ir.ui.view,arch_db:auth_signup.reset_password_email diff --git a/addons/auth_signup/i18n/fa.po b/addons/auth_signup/i18n/fa.po index b135b2edf1ecc8..1789085a496739 100644 --- a/addons/auth_signup/i18n/fa.po +++ b/addons/auth_signup/i18n/fa.po @@ -12,13 +12,14 @@ # Hosein Nematolahi , 2025, 2026. # Weblate , 2025, 2026. # Aliraza Habibitanha , 2026. +# Heshmatkhah , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0beta\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-03 17:36+0000\n" -"PO-Revision-Date: 2026-08-09 10:11+0000\n" -"Last-Translator: Aliraza Habibitanha \n" +"PO-Revision-Date: 2026-08-23 06:16+0000\n" +"Last-Translator: Heshmatkhah \n" "Language-Team: Persian \n" "Language: fa\n" @@ -316,7 +317,7 @@ msgstr "" " حساب " "کاربریتان
\n" " Marc Demo\n" +"weight: bold;\" t-out=\"object.name or ''\">ماهان دمو‌پور
\n" " \n" " \n" @@ -344,7 +345,7 @@ msgstr "" "13px;\">\n" "
\n" " کاربر عزیز" -"Marc Demo,

\n" +"ماهان دمو‌پور,

\n" " به پورتال YourCompany خوش‌آمدید!

\n" " با نام کاربری زیر یک حساب کاربری برای " @@ -580,7 +581,7 @@ msgstr "" " به اودو خوش آمدید" "
\n" " \n" -" Marc Demo\n" +" ماهان دمو‌پور\n" " \n" " \n" @@ -604,7 +605,7 @@ msgstr "" "0px 8px 0px 8px; border-collapse:separate;\">\n" " \n" "
\n" -" عزیز Marc Demo" +" عزیز ماهان دمو‌پور" ",

\n" " شما توسط " "OdooBot از YourCompany " @@ -842,7 +843,7 @@ msgstr "" "\n" " " "\n" -" کارمند دمو\n" +" ماهان دمو‌پور\n" " \n" " \n" " \n" " \n" "
\n" -" کارمند دمو " +" ماهان دمو‌پور " "گرامی،

\n" " با سلام،

\n" " حساب کاربری شما با موفقیت ایجاد شد!
\n" @@ -1145,7 +1146,7 @@ msgstr "برای مشاهده اسناد، به مشتریان خود اجازه #. module: auth_signup #: model_terms:ir.ui.view,arch_db:auth_signup.reset_password_email msgid "Marc Demo" -msgstr "دموی مارچ" +msgstr "ماهان دمو‌پور" #. module: auth_signup #. odoo-python diff --git a/addons/auth_signup/i18n/zh_TW.po b/addons/auth_signup/i18n/zh_TW.po index 46e0fa965082c9..b09d5657ff0ba6 100644 --- a/addons/auth_signup/i18n/zh_TW.po +++ b/addons/auth_signup/i18n/zh_TW.po @@ -14,8 +14,8 @@ msgstr "" "Project-Id-Version: Odoo Server saas~18.3\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-03 17:36+0000\n" -"PO-Revision-Date: 2026-08-02 11:23+0000\n" -"Last-Translator: Weblate \n" +"PO-Revision-Date: 2026-08-24 03:13+0000\n" +"Last-Translator: \"Tony Ng (ngto)\" \n" "Language-Team: Chinese (Traditional Han script) \n" "Language: zh_TW\n" @@ -964,7 +964,7 @@ msgstr "如果新用戶未響應邀請,則自動發送給管理員" #. module: auth_signup #: model:mail.template,description:auth_signup.portal_set_password_email msgid "Sent to new portal user after you invited them" -msgstr "" +msgstr "會傳送至你邀請成為門戶網站新使用者的人" #. module: auth_signup #: model:mail.template,description:auth_signup.set_password_email @@ -984,7 +984,7 @@ msgstr "設定:門戶網站新用戶註冊" #. module: auth_signup #: model:mail.template,name:auth_signup.portal_set_password_email msgid "Settings: New Portal User Invite" -msgstr "" +msgstr "設定:邀請門戶網站新使用者" #. module: auth_signup #: model:mail.template,name:auth_signup.set_password_email @@ -1101,7 +1101,7 @@ msgstr "您不能對已存檔的用戶執行此操作。" #. module: auth_signup #: model:mail.template,subject:auth_signup.portal_set_password_email msgid "Your account at {{ object.company_id.name }}" -msgstr "" +msgstr "你的 {{ object.company_id.name }} 帳戶" #. module: auth_signup #. odoo-python diff --git a/addons/auth_signup/models/res_partner.py b/addons/auth_signup/models/res_partner.py index d4cf1ad68821e8..d60137f885b3f3 100644 --- a/addons/auth_signup/models/res_partner.py +++ b/addons/auth_signup/models/res_partner.py @@ -55,8 +55,7 @@ def _get_signup_url_for_action(self, url=None, action=None, view_type=None, menu signup_type = self.env.context.get('signup_force_type_in_url', partner.sudo().signup_type or '') if signup_type: route = 'reset_password' if signup_type == 'reset' else signup_type - - query['token'] = partner.sudo()._generate_signup_token() + query['token'] = partner.sudo()._generate_signup_token() if url: query['redirect'] = url diff --git a/addons/auth_signup/tests/test_auth_signup.py b/addons/auth_signup/tests/test_auth_signup.py index 6dc9e5d990ce0b..8611d63fd61336 100644 --- a/addons/auth_signup/tests/test_auth_signup.py +++ b/addons/auth_signup/tests/test_auth_signup.py @@ -3,6 +3,7 @@ from contextlib import contextmanager from unittest.mock import patch +from urllib.parse import parse_qs, urlsplit import odoo from odoo import http @@ -101,6 +102,12 @@ def test_compute_signup_url(self): with self.assertRaises(AccessError): partner.with_user(user.id)._get_signup_url() + # Assert no token generated if no signup type + partner.signup_cancel() + signup_url = partner._get_signup_url() + signup_url_params = parse_qs(urlsplit(signup_url).query) + self.assertFalse(signup_url_params.get('token')) + def test_copy_multiple_users(self): users = self.env['res.users'].create([ {'login': 'testuser1', 'name': 'Test User 1', 'email': 'test1@odoo.com'}, diff --git a/addons/auth_timeout/models/ir_http.py b/addons/auth_timeout/models/ir_http.py index 93818a3849048c..3a96dd1eda8454 100644 --- a/addons/auth_timeout/models/ir_http.py +++ b/addons/auth_timeout/models/ir_http.py @@ -3,6 +3,7 @@ import time from odoo import api, models +from odoo.exceptions import AccessDenied from odoo.http import request, root, SessionExpiredException @@ -88,6 +89,9 @@ def _check_identity(cls, credential): - {"mfa": True, "auth_methods": [...]} if a second factor is required, - None if re-authentication is complete. + :raises AccessDenied: if the credential type is not one of the user's enabled + authentication methods, or if the credential itself is invalid. + :rtype: dict or None """ check_identity = cls._must_check_identity() or {} @@ -99,6 +103,9 @@ def _check_identity(cls, credential): auth_methods.remove(first_fa) return {"user_id": user.id, "login": user.login, "auth_methods": auth_methods} + if credential.get("type") not in auth_methods: + raise AccessDenied() + if credential.get("type") in ("totp", "totp_mail"): credential["token"] = int(re.sub(r"\s", "", credential["token"])) diff --git a/addons/auth_totp/i18n/ar.po b/addons/auth_totp/i18n/ar.po index 476995fd8f195a..e68694e0c251a3 100644 --- a/addons/auth_totp/i18n/ar.po +++ b/addons/auth_totp/i18n/ar.po @@ -4,13 +4,14 @@ # # "Tiffany Chang (tic)" , 2025, 2026. # Weblate , 2025, 2026. +# "Malaz Siddig Elsayed Abuidris (msea)" , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server saas~18.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-05 02:50+0000\n" -"Last-Translator: Weblate \n" +"PO-Revision-Date: 2026-08-24 05:08+0000\n" +"Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -47,6 +48,10 @@ msgid "" " Enabled\n" " " msgstr "" +"\n" +" \n" +" مفعّل\n" +" " #. module: auth_totp #: model_terms:ir.ui.view,arch_db:auth_totp.view_totp_form @@ -56,6 +61,10 @@ msgid "" " Disabled\n" " " msgstr "" +"\n" +" \n" +" معطّل\n" +" " #. module: auth_totp #: model_terms:ir.ui.view,arch_db:auth_totp.view_totp_field @@ -69,7 +78,7 @@ msgstr "موصى به لمزيد من الأمان, 2025, 2026. # Weblate , 2025, 2026. +# "Malaz Siddig Elsayed Abuidris (msea)" , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server saas~18.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-02 14:11+0000\n" -"Last-Translator: Anonymous \n" +"PO-Revision-Date: 2026-08-24 05:12+0000\n" +"Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -226,7 +227,7 @@ msgstr "" #. module: auth_totp_mail #: model_terms:ir.ui.view,arch_db:auth_totp_mail.res_users_view_form msgid "Click to enable 2FA." -msgstr "" +msgstr "انقر لتمكين المصادقة الثنائية (2FA)." #. module: auth_totp_mail #: model:ir.model,name:auth_totp_mail.model_res_config_settings diff --git a/addons/barcodes/i18n/ar.po b/addons/barcodes/i18n/ar.po index cf0d1f87f23104..523a6af154b97f 100644 --- a/addons/barcodes/i18n/ar.po +++ b/addons/barcodes/i18n/ar.po @@ -8,13 +8,14 @@ # Malaz Abuidris , 2023 # "Tiffany Chang (tic)" , 2025, 2026. # "Dylan Kiss (dyki)" , 2025, 2026. +# "Malaz Siddig Elsayed Abuidris (msea)" , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server 15.5alpha1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-02 14:33+0000\n" -"Last-Translator: \"Tiffany Chang (tic)\" \n" +"PO-Revision-Date: 2026-08-24 05:11+0000\n" +"Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -411,7 +412,7 @@ msgstr "قيمة آخر باركود ممسوح ضوئيًا." #: code:addons/barcodes/models/barcode_nomenclature.py:0 msgid "" "You cannot delete '%(name)s' because it's the default barcode nomenclature." -msgstr "" +msgstr "لا يمكنك حذف '%(name)s' لأنها التسمية الافتراضية للباركود." #~ msgid "Barcode: " #~ msgstr "الباركود: " diff --git a/addons/barcodes_gs1_nomenclature/i18n/fa.po b/addons/barcodes_gs1_nomenclature/i18n/fa.po index 96c815deebfd54..72ff6fe524b2fb 100644 --- a/addons/barcodes_gs1_nomenclature/i18n/fa.po +++ b/addons/barcodes_gs1_nomenclature/i18n/fa.po @@ -4,13 +4,14 @@ # # Weblate , 2025, 2026. # Aliraza Habibitanha , 2026. +# Heshmatkhah , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server saas~18.2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-09 11:05+0000\n" -"Last-Translator: Aliraza Habibitanha \n" +"PO-Revision-Date: 2026-08-23 06:14+0000\n" +"Last-Translator: Heshmatkhah \n" "Language-Team: Persian \n" "Language: fa\n" @@ -204,7 +205,7 @@ msgid "" "encoding; * Alpha-Numeric Name: variable length barcode." msgstr "" "نوع محتوای GS1 تعریف می‌کند که قاعده، بارکد را به عنوان چه نوع داده‌ای پردازش " -"خواهد کرد: * تاریخ: بارکد به یک تاریخ و زمان در سازمان‌یار تبدیل خواهد شد؛ * " +"خواهد کرد: * تاریخ: بارکد به یک تاریخ و زمان در اودو تبدیل خواهد شد؛ * " "اندازه‌گیری: مقدار بارکد به یک واحد خاص مرتبط است؛ * شناسه عددی: بارکد با طول " "ثابت که از یک رمزگذاری خاص پیروی می‌کند؛ * نام الفبایی-عددی: بارکد با طول " "متغیر." diff --git a/addons/base_automation/i18n/sv.po b/addons/base_automation/i18n/sv.po index f32b1e977ad701..44be487f7d4cbf 100644 --- a/addons/base_automation/i18n/sv.po +++ b/addons/base_automation/i18n/sv.po @@ -19,7 +19,7 @@ msgstr "" "Project-Id-Version: Odoo Server 15.5alpha1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-14 13:09+0000\n" +"PO-Revision-Date: 2026-08-24 08:01+0000\n" "Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" @@ -884,7 +884,7 @@ msgstr "Tips: Automatisera allt med automatiseringsregler" #. module: base_automation #: model:ir.model.fields,field_description:base_automation.field_base_automation__trigger msgid "Trigger" -msgstr "Utlösare" +msgstr "Trigger" #. module: base_automation #: model:ir.model.fields,field_description:base_automation.field_base_automation__trg_date_id diff --git a/addons/base_import/i18n/ar.po b/addons/base_import/i18n/ar.po index e03e194a946032..889d5b4871ad3b 100644 --- a/addons/base_import/i18n/ar.po +++ b/addons/base_import/i18n/ar.po @@ -6,13 +6,14 @@ # Martin Trigaux, 2022 # Malaz Abuidris , 2023 # "Tiffany Chang (tic)" , 2025, 2026. +# "Malaz Siddig Elsayed Abuidris (msea)" , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server 15.5alpha1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-04 22:25+0000\n" -"Last-Translator: Anonymous \n" +"PO-Revision-Date: 2026-08-24 05:12+0000\n" +"Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -339,7 +340,7 @@ msgstr "معرف خارجي" #. odoo-python #: code:addons/base_import/models/base_import.py:0 msgid "Field '%(field)s' does not accept date/time values." -msgstr "" +msgstr "الحقل '%(field)s' لا يقبل قيم التاريخ/الوقت." #. module: base_import #: model:ir.model.fields,field_description:base_import.field_base_import_mapping__field_name @@ -807,6 +808,8 @@ msgid "" "There was an issue decoding the file using encoding “%s”.\n" "This encoding was automatically detected." msgstr "" +"حدثت مشكلة في فك تشفير الملف باستخدام الترميز \"%s\".\n" +"تم رصد هذا الترميز تلقائيًا." #. module: base_import #. odoo-python @@ -815,6 +818,8 @@ msgid "" "There was an issue decoding the file using encoding “%s”.\n" "This encoding was manually selected." msgstr "" +"حدثت مشكلة في فك تشفير الملف باستخدام الترميز \"%s\".\n" +"تم تحديد هذا الترميز يدويًّا." #. module: base_import #. odoo-javascript diff --git a/addons/base_import_module/i18n/ar.po b/addons/base_import_module/i18n/ar.po index a1475b567102e9..f7f7ea24520166 100644 --- a/addons/base_import_module/i18n/ar.po +++ b/addons/base_import_module/i18n/ar.po @@ -14,8 +14,8 @@ msgstr "" "Project-Id-Version: Odoo Server saas~12.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-06 05:52+0000\n" -"Last-Translator: Weblate \n" +"PO-Revision-Date: 2026-08-24 05:10+0000\n" +"Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -266,6 +266,8 @@ msgid "" "Note: you can only import data modules (.xml files, i18n files and static " "assets)" msgstr "" +"ملاحظة: يمكنك فقط استيراد تطبيقات البيانات (ملفات .xml وملفات i18n والأصول " +"الثابتة)" #. module: base_import_module #: model:ir.model.fields.selection,name:base_import_module.selection__ir_module_module__module_type__official diff --git a/addons/base_setup/i18n/ar.po b/addons/base_setup/i18n/ar.po index 524315e35d6799..805bf85b3eec87 100644 --- a/addons/base_setup/i18n/ar.po +++ b/addons/base_setup/i18n/ar.po @@ -7,13 +7,14 @@ # Malaz Abuidris , 2023 # "Tiffany Chang (tic)" , 2025, 2026. # Weblate , 2025, 2026. +# "Malaz Siddig Elsayed Abuidris (msea)" , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-07 01:19+0000\n" -"Last-Translator: Weblate \n" +"PO-Revision-Date: 2026-08-24 05:13+0000\n" +"Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -535,7 +536,7 @@ msgstr "احمِ استماراتك باستخدام CF Turnstile." #. odoo-javascript #: code:addons/base_setup/static/src/views/module_views.xml:0 msgid "Reset Updating States" -msgstr "" +msgstr "إعادة تعيين خاصية تحديث الحالات " #. module: base_setup #: model:ir.model.fields,field_description:base_setup.field_res_config_settings__module_sms diff --git a/addons/base_vat/i18n/ar.po b/addons/base_vat/i18n/ar.po index 590ca0022d72f5..48e5e2b589c635 100644 --- a/addons/base_vat/i18n/ar.po +++ b/addons/base_vat/i18n/ar.po @@ -12,7 +12,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-24 17:38+0000\n" -"PO-Revision-Date: 2026-08-06 05:42+0000\n" +"PO-Revision-Date: 2026-08-24 05:11+0000\n" "Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" @@ -40,7 +40,7 @@ msgstr "10XXXXXXXXY أو 20XXXXXXXXY أو 15XXXXXXXXY أو 16XXXXXXXXY أو 17XX #. odoo-python #: code:addons/base_vat/models/res_partner.py:0 msgid "11111111111 (NIN) or 2222222222 (VKN)" -msgstr "" +msgstr "11111111111 (NIN) أو 2222222222 (VKN)" #. module: base_vat #. odoo-python @@ -48,12 +48,13 @@ msgstr "" msgid "" "12345678 or UA12345678 (EDRPOU), 1234567890 (RNOPP) or 123456789012 (IPN)" msgstr "" +"12345678 أو UA12345678 (EDRPOU), 1234567890 (RNOPP) أو 123456789012 (IPN)" #. module: base_vat #. odoo-python #: code:addons/base_vat/models/res_partner.py:0 msgid "123456789 (TIN) or 12345678901234 (PINFL)" -msgstr "" +msgstr "123456789 (TIN) أو 12345678901234 (PINFL)" #. module: base_vat #. odoo-python @@ -76,7 +77,7 @@ msgstr "49-098-576 أو 49098576" #. module: base_vat #: model:ir.actions.server,name:base_vat.vies_iap_check_update_ir_actions_server msgid "Base VAT: Sync updates from IAP VIES" -msgstr "" +msgstr "ضريبة القيمة المضافة الأساسية: مزامنة التحديثات من IAP VIES" #. module: base_vat #. odoo-python @@ -183,7 +184,7 @@ msgstr "" #: code:addons/base_vat/models/res_partner.py:0 msgid "" "If you are trying to input a European number, this is the expected format: " -msgstr "" +msgstr "إذا كنت تحاول إدخال رقم أوروبي، فهذا هو التنسيق المتوقع: " #. module: base_vat #: model:ir.model.fields,field_description:base_vat.field_res_partner__vies_valid @@ -195,7 +196,7 @@ msgstr "Intra-Community Valid" #. odoo-python #: code:addons/base_vat/models/res_partner.py:0 msgid "Invalid IAP VIES endpoint" -msgstr "" +msgstr "نقطة نهاية IAP VIES غير صالحة" #. module: base_vat #. odoo-python @@ -238,13 +239,13 @@ msgstr "" #. odoo-python #: code:addons/base_vat/models/res_partner.py:0 msgid "The Intra-Community validity has been updated to: %s." -msgstr "" +msgstr "تم تحديث صلاحية \"داخل الاتحاد\" لتصبح: %s." #. module: base_vat #. odoo-python #: code:addons/base_vat/models/res_partner.py:0 msgid "The VIES check failed. Please check the Tax ID manually." -msgstr "" +msgstr "فشل التحقق من VIES. يرجى التحقق من رقم التعريف الضريبي يدويًّا." #. module: base_vat #. odoo-python @@ -285,13 +286,13 @@ msgstr "XXXXXXXXX [9 أرقام] ويجب أن تضع بعين الاعتبار #: model:ir.model.fields,help:base_vat.field_res_partner__vat #: model:ir.model.fields,help:base_vat.field_res_users__vat msgid "You can use '/' to indicate that the customer has no Tax ID." -msgstr "" +msgstr "يمكنك استخدام '/' للإشارة إلى أن العميل ليس لديه رقم تعريف ضريبي." #. module: base_vat #. odoo-python #: code:addons/base_vat/models/res_partner.py:0 msgid "either 11 digits for CPF or 14 characters for CNPJ" -msgstr "" +msgstr "إما 11 رقم لـ CPF أو 14 حرف لـ CNPJ " #. module: base_vat #. odoo-python diff --git a/addons/bus/i18n/ar.po b/addons/bus/i18n/ar.po index 4f5b409b6fbcdf..8c32483a1f00af 100644 --- a/addons/bus/i18n/ar.po +++ b/addons/bus/i18n/ar.po @@ -6,13 +6,14 @@ # Martin Trigaux, 2022 # Malaz Abuidris , 2023 # "Tiffany Chang (tic)" , 2025, 2026. +# "Malaz Siddig Elsayed Abuidris (msea)" , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0beta\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-31 17:37+0000\n" -"PO-Revision-Date: 2026-08-02 19:17+0000\n" -"Last-Translator: Anonymous \n" +"PO-Revision-Date: 2026-08-24 05:51+0000\n" +"Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" "Language: ar\n" "MIME-Version: 1.0\n" @@ -55,7 +56,7 @@ msgstr "جهة الاتصال" #. module: bus #: model:ir.model.fields,field_description:bus.field_bus_bus__create_date msgid "Create Date" -msgstr "" +msgstr "تاريخ الإنشاء" #. module: bus #: model:ir.model,website_form_label:bus.model_res_partner @@ -137,7 +138,7 @@ msgstr "النماذج" #. module: bus #: model:ir.model,name:bus.model_ir_qweb msgid "Qweb" -msgstr "" +msgstr "Qweb" #. module: bus #. odoo-javascript diff --git a/addons/calendar/i18n/ar.po b/addons/calendar/i18n/ar.po index 1f6fe6afd3abab..296cf02b071cdf 100644 --- a/addons/calendar/i18n/ar.po +++ b/addons/calendar/i18n/ar.po @@ -14,7 +14,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-06-12 17:36+0000\n" -"PO-Revision-Date: 2026-08-02 12:32+0000\n" +"PO-Revision-Date: 2026-08-24 05:51+0000\n" "Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" @@ -1990,7 +1990,7 @@ msgstr "تفاصيل جهة الاتصال" #. odoo-python #: code:addons/calendar/models/res_partner.py:0 msgid "Contact created through Calendar sync." -msgstr "" +msgstr "تم إنشاء جهات الاتصال من خلال مزامنة التقويم." #. module: calendar #: model:ir.model.fields,field_description:calendar.field_calendar_popover_delete_wizard__body diff --git a/addons/calendar/i18n/fi.po b/addons/calendar/i18n/fi.po index fa830f70521904..92677750ec7c8e 100644 --- a/addons/calendar/i18n/fi.po +++ b/addons/calendar/i18n/fi.po @@ -26,7 +26,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-06-12 17:36+0000\n" -"PO-Revision-Date: 2026-08-11 10:42+0000\n" +"PO-Revision-Date: 2026-08-24 05:52+0000\n" "Last-Translator: Saara Hakanen \n" "Language-Team: Finnish \n" @@ -2316,7 +2316,7 @@ msgstr "Tapahtuman hälytys" #. module: calendar #: model:ir.model,name:calendar.model_calendar_alarm_manager msgid "Event Alarm Manager" -msgstr "Tapahtuman hälytysten hallinta" +msgstr "Tapahtumahälytysten hallinta" #. module: calendar #: model:ir.actions.act_window,name:calendar.action_event_delete_wizard @@ -2510,7 +2510,7 @@ msgstr "Google" #. module: calendar #: model_terms:ir.ui.view,arch_db:calendar.res_config_settings_view_form msgid "Google Calendar" -msgstr "Google-kalenteri" +msgstr "Google Kalenteri" #. module: calendar #: model_terms:ir.ui.view,arch_db:calendar.calendar_provider_config_view_form @@ -3241,7 +3241,7 @@ msgstr "Toista x kertaa" #. odoo-javascript #: code:addons/calendar/static/src/activity/activity_patch.xml:0 msgid "Reschedule" -msgstr "Ajoita uudelleen" +msgstr "Muuta aikaa" #. module: calendar #: model_terms:ir.ui.view,arch_db:calendar.view_calendar_event_search diff --git a/addons/calendar/i18n/sl.po b/addons/calendar/i18n/sl.po index 3fa6abab88a635..93fda34536698a 100644 --- a/addons/calendar/i18n/sl.po +++ b/addons/calendar/i18n/sl.po @@ -14,13 +14,14 @@ # "Tiffany Chang (tic)" , 2025, 2026. # Weblate , 2025, 2026. # NEDŽAD BEUS , 2026. +# Jan Zorko , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-06-12 17:36+0000\n" -"PO-Revision-Date: 2026-08-07 23:03+0000\n" -"Last-Translator: NEDŽAD BEUS \n" +"PO-Revision-Date: 2026-08-17 17:08+0000\n" +"Last-Translator: Jan Zorko \n" "Language-Team: Slovenian \n" "Language: sl\n" @@ -3598,7 +3599,7 @@ msgstr "Ta in naslednji dogodki" #: code:addons/calendar/static/src/views/ask_recurrence_update_policy_dialog.js:0 #: model:ir.model.fields.selection,name:calendar.selection__calendar_event__recurrence_update__self_only msgid "This event" -msgstr "Ta in naslednji dogodki" +msgstr "Ta dogodek" #. module: calendar #: model:ir.model.fields,field_description:calendar.field_calendar_event__thu diff --git a/addons/calendar/i18n/sv.po b/addons/calendar/i18n/sv.po index 9cde3ff200aaa6..e375c175cff5c6 100644 --- a/addons/calendar/i18n/sv.po +++ b/addons/calendar/i18n/sv.po @@ -26,7 +26,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-06-12 17:36+0000\n" -"PO-Revision-Date: 2026-08-14 13:09+0000\n" +"PO-Revision-Date: 2026-08-24 07:59+0000\n" "Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" @@ -850,7 +850,7 @@ msgid "" msgstr "" "Att upprepa på" +"'weekly')\">Upprepas varje" #. module: calendar #: model_terms:ir.ui.view,arch_db:calendar.calendar_provider_config_view_form @@ -989,7 +989,7 @@ msgstr "Lägg till beskrivning" #. module: calendar #: model_terms:ir.ui.view,arch_db:calendar.view_calendar_event_form msgid "Add notes about this meeting..." -msgstr "" +msgstr "Lägg till anteckningar om den här händelsen..." #. module: calendar #: model:ir.model.fields,field_description:calendar.field_calendar_alarm__body @@ -1009,7 +1009,7 @@ msgstr "" #: model:ir.model.fields,field_description:calendar.field_calendar_event__allday #: model_terms:ir.ui.view,arch_db:calendar.view_calendar_event_form_quick_create msgid "All Day" -msgstr "Hela dagen" +msgstr "Heldag" #. module: calendar #. odoo-python @@ -1070,7 +1070,7 @@ msgstr "Tillgänglig/Upptagen" #. module: calendar #: model_terms:ir.ui.view,arch_db:calendar.view_calendar_event_form msgid "Awaiting" -msgstr "" +msgstr "väntande" #. module: calendar #: model:ir.model.fields,field_description:calendar.field_calendar_event__awaiting_count @@ -1122,7 +1122,7 @@ msgstr "Kalender" #: model_terms:ir.ui.view,arch_db:calendar.calendar_alarm_view_form #: model_terms:ir.ui.view,arch_db:calendar.view_calendar_alarm_tree msgid "Calendar Alarm" -msgstr "Kalenderlarm" +msgstr "Påminnelser" #. module: calendar #: model:ir.model,name:calendar.model_calendar_attendee @@ -1176,7 +1176,7 @@ msgstr "Kalenderinställningar" #. module: calendar #: model_terms:ir.ui.view,arch_db:calendar.view_calendar_event_form msgid "Calendar description" -msgstr "" +msgstr "Beskrivning i kalender" #. module: calendar #: model:mail.template,name:calendar.calendar_template_meeting_changedate @@ -1425,7 +1425,7 @@ msgstr "Avvisad räkning" #. module: calendar #: model_terms:ir.ui.view,arch_db:calendar.view_calendar_event_search msgid "Default Privacy" -msgstr "Standard sekretess" +msgstr "Standard synlighet" #. module: calendar #: model:ir.model.fields,help:calendar.field_res_users_settings__calendar_default_privacy @@ -1553,7 +1553,7 @@ msgstr "Dtstart" #: model:ir.model.fields,field_description:calendar.field_calendar_event__duration #: model_terms:ir.ui.view,arch_db:calendar.view_calendar_event_form msgid "Duration" -msgstr "Varaktighet" +msgstr "Tidsperiod" #. module: calendar #: model:ir.model.fields,field_description:calendar.field_calendar_alarm__duration_minutes @@ -1798,7 +1798,7 @@ msgstr "Fjärde" #. module: calendar #: model_terms:ir.ui.view,arch_db:calendar.view_calendar_event_search msgid "Free" -msgstr "Gratis" +msgstr "Tillgänglig" #. module: calendar #: model:ir.model.fields,field_description:calendar.field_calendar_event__fri @@ -2048,7 +2048,7 @@ msgstr "Låt händelsen återkomma automatiskt med det intervallet" #. module: calendar #: model_terms:ir.ui.view,arch_db:calendar.view_calendar_event_form msgid "Linked to" -msgstr "" +msgstr "Koppla till" #. module: calendar #: model:ir.model.fields,field_description:calendar.field_calendar_event__location @@ -2125,7 +2125,7 @@ msgstr "Länkat möte" #: model_terms:ir.ui.view,arch_db:calendar.view_calendar_event_tree #: model_terms:ir.ui.view,arch_db:calendar.view_partners_form msgid "Meetings" -msgstr "Möten" +msgstr "Kalender" #. module: calendar #: model:ir.model.fields,field_description:calendar.field_calendar_event__message_has_error @@ -2189,7 +2189,7 @@ msgstr "Fler alternativ" #. module: calendar #: model_terms:ir.ui.view,arch_db:calendar.view_calendar_event_search msgid "My Meetings" -msgstr "Mina möten" +msgstr "Mina händelser" #. module: calendar #: model:ir.model.fields,field_description:calendar.field_calendar_alarm__name @@ -2257,7 +2257,7 @@ msgstr "Anteckningar" #. module: calendar #: model:ir.model.fields.selection,name:calendar.selection__calendar_alarm__alarm_type__notification msgid "Notification" -msgstr "Avisering" +msgstr "Notis" #. module: calendar #: model:calendar.alarm,name:calendar.alarm_notif_5 @@ -2325,7 +2325,7 @@ msgstr "Antal meddelanden med leveransfel" #: model:ir.model.fields.selection,name:calendar.selection__calendar_event__end_type__count #: model:ir.model.fields.selection,name:calendar.selection__calendar_recurrence__end_type__count msgid "Number of repetitions" -msgstr "Antal repetioner" +msgstr "Antal upprepningar" #. module: calendar #. odoo-javascript @@ -2336,7 +2336,7 @@ msgstr "OK" #. module: calendar #: model_terms:ir.ui.view,arch_db:calendar.view_calendar_event_form msgid "Online Meeting" -msgstr "Möte online" +msgstr "Online" #. module: calendar #: model_terms:ir.ui.view,arch_db:calendar.view_calendar_event_search @@ -2463,7 +2463,7 @@ msgstr "" #: model:ir.model.fields.selection,name:calendar.selection__res_users_settings__calendar_default_privacy__public #: model_terms:ir.ui.view,arch_db:calendar.view_calendar_event_search msgid "Public" -msgstr "Publik" +msgstr "Offentligt" #. module: calendar #: model:ir.model.fields.selection,name:calendar.selection__res_users__calendar_default_privacy__public @@ -2511,7 +2511,7 @@ msgstr "Återkommande regel" #. module: calendar #: model:ir.model.fields,field_description:calendar.field_calendar_alarm__duration msgid "Remind Before" -msgstr "Påminn före" +msgstr "Påminnelse innan" #. module: calendar #: model:ir.model.fields,field_description:calendar.field_calendar_event__alarm_ids @@ -2527,7 +2527,7 @@ msgstr "Renderingsmodell" #. module: calendar #: model:ir.model.fields,field_description:calendar.field_calendar_event__rrule_type_ui msgid "Repeat" -msgstr "Repetera" +msgstr "Upprepa" #. module: calendar #: model:ir.model.fields,field_description:calendar.field_calendar_event__interval @@ -2672,6 +2672,8 @@ msgid "" "of this event to others. Manage your default privacy in your user " "preferences." msgstr "" +"Ange om du är tillgänglig för samtida händelser samt händelsens synlighet " +"för andra. Hantera standard synlighet i dina användarinställningar." #. module: calendar #: model:ir.actions.act_window,name:calendar.calendar_settings_action @@ -2698,7 +2700,7 @@ msgstr "Skjut fram" #. module: calendar #: model:ir.model.fields,field_description:calendar.field_calendar_event__start msgid "Start" -msgstr "Start" +msgstr "När" #. module: calendar #: model:ir.model.fields,field_description:calendar.field_calendar_event__start_date @@ -2709,7 +2711,7 @@ msgstr "Startdatum" #. module: calendar #: model:ir.model.fields,help:calendar.field_calendar_event__start msgid "Start date of an event, without time for full days events" -msgstr "Startdatum för en händelse, utan tid för heldags händelser" +msgstr "Anger när en händelse inträffar." #. module: calendar #: model:ir.model.fields,field_description:calendar.field_calendar_attendee__state @@ -2921,7 +2923,7 @@ msgstr "Dagens möten" #. module: calendar #: model:ir.model.fields,field_description:calendar.field_calendar_recurrence__trigger_id msgid "Trigger" -msgstr "Utlösare" +msgstr "Trigger" #. module: calendar #: model:ir.model.fields,field_description:calendar.field_calendar_event__tue @@ -3130,7 +3132,7 @@ msgstr "avvisad" #. module: calendar #: model_terms:ir.ui.view,arch_db:calendar.view_calendar_event_form msgid "e.g. Business Lunch" -msgstr "t.ex. Affärslunch" +msgstr "T.ex. Lunch med kunder" #. module: calendar #: model_terms:ir.ui.view,arch_db:calendar.view_calendar_event_form diff --git a/addons/calendar_sms/i18n/fi.po b/addons/calendar_sms/i18n/fi.po index cea2a39f5383da..91b926d6e0a9cb 100644 --- a/addons/calendar_sms/i18n/fi.po +++ b/addons/calendar_sms/i18n/fi.po @@ -15,7 +15,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0+e\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-06 03:32+0000\n" +"PO-Revision-Date: 2026-08-24 05:53+0000\n" "Last-Translator: Saara Hakanen \n" "Language-Team: Finnish \n" @@ -51,7 +51,7 @@ msgstr "Tapahtuman hälytys" #. module: calendar_sms #: model:ir.model,name:calendar_sms.model_calendar_alarm_manager msgid "Event Alarm Manager" -msgstr "Hälytysten hallinta" +msgstr "Tapahtumahälytysten hallinta" #. module: calendar_sms #. odoo-python diff --git a/addons/certificate/i18n/ar.po b/addons/certificate/i18n/ar.po index 30348266e394c8..fbaad2ef136756 100644 --- a/addons/certificate/i18n/ar.po +++ b/addons/certificate/i18n/ar.po @@ -10,8 +10,8 @@ msgstr "" "Project-Id-Version: Odoo Server 18.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-10 17:36+0000\n" -"PO-Revision-Date: 2026-08-02 19:16+0000\n" -"Last-Translator: Anonymous \n" +"PO-Revision-Date: 2026-08-24 05:51+0000\n" +"Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -178,7 +178,7 @@ msgstr "غير صالح" #. module: certificate #: model:ir.model.fields,field_description:certificate.field_certificate_certificate__issuer_cert_id msgid "Issuer Certificate" -msgstr "" +msgstr "شهادة المُصدر" #. module: certificate #: model_terms:ir.ui.view,arch_db:certificate.certificate_key_view_list @@ -229,7 +229,7 @@ msgstr "احرص على استخدام مفتاح خاص للتوقيع على #. odoo-python #: code:addons/certificate/models/key.py:0 msgid "Make sure to use a public key to verify the signature of documents." -msgstr "" +msgstr "تأكد من استخدام مفتاح عام للتحقق من توقيع المستندات." #. module: certificate #: model_terms:ir.ui.view,arch_db:certificate.res_config_settings_view_form @@ -411,7 +411,7 @@ msgstr "تعذر تحميل هذه الشهادة. إما أن المحتوى أ msgid "" "This certificate could not be loaded. Please provide the certificate " "password." -msgstr "" +msgstr "تعذر تحميل هذه الشهادة. يرجى إدخال كلمة مرور الشهادة." #. module: certificate #. odoo-python @@ -454,6 +454,8 @@ msgid "" "Unsupported asymmetric cryptography algorithm '%s'. Currently supported for " "signature: ED25519, EC and RSA." msgstr "" +"خوارزمية التشفير غير المتماثلة غير مدعومة '%s'. الخوارزميات المدعومة حاليًا " +"للتوقيع: ED25519 وEC وRSA." #. module: certificate #. odoo-python diff --git a/addons/cloud_storage/i18n/ar.po b/addons/cloud_storage/i18n/ar.po index 392ae36a42d3c0..73644c1505f981 100644 --- a/addons/cloud_storage/i18n/ar.po +++ b/addons/cloud_storage/i18n/ar.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: Odoo Server 18.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-05 21:43+0000\n" +"PO-Revision-Date: 2026-08-24 05:52+0000\n" "Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" @@ -79,6 +79,7 @@ msgstr "اسم العرض" msgid "" "Failed to download attachment (%(id)s) from cloud: %(code)s - %(reason)s" msgstr "" +"فشل تنزيل المرفق (%(id)s) من منصة التخزين السحابية: %(code)s - %(reason)s" #. module: cloud_storage #: model:ir.model,name:cloud_storage.model_ir_http @@ -95,12 +96,12 @@ msgstr "المُعرف" #. module: cloud_storage #: model_terms:ir.ui.view,arch_db:cloud_storage.cloud_storage_config_settings_view_form msgid "Minimum File Size" -msgstr "" +msgstr "الحد الأدنى لحجم الملف" #. module: cloud_storage #: model:ir.model.fields,field_description:cloud_storage.field_res_config_settings__cloud_storage_min_file_size_mb msgid "Minimum File Size (MB)" -msgstr "" +msgstr "الحد الأدنى لحجم الملف (ميغابايت)" #. module: cloud_storage #: model:ir.model.fields,field_description:cloud_storage.field_res_config_settings__cloud_storage_min_file_size @@ -110,7 +111,7 @@ msgstr "تقليل مساحة الملف (بايت)" #. module: cloud_storage #: model_terms:ir.ui.view,arch_db:cloud_storage.cloud_storage_config_settings_view_form msgid "Minimum size for attachments to be stored in the cloud storage" -msgstr "" +msgstr "الحجم الأدنى للمرفقات المراد تخزينها في منصة التخزين السحابية" #. module: cloud_storage #. odoo-python diff --git a/addons/cloud_storage/models/ir_attachment.py b/addons/cloud_storage/models/ir_attachment.py index 941ded3067e11d..3e87f6beba8e60 100644 --- a/addons/cloud_storage/models/ir_attachment.py +++ b/addons/cloud_storage/models/ir_attachment.py @@ -84,6 +84,12 @@ def _generate_cloud_storage_url(self): """ raise NotImplementedError() + def _get_cloud_storage_download_url_time_to_expiry(self): + return self.env.context.get( + 'cloud_storage_download_url_time_to_expiry', + self._cloud_storage_download_url_time_to_expiry, + ) + def _generate_cloud_storage_download_info(self): """ Generate the download info for the public client to directly download diff --git a/addons/cloud_storage_azure/models/ir_attachment.py b/addons/cloud_storage_azure/models/ir_attachment.py index 9287f6b2656a5c..bba8be29a33e29 100644 --- a/addons/cloud_storage_azure/models/ir_attachment.py +++ b/addons/cloud_storage_azure/models/ir_attachment.py @@ -97,17 +97,18 @@ def _generate_cloud_storage_download_info(self): if self.env['ir.config_parameter'].sudo().get_param('cloud_storage_provider') != 'azure': return super()._generate_cloud_storage_download_info() info = self._get_cloud_storage_azure_info() - expiry = datetime.now(timezone.utc) + timedelta(seconds=self._cloud_storage_download_url_time_to_expiry) + time_to_expiry = self._get_cloud_storage_download_url_time_to_expiry() + expiry = datetime.now(timezone.utc) + timedelta(seconds=time_to_expiry) return { 'url': self._generate_cloud_storage_azure_sas_url( **info, permission='r', expiry=expiry, - cache_control=f'private, max-age={self._cloud_storage_download_url_time_to_expiry}', + cache_control=f'private, max-age={time_to_expiry}', content_type=self.mimetype or None, content_disposition=content_disposition(self.name) if self.env.context.get('download_attachments') else None, ), - 'time_to_expiry': self._cloud_storage_download_url_time_to_expiry, + 'time_to_expiry': time_to_expiry, } def _generate_cloud_storage_upload_info(self): diff --git a/addons/cloud_storage_google/models/ir_attachment.py b/addons/cloud_storage_google/models/ir_attachment.py index 3c714fb23ee068..fec13d816754f0 100644 --- a/addons/cloud_storage_google/models/ir_attachment.py +++ b/addons/cloud_storage_google/models/ir_attachment.py @@ -64,15 +64,16 @@ def _generate_cloud_storage_download_info(self): if self.env['ir.config_parameter'].sudo().get_param('cloud_storage_provider') != 'google': return super()._generate_cloud_storage_download_info() info = self._get_cloud_storage_google_info() + time_to_expiry = self._get_cloud_storage_download_url_time_to_expiry() return { 'url': self._generate_cloud_storage_google_signed_url( info['bucket_name'], info['blob_name'], method='GET', - expiration=self._cloud_storage_download_url_time_to_expiry, + expiration=time_to_expiry, response_type=self.mimetype or None, response_disposition=content_disposition(self.name) if self.env.context.get('download_attachments') else None, ), - 'time_to_expiry': self._cloud_storage_download_url_time_to_expiry, + 'time_to_expiry': time_to_expiry, } def _generate_cloud_storage_upload_info(self): diff --git a/addons/cloud_storage_migration/i18n/ar.po b/addons/cloud_storage_migration/i18n/ar.po index da817f108f2957..09e7d2c0bf7006 100644 --- a/addons/cloud_storage_migration/i18n/ar.po +++ b/addons/cloud_storage_migration/i18n/ar.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: Odoo Server 19.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-04 17:58+0000\n" +"PO-Revision-Date: 2026-08-24 05:52+0000\n" "Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" @@ -23,17 +23,17 @@ msgstr "" #. module: cloud_storage_migration #: model:ir.model.fields,field_description:cloud_storage_migration.field_res_config_settings__cloud_storage_migration_all_model_ids msgid "All Attachments" -msgstr "" +msgstr "جميع المرفقات" #. module: cloud_storage_migration #: model:ir.model.fields,field_description:cloud_storage_migration.field_cloud_storage_migration_report__all_to_migrate msgid "All Attachments Migration" -msgstr "" +msgstr "نقل جميع المرفقات " #. module: cloud_storage_migration #: model:ir.model,name:cloud_storage_migration.model_ir_attachment msgid "Attachment" -msgstr "" +msgstr "مرفق" #. module: cloud_storage_migration #. odoo-python @@ -42,6 +42,7 @@ msgid "" "Attachment (%s) does not have a stored filename and cannot be migrated to " "cloud storage." msgstr "" +"لا يحتوي المرفق (%s) على اسم ملف مخزن ولا يمكن نقله إلى مساحة التخزين السحابية." #. module: cloud_storage_migration #. odoo-python @@ -50,54 +51,55 @@ msgid "" "Attachment (%s) is not a binary attachment and cannot be migrated to cloud " "storage." msgstr "" +"المرفق (%s) ليس مرفقًا ثنائيًا ولا يمكن نقله إلى مساحة التخزين السحابية." #. module: cloud_storage_migration #: model_terms:ir.ui.view,arch_db:cloud_storage_migration.cloud_storage_migration_config_settings_view_form msgid "Attachments Report" -msgstr "" +msgstr "تقرير المرفقات" #. module: cloud_storage_migration #: model_terms:ir.ui.view,arch_db:cloud_storage_migration.cloud_storage_migration_config_settings_view_form msgid "Cloud Storage Migration" -msgstr "" +msgstr "النقل إلى مساحة التخزين السحابية " #. module: cloud_storage_migration #: model:ir.model.fields,field_description:cloud_storage_migration.field_res_config_settings__cloud_storage_migration_all_models msgid "Cloud Storage Migration All Models" -msgstr "" +msgstr "نقل جميع النماذج إلى مساحة التخزين السحابية " #. module: cloud_storage_migration #: model:ir.actions.act_window,name:cloud_storage_migration.action_cloud_storage_migration_cron msgid "Cloud Storage Migration Cron" -msgstr "" +msgstr "Cron الخاص بالنقل إلى مساحة التخزين السحابية " #. module: cloud_storage_migration #: model:ir.model.fields,field_description:cloud_storage_migration.field_res_config_settings__cloud_storage_migration_message_models msgid "Cloud Storage Migration Message Models" -msgstr "" +msgstr "نقل نماذج الرسائل إلى مساحة التخزين السحابية " #. module: cloud_storage_migration #: model:ir.actions.act_window,name:cloud_storage_migration.action_cloud_storage_migration_report #: model:ir.model,name:cloud_storage_migration.model_cloud_storage_migration_report #: model_terms:ir.ui.view,arch_db:cloud_storage_migration.view_cloud_storage_migration_report_list msgid "Cloud Storage Migration Report" -msgstr "" +msgstr "تقرير النقل إلى مساحة التخزين السحابية " #. module: cloud_storage_migration #. odoo-python #: code:addons/cloud_storage_migration/models/ir_attachment.py:0 msgid "Cloud storage provider is not configured" -msgstr "" +msgstr "لم تتم تهيئة مزوّد النقل إلى مساحة التخزين السحابية " #. module: cloud_storage_migration #: model:ir.model,name:cloud_storage_migration.model_res_config_settings msgid "Config Settings" -msgstr "" +msgstr "إعدادات التكوين" #. module: cloud_storage_migration #: model_terms:ir.ui.view,arch_db:cloud_storage_migration.cloud_storage_migration_config_settings_view_form msgid "Cron Job" -msgstr "" +msgstr "مهمة Cron" #. module: cloud_storage_migration #: model:ir.model.fields,field_description:cloud_storage_migration.field_cloud_storage_migration_report__display_name @@ -110,12 +112,12 @@ msgstr "الاسم المعروض" #. odoo-python #: code:addons/cloud_storage_migration/models/ir_attachment.py:0 msgid "Failed to upload attachment %(id)s to cloud storage: %(code)s" -msgstr "" +msgstr "فشل تحميل المرفق %(id)s إلى مساحة التخزين السحابية: %(code)s" #. module: cloud_storage_migration #: model:ir.model.fields,field_description:cloud_storage_migration.field_cloud_storage_migration_report__has_attachment_rel msgid "Has Attachment Field" -msgstr "" +msgstr "يحتوي على حقل المرفقات" #. module: cloud_storage_migration #: model:ir.model.fields,field_description:cloud_storage_migration.field_cloud_storage_migration_report__id @@ -130,6 +132,8 @@ msgid "" "Indicates whether all attachments associated with this model are scheduled " "for cloud storage migration" msgstr "" +"يشير إلى ما إذا كانت جميع المرفقات المرتبطة بهذا النموذج مجدولة ليتم نقلها " +"إلى مساحة التخزين السحابية " #. module: cloud_storage_migration #: model:ir.model.fields,help:cloud_storage_migration.field_cloud_storage_migration_report__message_to_migrate @@ -137,6 +141,8 @@ msgid "" "Indicates whether attachments linked to mail messages for this model are " "scheduled for cloud storage migration" msgstr "" +"يشير إلى ما إذا كانت المرفقات المرتبطة برسائل البريد الإلكتروني لهذا النموذج " +"مجدولة ليتم نقلها إلى مساحة التخزين السحابية " #. module: cloud_storage_migration #: model:ir.model.fields,help:cloud_storage_migration.field_cloud_storage_migration_report__has_attachment_rel @@ -144,99 +150,102 @@ msgid "" "Indicates whether this model has a relational field linking to ir.attachment" " model" msgstr "" +"يشير إلى ما إذا كان هذا النموذج يحتوي على حقل علائقي يرتبط بنموذج " +"ir.attachment" #. module: cloud_storage_migration #: model:ir.model.fields,field_description:cloud_storage_migration.field_cloud_storage_migration_report__all_max_size msgid "Largest Attachment (MB)" -msgstr "" +msgstr "أكبر مرفق (ميغابايت)" #. module: cloud_storage_migration #: model:ir.model.fields,field_description:cloud_storage_migration.field_res_config_settings__cloud_storage_migration_message_model_ids msgid "Message Attachments" -msgstr "" +msgstr "مرفقات الرسائل" #. module: cloud_storage_migration #: model:ir.model.fields,field_description:cloud_storage_migration.field_cloud_storage_migration_report__message_count msgid "Message Attachments Count" -msgstr "" +msgstr "عدد مرفقات الرسائل" #. module: cloud_storage_migration #: model:ir.model.fields,field_description:cloud_storage_migration.field_cloud_storage_migration_report__message_to_migrate msgid "Message Attachments Migration" -msgstr "" +msgstr "نقل مرفقات الرسائل" #. module: cloud_storage_migration #: model:ir.model.fields,field_description:cloud_storage_migration.field_cloud_storage_migration_report__message_sum_size msgid "Message Attachments Size (MB)" -msgstr "" +msgstr "حجم مرفقات الرسائل (ميجابايت)" #. module: cloud_storage_migration #: model:ir.model.fields,field_description:cloud_storage_migration.field_cloud_storage_migration_report__message_max_size msgid "Message Largest Attachment (MB)" -msgstr "" +msgstr "أكبر مرفق للرسالة (ميجابايت)" #. module: cloud_storage_migration #: model:ir.actions.server,name:cloud_storage_migration.ir_cron_manual_migrate_local_to_cloud_storage_ir_actions_server msgid "Migrate Local Attachment Binaries to Cloud Storage" -msgstr "" +msgstr "نقل الملفات الثنائية المرفقة المحلية إلى مساحة التخزين السحابية " #. module: cloud_storage_migration #: model:ir.model.fields,help:cloud_storage_migration.field_res_config_settings__cloud_storage_migration_all_model_ids msgid "Migrate Models' All Attachments" -msgstr "" +msgstr "نقل جميع مرفقات النماذج" #. module: cloud_storage_migration #: model:ir.model.fields,help:cloud_storage_migration.field_res_config_settings__cloud_storage_migration_message_model_ids msgid "Migrate Models' Message Attachments" -msgstr "" +msgstr "نقل مرفقات رسائل النماذج" #. module: cloud_storage_migration #: model_terms:ir.ui.view,arch_db:cloud_storage_migration.cloud_storage_migration_config_settings_view_form msgid "Migrate local attachments to cloud storage" -msgstr "" +msgstr "نقل المرفقات المحلية إلى مساحة التخزين السحابية " #. module: cloud_storage_migration #: model:ir.model.fields,field_description:cloud_storage_migration.field_res_config_settings__cloud_storage_migration_progress msgid "Migration Progress" -msgstr "" +msgstr "تقدم عملية نقل البيانات " #. module: cloud_storage_migration #: model:ir.model.fields,field_description:cloud_storage_migration.field_cloud_storage_migration_report__res_model msgid "Model" -msgstr "" +msgstr "النموذج " #. module: cloud_storage_migration #: model:ir.model.fields,field_description:cloud_storage_migration.field_cloud_storage_migration_report__res_model_name msgid "Model Name" -msgstr "" +msgstr "اسم النموذج " #. module: cloud_storage_migration #: model_terms:ir.actions.act_window,help:cloud_storage_migration.action_cloud_storage_migration_report msgid "No attachment data found" -msgstr "" +msgstr "لم يتم العثور على البيانات الخاصة بالمرفق " #. module: cloud_storage_migration #. odoo-python #: code:addons/cloud_storage_migration/models/ir_attachment.py:0 msgid "No model for cloud storage migration" -msgstr "" +msgstr "لا يوجد نموذج لنقله إلى مساحة التخزين السحابية " #. module: cloud_storage_migration #: model_terms:ir.ui.view,arch_db:cloud_storage_migration.cloud_storage_migration_config_settings_view_form msgid "Parameters" -msgstr "" +msgstr "المعايير" #. module: cloud_storage_migration #: model:ir.model.fields,help:cloud_storage_migration.field_res_config_settings__cloud_storage_migration_progress msgid "Shows the progress of cloud storage migration (current/total)" msgstr "" +"يعرض تقدم عملية نقل البيانات إلى مساحة التخزين السحابية (الحالي/الإجمالي)" #. module: cloud_storage_migration #: model:ir.model.fields,help:cloud_storage_migration.field_cloud_storage_migration_report__all_max_size msgid "" "Size in megabytes of the largest attachment associated with any record of " "this model" -msgstr "" +msgstr "حجم المرفق الأكبر المرتبط بأي سجل من هذا النموذج بالميغابايت" #. module: cloud_storage_migration #: model:ir.model.fields,help:cloud_storage_migration.field_cloud_storage_migration_report__message_max_size @@ -244,6 +253,7 @@ msgid "" "Size in megabytes of the largest attachment linked to mail messages for this" " model" msgstr "" +"حجم المرفق الأكبر المرتبط برسائل البريد الإلكتروني لهذا النموذج بالميغابايت" #. module: cloud_storage_migration #: model_terms:ir.actions.act_window,help:cloud_storage_migration.action_cloud_storage_migration_report @@ -251,33 +261,35 @@ msgid "" "This report shows statistics about attachments that could be migrated to " "cloud storage." msgstr "" +"يُظهر هذا التقرير الإحصائيات المتعلقة بالمرفقات التي يمكن نقلها إلى مساحة " +"التخزين السحابية." #. module: cloud_storage_migration #: model:ir.model.fields,field_description:cloud_storage_migration.field_cloud_storage_migration_report__all_count msgid "Total Attachments Count" -msgstr "" +msgstr "إجمالي عدد المرفقات" #. module: cloud_storage_migration #: model:ir.model.fields,field_description:cloud_storage_migration.field_cloud_storage_migration_report__all_sum_size msgid "Total Attachments Size (MB)" -msgstr "" +msgstr "إجمالي حجم المرفقات (ميغابايت)" #. module: cloud_storage_migration #: model:ir.model.fields,help:cloud_storage_migration.field_cloud_storage_migration_report__all_count msgid "Total number of attachments associated with all records of this model" -msgstr "" +msgstr "إجمالي عدد المرفقات المرتبطة بجميع سجلات هذا النموذج" #. module: cloud_storage_migration #: model:ir.model.fields,help:cloud_storage_migration.field_cloud_storage_migration_report__message_count msgid "Total number of attachments linked to mail messages for this model" -msgstr "" +msgstr "إجمالي عدد المرفقات المرتبطة برسائل البريد الإلكتروني لهذا النموذج" #. module: cloud_storage_migration #: model:ir.model.fields,help:cloud_storage_migration.field_cloud_storage_migration_report__all_sum_size msgid "" "Total size in megabytes of all attachments associated with records of this " "model" -msgstr "" +msgstr "الحجم الإجمالي بالميغابايت لجميع المرفقات المرتبطة بسجلات هذا النموذج" #. module: cloud_storage_migration #: model:ir.model.fields,help:cloud_storage_migration.field_cloud_storage_migration_report__message_sum_size @@ -285,3 +297,5 @@ msgid "" "Total size in megabytes of all attachments linked to mail messages for this " "model" msgstr "" +"الحجم الإجمالي بالميغابايت لجميع المرفقات المرتبطة برسائل البريد الإلكتروني " +"لهذا النموذج" diff --git a/addons/contacts/i18n/sv.po b/addons/contacts/i18n/sv.po index dc499cd71a7120..f06f9857fbaa2c 100644 --- a/addons/contacts/i18n/sv.po +++ b/addons/contacts/i18n/sv.po @@ -17,7 +17,7 @@ msgstr "" "Project-Id-Version: Odoo Server 15.5alpha1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-07 05:06+0000\n" +"PO-Revision-Date: 2026-08-24 07:59+0000\n" "Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" @@ -42,7 +42,7 @@ msgstr "Banker" #. module: contacts #: model:ir.ui.menu,name:contacts.res_partner_menu_config msgid "Configuration" -msgstr "Konfiguration" +msgstr "Inställningar" #. module: contacts #: model:ir.model,name:contacts.model_res_partner diff --git a/addons/crm/i18n/ar.po b/addons/crm/i18n/ar.po index 6666adb1d58e70..c7612524c6336f 100644 --- a/addons/crm/i18n/ar.po +++ b/addons/crm/i18n/ar.po @@ -18,7 +18,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0beta\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-20 18:35+0000\n" -"PO-Revision-Date: 2026-08-10 13:15+0000\n" +"PO-Revision-Date: 2026-08-24 05:52+0000\n" "Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -3698,7 +3698,7 @@ msgstr "الإعدادات" #. module: crm #: model_terms:ir.ui.view,arch_db:crm.crm_stage_form msgid "Shared with all teams" -msgstr "" +msgstr "تمت مشاركته مع جميع الفرق" #. module: crm #: model:res.groups,name:crm.group_use_lead diff --git a/addons/crm/i18n/fa.po b/addons/crm/i18n/fa.po index 9d16908d97decf..bc497f666dce0f 100644 --- a/addons/crm/i18n/fa.po +++ b/addons/crm/i18n/fa.po @@ -25,8 +25,8 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0beta\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-20 18:35+0000\n" -"PO-Revision-Date: 2026-08-09 10:28+0000\n" -"Last-Translator: Aliraza Habibitanha \n" +"PO-Revision-Date: 2026-08-16 08:10+0000\n" +"Last-Translator: Heshmatkhah \n" "Language-Team: Persian " "\n" "Language: fa\n" @@ -2669,7 +2669,7 @@ msgstr "چند تیم" #: model_terms:ir.ui.view,arch_db:crm.view_crm_case_leads_filter #: model_terms:ir.ui.view,arch_db:crm.view_crm_case_my_activities_filter msgid "My Activities" -msgstr "فعالیتهای من" +msgstr "فعالیت‌های من" #. module: crm #: model:ir.model.fields,field_description:crm.field_crm_lead__my_activity_date_deadline diff --git a/addons/crm/i18n/fi.po b/addons/crm/i18n/fi.po index e9494a51b82050..616445e076ddac 100644 --- a/addons/crm/i18n/fi.po +++ b/addons/crm/i18n/fi.po @@ -37,7 +37,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0beta\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-20 18:35+0000\n" -"PO-Revision-Date: 2026-08-14 15:10+0000\n" +"PO-Revision-Date: 2026-08-17 09:59+0000\n" "Last-Translator: Saara Hakanen \n" "Language-Team: Finnish " "\n" @@ -138,43 +138,44 @@ msgstr "" #. module: crm #: model_terms:ir.ui.view,arch_db:crm.quick_create_opportunity_form msgid "" -msgstr "" +msgstr "" #. module: crm #: model_terms:ir.ui.view,arch_db:crm.quick_create_opportunity_form msgid "" -msgstr "" +msgstr "" #. module: crm #: model_terms:ir.ui.view,arch_db:crm.quick_create_opportunity_form msgid "" -msgstr "" +msgstr "" #. module: crm #: model_terms:ir.ui.view,arch_db:crm.sales_team_form_view_in_crm msgid "" msgstr "" -"" +"" #. module: crm #: model_terms:ir.ui.view,arch_db:crm.quick_create_opportunity_form msgid "" -msgstr "" +msgstr "" #. module: crm #: model_terms:ir.ui.view,arch_db:crm.quick_create_opportunity_form msgid "" -msgstr "" +msgstr "" #. module: crm #: model_terms:ir.ui.view,arch_db:crm.quick_create_opportunity_form msgid "" -msgstr "" +msgstr "" #. module: crm #: model_terms:ir.ui.view,arch_db:crm.quick_create_opportunity_form msgid "" -msgstr "" +msgstr "" #. module: crm #: model_terms:ir.ui.view,arch_db:crm.res_config_settings_view_form @@ -202,8 +203,8 @@ msgid "" "invisible=\"not partner_email_update\"/>" msgstr "" "" +"title=\"Tämän muutoksen tallentaminen päivittää myös asiakkaan " +"sähköpostiosoitteen.\" invisible=\"not partner_email_update\"/>" #. module: crm #: model_terms:ir.ui.view,arch_db:crm.crm_lead_view_form @@ -213,8 +214,8 @@ msgid "" "updated.\" invisible=\"not partner_phone_update\"/>" msgstr "" "" +"title=\"Tämän muutoksen tallentaminen päivittää myös asiakkaan " +"puhelinnumeron.\" invisible=\"not partner_phone_update\"/>" #. module: crm #: model_terms:ir.ui.view,arch_db:crm.crm_lead_view_form @@ -225,9 +226,9 @@ msgid "" "invisible=\"duplicate_lead_count > 1\">Similar Lead" msgstr "" "" -"Samankaltaiset liidit \n" +"Samankaltaiset liidit\n" " Samanlainen liidi" +"invisible=\"duplicate_lead_count > 1\">Samankaltainen liidi" #. module: crm #: model_terms:ir.ui.view,arch_db:crm.utm_campaign_view_form @@ -243,13 +244,13 @@ msgstr "" #. module: crm #: model_terms:ir.ui.view,arch_db:crm.crm_lost_reason_view_form msgid " Leads" -msgstr "Liidiä" +msgstr " Liidit" #. module: crm #: model_terms:ir.ui.view,arch_db:crm.crm_lead_view_form msgid "" " at " -msgstr "" +msgstr " " #. module: crm #: model_terms:ir.ui.view,arch_db:crm.crm_lead_view_form @@ -260,7 +261,7 @@ msgstr " %" #: model_terms:ir.ui.view,arch_db:crm.crm_team_member_view_form msgid " leads assigned the last 30 days out of a maximum of " msgstr "" -"liidiä vastuutettu viimeisen 30 päivän aikana enimmäismäärästä" +" liidiä määritetty viimeisen 30 päivän aikana enimmäismäärästä " #. module: crm #: model_terms:ir.ui.view,arch_db:crm.sales_team_form_view_in_crm @@ -274,12 +275,12 @@ msgstr "" #. module: crm #: model_terms:ir.ui.view,arch_db:crm.crm_lead_merge_summary msgid "Expected Revenues:" -msgstr "Odotetut liikevoitot:" +msgstr "Odotettu liikevaihto:" #. module: crm #: model_terms:ir.ui.view,arch_db:crm.crm_lead_merge_summary msgid "Merged the Lead/Opportunity" -msgstr "Yhdistetty liidi/myyntimahdollisuus" +msgstr "Liidi/myyntimahdollisuus yhdistetty" #. module: crm #: model_terms:ir.ui.view,arch_db:crm.res_config_settings_view_form diff --git a/addons/crm/i18n/sv.po b/addons/crm/i18n/sv.po index a3ab312738ab1a..74005e8374fe06 100644 --- a/addons/crm/i18n/sv.po +++ b/addons/crm/i18n/sv.po @@ -27,7 +27,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0beta\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-20 18:35+0000\n" -"PO-Revision-Date: 2026-08-14 13:10+0000\n" +"PO-Revision-Date: 2026-08-24 08:00+0000\n" "Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish " "\n" @@ -1550,7 +1550,7 @@ msgstr "Dra din möjlighet till Vunnen när du får affären. Grattis!" #. odoo-python #: code:addons/crm/models/crm_lead.py:0 msgid "Duration: %s" -msgstr "Varaktighet: %s" +msgstr "Tidsperiod: %s" #. module: crm #: model_terms:ir.actions.act_window,help:crm.crm_lead_action_forecast diff --git a/addons/crm_iap_mine/i18n/ar.po b/addons/crm_iap_mine/i18n/ar.po index 606a5d9ce02566..cecde020d6e52a 100644 --- a/addons/crm_iap_mine/i18n/ar.po +++ b/addons/crm_iap_mine/i18n/ar.po @@ -7,13 +7,14 @@ # MOHAMMAD IBRAHIM , 2022 # Malaz Abuidris , 2023 # "Tiffany Chang (tic)" , 2025, 2026. +# "Malaz Siddig Elsayed Abuidris (msea)" , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0beta\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-07 21:26+0000\n" -"Last-Translator: Anonymous \n" +"PO-Revision-Date: 2026-08-24 05:51+0000\n" +"Last-Translator: \"Malaz Siddig Elsayed Abuidris (msea)\" \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -66,6 +67,8 @@ msgid "" "\n" " Address" msgstr "" +"\n" +" العنوان" #. module: crm_iap_mine #: model_terms:ir.ui.view,arch_db:crm_iap_mine.enrich_company @@ -73,6 +76,8 @@ msgid "" "\n" " Industries" msgstr "" +"\n" +" قطاعات الأعمال" #. module: crm_iap_mine #: model_terms:ir.ui.view,arch_db:crm_iap_mine.enrich_company @@ -80,6 +85,8 @@ msgid "" "\n" " Website" msgstr "" +"\n" +" الموقع الإلكتروني" #. module: crm_iap_mine #: model_terms:ir.ui.view,arch_db:crm_iap_mine.enrich_company @@ -87,6 +94,8 @@ msgid "" "\n" " Tax ID" msgstr "" +"\n" +" المعرّف الضريبي" #. module: crm_iap_mine #: model_terms:ir.ui.view,arch_db:crm_iap_mine.crm_iap_lead_mining_request_view_form diff --git a/addons/crm_iap_mine/i18n/fa.po b/addons/crm_iap_mine/i18n/fa.po index f3ee7b4a6c119f..dc93553f20facc 100644 --- a/addons/crm_iap_mine/i18n/fa.po +++ b/addons/crm_iap_mine/i18n/fa.po @@ -10,8 +10,8 @@ msgstr "" "Project-Id-Version: Odoo Server 18.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-09 10:21+0000\n" -"Last-Translator: Aliraza Habibitanha \n" +"PO-Revision-Date: 2026-08-23 06:14+0000\n" +"Last-Translator: Heshmatkhah \n" "Language-Team: Persian \n" "Language: fa\n" @@ -54,7 +54,7 @@ msgstr "" " می‌توانید حساب کاربری تولید سرنخ IAP را در تنظیمات برنامه CRM شارژ " "کنید.

\n" "

با احترام

\n" -"

سازمان‌یار

\n" +"

اودو

\n" "
" #. module: crm_iap_mine diff --git a/addons/crm_iap_mine/i18n/ja.po b/addons/crm_iap_mine/i18n/ja.po index 662c776cd9b559..f47f547e9229b1 100644 --- a/addons/crm_iap_mine/i18n/ja.po +++ b/addons/crm_iap_mine/i18n/ja.po @@ -11,8 +11,8 @@ msgstr "" "Project-Id-Version: Odoo Server 18.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-06 13:33+0000\n" -"Last-Translator: Weblate \n" +"PO-Revision-Date: 2026-08-24 03:11+0000\n" +"Last-Translator: \"Junko Augias (juau)\" \n" "Language-Team: Japanese \n" "Language: ja\n" @@ -286,7 +286,7 @@ msgstr "完了" #: model:ir.model.fields.selection,name:crm_iap_mine.selection__crm_iap_lead_mining_request__state__draft #: model_terms:ir.ui.view,arch_db:crm_iap_mine.crm_iap_lead_mining_request_view_search msgid "Draft" -msgstr "ドラフト" +msgstr "下書き" #. module: crm_iap_mine #: model:crm.iap.lead.role,name:crm_iap_mine.crm_iap_mine_role_5 diff --git a/addons/delivery/i18n/fi.po b/addons/delivery/i18n/fi.po index 603a70f47d89a3..0ff6bf788f01a8 100644 --- a/addons/delivery/i18n/fi.po +++ b/addons/delivery/i18n/fi.po @@ -26,8 +26,8 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0+e\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-07 04:25+0000\n" -"Last-Translator: Weblate \n" +"PO-Revision-Date: 2026-08-24 05:52+0000\n" +"Last-Translator: Saara Hakanen \n" "Language-Team: Finnish \n" "Language: fi\n" @@ -627,7 +627,7 @@ msgstr "Laskutusviesti" #. module: delivery #: model:ir.model.fields,field_description:delivery.field_delivery_carrier__invoice_policy msgid "Invoicing Policy" -msgstr "Laskutus perustuu" +msgstr "Laskutuskäytäntö" #. module: delivery #: model:ir.model.fields,field_description:delivery.field_res_partner__is_pickup_location diff --git a/addons/delivery/models/sale_order.py b/addons/delivery/models/sale_order.py index 52fc9e88ed57a6..6f4ef9d2ff55e1 100644 --- a/addons/delivery/models/sale_order.py +++ b/addons/delivery/models/sale_order.py @@ -226,6 +226,7 @@ def _prepare_delivery_line_vals(self, carrier, price_unit): 'price_unit': price_unit, 'product_uom_qty': 1, 'product_id': carrier.product_id.id, + 'product_uom_id': carrier.product_id.uom_id.id, 'tax_ids': [(6, 0, taxes_ids)], 'is_delivery': True, } diff --git a/addons/event/i18n/fa.po b/addons/event/i18n/fa.po index 5d6a3c300a5289..97259aadd7d70d 100644 --- a/addons/event/i18n/fa.po +++ b/addons/event/i18n/fa.po @@ -16,13 +16,14 @@ # Weblate , 2025, 2026. # Omid Ghaffarinia , 2026. # Aliraza Habibitanha , 2026. +# Heshmatkhah , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-24 17:38+0000\n" -"PO-Revision-Date: 2026-08-09 11:35+0000\n" -"Last-Translator: Aliraza Habibitanha \n" +"PO-Revision-Date: 2026-08-23 06:10+0000\n" +"Last-Translator: Heshmatkhah \n" "Language-Team: Persian \n" "Language: fa\n" @@ -3503,7 +3504,7 @@ msgstr "پاسخ اجباری" #. module: event #: model_terms:ir.ui.view,arch_db:event.event_report_full_page_ticket_layout msgid "Marc Demo" -msgstr "دموی مارچ" +msgstr "ماهان دمو‌پور" #. module: event #: model_terms:ir.ui.view,arch_db:event.view_event_registration_tree diff --git a/addons/event/i18n/sv.po b/addons/event/i18n/sv.po index 7d1c13c154d5da..5789cf304c3e01 100644 --- a/addons/event/i18n/sv.po +++ b/addons/event/i18n/sv.po @@ -29,7 +29,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-24 17:38+0000\n" -"PO-Revision-Date: 2026-08-14 13:09+0000\n" +"PO-Revision-Date: 2026-08-24 08:01+0000\n" "Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" @@ -1456,7 +1456,7 @@ msgstr "" "
\n" " " "Har du frågor om evenemanget?\n" -"
Vänligen kontakta organisatören:
\n" +"
Vänligen kontakta arrangören:
\n" "
    \n" "
  • " "YourCompany
  • \n" @@ -3038,7 +3038,7 @@ msgstr "Inställningar" #. module: event #: model:ir.ui.menu,name:event.menu_event_configuration msgid "Configuration" -msgstr "Konfiguration" +msgstr "Inställningar" #. module: event #. odoo-javascript @@ -5793,12 +5793,12 @@ msgstr "" #. module: event #: model:ir.model.fields,field_description:event.field_event_type_mail__interval_type msgid "Trigger" -msgstr "Utlösare" +msgstr "Trigger" #. module: event #: model:ir.model.fields,field_description:event.field_event_mail__interval_type msgid "Trigger " -msgstr "Utlösare " +msgstr "Trigger " #. module: event #: model:event.tag.category,name:event.event_tag_category_3 diff --git a/addons/event_booth_sale/i18n/fi.po b/addons/event_booth_sale/i18n/fi.po index 3146ecec8d061a..53de475ef802c5 100644 --- a/addons/event_booth_sale/i18n/fi.po +++ b/addons/event_booth_sale/i18n/fi.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: Odoo Server 18.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-12 07:01+0000\n" +"PO-Revision-Date: 2026-08-24 05:52+0000\n" "Last-Translator: Saara Hakanen \n" "Language-Team: Finnish \n" @@ -252,7 +252,7 @@ msgstr "OK" #. module: event_booth_sale #: model:ir.model.fields,field_description:event_booth_sale.field_event_booth__sale_order_id msgid "Order Reference" -msgstr "Tilauksen viite" +msgstr "Tilausviite" #. module: event_booth_sale #: model_terms:ir.ui.view,arch_db:event_booth_sale.event_booth_view_form_from_event diff --git a/addons/event_sale/models/sale_order_line.py b/addons/event_sale/models/sale_order_line.py index 568975a24d5541..3bc5f2fc35031e 100644 --- a/addons/event_sale/models/sale_order_line.py +++ b/addons/event_sale/models/sale_order_line.py @@ -50,7 +50,9 @@ def _init_registrations(self): if so_line.service_tracking != 'event': continue - for _count in range(int(so_line.product_uom_qty) - len(so_line.registration_ids)): + for _count in range( + int(so_line.product_uom_qty) - len(so_line.registration_ids.filtered(lambda reg: reg.state != 'cancel')), + ): values = { 'sale_order_line_id': so_line.id, 'sale_order_id': so_line.order_id.id, diff --git a/addons/event_sale/tests/test_event_sale.py b/addons/event_sale/tests/test_event_sale.py index 30e6c2a7a40e83..706b1a8ea6eed1 100644 --- a/addons/event_sale/tests/test_event_sale.py +++ b/addons/event_sale/tests/test_event_sale.py @@ -577,15 +577,20 @@ def test_unlink_soline(self): self.assertEqual(len(event.registration_ids), 0) @users('user_salesman') - def test_cancel_so(self): + def test_cancel_and_draft_so(self): """ This test ensures that when canceling a sale order, if the latter is linked to an event registration, - it is also cancelled """ + it is also cancelled and that after the sale order is set back to draft, new registrations are created on confirmation. """ event = self.env['event.event'].browse(self.event_0.ids) self.register_person.action_make_registration() self.assertEqual(len(event.registration_ids), 1) self.sale_order._action_cancel() self.assertEqual(len(event.registration_ids), 1) self.assertEqual(event.registration_ids.state, 'cancel') + # After canceling the sale order, we set it back to draft and confirm it again. This should create new registrations. + self.sale_order.action_draft() + self.sale_order.action_confirm() + self.assertEqual(len(event.registration_ids), 2) + self.assertEqual(event.registration_ids.mapped('state'), ['cancel', 'draft']) @users('user_salesman') def test_compute_sale_status(self): diff --git a/addons/fleet/i18n/fa.po b/addons/fleet/i18n/fa.po index 1b5e7b4730a8dc..99993fc2caba15 100644 --- a/addons/fleet/i18n/fa.po +++ b/addons/fleet/i18n/fa.po @@ -22,8 +22,8 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0beta\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-09 11:27+0000\n" -"Last-Translator: Aliraza Habibitanha \n" +"PO-Revision-Date: 2026-08-23 06:11+0000\n" +"Last-Translator: Heshmatkhah \n" "Language-Team: Persian \n" "Language: fa\n" @@ -1532,7 +1532,7 @@ msgstr "" #. module: fleet #: model_terms:ir.actions.act_window,help:fleet.fleet_vehicle_odometer_reporting_action msgid "Manage efficiently your vehicle Odometers with Odoo." -msgstr "کیلومترشمار وسایل نقلیه خود را به طور کارآمد با سازمان‌یار مدیریت کنید." +msgstr "کیلومترشمار وسایل نقلیه خود را به طور کارآمد با اودو مدیریت کنید." #. module: fleet #: model:fleet.service.type,name:fleet.type_service_service_10 diff --git a/addons/fleet/i18n/sv.po b/addons/fleet/i18n/sv.po index 1e25ea77c1fce4..8ae05359d0f10a 100644 --- a/addons/fleet/i18n/sv.po +++ b/addons/fleet/i18n/sv.po @@ -27,7 +27,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0beta\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-14 15:10+0000\n" +"PO-Revision-Date: 2026-08-24 08:00+0000\n" "Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" @@ -555,7 +555,7 @@ msgstr "Inställningar" #: model:ir.ui.menu,name:fleet.fleet_configuration #: model_terms:ir.ui.view,arch_db:fleet.fleet_vehicle_model_brand_view_kanban msgid "Configuration" -msgstr "Konfiguration" +msgstr "Inställningar" #. module: fleet #: model_terms:ir.ui.view,arch_db:fleet.fleet_vehicle_model_view_search diff --git a/addons/gamification/i18n/fa.po b/addons/gamification/i18n/fa.po index 6c0dc6b65cff53..e72c3ea4ccc5e7 100644 --- a/addons/gamification/i18n/fa.po +++ b/addons/gamification/i18n/fa.po @@ -15,13 +15,14 @@ # Weblate , 2025, 2026. # Aliraza Habibitanha , 2026. # سید سعید محمودزاده حسینی , 2026. +# Heshmatkhah , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0beta\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:37+0000\n" -"PO-Revision-Date: 2026-08-09 11:29+0000\n" -"Last-Translator: Aliraza Habibitanha \n" +"PO-Revision-Date: 2026-08-23 06:13+0000\n" +"Last-Translator: Heshmatkhah \n" "Language-Team: Persian \n" "Language: fa\n" @@ -458,8 +459,7 @@ msgstr "" " \n" " قدرت گرفته از " -"سازمان‌یار\n" +"{object.user_id.company_id.email_secondary_color or '#875A7B'}};\">اودو\n" " \n" " \n" "\n" diff --git a/addons/gamification/i18n/ja.po b/addons/gamification/i18n/ja.po index 9669199c0ef10e..5ac29a5c9937f9 100644 --- a/addons/gamification/i18n/ja.po +++ b/addons/gamification/i18n/ja.po @@ -14,7 +14,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0beta\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:37+0000\n" -"PO-Revision-Date: 2026-08-04 20:54+0000\n" +"PO-Revision-Date: 2026-08-24 03:09+0000\n" "Last-Translator: \"Junko Augias (juau)\" \n" "Language-Team: Japanese \n" @@ -1625,7 +1625,7 @@ msgstr "完了" #: model:ir.model.fields.selection,name:gamification.selection__gamification_goal__state__draft #: model_terms:ir.ui.view,arch_db:gamification.goal_search_view msgid "Draft" -msgstr "ドラフト" +msgstr "下書き" #. module: gamification #: model_terms:gamification.karma.rank,description_motivational:gamification.rank_newbie diff --git a/addons/gamification/i18n/sv.po b/addons/gamification/i18n/sv.po index 716ba5c5102397..f7458a64e418f5 100644 --- a/addons/gamification/i18n/sv.po +++ b/addons/gamification/i18n/sv.po @@ -23,8 +23,8 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0beta\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:37+0000\n" -"PO-Revision-Date: 2026-08-08 01:33+0000\n" -"Last-Translator: Jakob Krabbe \n" +"PO-Revision-Date: 2026-08-17 10:00+0000\n" +"Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" "Language: sv\n" @@ -1174,7 +1174,7 @@ msgstr "Förekommer i" #: model_terms:ir.ui.view,arch_db:gamification.badge_form_view #: model_terms:ir.ui.view,arch_db:gamification.gamification_badge_view_search msgid "Archived" -msgstr "Arkiverad" +msgstr "Arkiverade" #. module: gamification #: model_terms:ir.ui.view,arch_db:gamification.challenge_form_view diff --git a/addons/google_calendar/i18n/fi.po b/addons/google_calendar/i18n/fi.po index 3c467cd91a111d..9f695a8c72106a 100644 --- a/addons/google_calendar/i18n/fi.po +++ b/addons/google_calendar/i18n/fi.po @@ -18,7 +18,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0beta\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-02 11:05+0000\n" +"PO-Revision-Date: 2026-08-24 05:52+0000\n" "Last-Translator: Saara Hakanen \n" "Language-Team: Finnish \n" @@ -73,8 +73,8 @@ msgid "" "An administrator needs to configure Google Synchronization before you can " "use it!" msgstr "" -"Ylläpitäjän pitää määrittää Google-synkronoinnin asetukset, jotta voit ottaa " -"sen käyttöön!" +"Ylläpitäjän täytyy määrittää Google-synkronoinnin asetukset ennen kuin voit " +"käyttää sitä!" #. module: google_calendar #. odoo-python @@ -85,9 +85,9 @@ msgid "" "secret on the Google APIs plateform or try to stop and restart your calendar " "synchronization." msgstr "" -"Tunnuksen luomisessa tapahtui virhe. Valtuutuskoodisi voi olla virheellinen " +"Tunnisteen luomisessa tapahtui virhe. Valtuutuskoodisi voi olla virheellinen " "tai se on jo vanhentunut [%s]. Tarkista asiakastunnuksesi ja salauksesi " -"Google API -alustalta tai yritä keskeyttää ja käynnistää kalenterin " +"Google API -alustalla tai yritä keskeyttää ja käynnistää kalenterin " "synkronointi uudelleen." #. module: google_calendar @@ -104,7 +104,7 @@ msgstr "Kalenteritapahtuma" #: model:ir.model.fields,field_description:google_calendar.field_res_users__google_calendar_cal_id #: model:ir.model.fields,field_description:google_calendar.field_res_users_settings__google_calendar_cal_id msgid "Calendar ID" -msgstr "Calendar ID" +msgstr "Kalenteritunnus" #. module: google_calendar #: model_terms:ir.ui.view,arch_db:google_calendar.google_calendar_reset_account_view_form @@ -206,7 +206,7 @@ msgstr "Sähköposti" #. module: google_calendar #: model:ir.model,name:google_calendar.model_calendar_alarm_manager msgid "Event Alarm Manager" -msgstr "Tapahtuman hälytysten hallinta" +msgstr "Tapahtumahälytysten hallinta" #. module: google_calendar #: model:ir.model,name:google_calendar.model_calendar_recurrence @@ -222,29 +222,29 @@ msgstr "Google" #. module: google_calendar #: model_terms:ir.ui.view,arch_db:google_calendar.view_users_form msgid "Google Calendar" -msgstr "Google-kalenteri" +msgstr "Google Kalenteri" #. module: google_calendar #: model:ir.actions.act_window,name:google_calendar.google_calendar_reset_account_action #: model:ir.model,name:google_calendar.model_google_calendar_account_reset msgid "Google Calendar Account Reset" -msgstr "Google-kalenteritilin nollaus" +msgstr "Google Kalenteri -tilin nollaus" #. module: google_calendar #: model:ir.model.fields,field_description:google_calendar.field_calendar_event__google_id msgid "Google Calendar Event Id" -msgstr "Google Calendar Event Id" +msgstr "Google Kalenterin tapahtumatunnus" #. module: google_calendar #: model:ir.model.fields,field_description:google_calendar.field_calendar_recurrence__google_id #: model:ir.model.fields,field_description:google_calendar.field_google_calendar_sync__google_id msgid "Google Calendar Id" -msgstr "Google-kalenterin tunnus" +msgstr "Google Kalenterin tunnus" #. module: google_calendar #: model:ir.actions.server,name:google_calendar.ir_cron_sync_all_cals_ir_actions_server msgid "Google Calendar: synchronization" -msgstr "Google-kalenteri: synkronointi" +msgstr "Google Kalenteri: synkronointi" #. module: google_calendar #: model:ir.model.fields.selection,name:google_calendar.selection__calendar_event__videocall_source__google_meet @@ -271,7 +271,7 @@ msgstr "Google antoi seuraavan selityksen: %s" #. module: google_calendar #: model:ir.model.fields,field_description:google_calendar.field_calendar_event__guests_readonly msgid "Guests Event Modification Permission" -msgstr "Vieraat Tapahtuma Muutos Lupa" +msgstr "Vieraan tapahtumamuutoksen lupa" #. module: google_calendar #: model:ir.model.fields,field_description:google_calendar.field_calendar_alarm_manager__id @@ -284,7 +284,7 @@ msgstr "Vieraat Tapahtuma Muutos Lupa" #: model:ir.model.fields,field_description:google_calendar.field_res_users__id #: model:ir.model.fields,field_description:google_calendar.field_res_users_settings__id msgid "ID" -msgstr "ID" +msgstr "Tunnus" #. module: google_calendar #: model:ir.model.fields,help:google_calendar.field_calendar_event__active @@ -292,20 +292,20 @@ msgid "" "If the active field is set to false, it will allow you to hide the event " "alarm information without removing it." msgstr "" -"Jos aktiivinen-kentän arvoksi asetetaan false, voit piilottaa " -"tapahtumahälytystiedot poistamatta niitä." +"Jos 'aktiivinen'-kenttä on asetettu tilaan 'false', voit piilottaa " +"tapahtumahälytyksen tiedot poistamatta niitä." #. module: google_calendar #: model:ir.model.fields,help:google_calendar.field_res_config_settings__cal_sync_paused msgid "Indicates if synchronization with Google Calendar is paused or not." msgstr "" -"Ilmaisee, onko synkronointi Google-kalenterin kanssa keskeytetty vai ei." +"Ilmaisee, onko synkronointi Google Kalenterin kanssa keskeytetty vai ei." #. module: google_calendar #. odoo-python #: code:addons/google_calendar/models/google_sync.py:0 msgid "It will not be synced as long at it is not updated." -msgstr "Ei synkronoida niin kauan kuin kohdetta ei ole päivitetty." +msgstr "Ei synkronoida niin kauan kun sitä ei päivitetä." #. module: google_calendar #: model:ir.model.fields,help:google_calendar.field_res_users__google_calendar_cal_id @@ -314,9 +314,8 @@ msgid "" "Last Calendar ID who has been synchronized. If it is changed, we remove all " "links between GoogleID and Odoo Google Internal ID" msgstr "" -"Viimeinen Calendar ID, joka on synkronoitu. Jos tätä muutetaan, kaikki " -"linkitykset poistetaan GoogleID:n ja Odoon sisäisen Google Internal ID:n " -"väliltä" +"Viimeisin synkronoitu kalenteritunnus. Jos sitä muutetaan, poistamme kaikki " +"linkit GoogleID:n ja Odoon sisäisen Google-tunnuksen väliltä" #. module: google_calendar #: model:ir.model.fields,field_description:google_calendar.field_google_calendar_account_reset__write_uid @@ -338,7 +337,7 @@ msgstr "Jätä ne koskematta" #: model:ir.model.fields,field_description:google_calendar.field_calendar_recurrence__need_sync #: model:ir.model.fields,field_description:google_calendar.field_google_calendar_sync__need_sync msgid "Need Sync" -msgstr "Tarvitaan synkronointia" +msgstr "Tarvitaan synkronointi" #. module: google_calendar #: model:ir.model.fields,field_description:google_calendar.field_res_users__google_calendar_sync_token @@ -360,13 +359,13 @@ msgstr "Ilmoitus" #. module: google_calendar #: model_terms:ir.ui.view,arch_db:google_calendar.res_config_settings_view_form msgid "Pause Synchronization" -msgstr "Pysäytä synkronointi" +msgstr "Tauota synkronointi" #. module: google_calendar #: model:ir.model.fields,field_description:google_calendar.field_res_users__google_calendar_rtoken #: model:ir.model.fields,field_description:google_calendar.field_res_users_settings__google_calendar_rtoken msgid "Refresh Token" -msgstr "Uudista pääsytunniste" +msgstr "Päivitä tunniste" #. module: google_calendar #: model_terms:ir.ui.view,arch_db:google_calendar.view_users_form @@ -400,8 +399,8 @@ msgid "" "The Google Synchronization needs to be configured before you can use it, do " "you want to do it now?" msgstr "" -"Google-synkronoinnin asetukset tulee määritellä ennen käyttöönottoa. " -"Haluatko tehdä sen nyt?" +"Google-synkronoinnin asetukset tulee määrittää ennen käyttöönottoa. Haluatko " +"tehdä sen nyt?" #. module: google_calendar #. odoo-python @@ -410,19 +409,20 @@ msgid "" "The following event can only be updated by the organizer according to the " "event permissions set on Google Calendar." msgstr "" -"Seuraavaa tapahtumaa voi päivittää vain järjestäjä Google-kalenterissa " -"asetettujen tapahtumaoikeuksien mukaisesti." +"Seuraavan tapahtuman voi päivittää ainoastaan sen järjestäjä, Google " +"Kalenterissa määritettyjen tapahtumaoikeuksien mukaisesti." #. module: google_calendar #. odoo-python #: code:addons/google_calendar/models/google_sync.py:0 msgid "The following event could not be synced with Google Calendar." -msgstr "Seuraavaa tapahtumaa ei voitu synkronoida Google-kalenterin kanssa." +msgstr "" +"Seuraavan tapahtuman synkronointi Google Kalenterin kanssa ei onnistunut." #. module: google_calendar #: model:ir.model.fields,help:google_calendar.field_google_calendar_account_reset__delete_policy msgid "This will only affect events for which the user is the owner" -msgstr "Tämä vaikuttaa vain tapahtumiin, joiden omistaja on käyttäjä" +msgstr "Tämä vaikuttaa vain tapahtumiin, jotka käyttäjä omistaa" #. module: google_calendar #: model:ir.model.fields,field_description:google_calendar.field_res_users__google_calendar_token_validity @@ -445,7 +445,7 @@ msgstr "Käyttäjäasetukset" #: model:ir.model.fields,field_description:google_calendar.field_res_users__google_calendar_token #: model:ir.model.fields,field_description:google_calendar.field_res_users_settings__google_calendar_token msgid "User token" -msgstr "User token" +msgstr "Käyttäjätunniste" #. module: google_calendar #: model:ir.model.fields,field_description:google_calendar.field_google_calendar_account_reset__delete_policy @@ -469,7 +469,7 @@ msgstr "määrittelemätön aika" msgid "" "you don't seem to have permission to modify this event on Google Calendar" msgstr "" -"sinulla ei näytä olevan lupaa muokata tätä tapahtumaa Google-kalenterissa" +"sinulla ei näytä olevan lupaa muokata tätä tapahtumaa Google Kalenterissa" #~ msgid "" #~ "An error occurred while generating the token. Your authorization code may " diff --git a/addons/google_gmail/i18n/fi.po b/addons/google_gmail/i18n/fi.po index 91dfa2875f8b99..faf9feb504e186 100644 --- a/addons/google_gmail/i18n/fi.po +++ b/addons/google_gmail/i18n/fi.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: Odoo Server 18.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-25 18:36+0000\n" -"PO-Revision-Date: 2026-08-02 19:27+0000\n" +"PO-Revision-Date: 2026-08-24 05:53+0000\n" "Last-Translator: Saara Hakanen \n" "Language-Team: Finnish \n" @@ -279,7 +279,7 @@ msgstr "Lue lisää" #: model:ir.model.fields,field_description:google_gmail.field_google_gmail_mixin__google_gmail_refresh_token #: model:ir.model.fields,field_description:google_gmail.field_ir_mail_server__google_gmail_refresh_token msgid "Refresh Token" -msgstr "Uudista pääsytunniste" +msgstr "Päivitä tunniste" #. module: google_gmail #. odoo-python diff --git a/addons/hr/i18n/sv.po b/addons/hr/i18n/sv.po index 3ed1982b548557..3a88ffa3295bce 100644 --- a/addons/hr/i18n/sv.po +++ b/addons/hr/i18n/sv.po @@ -32,7 +32,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-06-19 17:35+0000\n" -"PO-Revision-Date: 2026-08-14 15:09+0000\n" +"PO-Revision-Date: 2026-08-24 08:00+0000\n" "Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" "Language: sv\n" @@ -894,7 +894,7 @@ msgstr "Arkiv" #: model_terms:ir.ui.view,arch_db:hr.view_hr_job_form #: model_terms:ir.ui.view,arch_db:hr.view_job_filter msgid "Archived" -msgstr "Arkiverad" +msgstr "Arkiverade" #. module: hr #: model_terms:hr.job,website_description:hr.job_hrm @@ -1463,7 +1463,7 @@ msgstr "Inställningar" #: model:ir.ui.menu,name:hr.menu_human_resources_configuration #: model_terms:ir.ui.view,arch_db:hr.hr_department_view_kanban msgid "Configuration" -msgstr "Konfiguration" +msgstr "Inställningar" #. module: hr #. odoo-python diff --git a/addons/hr/models/hr_employee.py b/addons/hr/models/hr_employee.py index 4d8aaad08adf75..1660d6e6d203b3 100644 --- a/addons/hr/models/hr_employee.py +++ b/addons/hr/models/hr_employee.py @@ -1241,11 +1241,24 @@ def _search(self, domain, offset=0, limit=None, order=None, *, bypass_access=Fal if self.browse().has_access('read') or bypass_access: return super()._search(domain, offset, limit, order, bypass_access=bypass_access, **kwargs) domain = Domain(domain) + # HACK Some fields are inherited from the `current_version_id` and may have been already # optimized, showing current_version_id in the domain, but public employee does not have # that field and may have fields directly on the model, just change the condition to `id` in # that case. - domain = domain.map_conditions(lambda cond: Domain('id', cond.operator, cond.value) if cond.field_expr == 'current_version_id' else cond) + def adapt_condition(cond): + if cond.field_expr != 'current_version_id': + return cond + if cond.operator in ('any', 'not any'): + raise AccessError(self.env._('You do not have access to this document.')) + if cond.operator in ('any!', 'not any!'): + if isinstance(cond.value, Query): + query = cond.value.subselect(SQL.identifier(cond.value.table, 'employee_id')) + else: + query = self.env['hr.version'].sudo()._search(Domain('id', 'any!', cond.value), active_test=False) + return Domain('id', cond.operator, query) + return Domain('id', cond.operator, cond.value) + domain = domain.map_conditions(adapt_condition) try: ids = self.env['hr.employee.public']._search(domain, offset, limit, order, **kwargs) except ValueError as e: @@ -1337,7 +1350,7 @@ def _sync_user(self, user, employee_has_image=False): def _prepare_resource_values(self, vals, tz): resource_vals = super()._prepare_resource_values(vals, tz) - vals.pop('name') # Already considered by super call but no popped + vals.pop('name', False) # Already considered by super call but no popped # We need to pop it to avoid useless resource update (& write) call # on every newly created resource (with the correct name already) user_id = vals.pop('user_id', None) diff --git a/addons/hr/models/hr_job.py b/addons/hr/models/hr_job.py index 9068ed87f79a7c..5909f97613a702 100644 --- a/addons/hr/models/hr_job.py +++ b/addons/hr/models/hr_job.py @@ -36,7 +36,7 @@ class HrJob(models.Model): # TODO (master): remove the field `allowed_user_ids`. allowed_user_ids = fields.Many2many('res.users', compute='_compute_allowed_user_ids', readonly=True) department_id = fields.Many2one('hr.department', string='Department', check_company=True, tracking=True, index='btree_not_null') - company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company, tracking=True) + company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company, tracking=True, domain=lambda self: [('id', 'in', self.env.companies.ids)]) contract_type_id = fields.Many2one('hr.contract.type', string='Employment Type', tracking=True) _name_company_uniq = models.Constraint( diff --git a/addons/hr/models/res_partner.py b/addons/hr/models/res_partner.py index 3856a7a3713926..e8d7445351f0ae 100644 --- a/addons/hr/models/res_partner.py +++ b/addons/hr/models/res_partner.py @@ -49,8 +49,10 @@ def _get_all_addr(self): pstl_addr = { 'contact_type': 'employee', 'street': employee_id.private_street, + 'street2': employee_id.private_street2, 'zip': employee_id.private_zip, 'city': employee_id.private_city, + 'state': employee_id.private_state_id.code, 'country': employee_id.private_country_id.code, } return [pstl_addr] + super()._get_all_addr() diff --git a/addons/hr/tests/test_hr_employee_public.py b/addons/hr/tests/test_hr_employee_public.py index 53fcae55083719..ac2a2793adbf5a 100644 --- a/addons/hr/tests/test_hr_employee_public.py +++ b/addons/hr/tests/test_hr_employee_public.py @@ -23,5 +23,12 @@ def test_access_related_field_to_hr_employee(self): self.env['hr.employee.public'].with_user(self.res_users_without_hr_right).search([("email", "!=", False)]) def test_access_search_on_users_department(self): + dep = self.env['hr.department'].create({'name': 'test'}) + emp = self.env['hr.employee'].create({'department_id': dep.id, 'name': 'Joe'}) + # the search can be performed on the user (without access error) User = self.env['res.users'].with_user(self.res_users_without_hr_right) - User.search([('employee_id.department_id', '=', 1)]) + User.search([('employee_id.department_id', '=', dep.id)]) + # the search on the resource should find the user + res = self.env['resource.resource'].with_user(self.res_users_without_hr_right) + res = res.search([('employee_id.department_id', '=', dep.id)]) + self.assertIn(emp.resource_id, res, "Resource should be found") diff --git a/addons/hr_attendance/i18n/sv.po b/addons/hr_attendance/i18n/sv.po index b6adf379e4b6a2..f17c2c9d977603 100644 --- a/addons/hr_attendance/i18n/sv.po +++ b/addons/hr_attendance/i18n/sv.po @@ -22,8 +22,8 @@ msgstr "" "Project-Id-Version: Odoo Server 15.5alpha1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-17 17:37+0000\n" -"PO-Revision-Date: 2026-08-07 05:39+0000\n" -"Last-Translator: symbiotech \n" +"PO-Revision-Date: 2026-08-17 12:10+0000\n" +"Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" "Language: sv\n" @@ -296,7 +296,7 @@ msgstr "Godkänd av chefen" #. module: hr_attendance #: model_terms:ir.ui.view,arch_db:hr_attendance.hr_attendance_overtime_ruleset_view_filter msgid "Archived" -msgstr "Arkiverad" +msgstr "Arkiverade" #. module: hr_attendance #: model_terms:ir.ui.view,arch_db:hr_attendance.hr_attendance_management_view_filter diff --git a/addons/hr_attendance/models/hr_attendance.py b/addons/hr_attendance/models/hr_attendance.py index 459dc5aa2b603d..3330316f063783 100644 --- a/addons/hr_attendance/models/hr_attendance.py +++ b/addons/hr_attendance/models/hr_attendance.py @@ -285,7 +285,7 @@ def _get_overtimes_to_update_domain(self): domain_list.append(Domain.AND([ Domain('employee_id', '=', employee.id), Domain('check_in', '<=', tz.localize(datetime.combine(date_to, datetime.max.time())).astimezone(utc).replace(tzinfo=None)), - Domain('check_out', '>=', tz.localize(datetime.combine(date_from, datetime.min.time())).astimezone(utc).replace(tzinfo=None)), + Domain('check_out', '>', tz.localize(datetime.combine(date_from, datetime.min.time())).astimezone(utc).replace(tzinfo=None)), ])) if not domain_list: return Domain.FALSE diff --git a/addons/hr_attendance/static/src/components/attendance_menu/attendance_menu.js b/addons/hr_attendance/static/src/components/attendance_menu/attendance_menu.js index da57ecceb11a26..a1dfe042946633 100644 --- a/addons/hr_attendance/static/src/components/attendance_menu/attendance_menu.js +++ b/addons/hr_attendance/static/src/components/attendance_menu/attendance_menu.js @@ -93,7 +93,7 @@ export class ActivityMenu extends Component { body: _t("Unable to get a valid location. Do you want to proceed with your check-in/out anyway?"), confirmLabel: _t("Proceed Anyway"), confirm: async () => await this.checking(), - cancel: () => this._attendanceInProgress = false, + cancel: () => { this._attendanceInProgress = false; }, }); } diff --git a/addons/hr_attendance/tests/test_hr_attendance_overtime.py b/addons/hr_attendance/tests/test_hr_attendance_overtime.py index 2413be7a56b0ec..81957c370f04d8 100644 --- a/addons/hr_attendance/tests/test_hr_attendance_overtime.py +++ b/addons/hr_attendance/tests/test_hr_attendance_overtime.py @@ -1774,6 +1774,37 @@ def test_overtime_recomputation_attendance_overlapping_midnight(self): }) self.assertEqual(attendance2.overtime_hours, 4.0, "The whole attendance should be in overtime.") + def test_overtime_recomputation_attendance_ending_at_midnight(self): + self.env['hr.attendance'].create({ + 'employee_id': self.employee.id, + 'check_in': datetime(2021, 1, 4, 8, 0), + 'check_out': datetime(2021, 1, 4, 17, 0), + }) + attendance = self.env['hr.attendance'].create({ + 'employee_id': self.employee.id, + 'check_in': datetime(2021, 1, 4, 21, 0), + 'check_out': datetime(2021, 1, 5, 0, 0), + }) + + self.assertEqual( + attendance.overtime_hours, + 3.0, + "There should be 3 hours of overtime on the 4th.", + ) + + self.env['hr.attendance'].create({ + 'employee_id': self.employee.id, + 'check_in': datetime(2021, 1, 5, 8, 0), + 'check_out': datetime(2021, 1, 5, 17, 0), + }) + + self.assertEqual( + attendance.overtime_hours, + 3.0, + "An attendance ending at midnight should not be recomputed " + "when updating overtime for the following day.", + ) + def test_weekly_overtime_flexible_resource_public_holiday(self): self.ruleset.rule_ids.write({ 'expected_hours_from_contract': True, diff --git a/addons/hr_expense/i18n/fa.po b/addons/hr_expense/i18n/fa.po index 44e8622fc422b1..c4c23fffca3707 100644 --- a/addons/hr_expense/i18n/fa.po +++ b/addons/hr_expense/i18n/fa.po @@ -16,13 +16,14 @@ # Martin Trigaux, 2023 # Weblate , 2025, 2026. # Aliraza Habibitanha , 2026. +# Heshmatkhah , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-03 17:35+0000\n" -"PO-Revision-Date: 2026-08-09 10:53+0000\n" -"Last-Translator: Aliraza Habibitanha \n" +"PO-Revision-Date: 2026-08-23 06:12+0000\n" +"Last-Translator: Heshmatkhah \n" "Language-Team: Persian \n" "Language: fa\n" @@ -462,7 +463,7 @@ msgid "" "expenses, integrated into Odoo through Stripe Issuing." msgstr "" "کارت‌های مجازی و فیزیکی پیش‌پرداخت را برای هزینه‌های یک‌باره و تکراری ایجاد کنید " -"که از طریق Stripe Issuing با سازمان‌یار یکپارچه شده‌اند." +"که از طریق Stripe Issuing با اودو یکپارچه شده‌اند." #. module: hr_expense #: model:ir.model.fields,field_description:hr_expense.field_hr_expense__create_uid @@ -1164,7 +1165,7 @@ msgid "" "employees through Odoo" msgstr "" "حساب صادرکننده stripe خود را برای مدیریت کارت‌های اعتباری شرکت برای کارکنان " -"خود از طریق سازمان‌یار پیوند دهید" +"خود از طریق اودو پیوند دهید" #. module: hr_expense #. odoo-python @@ -1202,7 +1203,7 @@ msgstr "مدیران می‌توانند تمام هزینه‌ها را از ا #. module: hr_expense #: model_terms:ir.ui.view,arch_db:hr_expense.report_expense msgid "Marc Demo" -msgstr "دموی مارچ" +msgstr "ماهان دمو‌پور" #. module: hr_expense #: model:product.template,name:hr_expense.expense_product_meal_product_template @@ -1227,7 +1228,7 @@ msgstr "کبفیت مصرف سوخت" #. module: hr_expense #: model_terms:ir.ui.view,arch_db:hr_expense.report_expense msgid "Mitchell Admin" -msgstr "میچل ادمین" +msgstr "میکائیل ادمین‌زاده" #. module: hr_expense #: model_terms:ir.ui.view,arch_db:hr_expense.hr_expense_view_search @@ -1383,9 +1384,8 @@ msgid "" "Give Odoo a hand to find an account by setting up an expense account.\n" "%(expense)s %(expense_name)s.\n" msgstr "" -"سازمان‌یار هزینه، محصول، شرکت و دفتر روزنامه شما را بررسی کرد اما نتیجه‌ای " -"نیافت.\n" -"با تنظیم حساب هزینه به سازمان‌یار کمک کنید تا یک حساب پیدا کند.\n" +"اودو هزینه، محصول، شرکت و دفتر روزنامه شما را بررسی کرد اما نتیجه‌ای نیافت.\n" +"با تنظیم حساب هزینه به اودو کمک کنید تا یک حساب پیدا کند.\n" "%(expense)s %(expense_name)s.\n" #. module: hr_expense diff --git a/addons/hr_expense/i18n/ja.po b/addons/hr_expense/i18n/ja.po index 249130bef2fc4d..aa74c13cff72ac 100644 --- a/addons/hr_expense/i18n/ja.po +++ b/addons/hr_expense/i18n/ja.po @@ -16,8 +16,8 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-03 17:35+0000\n" -"PO-Revision-Date: 2026-08-08 02:26+0000\n" -"Last-Translator: Weblate \n" +"PO-Revision-Date: 2026-08-24 03:11+0000\n" +"Last-Translator: \"Junko Augias (juau)\" \n" "Language-Team: Japanese \n" "Language: ja\n" @@ -597,7 +597,7 @@ msgstr "アプリをダウンロード" #: model:ir.model.fields.selection,name:hr_expense.selection__hr_expense__state__draft #: model:mail.message.subtype,name:hr_expense.mt_expense_reset msgid "Draft" -msgstr "ドラフト" +msgstr "下書き" #. module: hr_expense #. odoo-python @@ -781,7 +781,7 @@ msgstr "経費が却下されました" #. module: hr_expense #: model:mail.message.subtype,description:hr_expense.mt_expense_reset msgid "Expense reset to Draft" -msgstr "経費がドラフトにリセットされました" +msgstr "経費が下書きにリセットされました" #. module: hr_expense #. odoo-python @@ -1073,7 +1073,7 @@ msgstr "仕訳エントリが削除されました" #. module: hr_expense #: model:mail.message.subtype,name:hr_expense.mt_expense_entry_draft msgid "Journal Entry Reset to Draft" -msgstr "仕訳がドラフトにリセットされました" +msgstr "仕訳が下書きにリセットされました" #. module: hr_expense #: model:ir.model,name:hr_expense.model_account_move_line @@ -1100,7 +1100,7 @@ msgstr "仕訳帳エントリが削除されました" #. module: hr_expense #: model:mail.message.subtype,description:hr_expense.mt_expense_entry_draft msgid "Journal entry reset to draft" -msgstr "仕訳がドラフトにリセットされました" +msgstr "仕訳が下書きにリセットされました" #. module: hr_expense #: model:ir.model.fields,field_description:hr_expense.field_hr_expense__label_currency_rate @@ -1396,15 +1396,13 @@ msgstr "経費が準備できたら、マネジャーに提出し、承 #: code:addons/hr_expense/models/hr_expense.py:0 msgid "" "Only HR Officers, accountants, or the concerned employee can reset to draft." -msgstr "" -"人事担当者、会計担当者、または関係する従業員のみがドラフトにリセットできます" -"。" +msgstr "人事担当者、会計担当者、または関係する従業員のみが下書きにリセットできます。" #. module: hr_expense #. odoo-python #: code:addons/hr_expense/models/hr_expense.py:0 msgid "Only draft expenses can have a total of 0." -msgstr "合計金額0の値を持てるのはドラフト経費のみです。" +msgstr "合計金額0の値を持てるのは下書き経費のみです。" #. module: hr_expense #. odoo-python @@ -2338,7 +2336,7 @@ msgstr "" #. odoo-python #: code:addons/hr_expense/models/hr_expense.py:0 msgid "You cannot reset to draft an expense linked to a posted journal entry." -msgstr "記帳済の仕訳にリンクした経費をドラフトに戻すことはできません。" +msgstr "記帳済の仕訳にリンクした経費を下書きに戻すことはできません。" #. module: hr_expense #. odoo-python diff --git a/addons/hr_expense/i18n/sv.po b/addons/hr_expense/i18n/sv.po index cb477ea7b032e8..ae3a0cd4f77455 100644 --- a/addons/hr_expense/i18n/sv.po +++ b/addons/hr_expense/i18n/sv.po @@ -25,7 +25,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-03 17:35+0000\n" -"PO-Revision-Date: 2026-08-14 15:09+0000\n" +"PO-Revision-Date: 2026-08-17 12:10+0000\n" "Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" @@ -317,7 +317,7 @@ msgstr "Godkänd" #. module: hr_expense #: model_terms:ir.ui.view,arch_db:hr_expense.product_product_expense_form_view msgid "Archived" -msgstr "Arkiverad" +msgstr "Arkiverade" #. module: hr_expense #. odoo-javascript diff --git a/addons/hr_expense/i18n/vi.po b/addons/hr_expense/i18n/vi.po index b1f47a306edb63..7598e731458d06 100644 --- a/addons/hr_expense/i18n/vi.po +++ b/addons/hr_expense/i18n/vi.po @@ -1085,7 +1085,7 @@ msgstr "Đặt lại bút toán thành nháp" #. module: hr_expense #: model:ir.model,name:hr_expense.model_account_move_line msgid "Journal Item" -msgstr "Hạng mục bút toán" +msgstr "Phát sinh kế toán" #. module: hr_expense #. odoo-python diff --git a/addons/hr_expense/models/account_tax.py b/addons/hr_expense/models/account_tax.py index d308ace25bb624..fd1382eb5c271a 100644 --- a/addons/hr_expense/models/account_tax.py +++ b/addons/hr_expense/models/account_tax.py @@ -1,32 +1,25 @@ -# -*- coding: utf-8 -*- - -from odoo import models +from odoo import api, fields, models class AccountTax(models.Model): _inherit = "account.tax" - def _hook_compute_is_used(self, taxes_to_compute): - # OVERRIDE in order to fetch taxes used in expenses - - used_taxes = super()._hook_compute_is_used(taxes_to_compute) - taxes_to_compute -= used_taxes - - if taxes_to_compute: - self.env['hr.expense'].flush_model(['tax_ids']) - self.env.cr.execute(""" - SELECT id - FROM account_tax - WHERE EXISTS( - SELECT 1 - FROM expense_tax AS exp - WHERE account_tax.id = exp.tax_id - ) AND id IN %s - """, [tuple(taxes_to_compute)]) - - used_taxes.update([tax[0] for tax in self.env.cr.fetchall()]) - - return used_taxes + hr_expense_ids = fields.Many2many( + comodel_name='hr.expense', + relation='expense_tax', + column1='tax_id', + column2='expense_id', + copy=False, + readonly=True, + ) + + @api.depends('hr_expense_ids') + def _compute_is_used(self): + super()._compute_is_used() + self.sudo().search([ + ('id', 'in', self.filtered(lambda t: not t.is_used).ids), + ('hr_expense_ids', '!=', False), + ]).is_used = True def _prepare_base_line_for_taxes_computation(self, record, **kwargs): # EXTENDS 'account' diff --git a/addons/hr_expense/tests/test_expenses.py b/addons/hr_expense/tests/test_expenses.py index d1372423b0d860..97d760e7033f4d 100644 --- a/addons/hr_expense/tests/test_expenses.py +++ b/addons/hr_expense/tests/test_expenses.py @@ -898,7 +898,6 @@ def test_tax_is_used_when_in_transactions(self): }) self.create_expenses({'tax_ids': [Command.set(tax_expense.ids)]}) - tax_expense.invalidate_model(fnames=['is_used']) self.assertTrue(tax_expense.is_used) def test_expense_by_company_with_caba_tax(self): diff --git a/addons/hr_holidays/i18n/fa.po b/addons/hr_holidays/i18n/fa.po index 33a5f4fba22531..862eacf257f9b2 100644 --- a/addons/hr_holidays/i18n/fa.po +++ b/addons/hr_holidays/i18n/fa.po @@ -19,13 +19,14 @@ # ebrahim khorami , 2026. # سید سعید محمودزاده حسینی , 2026. # Aliraza Habibitanha , 2026. +# Heshmatkhah , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-17 17:37+0000\n" -"PO-Revision-Date: 2026-08-09 11:34+0000\n" -"Last-Translator: Aliraza Habibitanha \n" +"PO-Revision-Date: 2026-08-23 06:11+0000\n" +"Last-Translator: Heshmatkhah \n" "Language-Team: Persian \n" "Language: fa\n" @@ -3805,8 +3806,8 @@ msgid "" "%s" msgstr "" "برخی از کارمندان دارای درخواست‌های مرخصی هستند که ساعات آنها با بازه زمانی " -"انتخاب شده همپوشانی دارد. سازمان‌یار نمی‌تواند به صورت خودکار ساعات مرخصی را " -"در تولید دسته‌ای تنظیم یا تقسیم کند. مرخصی دارای مغایرت:\n" +"انتخاب شده همپوشانی دارد. اودو نمی‌تواند به صورت خودکار ساعات مرخصی را در " +"تولید دسته‌ای تنظیم یا تقسیم کند. مرخصی دارای مغایرت:\n" "%s" #. module: hr_holidays diff --git a/addons/hr_holidays/i18n/fi.po b/addons/hr_holidays/i18n/fi.po index c1a66ea01c74e1..723c2e210d58f1 100644 --- a/addons/hr_holidays/i18n/fi.po +++ b/addons/hr_holidays/i18n/fi.po @@ -36,8 +36,8 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-17 17:37+0000\n" -"PO-Revision-Date: 2026-08-07 16:39+0000\n" -"Last-Translator: Weblate \n" +"PO-Revision-Date: 2026-08-24 05:53+0000\n" +"Last-Translator: Saara Hakanen \n" "Language-Team: Finnish \n" "Language: fi\n" @@ -3013,7 +3013,7 @@ msgstr "Uusi varaus" #. odoo-javascript #: code:addons/hr_holidays/static/src/dashboard/time_off_dashboard.xml:0 msgid "New Allocation Request" -msgstr "Uusi varauspyyntö" +msgstr "Uusi lomapyyntö" #. module: hr_holidays #. odoo-python @@ -3022,7 +3022,7 @@ msgid "" "New Allocation Request created by %(user)s: %(count)s Days of %" "(allocation_type)s" msgstr "" -"Uusi kohdennuspyyntö, jonka on luonut %(user)s: %(count)s päivää %" +"%(user)s luoma uusi lomapyyntö: %(count)s päivää vapaatyyppiä %" "(allocation_type)s" #. module: hr_holidays @@ -4312,7 +4312,7 @@ msgstr "Vapaat" #. module: hr_holidays #: model:ir.model,name:hr_holidays.model_hr_leave_allocation msgid "Time Off Allocation" -msgstr "Vapaiden ajankohta" +msgstr "Vapaiden myöntäminen" #. module: hr_holidays #: model:ir.actions.act_window,name:hr_holidays.hr_leave_report_action diff --git a/addons/hr_holidays/i18n/sv.po b/addons/hr_holidays/i18n/sv.po index 142f20b409444b..802ec2d8b085ad 100644 --- a/addons/hr_holidays/i18n/sv.po +++ b/addons/hr_holidays/i18n/sv.po @@ -28,7 +28,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-17 17:37+0000\n" -"PO-Revision-Date: 2026-08-14 15:10+0000\n" +"PO-Revision-Date: 2026-08-17 15:23+0000\n" "Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" @@ -1022,7 +1022,7 @@ msgstr "april" #: model_terms:ir.ui.view,arch_db:hr_holidays.hr_accrual_plan_view_search #: model_terms:ir.ui.view,arch_db:hr_holidays.view_holidays_status_filter msgid "Archived" -msgstr "Arkiverad" +msgstr "Arkiverade" #. module: hr_holidays #. odoo-javascript diff --git a/addons/hr_holidays/models/hr_leave_type.py b/addons/hr_holidays/models/hr_leave_type.py index 75712650f847b6..f2601420a4ae4c 100644 --- a/addons/hr_holidays/models/hr_leave_type.py +++ b/addons/hr_holidays/models/hr_leave_type.py @@ -44,7 +44,11 @@ def _model_sorting_key(self, leave_type): name = fields.Char('Time Off Type', required=True, translate=True) sequence = fields.Integer(default=100, help='The type with the smallest sequence is the default value in time off request') - create_calendar_meeting = fields.Boolean(string="Display Time Off in Calendar", default=True) + create_calendar_meeting = fields.Boolean( + string="Display Time Off in Calendar", + default=True, + help="If this field is checked, every leave request of this type will have a corresponding entry in the calendar application. There will be no entry if this stays unchecked." + ) color = fields.Integer(string='Color', help="The color selected here will be used in every screen with the time off type.") icon_id = fields.Many2one('ir.attachment', string='Cover Image', domain="[('res_model', '=', 'hr.leave.type'), ('res_field', '=', 'icon_id')]") active = fields.Boolean('Active', default=True, diff --git a/addons/hr_holidays/views/hr_leave_type_views.xml b/addons/hr_holidays/views/hr_leave_type_views.xml index 533a4f1286850a..e0f94864f7dfec 100644 --- a/addons/hr_holidays/views/hr_leave_type_views.xml +++ b/addons/hr_holidays/views/hr_leave_type_views.xml @@ -96,6 +96,7 @@
@@ -103,6 +104,7 @@ +
diff --git a/addons/hr_holidays_attendance/i18n/fi.po b/addons/hr_holidays_attendance/i18n/fi.po index af4d0517a6d5b6..157273a4c00f60 100644 --- a/addons/hr_holidays_attendance/i18n/fi.po +++ b/addons/hr_holidays_attendance/i18n/fi.po @@ -10,8 +10,8 @@ msgstr "" "Project-Id-Version: Odoo Server 18.2a1+e\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-27 18:37+0000\n" -"PO-Revision-Date: 2026-08-07 03:45+0000\n" -"Last-Translator: Weblate \n" +"PO-Revision-Date: 2026-08-24 05:52+0000\n" +"Last-Translator: Saara Hakanen \n" "Language-Team: Finnish \n" "Language: fi\n" @@ -169,7 +169,7 @@ msgstr "Odotetut tunnit" #: model_terms:ir.ui.view,arch_db:hr_holidays_attendance.hr_attendance_holidays_hr_leave_allocation_view_form_inherit #: model_terms:ir.ui.view,arch_db:hr_holidays_attendance.hr_leave_view_form msgid "Extra Hours Available" -msgstr "Saatavilla olevat ylimääräiset tunnit" +msgstr "Saatavilla olevat lisätunnit" #. module: hr_holidays_attendance #: model:ir.model.fields,field_description:hr_holidays_attendance.field_hr_leave_accrual_level__frequency @@ -234,7 +234,7 @@ msgstr "Puuttuvat tunnit" #. module: hr_holidays_attendance #: model:ir.actions.act_window,name:hr_holidays_attendance.hr_leave_allocation_overtime_manager_action msgid "New Allocation Request" -msgstr "Uusi varauspyyntö" +msgstr "Uusi lomapyyntö" #. module: hr_holidays_attendance #: model:ir.model.fields,help:hr_holidays_attendance.field_hr_leave_type__overtime_deductible @@ -250,7 +250,8 @@ msgid "" "Only an Officer or Administrator is allowed to edit the allocation duration " "in this status." msgstr "" -"Ainoastaan esimies tai ylläpitäjä voi muokata varauksen kestoa tässä tilassa." +"Vain virkailija tai ylläpitäjä voi muokata tässä tilassa olevien lomien " +"myöntämisen kestoa." #. module: hr_holidays_attendance #: model:ir.model.fields,field_description:hr_holidays_attendance.field_hr_leave__overtime_deductible @@ -306,7 +307,7 @@ msgstr "Vapaat" #. module: hr_holidays_attendance #: model:ir.model,name:hr_holidays_attendance.model_hr_leave_allocation msgid "Time Off Allocation" -msgstr "Vapaiden ajankohta" +msgstr "Vapaiden myöntäminen" #. module: hr_holidays_attendance #: model:ir.actions.act_window,name:hr_holidays_attendance.hr_leave_attendance_report_action diff --git a/addons/hr_holidays_attendance/i18n/sv.po b/addons/hr_holidays_attendance/i18n/sv.po index 1348b24f17e4a9..c10574aa155bb5 100644 --- a/addons/hr_holidays_attendance/i18n/sv.po +++ b/addons/hr_holidays_attendance/i18n/sv.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: Odoo Server 18.2a1+e\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-27 18:37+0000\n" -"PO-Revision-Date: 2026-08-04 02:17+0000\n" +"PO-Revision-Date: 2026-08-24 08:00+0000\n" "Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" @@ -140,7 +140,7 @@ msgstr "Visningsnamn" #. module: hr_holidays_attendance #: model_terms:ir.ui.view,arch_db:hr_holidays_attendance.hr_leave_attendance_report_view_form msgid "Duration" -msgstr "Varaktighet" +msgstr "Tidsperiod" #. module: hr_holidays_attendance #: model:ir.model,name:hr_holidays_attendance.model_hr_employee diff --git a/addons/hr_holidays_attendance/models/resource_calendar_leaves.py b/addons/hr_holidays_attendance/models/resource_calendar_leaves.py index d1a0ba6be4f6c7..2779c489deab50 100644 --- a/addons/hr_holidays_attendance/models/resource_calendar_leaves.py +++ b/addons/hr_holidays_attendance/models/resource_calendar_leaves.py @@ -1,3 +1,5 @@ +import pytz +from datetime import datetime, time from odoo import api, models from odoo.fields import Domain @@ -11,16 +13,34 @@ def _get_attendance_domain(self): leaves_time_type_leave = self.filtered(lambda leave: leave.time_type == 'leave') resource_leaves = leaves_time_type_leave.filtered(lambda leave: leave.resource_id.employee_id) - domain.extend([[ - ('check_in', '<=', max(leaves.mapped('date_to'))), - ('check_out', '>=', min(leaves.mapped('date_from'))), - ('employee_id', 'in', resource.employee_id.ids), - ] for resource, leaves in resource_leaves.grouped('resource_id').items()]) + + for resource, leaves in resource_leaves.grouped('resource_id').items(): + tz = pytz.timezone(resource.tz) + + min_date = pytz.utc.localize(min(leaves.mapped('date_from'))).astimezone(tz).date() + utc_start = tz.localize(datetime.combine(min_date, time(0, 0, 0))).astimezone(pytz.utc).replace(tzinfo=None) + + max_date = pytz.utc.localize(max(leaves.mapped('date_to'))).astimezone(tz).date() + utc_end = tz.localize(datetime.combine(max_date, time(23, 59, 59))).astimezone(pytz.utc).replace(tzinfo=None) + + domain.append([ + ('check_in', '<=', utc_end), + ('check_out', '>=', utc_start), + ('employee_id', 'in', resource.employee_id.ids), + ]) for leave in (leaves_time_type_leave - resource_leaves): + tz = pytz.timezone(leave.calendar_id.tz or leave.company_id.resource_calendar_id.tz) + + start_date = pytz.utc.localize(leave.date_from).astimezone(tz).date() + utc_start = tz.localize(datetime.combine(start_date, time(0, 0, 0))).astimezone(pytz.utc).replace(tzinfo=None) + + end_date = pytz.utc.localize(leave.date_to).astimezone(tz).date() + utc_end = tz.localize(datetime.combine(end_date, time(23, 59, 59))).astimezone(pytz.utc).replace(tzinfo=None) + leave_domain = [ - ('check_in', '<=', leave.date_to), - ('check_out', '>=', leave.date_from), + ('check_in', '<=', utc_end), + ('check_out', '>=', utc_start), ] if leave.company_id: leave_domain.append(('employee_id.company_id', '=', leave.company_id.id)) diff --git a/addons/hr_holidays_attendance/tests/test_holidays_overtime.py b/addons/hr_holidays_attendance/tests/test_holidays_overtime.py index e268be1431b827..9d687d4609a5e3 100644 --- a/addons/hr_holidays_attendance/tests/test_holidays_overtime.py +++ b/addons/hr_holidays_attendance/tests/test_holidays_overtime.py @@ -531,3 +531,38 @@ def test_absence_management_with_timeoff(self): ('employee_id', '=', self.employee.id), ('check_in', '=', '2026-01-14'), ]), 1) + + def test_timeoff_after_absence_management(self): + """ + Test that when an employee creates a leave covering an autogenerated + absence attendance (created at 12:00 AM), the negative extra hours + are correctly reset to 0. + """ + self.company.write({ + 'absence_management': True, + }) + self.employee.contract_date_start = datetime(2026, 1, 1) + self.env.user.tz = 'Europe/Brussels' + self.employee.tz = 'Europe/Brussels' + self.employee.resource_calendar_id.tz = 'Europe/Brussels' + self.employee.ruleset_id = self.env.ref('hr_attendance.hr_attendance_default_ruleset') + + with freeze_time('2026-01-14'): + self.env['hr.attendance']._cron_absence_detection() + + self.assertEqual(self.env['hr.attendance'].search_count([ + ('employee_id', '=', self.employee.id), + ('check_in', '=', '2026-01-13'), + ]), 1) + self.assertAlmostEqual(self.employee.total_overtime, -8.0, 2) + + leave = self.env['hr.leave'].create({ + 'name': 'Vacation Yippie', + 'employee_id': self.employee.id, + 'holiday_status_id': self.regular_leave_type.id, + 'request_date_from': datetime(2026, 1, 13), + 'request_date_to': datetime(2026, 1, 13), + }) + leave.action_approve() + + self.assertAlmostEqual(self.employee.total_overtime, 0.0, 2) diff --git a/addons/hr_recruitment/i18n/fa.po b/addons/hr_recruitment/i18n/fa.po index eeae86d5fc0ecc..857dbabcdb7430 100644 --- a/addons/hr_recruitment/i18n/fa.po +++ b/addons/hr_recruitment/i18n/fa.po @@ -18,13 +18,14 @@ # Hamed Mohammadi , 2023 # Weblate , 2025, 2026. # Aliraza Habibitanha , 2026. +# Heshmatkhah , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0+e\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-03 17:35+0000\n" -"PO-Revision-Date: 2026-08-09 10:34+0000\n" -"Last-Translator: Aliraza Habibitanha \n" +"PO-Revision-Date: 2026-08-23 06:12+0000\n" +"Last-Translator: Heshmatkhah \n" "Language-Team: Persian \n" "Language: fa\n" @@ -385,7 +386,7 @@ msgstr "" "
\n" " \n" " --
\n" -" احسان امیریماهان دمو‌پور
\n" " ایمیل: " "ehsanamiri02@example.com
\n" diff --git a/addons/hr_recruitment/i18n/sv.po b/addons/hr_recruitment/i18n/sv.po index dd0c21e721bf7e..de908af222de59 100644 --- a/addons/hr_recruitment/i18n/sv.po +++ b/addons/hr_recruitment/i18n/sv.po @@ -31,7 +31,7 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0+e\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-07-03 17:35+0000\n" -"PO-Revision-Date: 2026-08-14 15:10+0000\n" +"PO-Revision-Date: 2026-08-24 08:00+0000\n" "Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" @@ -846,7 +846,7 @@ msgstr "Arkiv" #: model_terms:ir.ui.view,arch_db:hr_recruitment.hr_applicant_view_search_bis #: model_terms:ir.ui.view,arch_db:hr_recruitment.hr_kanban_view_applicant msgid "Archived" -msgstr "Arkiverad" +msgstr "Arkiverade" #. module: hr_recruitment #. odoo-javascript @@ -1038,7 +1038,7 @@ msgstr "Inställningar" #: model_terms:ir.ui.view,arch_db:hr_recruitment.hr_talent_pool_view_kanban #: model_terms:ir.ui.view,arch_db:hr_recruitment.view_hr_job_kanban msgid "Configuration" -msgstr "Konfiguration" +msgstr "Inställningar" #. module: hr_recruitment #: model_terms:ir.ui.view,arch_db:hr_recruitment.view_hr_job_kanban diff --git a/addons/hr_recruitment/models/hr_applicant.py b/addons/hr_recruitment/models/hr_applicant.py index b7be350a8bb395..e71648734fab24 100644 --- a/addons/hr_recruitment/models/hr_applicant.py +++ b/addons/hr_recruitment/models/hr_applicant.py @@ -82,7 +82,7 @@ class HrApplicant(models.Model): last_stage_id = fields.Many2one('hr.recruitment.stage', "Last Stage", help="Stage of the applicant before being in the current stage. Used for lost cases analysis.") categ_ids = fields.Many2many('hr.applicant.category', string="Tags") - company_id = fields.Many2one('res.company', "Company", compute='_compute_company', store=True, readonly=False, tracking=True) + company_id = fields.Many2one('res.company', "Company", compute='_compute_company', store=True, readonly=False, tracking=True, domain=lambda self: [('id', 'in', self.env.companies.ids)]) user_id = fields.Many2one( 'res.users', "Recruiter", compute='_compute_user', domain="[('share', '=', False), ('company_ids', 'in', company_id)]", tracking=True, store=True, readonly=False) @@ -555,17 +555,17 @@ def _read_group_stage_ids(self, stages, domain): stage_ids = stages.sudo()._search(search_domain, order=stages._order) return stages.browse(stage_ids) - @api.depends('job_id', 'department_id') + @api.depends('job_id', 'department_id', 'job_id.company_id') def _compute_company(self): for applicant in self: company_id = False - if applicant.department_id: + if applicant.department_id.company_id == applicant.job_id.company_id: company_id = applicant.department_id.company_id.id if not company_id and applicant.job_id: company_id = applicant.job_id.company_id.id applicant.company_id = company_id or self.env.company.id - @api.depends('job_id') + @api.depends('job_id', 'job_id.department_id') def _compute_department(self): for applicant in self: applicant.department_id = applicant.job_id.department_id.id diff --git a/addons/hr_recruitment/tests/test_recruitment.py b/addons/hr_recruitment/tests/test_recruitment.py index 3e7e94aa07319d..f3e67bd61d2c7a 100644 --- a/addons/hr_recruitment/tests/test_recruitment.py +++ b/addons/hr_recruitment/tests/test_recruitment.py @@ -521,3 +521,35 @@ def test_default_template_applicant_refuse_reason_when_archived(self): }) self.assertFalse(wizard.template_id) + + def test_send_mail_create_new_partner_id(self): + """ + Ensure that the contact created when sending an email to + multiple applicants where one applicant has no partner_id is + only assigned to that specific applicant + """ + applicant_1, applicant_2 = self.env['hr.applicant'].create([ + { + 'partner_name': 'Test Applicant 1', + 'email_from': 'test1@example.com', + 'partner_phone': '123456789', + }, + { + 'partner_name': 'Test Applicant 2', + 'email_from': 'test2@example.com', + 'partner_phone': '123456789', + } + ]) + applicant_1.partner_id.unlink() + applicant_2_partner = applicant_2.partner_id + + action = (applicant_1 + applicant_2).action_send_email() + composer = self.env['applicant.send.mail'].with_context(**action['context']).create({}) + composer.body = '

Test

' + composer.subject = 'Test' + composer.action_send() + + # Verify that a new contact was created as applicant_1's partner_id + self.assertTrue(applicant_1.partner_id.exists()) + # Verify that applicant_2's partner_id has not changed + self.assertEqual(applicant_2_partner, applicant_2.partner_id) diff --git a/addons/hr_recruitment/wizard/applicant_send_mail.py b/addons/hr_recruitment/wizard/applicant_send_mail.py index fbccbae110a933..de53bae4f246cc 100644 --- a/addons/hr_recruitment/wizard/applicant_send_mail.py +++ b/addons/hr_recruitment/wizard/applicant_send_mail.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from odoo import api, fields, models, _ +from odoo.tools.misc import clean_context class ApplicantSendMail(models.TransientModel): @@ -39,7 +40,7 @@ def action_send(self): for applicant in self.applicant_ids: if not applicant.partner_id: - applicant.partner_id = self.env['res.partner'].create({ + applicant.partner_id = self.env['res.partner'].with_context(clean_context(self.env.context)).create({ 'is_company': False, 'name': applicant.partner_name, 'email': applicant.email_from, diff --git a/addons/hr_skills/i18n/fa.po b/addons/hr_skills/i18n/fa.po index d1f4f9223385fa..cfff3bf890782c 100644 --- a/addons/hr_skills/i18n/fa.po +++ b/addons/hr_skills/i18n/fa.po @@ -5,13 +5,14 @@ # Weblate , 2025, 2026. # ebrahim khorami , 2026. # Aliraza Habibitanha , 2026. +# Heshmatkhah , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server saas~18.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-04-10 18:38+0000\n" -"PO-Revision-Date: 2026-08-09 11:37+0000\n" -"Last-Translator: Aliraza Habibitanha \n" +"PO-Revision-Date: 2026-08-23 06:09+0000\n" +"Last-Translator: Heshmatkhah \n" "Language-Team: Persian \n" "Language: fa\n" @@ -1292,7 +1293,7 @@ msgstr "ماراتی" #. module: hr_skills #: model_terms:ir.ui.view,arch_db:hr_skills.report_employee_cv_sidepanel msgid "Marc Demo" -msgstr "دموی مارچ" +msgstr "ماهان دمو‌پور" #. module: hr_skills #: model:hr.skill.type,name:hr_skills.hr_skill_type_marketing diff --git a/addons/hr_skills/i18n/sv.po b/addons/hr_skills/i18n/sv.po index 3dbdca70e71ace..887071b58c8ff0 100644 --- a/addons/hr_skills/i18n/sv.po +++ b/addons/hr_skills/i18n/sv.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: Odoo Server saas~18.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-04-10 18:38+0000\n" -"PO-Revision-Date: 2026-08-02 13:56+0000\n" +"PO-Revision-Date: 2026-08-24 07:59+0000\n" "Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" @@ -119,7 +119,7 @@ msgstr "Arkitekturteknolog" #: model_terms:ir.ui.view,arch_db:hr_skills.hr_employee_skill_type_view_form #: model_terms:ir.ui.view,arch_db:hr_skills.hr_skill_type_view_search msgid "Archived" -msgstr "Arkiverad" +msgstr "Arkiverade" #. module: hr_skills #: model:hr.resume.line,name:hr_skills.employee_resume_ngh_armidale_city_public_school @@ -624,7 +624,7 @@ msgstr "Douglas, Thompson och Conner" #: model:ir.model.fields,field_description:hr_skills.field_hr_resume_line__duration #: model_terms:ir.ui.view,arch_db:hr_skills.resume_line_view_form msgid "Duration" -msgstr "Varaktighet" +msgstr "Tidsperiod" #. module: hr_skills #: model:hr.resume.line.type,name:hr_skills.resume_type_education diff --git a/addons/hr_skills/views/hr_views.xml b/addons/hr_skills/views/hr_views.xml index 19233edcd7edc9..cd66dfb8fa25df 100644 --- a/addons/hr_skills/views/hr_views.xml +++ b/addons/hr_skills/views/hr_views.xml @@ -108,7 +108,7 @@ Adding fields in the list arch below makes them accessible to the widget --> - + @@ -175,7 +175,7 @@ Adding fields in the list arch below makes them accessible to the widget --> - + diff --git a/addons/hr_skills_event/__manifest__.py b/addons/hr_skills_event/__manifest__.py index 64278fc0554c34..c1eedaed8d4cea 100644 --- a/addons/hr_skills_event/__manifest__.py +++ b/addons/hr_skills_event/__manifest__.py @@ -19,7 +19,15 @@ 'views/hr_views.xml', ], 'auto_install': True, - 'assets': {}, + 'assets': { + 'web.assets_tests': [ + 'hr_skills_event/static/tests/tours/**/*', + ], + 'web.assets_unit_tests': [ + 'hr_skills_event/static/tests/**/*', + ('remove', 'hr_skills_event/static/tests/tours/**/*'), + ], + }, 'author': 'Odoo S.A.', 'license': 'LGPL-3', } diff --git a/addons/hr_skills_event/models/__init__.py b/addons/hr_skills_event/models/__init__.py index 5c7a095038ffdc..8293e84c0c7e70 100644 --- a/addons/hr_skills_event/models/__init__.py +++ b/addons/hr_skills_event/models/__init__.py @@ -1,3 +1,4 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import hr_resume_line +from . import event_event diff --git a/addons/hr_skills_event/models/event_event.py b/addons/hr_skills_event/models/event_event.py new file mode 100644 index 00000000000000..dd87ca4ec71a8b --- /dev/null +++ b/addons/hr_skills_event/models/event_event.py @@ -0,0 +1,23 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, api + + +class EventEvent(models.Model): + _inherit = 'event.event' + + @api.model_create_multi + def create(self, vals_list): + # TODO: Should be replaced by a boolean flag on events in master to make 'onsite' events easier to identify. + # When creating an event considered as "onsite event" register + # current employee as attendee of the event + events = super().create(vals_list) + if self.env.context.get('hr_skills_event_add_employee'): + if employee := self.env['hr.employee'].search([('id', '=', self.env.context['default_employee_id']), ('work_contact_id', '!=', False)], limit=1): + partner = employee.work_contact_id + vals_list = [ + {'partner_id': partner.id, 'event_id': event.id} + for event in events.filtered(lambda e: partner not in e.registration_ids.partner_id) + ] + self.env['event.registration'].create(vals_list) + return events diff --git a/addons/hr_skills_event/models/hr_resume_line.py b/addons/hr_skills_event/models/hr_resume_line.py index f49c1825afc29c..6cc4f9c7668c4a 100644 --- a/addons/hr_skills_event/models/hr_resume_line.py +++ b/addons/hr_skills_event/models/hr_resume_line.py @@ -9,7 +9,8 @@ class HrResumeLine(models.Model): event_id = fields.Many2one( 'event.event', string="Onsite Course", compute='_compute_event_id', store=True, readonly=True, index='btree_not_null', - domain="[('is_multi_slots', '=', True), ('registration_ids', 'any', [('partner_id.employee', '=', True)])]" + domain="[('registration_ids', 'any', [('partner_id.employee', '=', True)])]", + context={'hr_skills_event_add_employee': True}, ) course_type = fields.Selection( selection_add=[('onsite', 'Onsite')], diff --git a/addons/hr_skills_event/static/tests/tours/onsite_skill_tour.js b/addons/hr_skills_event/static/tests/tours/onsite_skill_tour.js new file mode 100644 index 00000000000000..e21c055fb9f827 --- /dev/null +++ b/addons/hr_skills_event/static/tests/tours/onsite_skill_tour.js @@ -0,0 +1,113 @@ +import { registry } from "@web/core/registry"; +import { stepUtils } from "@web_tour/tour_utils"; + +registry.category("web_tour.tours").add("hr_skills_event_onsite_tour", { + url: "/odoo", + steps: () => [ + stepUtils.showAppsMenuItem(), + { + content: "Open Employees app", + trigger: ".o_app[data-menu-xmlid='hr.menu_hr_root']", + run: "click", + }, + { + content: "Go to test employee", + trigger: "span:contains('Test Employee')", + run: "click", + }, + { + content: "Go to Resume Tab", + trigger: "a.nav-link[name='resume']", + run: "click", + }, + { + content: "Open New Resume Line form", + trigger: "button:contains('Create Resume Lines')", + run: "click", + }, + { + content: "Go to Training Tab", + trigger: "span.o_selection_badge:contains('Training')", + run: "click", + }, + { + content: "Select Onsite course type", + trigger: "input[id='radio_field_0_onsite']", + run: "click", + }, + { + content: "Open dropdown menu", + trigger: "input[id='event_id_0']", + run: "click", + }, + { + content: "Ensure we don't have any options except 'Create'", + trigger: "div[name='event_id'] ul", + async run() { + const liList = document.querySelectorAll("div[name='event_id'] ul>li"); + if (liList.length !== 1) { + throw new Error(`Expected 1 item, found ${liList.length}`); + } + }, + }, + { + content: "Open Form to create a new event", + trigger: "li.o_m2o_dropdown_option_create_edit", + run: "click", + }, + { + content: "Write a name for the event", + trigger: "textarea[id='name_0']", + run: "edit Event1", + }, + { + content: "Save the event", + trigger: "div.modal-content:has(h4:contains('Create Onsite Course')) button.o_form_button_save", + run: "click", + }, + { + content: "Select External course type to refresh the event dropdown", + trigger: "input[id='radio_field_0_external']", + run: "click", + }, + { + content: "Select Onsite course type", + trigger: "input[id='radio_field_0_onsite']", + run: "click", + }, + { + content: "Open dropdown menu", + trigger: "input[id='event_id_0']", + run: "click", + }, + { + content: "Ensure we now have the new event as an option", + trigger: "div[name='event_id'] ul", + async run() { + const liList = document.querySelectorAll("div[name='event_id'] ul>li"); + if (liList.length !== 2) { + throw new Error(`Expected 2 items, found ${liList.length}`); + } + }, + }, + { + content: "Save the resume line with the event", + trigger: "div.modal-content:has(h4:contains('New Resume Line')) button.o_form_button_save", + run: "click", + }, + { + content: "Check that the event is correctly displayed in the resume line", + trigger: ".o_resume_line_title:contains('Event1')", + }, + { + content: "Save the employee form", + trigger: "button.o_form_button_save", + run: "click", + }, + { + content: "Wait for the form to save completely", + trigger: "body:not(:has(button.o_form_button_save:visible))", + }, + + ], +}); diff --git a/addons/hr_skills_event/tests/__init__.py b/addons/hr_skills_event/tests/__init__.py new file mode 100644 index 00000000000000..54746c30f4f031 --- /dev/null +++ b/addons/hr_skills_event/tests/__init__.py @@ -0,0 +1,3 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_onsite diff --git a/addons/hr_skills_event/tests/test_onsite.py b/addons/hr_skills_event/tests/test_onsite.py new file mode 100644 index 00000000000000..5f3abe59da6485 --- /dev/null +++ b/addons/hr_skills_event/tests/test_onsite.py @@ -0,0 +1,16 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.tests import HttpCase, tagged + + +@tagged('-at_install', 'post_install') +class TestOnsite(HttpCase): + def test_onsite_employee_registration(self): + self.env['hr.employee'].create({ + 'name': 'Test Employee', + }) + # We create this event to check that only events with employees registered are proposed in the resume line form view + self.env['event.event'].create({ + 'name': 'Test Event', + }) + self.start_tour("/odoo", 'hr_skills_event_onsite_tour', login='admin') diff --git a/addons/hr_skills_event/views/event_event_views.xml b/addons/hr_skills_event/views/event_event_views.xml index 1380001d79473f..27ef8aaf8f9104 100644 --- a/addons/hr_skills_event/views/event_event_views.xml +++ b/addons/hr_skills_event/views/event_event_views.xml @@ -4,7 +4,8 @@ Onsite Courses event.event kanban,calendar,list,form,pivot,graph,activity - [('is_multi_slots', '=', True), ('registration_ids', 'any', [('partner_id.employee', '=', True)])] + [('registration_ids', 'any', [('partner_id.employee', '=', True)])] + {'hr_skills_event_add_employee': True}

Create an Event diff --git a/addons/hr_skills_event/views/hr_resume_line_views.xml b/addons/hr_skills_event/views/hr_resume_line_views.xml index 7498cb4abe2fc5..60254267070b41 100644 --- a/addons/hr_skills_event/views/hr_resume_line_views.xml +++ b/addons/hr_skills_event/views/hr_resume_line_views.xml @@ -6,7 +6,7 @@ - + 0 diff --git a/addons/hr_timesheet/i18n/sv.po b/addons/hr_timesheet/i18n/sv.po index e8c705b3b58b15..569ae10240c0e6 100644 --- a/addons/hr_timesheet/i18n/sv.po +++ b/addons/hr_timesheet/i18n/sv.po @@ -25,8 +25,8 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-06-12 17:36+0000\n" -"PO-Revision-Date: 2026-08-02 18:27+0000\n" -"Last-Translator: Weblate \n" +"PO-Revision-Date: 2026-08-24 08:00+0000\n" +"Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" "Language: sv\n" @@ -365,7 +365,7 @@ msgstr "Inställningar" #. module: hr_timesheet #: model:ir.ui.menu,name:hr_timesheet.hr_timesheet_menu_configuration msgid "Configuration" -msgstr "Konfiguration" +msgstr "Inställningar" #. module: hr_timesheet #. odoo-python diff --git a/addons/hr_timesheet/models/hr_timesheet.py b/addons/hr_timesheet/models/hr_timesheet.py index 274139254c6d33..f0fa9e843f6807 100644 --- a/addons/hr_timesheet/models/hr_timesheet.py +++ b/addons/hr_timesheet/models/hr_timesheet.py @@ -66,7 +66,7 @@ def _domain_employee_id(self): parent_task_id = fields.Many2one('project.task', related='task_id.parent_id', store=True, index='btree_not_null') project_id = fields.Many2one( 'project.project', 'Project', domain=_domain_project_id, index=True, - compute='_compute_project_id', store=True, readonly=False) + compute='_compute_project_id', inverse='_inverse_project_id', store=True, readonly=False) user_id = fields.Many2one(compute='_compute_user_id', store=True, readonly=False) employee_id = fields.Many2one('hr.employee', "Employee", domain=_domain_employee_id, context={'active_test': False}, index=True, help="Define an 'hourly cost' on the employee to track the cost of their time.") @@ -142,6 +142,11 @@ def _compute_project_id(self): continue line.project_id = line.task_id.project_id + def _inverse_project_id(self): + for line in self: + if line.task_id.project_id != line.project_id: + line.sudo().task_id = False + @api.depends('project_id') def _compute_task_id(self): self.filtered(lambda t: not t.project_id).task_id = False diff --git a/addons/hr_timesheet/models/project_project.py b/addons/hr_timesheet/models/project_project.py index 34f99346b9528b..57af0b70d22233 100644 --- a/addons/hr_timesheet/models/project_project.py +++ b/addons/hr_timesheet/models/project_project.py @@ -86,14 +86,12 @@ def _search_is_project_overtime(self, operator, value): sql = SQL("""( SELECT Project.id FROM project_project AS Project - JOIN project_task AS Task - ON Project.id = Task.project_id + JOIN account_analytic_line AS aal + ON Project.id = aal.project_id WHERE Project.allocated_hours > 0 AND Project.allow_timesheets = TRUE - AND Task.parent_id IS NULL - AND Task.state IN ('01_in_progress', '02_changes_requested', '03_approved', '04_waiting_normal') GROUP BY Project.id - HAVING Project.allocated_hours - SUM(Task.effective_hours) < 0 + HAVING Project.allocated_hours - SUM(aal.unit_amount) < 0 )""") return [('id', operator, sql)] diff --git a/addons/hr_timesheet/tests/test_timesheet.py b/addons/hr_timesheet/tests/test_timesheet.py index 9aa14bd659e619..39203a3191035e 100644 --- a/addons/hr_timesheet/tests/test_timesheet.py +++ b/addons/hr_timesheet/tests/test_timesheet.py @@ -1047,3 +1047,52 @@ def test_log_timesheet_with_user_has_two_employees_from_different_companies(self 'user_id': self.user_manager.id, }) self.assertEqual(timesheet.company_id, self.env.company) + + def test_is_project_overtime_filter(self): + self.project.allocated_hours = 3.0 + self.assertEqual(self.project.remaining_hours, 3.0) + task_1, task_2 = self.env['project.task'].create([ + { + 'name': 'Task 1', + 'project_id': self.project.id, + }, + { + 'name': 'Task 2 (done)', + 'project_id': self.project.id, + 'state': '1_done', + } + ]) + self.env['account.analytic.line'].create([ + { + 'name': 'Timesheet Task 1', + 'unit_amount': 2.0, + 'project_id': self.project.id, + 'employee_id': self.empl_employee.id, + 'task_id': task_1.id, + }, + { + 'name': 'Timesheet Task 2 (done)', + 'unit_amount': 2.0, + 'project_id': self.project.id, + 'employee_id': self.empl_employee.id, + 'task_id': task_2.id, + }, + ]) + self.assertRecordValues(self.project, [{ + 'is_project_overtime': True, + 'remaining_hours': -1.0 + }]) + self.project.flush_model() # Ensures the `project.allocated_hours` is saved in the database + self.assertIn( + self.project, + self.env['project.project'].search([('is_project_overtime', '=', True)]) + ) + + def test_task_reset_on_project_change(self): + """ Changing the project_id of a timesheet should reset its task_id + if the task doesn't belong to the new project""" + + self.assertTrue(self.timesheet.task_id) + self.timesheet.write({'project_id': self.project_customer.id}) + self.assertFalse(self.timesheet.task_id) + self.assertEqual(self.timesheet.project_id, self.project_customer) diff --git a/addons/hr_work_entry/i18n/ja.po b/addons/hr_work_entry/i18n/ja.po index e37bc296de4b9c..c31b4ccd9161fa 100644 --- a/addons/hr_work_entry/i18n/ja.po +++ b/addons/hr_work_entry/i18n/ja.po @@ -10,8 +10,8 @@ msgstr "" "Project-Id-Version: Odoo Server saas~18.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-04-10 18:38+0000\n" -"PO-Revision-Date: 2026-08-02 14:15+0000\n" -"Last-Translator: Anonymous \n" +"PO-Revision-Date: 2026-08-24 03:09+0000\n" +"Last-Translator: \"Junko Augias (juau)\" \n" "Language-Team: Japanese \n" "Language: ja\n" @@ -338,7 +338,7 @@ msgstr "ダブルタイム勤務時間" #. module: hr_work_entry #: model_terms:ir.ui.view,arch_db:hr_work_entry.hr_work_entry_view_search msgid "Draft" -msgstr "ドラフト" +msgstr "下書き" #. module: hr_work_entry #: model:ir.model.fields,field_description:hr_work_entry.field_hr_work_entry__duration @@ -1013,7 +1013,7 @@ msgstr "設定" #. module: hr_work_entry #: model:ir.actions.server,name:hr_work_entry.action_hr_work_entry_set_to_draft msgid "Set to Draft" -msgstr "ドラフトとして設定" +msgstr "下書きとして設定" #. module: hr_work_entry #: model:hr.work.entry.type,name:hr_work_entry.l10n_sa_work_entry_type_sick_leave_100 diff --git a/addons/hr_work_entry/i18n/sv.po b/addons/hr_work_entry/i18n/sv.po index d81d461e9dd63a..1fefe30dafb487 100644 --- a/addons/hr_work_entry/i18n/sv.po +++ b/addons/hr_work_entry/i18n/sv.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: Odoo Server saas~18.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-04-10 18:38+0000\n" -"PO-Revision-Date: 2026-08-03 00:58+0000\n" +"PO-Revision-Date: 2026-08-17 15:24+0000\n" "Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" @@ -141,7 +141,7 @@ msgstr "" #: model_terms:ir.ui.view,arch_db:hr_work_entry.hr_work_entry_type_view_search #: model_terms:ir.ui.view,arch_db:hr_work_entry.hr_work_entry_view_search msgid "Archived" -msgstr "Arkiverad" +msgstr "Arkiverade" #. module: hr_work_entry #: model:hr.work.entry.type,name:hr_work_entry.work_entry_type_attendance diff --git a/addons/html_editor/__manifest__.py b/addons/html_editor/__manifest__.py index 97ef0da2fb50a5..6f0356c53e7ea2 100644 --- a/addons/html_editor/__manifest__.py +++ b/addons/html_editor/__manifest__.py @@ -34,6 +34,8 @@ 'web.assets_backend': [ ('include', 'html_editor.assets_editor'), 'html_editor/static/src/others/dynamic_placeholder_plugin.js', + 'html_editor/static/src/others/editor_dynamic_placeholder_popover.js', + 'html_editor/static/src/others/editor_dynamic_placeholder_popover.xml', 'html_editor/static/src/backend/**/*', 'html_editor/static/src/fields/**/*', 'html_editor/static/lib/vkbeautify/**/*', diff --git a/addons/html_editor/controllers/main.py b/addons/html_editor/controllers/main.py index 7fdb8d47a7dd0b..df0db6c76ab9e4 100644 --- a/addons/html_editor/controllers/main.py +++ b/addons/html_editor/controllers/main.py @@ -409,6 +409,9 @@ def modify_image(self, attachment, res_model=None, res_id=None, name=None, data= Creates a modified copy of an attachment and returns its image_src to be inserted into the DOM. """ + format_error_msg = _("Uploaded image's format is not supported. Try with: %s", ', '.join(SUPPORTED_IMAGE_MIMETYPES.values())) + if mimetype and mimetype not in SUPPORTED_IMAGE_MIMETYPES: + return {'error': format_error_msg} self._clean_context() attachment = request.env['ir.attachment'].browse(attachment.id) if not data and attachment.datas: diff --git a/addons/html_editor/i18n/sv.po b/addons/html_editor/i18n/sv.po index cec00b77d5e101..5511d0a8a56f31 100644 --- a/addons/html_editor/i18n/sv.po +++ b/addons/html_editor/i18n/sv.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: Odoo Server 18.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-06-05 19:29+0000\n" -"PO-Revision-Date: 2026-08-02 13:39+0000\n" +"PO-Revision-Date: 2026-08-24 07:59+0000\n" "Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" @@ -2121,7 +2121,7 @@ msgstr "Typ" #. odoo-javascript #: code:addons/html_editor/static/src/main/powerbox/powerbox_plugin.js:0 msgid "Type \"/\" for commands" -msgstr "Skriv \"/\" för kommandon" +msgstr "Skriv \"/\" för kortkommandon" #. module: html_editor #. odoo-javascript @@ -2199,7 +2199,7 @@ msgstr "Videokod" #. odoo-javascript #: code:addons/html_editor/static/src/others/embedded_components/core/video/readonly_video.xml:0 msgid "Video player" -msgstr "" +msgstr "Videospelare" #. module: html_editor #. odoo-javascript diff --git a/addons/html_editor/static/src/core/clipboard_plugin.js b/addons/html_editor/static/src/core/clipboard_plugin.js index 858538650e758c..3e1fcfc8d83fc8 100644 --- a/addons/html_editor/static/src/core/clipboard_plugin.js +++ b/addons/html_editor/static/src/core/clipboard_plugin.js @@ -16,6 +16,7 @@ import { } from "@html_editor/utils/base_container"; import { DIRECTIONS } from "../utils/position"; import { isHtmlContentSupported } from "./selection_plugin"; +import { getRowIndex } from "@html_editor/utils/table"; /** * @typedef { import("./selection_plugin").EditorSelection } EditorSelection @@ -494,6 +495,11 @@ export class ClipboardPlugin extends Plugin { } } else if (node.nodeType !== Node.TEXT_NODE) { if (["TD", "TH"].includes(node.nodeName)) { + // Convert table headers to cells when they are not + // in the first row. + if (node.nodeName === "TH" && getRowIndex(node) !== 0) { + node = this.dependencies.dom.setTagName(node, "td"); + } // Insert base container into empty TD. if (isEmptyBlock(node)) { const baseContainer = this.dependencies.baseContainer.createBaseContainer(); diff --git a/addons/html_editor/static/src/core/input_plugin.js b/addons/html_editor/static/src/core/input_plugin.js index 11ee56e06c9076..95b1b4996c508a 100644 --- a/addons/html_editor/static/src/core/input_plugin.js +++ b/addons/html_editor/static/src/core/input_plugin.js @@ -26,7 +26,12 @@ export class InputPlugin extends Plugin { // To work around this, we snapshot the selection here in `keydown` // and store it as a cached selection so that the `beforeinput` handler // can use this snapshot instead of calling `getSelection()` itself. - if (selection?.rangeCount && selection.isCollapsed && ev.key === "Unidentified") { + if ( + selection?.rangeCount && + selection.isCollapsed && + ev.key === "Unidentified" && + selection.focusOffset === 0 + ) { const range = selection.getRangeAt(0); this.dependencies.selection.setCachedSelection({ anchorNode: selection.anchorNode, diff --git a/addons/html_editor/static/src/core/split_plugin.js b/addons/html_editor/static/src/core/split_plugin.js index 52ee6a0295de22..b20cef826f5fa1 100644 --- a/addons/html_editor/static/src/core/split_plugin.js +++ b/addons/html_editor/static/src/core/split_plugin.js @@ -12,6 +12,7 @@ import { prepareUpdate } from "../utils/dom_state"; import { childNodes, closestElement, firstLeaf, lastLeaf, findUpTo } from "../utils/dom_traversal"; import { DIRECTIONS, childNodeIndex, nodeSize } from "../utils/position"; import { isProtected, isProtecting } from "@html_editor/utils/dom_info"; +import { isBrowserSafari } from "@web/core/browser/feature_detection"; /** * @typedef { Object } SplitShared @@ -88,6 +89,13 @@ export class SplitPlugin extends Plugin { }, }; + setup() { + super.setup(); + if (isBrowserSafari()) { + this.addDomListener(this.editable, "keydown", this.onKeyDown); + } + } + // -------------------------------------------------------------------------- // commands // -------------------------------------------------------------------------- @@ -350,8 +358,19 @@ export class SplitPlugin extends Plugin { onBeforeInput(e) { if (e.inputType === "insertParagraph") { e.preventDefault(); + // Safari reports Shift+Enter as "insertParagraph" instead of "insertLineBreak" + // Track it on keydown to handle it properly + if (this.forceLineBreak) { + this.forceLineBreak = false; + this.dependencies.lineBreak.insertLineBreak(); + return; + } this.splitBlock(); this.dependencies.history.addStep(); } } + + onKeyDown(e) { + this.forceLineBreak = e.key === "Enter" && e.shiftKey; + } } diff --git a/addons/html_editor/static/src/main/link/link.scss b/addons/html_editor/static/src/main/link/link.scss index 564c7fe9ed84a1..5d6736b420290e 100644 --- a/addons/html_editor/static/src/main/link/link.scss +++ b/addons/html_editor/static/src/main/link/link.scss @@ -3,6 +3,7 @@ color: black !important; border: 1px dashed #008f8c; margin: -1px; + -webkit-text-fill-color: black; } .odoo-editor-editable { diff --git a/addons/html_editor/static/src/main/link/link_plugin.js b/addons/html_editor/static/src/main/link/link_plugin.js index f45ee705e0790e..7f2cd652b8eae0 100644 --- a/addons/html_editor/static/src/main/link/link_plugin.js +++ b/addons/html_editor/static/src/main/link/link_plugin.js @@ -334,6 +334,11 @@ export class LinkPlugin extends Plugin { if (linkEl) { if (ev.ctrlKey || ev.metaKey) { window.open(linkEl.href, "_blank"); + } else if (!linkEl.isContentEditable) { + this.dependencies.selection.setSelection({ + anchorNode: linkEl, + anchorOffset: 0, + }); } ev.preventDefault(); } @@ -486,15 +491,17 @@ export class LinkPlugin extends Plugin { openLinkTools(linkElement, type) { this.currentOverlay.close(); this.LinkPopoverState.editing = false; - if (!this.isLinkAllowedOnSelection()) { + let selection = this.dependencies.selection.getEditableSelection(); + const commonAncestor = closestElement(selection.commonAncestorContainer); + const isNonEditableLink = + commonAncestor.nodeName === "A" && !commonAncestor.isContentEditable; + if (!this.isLinkAllowedOnSelection() && !isNonEditableLink) { return this.services.notification.add( _t("Unable to create a link on the current selection."), { type: "danger" } ); } - let selection = this.dependencies.selection.getEditableSelection(); let cursorsToRestore = this.dependencies.selection.preserveSelection(); - const commonAncestor = closestElement(selection.commonAncestorContainer); linkElement = linkElement || findInSelection(selection, "a"); this.type = type; if ( @@ -662,7 +669,9 @@ export class LinkPlugin extends Plugin { getAttachmentMetadata: this.getAttachmentMetadata, recordInfo: this.config.getRecordInfo?.() || {}, canEdit: - !this.linkInDocument || !this.linkInDocument.classList.contains("o_link_readonly"), + (!this.linkInDocument || + !this.linkInDocument.classList.contains("o_link_readonly")) && + this.linkInDocument?.isContentEditable, canRemove: this.linkInDocument && this.linkInDocument.parentElement.isContentEditable && @@ -680,7 +689,10 @@ export class LinkPlugin extends Plugin { const popover = this.getActivePopover(linkElement); if (popover) { this.currentOverlay = popover.overlay; - if (!linkElement.href) { + if ( + !linkElement.href && + (!this.linkInDocument || this.linkInDocument?.isContentEditable) + ) { this.LinkPopoverState.editing = true; } this.currentOverlay.open({ props: popover.getProps(props) }); @@ -856,7 +868,7 @@ export class LinkPlugin extends Plugin { const isLinkEditable = this.getResource("is_link_editable_predicates").some((p) => p(closestLinkElement) ); - if (closestLinkElement && closestLinkElement.isContentEditable) { + if (closestLinkElement) { if (closestLinkElement !== this.linkInDocument || !this.currentOverlay.isOpen) { this.openLinkTools(closestLinkElement); } @@ -1239,7 +1251,9 @@ export class LinkPlugin extends Plugin { const textNodeSplitted = textSliced.split(/\s/); const potentialUrl = textNodeSplitted.pop(); // In case of multiple matches, only the last one will be converted. - const match = [...potentialUrl.matchAll(new RegExp(URL_REGEX.source, URL_REGEX.flags + "g"))].pop(); + const match = [ + ...potentialUrl.matchAll(new RegExp(URL_REGEX.source, URL_REGEX.flags + "g")), + ].pop(); if (match) { const nodeForSelectionRestore = selection.anchorNode.splitText( diff --git a/addons/html_editor/static/src/main/list/list_plugin.js b/addons/html_editor/static/src/main/list/list_plugin.js index 07927db1afe6aa..925de890a79754 100644 --- a/addons/html_editor/static/src/main/list/list_plugin.js +++ b/addons/html_editor/static/src/main/list/list_plugin.js @@ -925,12 +925,13 @@ export class ListPlugin extends Plugin { handleSplitBlock(params) { const closestLI = closestElement(params.targetNode, "LI"); - const isBlockUnsplittable = + // Do not split the LI if the cursor is inside an unsplittable element. + const isTargetInUnsplittable = closestLI && - Array.from(closestLI.childNodes).some( - (node) => isBlock(node) && this.dependencies.split.isUnsplittable(node) + ancestors(params.targetNode, closestLI).find((node) => + this.dependencies.split.isUnsplittable(node) ); - if (!closestLI || isBlockUnsplittable) { + if (!closestLI || isTargetInUnsplittable) { return; } if (isEmptyBlock(closestLI)) { @@ -1128,7 +1129,7 @@ export class ListPlugin extends Plugin { const listItems = new Set( targetedNodes.map((n) => closestElement(n, "li")).filter(Boolean) ); - if (!listItems.size || mode !== "color" || isColorGradient(color)) { + if (!listItems.size || (mode !== "color" && color) || isColorGradient(color)) { return; } const cursors = this.dependencies.selection.preserveSelection(); @@ -1152,6 +1153,9 @@ export class ListPlugin extends Plugin { if (node.style.color) { removeStyle(node, "color"); } + if (node.style.backgroundColor) { + removeStyle(node, "background-color"); + } } if (color) { diff --git a/addons/html_editor/static/src/main/list/utils.js b/addons/html_editor/static/src/main/list/utils.js index fc4d38fa7e1d92..6683a7dedf7aa9 100644 --- a/addons/html_editor/static/src/main/list/utils.js +++ b/addons/html_editor/static/src/main/list/utils.js @@ -1,4 +1,4 @@ -import { unwrapContents } from "@html_editor/utils/dom"; +import { removeStyle, unwrapContents } from "@html_editor/utils/dom"; import { closestElement, firstLeaf, lastLeaf } from "@html_editor/utils/dom_traversal"; import { getFontSizeOrClass } from "@html_editor/utils/formatting"; @@ -22,7 +22,10 @@ export function insertListAfter(document, afterNode, mode, content = []) { const lastClosestFont = closestElement(lastLeafNode, "font"); if (firstClosestFont && lastClosestFont && firstClosestFont === lastClosestFont) { li.style.color = firstClosestFont.style.color; - unwrapContents(firstClosestFont); + removeStyle(firstClosestFont, "color"); + if (!firstClosestFont.hasAttributes()) { + unwrapContents(firstClosestFont); + } } const firstClosestSpan = closestElement(firstLeafNode, "span"); const lastClosestSpan = closestElement(lastLeafNode, "span"); diff --git a/addons/html_editor/static/src/main/movenode_plugin.js b/addons/html_editor/static/src/main/movenode_plugin.js index 4afb51b4f538e3..d805494ba946f7 100644 --- a/addons/html_editor/static/src/main/movenode_plugin.js +++ b/addons/html_editor/static/src/main/movenode_plugin.js @@ -122,12 +122,19 @@ export class MoveNodePlugin extends Plugin { } } updateHooks() { + const isRTL = this.config.direction === "rtl"; const editableStyles = getComputedStyle(this.editable); this.editableRect = this.editable.getBoundingClientRect(); - const paddingLeft = parseInt(editableStyles.paddingLeft, 10) || 0; - this.editableRect.x = this.editableRect.x + paddingLeft - (WIDGET_CONTAINER_WIDTH + 5); - this.editableRect.width = - this.editableRect.width - paddingLeft + (WIDGET_CONTAINER_WIDTH + 5); + if (isRTL) { + const paddingRight = parseInt(editableStyles.paddingRight, 10) || 0; + this.editableRect.width = + this.editableRect.width - paddingRight + (WIDGET_CONTAINER_WIDTH + 5); + } else { + const paddingLeft = parseInt(editableStyles.paddingLeft, 10) || 0; + this.editableRect.x = this.editableRect.x + paddingLeft - (WIDGET_CONTAINER_WIDTH + 5); + this.editableRect.width = + this.editableRect.width - paddingLeft + (WIDGET_CONTAINER_WIDTH + 5); + } const containerRect = this.widgetHookContainer.getBoundingClientRect(); const elements = this.getMovableElements(); @@ -181,7 +188,9 @@ export class MoveNodePlugin extends Plugin { let hookBox; if (element.tagName === "HR") { hookBox = new DOMRect( - elementRect.x - containerRect.left - WIDGET_CONTAINER_WIDTH, + isRTL + ? elementRect.x - containerRect.left + : elementRect.x - containerRect.left - WIDGET_CONTAINER_WIDTH, elementRect.y - containerRect.top - marginTop, elementRect.width + WIDGET_CONTAINER_WIDTH, elementRect.height + marginTop + marginBottom @@ -197,7 +206,9 @@ export class MoveNodePlugin extends Plugin { ); } else { hookBox = new DOMRect( - elementRect.x - containerRect.left - WIDGET_CONTAINER_WIDTH, + isRTL + ? elementRect.right - containerRect.left + : elementRect.x - containerRect.left - WIDGET_CONTAINER_WIDTH, elementRect.y - containerRect.top - marginTop, WIDGET_CONTAINER_WIDTH, elementRect.height + marginTop + marginBottom @@ -256,12 +267,16 @@ export class MoveNodePlugin extends Plugin { this.currentMovableElement = movableElement; this.dispatchTo("set_movable_element_handlers", movableElement); + const isRTL = this.config.direction === "rtl"; const containerRect = this.widgetContainer.getBoundingClientRect(); const anchorBlockRect = this.currentMovableElement.getBoundingClientRect(); - const anchorX = - this.currentMovableElement.tagName === "LI" - ? anchorBlockRect.x - WIDGET_MOVE_SIZE // Prevent overlap bullets. - : anchorBlockRect.x; + const anchorX = isRTL + ? this.currentMovableElement.tagName === "LI" + ? anchorBlockRect.right + WIDGET_MOVE_SIZE // Prevent overlap bullets. + : anchorBlockRect.right + : this.currentMovableElement.tagName === "LI" + ? anchorBlockRect.x - WIDGET_MOVE_SIZE // Prevent overlap bullets. + : anchorBlockRect.x; let anchorY = anchorBlockRect.y; if (this.currentMovableElement.tagName.match(/H[1-6]/)) { anchorY += (anchorBlockRect.height - WIDGET_MOVE_SIZE) / 2; @@ -280,7 +295,9 @@ export class MoveNodePlugin extends Plugin { this.moveWidget.style.width = `${WIDGET_MOVE_SIZE}px`; this.moveWidget.style.height = `${WIDGET_MOVE_SIZE}px`; this.moveWidget.style.top = `${anchorY - containerRect.y - moveWidgetOffsetTop}px`; - this.moveWidget.style.left = `${anchorX - containerRect.x - WIDGET_CONTAINER_WIDTH}px`; + this.moveWidget.style.left = isRTL + ? `${anchorX - containerRect.x + WIDGET_CONTAINER_WIDTH - WIDGET_MOVE_SIZE}px` + : `${anchorX - containerRect.x - WIDGET_CONTAINER_WIDTH}px`; const dragToMoveTooltip = _t("Drag to move"); const clickToSelectTooltip = _t("Click to select"); @@ -361,8 +378,11 @@ export class MoveNodePlugin extends Plugin { const marginLeft = parseInt(style.marginLeft, 10); const marginRight = parseInt(style.marginRight, 10); + const isRTL = this.config.direction === "rtl"; const dropzoneRect = new DOMRect( - originalRect.left - marginLeft - WIDGET_CONTAINER_WIDTH, + isRTL + ? originalRect.left - marginLeft + : originalRect.left - marginLeft - WIDGET_CONTAINER_WIDTH, originalRect.top - marginTop, originalRect.width + marginLeft + marginRight + WIDGET_CONTAINER_WIDTH, originalRect.height + marginTop + marginBottom diff --git a/addons/html_editor/static/src/main/table/table_plugin.js b/addons/html_editor/static/src/main/table/table_plugin.js index f7d5de545ed0fe..5da2b8b8cfc62f 100644 --- a/addons/html_editor/static/src/main/table/table_plugin.js +++ b/addons/html_editor/static/src/main/table/table_plugin.js @@ -233,7 +233,9 @@ export class TablePlugin extends Plugin { [...root.querySelectorAll("table")] .filter((table) => table.style["color"] || table.style["backgroundColor"]) .forEach((table) => { - const tds = table.querySelectorAll("td"); + const tds = [...table.querySelectorAll("td")].filter( + (td) => closestElement(td, "table") === table + ); for (const td of tds) { td.style["color"] = td.style["color"] || table.style["color"]; td.style["backgroundColor"] = diff --git a/addons/html_editor/static/src/others/dynamic_placeholder_plugin.js b/addons/html_editor/static/src/others/dynamic_placeholder_plugin.js index 49a7de21e1961c..c3c0ba5cbba84d 100644 --- a/addons/html_editor/static/src/others/dynamic_placeholder_plugin.js +++ b/addons/html_editor/static/src/others/dynamic_placeholder_plugin.js @@ -1,8 +1,8 @@ import { Plugin } from "@html_editor/plugin"; import { _t } from "@web/core/l10n/translation"; -import { DynamicPlaceholderPopover } from "@web/views/fields/dynamic_placeholder_popover"; import { withSequence } from "@html_editor/utils/resource"; import { isHtmlContentSupported } from "@html_editor/core/selection_plugin"; +import { EditorDynamicPlaceholderPopover } from "./editor_dynamic_placeholder_popover"; /** * @typedef {Object} DynamicPlaceholderShared @@ -39,7 +39,7 @@ export class DynamicPlaceholderPlugin extends Plugin { this.defaultResModel = this.config.dynamicPlaceholderResModel; /** @type {import("@html_editor/core/overlay_plugin").Overlay} */ - this.overlay = this.dependencies.overlay.createOverlay(DynamicPlaceholderPopover, { + this.overlay = this.dependencies.overlay.createOverlay(EditorDynamicPlaceholderPopover, { hasAutofocus: true, className: "popover", }); diff --git a/addons/html_editor/static/src/others/editor_dynamic_placeholder_popover.js b/addons/html_editor/static/src/others/editor_dynamic_placeholder_popover.js new file mode 100644 index 00000000000000..5a507add6d0c44 --- /dev/null +++ b/addons/html_editor/static/src/others/editor_dynamic_placeholder_popover.js @@ -0,0 +1,50 @@ +import { DynamicPlaceholderPopover } from "@web/views/fields/dynamic_placeholder_popover"; +import { ModelFieldSelectorPopover } from "@web/core/model_field_selector/model_field_selector_popover"; + +class EditorModelFieldSelectorPopover extends ModelFieldSelectorPopover { + // When clicking on a field of which we can follow relation, we return the + // display name by default. + async selectFieldDisplayname(fieldDef) { + const { modelsInfo } = await this.keepLast.add( + this.fieldService.loadPath( + fieldDef.is_property ? fieldDef.relation : this.state.page.resModel, + `${fieldDef.name}.*` + ) + ); + const { fieldDefs } = modelsInfo.at(-1); + const fieldName = `${fieldDef.name}.display_name`; + const fieldData = fieldDefs.display_name; + this.state.label = fieldDef.string; + return [fieldName, fieldData]; + } + + async selectField(field) { + if (field.type === "properties") { + return this.followRelation(field); + } + this.state.isFollowable = this.canFollowRelationFor(field); + const [fieldName, fieldData] = this.state.isFollowable + ? await this.selectFieldDisplayname(field) + : [field.name, field]; + this.keepLast.add(Promise.resolve()); + this.state.page.selectedName = fieldName; + if (this.state.isFollowable) { + this.props.update(this.state.page.path, fieldData, this.state.label); + } else { + this.props.update(this.state.page.path, fieldData); + } + this.props.close(true); + } +} + +export class EditorDynamicPlaceholderPopover extends DynamicPlaceholderPopover { + static template = "html_editor.EditorDynamicPlaceholderPopover"; + static components = { + EditorModelFieldSelectorPopover, + }; + setPath(path, fieldInfo, forceLabel = null) { + this.state.path = path; + this.state.fieldName = forceLabel || fieldInfo?.string; + this.fieldType = fieldInfo?.type; + } +} diff --git a/addons/html_editor/static/src/others/editor_dynamic_placeholder_popover.xml b/addons/html_editor/static/src/others/editor_dynamic_placeholder_popover.xml new file mode 100644 index 00000000000000..a65b1d8b32739b --- /dev/null +++ b/addons/html_editor/static/src/others/editor_dynamic_placeholder_popover.xml @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/addons/html_editor/static/src/others/embedded_components/core/table_of_content/table_of_content_manager.js b/addons/html_editor/static/src/others/embedded_components/core/table_of_content/table_of_content_manager.js index 31438d0f54ba32..d1901a91029511 100644 --- a/addons/html_editor/static/src/others/embedded_components/core/table_of_content/table_of_content_manager.js +++ b/addons/html_editor/static/src/others/embedded_components/core/table_of_content/table_of_content_manager.js @@ -50,7 +50,14 @@ export class TableOfContentManager { offset = Math.max(offset, parseInt(height)); } } - scrollTo(target, { behavior: "smooth", offset: -offset }).then(() => { + + // calculate offset to scroll heading near 1/3 of the screen + const { top, bottom } = scrollable.getBoundingClientRect(); + const scrollableOffset = (bottom - top) / 3; + // change offset based on scroll direction + const { top: targetTop } = target.getBoundingClientRect(); + offset = top > targetTop ? -scrollableOffset - offset : scrollableOffset * 2; + scrollTo(target, { behavior: "smooth", offset: offset }).then(() => { // Scroll again in case we actually went downwards. scrollTo(target, { behavior: "smooth" }); }); diff --git a/addons/html_editor/static/src/others/embedded_components/plugins/caption_plugin/caption_plugin.js b/addons/html_editor/static/src/others/embedded_components/plugins/caption_plugin/caption_plugin.js index 368b2ca04b90a6..a1efb8dfe3369f 100644 --- a/addons/html_editor/static/src/others/embedded_components/plugins/caption_plugin/caption_plugin.js +++ b/addons/html_editor/static/src/others/embedded_components/plugins/caption_plugin/caption_plugin.js @@ -82,7 +82,8 @@ export class CaptionPlugin extends Plugin { if (root.matches(CAPTION_SPAN_SELECTOR)) { figures = [closestElement(root, "figure")]; } else { - figures = [...root.querySelectorAll("figure")]; + figures = [...root.querySelectorAll("figure")] + .filter(figure => figure.querySelectorAll("img").length === 1); } figures.forEach((figure) => { const captionSpan = figure.querySelector(CAPTION_SPAN_SELECTOR); @@ -117,7 +118,9 @@ export class CaptionPlugin extends Plugin { }; setup() { - for (const figure of this.editable.querySelectorAll("figure")) { + const figures = [...this.editable.querySelectorAll("figure")] + .filter(figure => figure.querySelectorAll("img").length === 1); + for (const figure of figures) { const image = figure.querySelector("img"); figure.before(image); const caption = figure.querySelector("figcaption")?.textContent; @@ -246,7 +249,9 @@ export class CaptionPlugin extends Plugin { } cleanForSave({ root }) { - for (const figure of root.querySelectorAll("figure")) { + const figures = [...root.querySelectorAll("figure")] + .filter(figure => figure.querySelectorAll("img").length === 1); + for (const figure of figures) { figure.removeAttribute("contenteditable"); const image = figure.querySelector("img"); const span = figure.querySelector(CAPTION_SPAN_SELECTOR); diff --git a/addons/html_editor/static/tests/caption.test.js b/addons/html_editor/static/tests/caption.test.js index 1ab3a098789d38..c9a638f4a9cd8d 100644 --- a/addons/html_editor/static/tests/caption.test.js +++ b/addons/html_editor/static/tests/caption.test.js @@ -1710,3 +1710,47 @@ test("pressing Enter inside o_caption_editable should do nothing", async () => { ), }); }); + +test("should ignore figure without image", async () => { + const caption = "Quote"; + await testEditor({ + config: configWithEmbeddedCaption, + contentBefore: unformat( + `

+
+ Random Quote +
+
${caption}
+
` + ), + contentAfter: unformat( + `
+
+ Random Quote +
+
${caption}
+
` + ), + }); +}); + +test("should ignore figure with multiple images", async () => { + const caption = "Two Images"; + await testEditor({ + config: configWithEmbeddedCaption, + contentBefore: unformat( + `
+ First image + Second image +
${caption}
+
` + ), + contentAfter: unformat( + `
+ First image + Second image +
${caption}
+
` + ), + }); +}); diff --git a/addons/html_editor/static/tests/delete/backward.test.js b/addons/html_editor/static/tests/delete/backward.test.js index 9988e86d98e436..fd1c0a829d3966 100644 --- a/addons/html_editor/static/tests/delete/backward.test.js +++ b/addons/html_editor/static/tests/delete/backward.test.js @@ -2582,5 +2582,28 @@ describe("Selection not collapsed", () => { await tick(); // Wait for the selection change to be handled expect(getContent(el)).toBe("

wonderful []

"); }); + + test.tags("mobile"); + test("should not break Gboard correction", async () => { + const dispatch = (type, eventInit) => + manuallyDispatchProgrammaticEvent(editor.editable, type, eventInit); + const { editor, el } = await setupEditor("

Gxf[]

"); + // Roughly as observed on Android Chrome with Gboard: + // - selection change + // - keydown event + // - input deleteContentBackward + // - keydown event + // - input insertText + const selection = editor.document.getSelection(); + await dispatch("keydown", { key: "Unidentified" }); + for (let i = 0; i < 2; i++) { + selection.modify("extend", "backward", "character"); + } + await backspaceAndroid(editor); + await dispatch("keydown", { key: "Unidentified" }); + await insertText(editor, "if "); + await tick(); // Wait for the selection change to be handled + expect(getContent(el)).toBe("

Gif []

"); + }); }); }); diff --git a/addons/html_editor/static/tests/dynamic_placeholder.test.js b/addons/html_editor/static/tests/dynamic_placeholder.test.js index 2e937a30d0bbde..b0b164d12b9778 100644 --- a/addons/html_editor/static/tests/dynamic_placeholder.test.js +++ b/addons/html_editor/static/tests/dynamic_placeholder.test.js @@ -1,9 +1,18 @@ import { expect, test } from "@odoo/hoot"; import { animationFrame } from "@odoo/hoot-mock"; -import { click, manuallyDispatchProgrammaticEvent, press } from "@odoo/hoot-dom"; +import { click, manuallyDispatchProgrammaticEvent, press, queryFirst } from "@odoo/hoot-dom"; import { MAIN_PLUGINS } from "@html_editor/plugin_sets"; import { DYNAMIC_PLACEHOLDER_PLUGINS } from "@html_editor/backend/plugin_sets"; -import { defineModels, models, onRpc, serverState } from "@web/../tests/web_test_helpers"; +import { unformat } from "@html_editor/../tests/_helpers/format"; +import { getContent } from "@html_editor/../tests/_helpers/selection"; +import { + defineModels, + models, + fields, + onRpc, + serverState, + contains, +} from "@web/../tests/web_test_helpers"; import { setupEditor } from "./_helpers/editor"; import { insertText } from "./_helpers/user_actions"; @@ -16,9 +25,20 @@ class ResUsers extends models.Model { ]; } +class OneModel extends models.Model { + name = fields.Char({ string: "The many2one model name" }); +} + +class SomeModel extends models.Model { + _name = "some.model"; + + field = fields.Char({ string: "My little field" }); + many2one_model_id = fields.Many2one({ relation: "one.model" }); +} + onRpc("has_group", () => true); onRpc("mail_allowed_qweb_expressions", () => []); -defineModels([ResUsers]); +defineModels([ResUsers, OneModel, SomeModel]); test("inserted value from dynamic placeholder should contain the data-oe-t-inline attribute", async () => { const { editor } = await setupEditor("

test[]

", { @@ -59,3 +79,31 @@ test("inserted value from dynamic placeholder should contain the data-oe-t-inlin expect("t[data-oe-t-inline]").toHaveCount(1); }); + +test("add many2one dynamic placeholder should take the name by default", async () => { + const { editor, el } = await setupEditor(`
[hop hop]
`, { + config: { + Plugins: [...MAIN_PLUGINS, ...DYNAMIC_PLACEHOLDER_PLUGINS], + dynamicPlaceholderResModel: "some.model", + }, + }); + await insertText(editor, "/"); + await contains(".o-we-powerbox .o-we-command-name:contains(/^Dynamic Placeholder$/)").click(); + + await contains( + ".o_model_field_selector_popover_page li[data-name='many2one_model_id'] button" + ).click(); + expect(queryFirst(".o_model_field_selector_popover span")).toHaveText("Many2one model"); + + await contains(".o_model_field_selector_popover button.btn-primary").click(); + await animationFrame(); + expect(getContent(el)).toBe( + unformat(` +


+
+ [] +
+


+ `) + ); +}); diff --git a/addons/html_editor/static/tests/link/popover.test.js b/addons/html_editor/static/tests/link/popover.test.js index c95a6554a3005d..34fcfe19b71183 100644 --- a/addons/html_editor/static/tests/link/popover.test.js +++ b/addons/html_editor/static/tests/link/popover.test.js @@ -104,17 +104,17 @@ describe("should open a popover", () => { await animationFrame(); expect(queryOne(".o-we-linkpopover").parentElement).toHaveAttribute("style", style); }); - test("link popover should close when clicking on a contenteditable false element", async () => { + test("link popover should update when clicking on a different link", async () => { await setupEditor( - '

li[]nk uneditable link

' + '

li[]nk uneditable link

' ); await waitFor(".o-we-linkpopover"); - expect(".o-we-linkpopover").toHaveCount(1); - // click on an uneditable element - const nodeEl = queryOne("a[contenteditable='false']"); + expect(`.o-we-linkpopover a[href="http://a.com/"]`).toHaveCount(1); + // click on the second link, the popover should update, not disappear + const nodeEl = queryOne(`a[href="http://b.com/"]`); setSelection({ anchorNode: nodeEl, anchorOffset: 0 }); - await waitForNone(".o-we-linkpopover", { timeout: 1500 }); - expect(".o-we-linkpopover").toHaveCount(0); + await waitFor(`.o-we-linkpopover a[href="http://b.com/"]`); + expect(`.o-we-linkpopover a[href="http://b.com/"]`).toHaveCount(1); }); }); @@ -1787,7 +1787,9 @@ describe("upload file via link popover", () => { await expectElementCount(".o_notification_manager .o_notification", 0); await mockedUpload; await tick(); - expect(getContent(el)).toBe(`

[]

`); + expect(getContent(el)).toBe( + `

[]

` + ); }); }); @@ -2069,3 +2071,12 @@ test("Should properly show the preview if fetching metadata fails", async () => await waitFor(".o-we-linkpopover"); expect(cleanLinkArtifacts(getContent(el))).toBe('

a[]b

'); }); + +test("Should open link popover in read only mode when link is not editable", async () => { + onRpc("/html_editor/link_preview_internal", () => ({})); + onRpc("/link", () => ({})); + await setupEditor('

link

'); + await click(queryOne(`a[contenteditable="false"]`)); + await waitFor(".o-we-linkpopover"); + expect(".o_we_edit_link").toHaveCount(0); +}); diff --git a/addons/html_editor/static/tests/list/color_list.test.js b/addons/html_editor/static/tests/list/color_list.test.js index 363e93b6c5341c..f2b1ac246569c4 100644 --- a/addons/html_editor/static/tests/list/color_list.test.js +++ b/addons/html_editor/static/tests/list/color_list.test.js @@ -375,3 +375,11 @@ test("should apply gradient color style only on font inside list item", async () '
  1. [abc]
  2. def
', }); }); + +test("should be able to remove background-color from list item", async () => { + await testEditor({ + contentBefore: `
  • [a]
`, + stepFunction: (editor) => execCommand(editor, "removeFormat"), + contentAfter: `
  • [a]
`, + }); +}); diff --git a/addons/html_editor/static/tests/list/paragraph_break.test.js b/addons/html_editor/static/tests/list/paragraph_break.test.js index 4ba1547b23e2f1..9b076919a68013 100644 --- a/addons/html_editor/static/tests/list/paragraph_break.test.js +++ b/addons/html_editor/static/tests/list/paragraph_break.test.js @@ -302,6 +302,93 @@ describe("Selection collapsed", () => { contentAfter: "
  • abc
  • []
", }); }); + + test("should split a list item when the cursor is before a table", async () => { + await testEditor({ + contentBefore: unformat(` +
    +
  • +

    ab[]

    +

    x

    +

    def

    +
  • +
`), + stepFunction: splitBlock, + contentAfter: unformat(` +
    +
  • ab

  • +
  • +

    []

    +

    x

    +

    def

    +
  • +
`), + }); + }); + + test("should split a list item when the cursor is after a table", async () => { + await testEditor({ + contentBefore: unformat(` +
    +
  • +

    ab

    +

    x

    +

    de[]f

    +
  • +
`), + stepFunction: splitBlock, + contentAfter: unformat(` +
    +
  • +

    ab

    +

    x

    +

    de

    +
  • +
  • []f

  • +
`), + }); + }); + + test("should split a list item from the placeholder after a table", async () => { + await testEditor({ + contentBefore: unformat(` +
    +
  • +

    ab

    +

    x

    +

    []

    +
  • +
`), + stepFunction: splitBlock, + contentAfter: unformat(` +
    +
  • +

    ab

    +

    x

    +
  • +
  • []

  • +
`), + }); + }); + + test("should split a list item from the placeholder before a table", async () => { + await testEditor({ + contentBefore: unformat(` +
    +
  • +

    []

    +

    x

    +


    +
  • +
`), + stepFunction: splitBlock, + contentAfter: unformat(` +
    +

  • +
  • []

    x

  • +
`), + }); + }); }); describe("Removing items", () => { test("should add an empty list item at the end of a list, then remove it", async () => { diff --git a/addons/html_editor/static/tests/list/toggle_ul.test.js b/addons/html_editor/static/tests/list/toggle_ul.test.js index 4a13c7c050fd08..1462e644fb62d9 100644 --- a/addons/html_editor/static/tests/list/toggle_ul.test.js +++ b/addons/html_editor/static/tests/list/toggle_ul.test.js @@ -24,6 +24,30 @@ describe("Range collapsed", () => { }); }); + test("should preserve color and background color when creating a list", async () => { + await testEditor({ + contentBefore: `

[a]

`, + stepFunction: toggleUnorderedList, + contentAfter: `
  • [a]
`, + }); + }); + + test("should preserve color creating a list (1)", async () => { + await testEditor({ + contentBefore: `

[a]

`, + stepFunction: toggleUnorderedList, + contentAfter: `
  • [a]
`, + }); + }); + + test("should preserve color creating a list (2)", async () => { + await testEditor({ + contentBefore: `

[a]

`, + stepFunction: toggleUnorderedList, + contentAfter: `
  • [a]
`, + }); + }); + test("should turn a paragraph into a list", async () => { await testEditor({ contentBefore: "

ab[]cd

", diff --git a/addons/html_editor/static/tests/movenode.test.js b/addons/html_editor/static/tests/movenode.test.js index d64b7feb65f850..edf7eeb6504804 100644 --- a/addons/html_editor/static/tests/movenode.test.js +++ b/addons/html_editor/static/tests/movenode.test.js @@ -343,6 +343,15 @@ describe("drag", () => { ) ); }); + test("should show the hook on the right side in RTL mode", async () => { + const { el } = await setupEditor("

a[]

b

", { + styleContent: styles, + config: { direction: "rtl" }, + }); + await hover(el.querySelector("p")); + expect(".oe-sidewidget-move").toHaveCount(1); + expect(".oe-sidewidget-move").toHaveRect({ top: 0, left: 135 }); + }); }); describe("click", () => { diff --git a/addons/html_editor/static/tests/normalize.test.js b/addons/html_editor/static/tests/normalize.test.js index e28dd622782bfe..5522b43cbdb74e 100644 --- a/addons/html_editor/static/tests/normalize.test.js +++ b/addons/html_editor/static/tests/normalize.test.js @@ -58,3 +58,34 @@ test("Should properly add feffs around icons", async () => { contentBeforeEdit: `
\ufeff\u200b\ufeff
`, }); }); + +test("should not distribute table color to tds of a nested table", async () => { + await testEditor({ + contentBefore: unformat(` + + + +
ab
+ + +
cd
+
+ `), + contentBeforeEdit: unformat(` +


+ + + + + +
ab
+ + + + +
cd
+
+


+ `), + }); +}); diff --git a/addons/html_editor/static/tests/paste.test.js b/addons/html_editor/static/tests/paste.test.js index 752a3a1ad04119..bd862a5a5d4392 100644 --- a/addons/html_editor/static/tests/paste.test.js +++ b/addons/html_editor/static/tests/paste.test.js @@ -110,6 +110,39 @@ describe("Html Paste cleaning - whitelist", () => { }); }); + test("should convert table headers in non-first rows to normal cells on paste", async () => { + await testEditor({ + contentBefore: ` +

[]

+ `, + stepFunction: async (editor) => { + pasteHtml( + editor, + unformat(` + + + + + + + + +
Header 1
Header 2
Cell
+ `) + ); + }, + contentAfter: unformat(` + + + + + + +
Header 1
Header 2
Cell[]
+ `), + }); + }); + test("should not keep span", async () => { await testEditor({ contentBefore: "

123[]

", diff --git a/addons/http_routing/controllers/main.py b/addons/http_routing/controllers/main.py index d15e4abe0786fa..a0c0d3597f0f1a 100644 --- a/addons/http_routing/controllers/main.py +++ b/addons/http_routing/controllers/main.py @@ -9,7 +9,7 @@ class Routing(Home): - @http.route('/website/translations', type='http', auth="public", website=True, readonly=True, sitemap=False) + @http.route('/website/translations', type='http', auth="public", readonly=True, sitemap=False) def get_website_translations(self, hash=None, lang=None, mods=None): IrHttp = request.env['ir.http'].sudo() modules = IrHttp.get_translation_frontend_modules() diff --git a/addons/im_livechat/i18n/fa.po b/addons/im_livechat/i18n/fa.po index a63d1f40f1f4fc..e1d6abf9075cc9 100644 --- a/addons/im_livechat/i18n/fa.po +++ b/addons/im_livechat/i18n/fa.po @@ -15,13 +15,14 @@ # Hamed Mohammadi , 2023 # Weblate , 2025, 2026. # Aliraza Habibitanha , 2026. +# Heshmatkhah , 2026. msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0+e\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-04-03 18:36+0000\n" -"PO-Revision-Date: 2026-08-09 11:31+0000\n" -"Last-Translator: Aliraza Habibitanha \n" +"PO-Revision-Date: 2026-08-23 06:10+0000\n" +"Last-Translator: Heshmatkhah \n" "Language-Team: Persian \n" "Language: fa\n" @@ -279,7 +280,7 @@ msgid "" "Agents who are inactive in Odoo for more than 30 minutes will be considered " "disconnected." msgstr "" -"اپراتورهایی که بیش از ۳۰ دقیقه در سازمان‌یار غیرفعال باشند، بدون اتصال در نظر " +"اپراتورهایی که بیش از ۳۰ دقیقه در اودو غیرفعال باشند، بدون اتصال در نظر " "گرفته می‌شوند." #. module: im_livechat @@ -1015,7 +1016,7 @@ msgstr "برای سایر وبسایتها" #. module: im_livechat #: model_terms:ir.ui.view,arch_db:im_livechat.im_livechat_channel_view_form msgid "FOR WEBSITES BUILT WITH ODOO" -msgstr "برای وب سایت‌های ساخته شده با سازمان‌یار" +msgstr "برای وب سایت‌های ساخته شده با اودو" #. module: im_livechat #: model:chatbot.script.answer,name:im_livechat.chatbot_script_troubleshooting_answer_firefox_demo diff --git a/addons/im_livechat/i18n/sv.po b/addons/im_livechat/i18n/sv.po index 697a611b1d8c27..44b9d9f943137a 100644 --- a/addons/im_livechat/i18n/sv.po +++ b/addons/im_livechat/i18n/sv.po @@ -24,8 +24,8 @@ msgstr "" "Project-Id-Version: Odoo Server 16.0+e\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-04-03 18:36+0000\n" -"PO-Revision-Date: 2026-08-05 05:04+0000\n" -"Last-Translator: symbiotech \n" +"PO-Revision-Date: 2026-08-17 15:24+0000\n" +"Last-Translator: Hanna Kharraziha \n" "Language-Team: Swedish \n" "Language: sv\n" @@ -333,7 +333,7 @@ msgstr "Svar" #: model_terms:ir.ui.view,arch_db:im_livechat.chatbot_script_view_form #: model_terms:ir.ui.view,arch_db:im_livechat.chatbot_script_view_search msgid "Archived" -msgstr "Arkiverad" +msgstr "Arkiverade" #. module: im_livechat #: model:ir.model.fields,field_description:im_livechat.field_im_livechat_channel__are_you_inside diff --git a/addons/l10n_account_withholding_tax/i18n/es_419.po b/addons/l10n_account_withholding_tax/i18n/es_419.po index cef9be589ed652..d88efe3c891f10 100644 --- a/addons/l10n_account_withholding_tax/i18n/es_419.po +++ b/addons/l10n_account_withholding_tax/i18n/es_419.po @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: Odoo Server 19.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-12-30 19:04+0000\n" -"PO-Revision-Date: 2026-04-18 10:42+0000\n" +"PO-Revision-Date: 2026-08-18 22:10+0000\n" "Last-Translator: \"Fernanda Alvarez (mfar)\" \n" "Language-Team: Spanish (Latin America) \n" @@ -18,7 +18,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.16.2\n" +"X-Generator: Weblate 2026.6.1\n" #. module: l10n_account_withholding_tax #. odoo-python @@ -36,7 +36,7 @@ msgstr "%(amount)s impuestos incluidos" #. odoo-python #: code:addons/l10n_account_withholding_tax/models/product_template.py:0 msgid "%(amount)s Tax Withheld" -msgstr "" +msgstr "%(amount)s de impuesto retenido" #. module: l10n_account_withholding_tax #: model_terms:ir.ui.view,arch_db:l10n_account_withholding_tax.report_payment_receipt_document @@ -69,13 +69,13 @@ msgstr "Cuenta" #. odoo-python #: code:addons/l10n_account_withholding_tax/wizards/account_payment_register_withholding_line.py:0 msgid "All withholding lines in self must have the same payment register." -msgstr "" +msgstr "Todas las líneas de retención deben tener el mismo registro de pago." #. module: l10n_account_withholding_tax #. odoo-python #: code:addons/l10n_account_withholding_tax/models/account_payment_withholding_line.py:0 msgid "All withholding lines in self must have the same payment." -msgstr "" +msgstr "Todas las líneas de retención deben tener el mismo pago." #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register_withholding_line__analytic_distribution @@ -96,28 +96,28 @@ msgstr "Precisión analítica" #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_withholding_line__comodel_currency_id #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_withholding_line__comodel_currency_id msgid "Comodel Currency" -msgstr "" +msgstr "Moneda del modelo relacionado" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register_withholding_line__comodel_date #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_withholding_line__comodel_date #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_withholding_line__comodel_date msgid "Comodel Date" -msgstr "" +msgstr "Fecha del modelo relacionado" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register_withholding_line__comodel_payment_type #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_withholding_line__comodel_payment_type #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_withholding_line__comodel_payment_type msgid "Comodel Payment Type" -msgstr "" +msgstr "Tipo de pago del modelo relacionado" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register_withholding_line__comodel_percentage_paid_factor #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_withholding_line__comodel_percentage_paid_factor #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_withholding_line__comodel_percentage_paid_factor msgid "Comodel Percentage Paid Factor" -msgstr "" +msgstr "Factor porcentual pagado del modelo relacionado" #. module: l10n_account_withholding_tax #: model:ir.model,name:l10n_account_withholding_tax.model_res_company @@ -177,7 +177,7 @@ msgstr "Nombre para mostrar" #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment__display_withholding #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register__display_withholding msgid "Display Withholding" -msgstr "" +msgstr "Mostrar retención" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register_withholding_line__distribution_analytic_account_ids @@ -189,7 +189,7 @@ msgstr "Cuenta analítica de distribución" #. module: l10n_account_withholding_tax #: model:ir.model,name:l10n_account_withholding_tax.model_account_tax msgid "ETA tax codes mixin" -msgstr "" +msgstr "Mixin de códigos fiscales ETA" #. module: l10n_account_withholding_tax #: model:ir.model.fields.selection,name:l10n_account_withholding_tax.selection__account_payment_register_withholding_line__placeholder_type__given_by_name @@ -199,7 +199,7 @@ msgstr "" #: model:ir.model.fields.selection,name:l10n_account_withholding_tax.selection__account_withholding_line__placeholder_type__given_by_name #: model:ir.model.fields.selection,name:l10n_account_withholding_tax.selection__account_withholding_line__previous_placeholder_type__given_by_name msgid "Given By the Name" -msgstr "" +msgstr "Otorgado por el nombre" #. module: l10n_account_withholding_tax #: model:ir.model.fields.selection,name:l10n_account_withholding_tax.selection__account_payment_register_withholding_line__placeholder_type__given_by_sequence @@ -209,7 +209,7 @@ msgstr "" #: model:ir.model.fields.selection,name:l10n_account_withholding_tax.selection__account_withholding_line__placeholder_type__given_by_sequence #: model:ir.model.fields.selection,name:l10n_account_withholding_tax.selection__account_withholding_line__previous_placeholder_type__given_by_sequence msgid "Given By the Sequence" -msgstr "" +msgstr "Otorgado por la secuencia" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment__id @@ -230,6 +230,8 @@ msgid "" "If enabled, this tax will not affect your accounts until the registration of " "payments." msgstr "" +"Si activas esta opción, este impuesto no afectará tus cuentas hasta que " +"registres los pagos." #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register_withholding_line__write_uid @@ -251,7 +253,7 @@ msgstr "Cantidad neta" #. module: l10n_account_withholding_tax #: model:ir.model.fields,help:l10n_account_withholding_tax.field_account_payment_register__withholding_net_amount msgid "Net amount after deducting the withholding lines" -msgstr "" +msgstr "Importe neto después de deducir las líneas de retención" #. module: l10n_account_withholding_tax #: model:ir.model.fields.selection,name:l10n_account_withholding_tax.selection__account_payment_register_withholding_line__placeholder_type__not_defined @@ -261,21 +263,21 @@ msgstr "" #: model:ir.model.fields.selection,name:l10n_account_withholding_tax.selection__account_withholding_line__placeholder_type__not_defined #: model:ir.model.fields.selection,name:l10n_account_withholding_tax.selection__account_withholding_line__previous_placeholder_type__not_defined msgid "Not defined" -msgstr "" +msgstr "Sin definir" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register_withholding_line__original_base_amount #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_withholding_line__original_base_amount #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_withholding_line__original_base_amount msgid "Original Base Amount" -msgstr "" +msgstr "Importe base original" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register_withholding_line__original_tax_amount #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_withholding_line__original_tax_amount #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_withholding_line__original_tax_amount msgid "Original Tax Amount" -msgstr "" +msgstr "Importe original del impuesto" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment__outstanding_account_id @@ -307,12 +309,12 @@ msgstr "Registro de pago" #. module: l10n_account_withholding_tax #: model:ir.model,name:l10n_account_withholding_tax.model_account_payment_register_withholding_line msgid "Payment register withholding line" -msgstr "" +msgstr "Línea de retención del registro de pago" #. module: l10n_account_withholding_tax #: model:ir.model,name:l10n_account_withholding_tax.model_account_payment_withholding_line msgid "Payment withholding line" -msgstr "" +msgstr "Línea de retención de pago" #. module: l10n_account_withholding_tax #: model:ir.model,name:l10n_account_withholding_tax.model_account_payment @@ -324,20 +326,20 @@ msgstr "Pagos" #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_withholding_line__placeholder_type #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_withholding_line__placeholder_type msgid "Placeholder Type" -msgstr "" +msgstr "Tipo de marcador de posición" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register_withholding_line__placeholder_value #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_withholding_line__placeholder_value #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_withholding_line__placeholder_value msgid "Placeholder Value" -msgstr "" +msgstr "Valor del marcador de posición" #. module: l10n_account_withholding_tax #. odoo-python #: code:addons/l10n_account_withholding_tax/models/account_withholding_line.py:0 msgid "Please enter the withholding number for the tax %(tax_name)s" -msgstr "" +msgstr "Ingresa el número de retención para el impuesto %(tax_name)s" #. module: l10n_account_withholding_tax #: model:ir.model.fields,help:l10n_account_withholding_tax.field_account_payment_register_withholding_line__placeholder_value @@ -345,13 +347,14 @@ msgstr "" #: model:ir.model.fields,help:l10n_account_withholding_tax.field_account_withholding_line__placeholder_value msgid "Populated by the comodel during edition of the line." msgstr "" +"El modelo relacionado completa este campo durante la edición de la línea." #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register_withholding_line__previous_placeholder_type #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_withholding_line__previous_placeholder_type #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_withholding_line__previous_placeholder_type msgid "Previous Placeholder Type" -msgstr "" +msgstr "Tipo de marcador de posición anterior" #. module: l10n_account_withholding_tax #: model:ir.model,name:l10n_account_withholding_tax.model_product_template @@ -385,14 +388,14 @@ msgstr "Número de secuencia" #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_withholding_line__source_base_amount #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_withholding_line__source_base_amount msgid "Source Base Amount" -msgstr "" +msgstr "Importe base de origen" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register_withholding_line__source_base_amount_currency #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_withholding_line__source_base_amount_currency #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_withholding_line__source_base_amount_currency msgid "Source Base Amount Currency" -msgstr "" +msgstr "Moneda del importe base de origen" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register_withholding_line__source_currency_id @@ -406,21 +409,21 @@ msgstr "Moneda de origen" #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_withholding_line__source_tax_id #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_withholding_line__source_tax_id msgid "Source Tax" -msgstr "" +msgstr "Impuesto de origen" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register_withholding_line__source_tax_amount #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_withholding_line__source_tax_amount #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_withholding_line__source_tax_amount msgid "Source Tax Amount" -msgstr "" +msgstr "Importe del impuesto de origen" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register_withholding_line__source_tax_amount_currency #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_withholding_line__source_tax_amount_currency #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_withholding_line__source_tax_amount_currency msgid "Source Tax Amount Currency" -msgstr "" +msgstr "Moneda del importe del impuesto de origen" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register_withholding_line__tax_id @@ -436,24 +439,27 @@ msgstr "Impuesto" msgid "" "The account \"%(account_name)s\" is not valid to use on withholding lines." msgstr "" +"La cuenta \"%(account_name)s\" no es válida para su uso en las líneas de " +"retención." #. module: l10n_account_withholding_tax #. odoo-python #: code:addons/l10n_account_withholding_tax/models/account_withholding_line.py:0 msgid "The base amount of a withholding tax line must be above 0." msgstr "" +"El importe base de una línea de impuesto retenido debe ser superior a 0." #. module: l10n_account_withholding_tax #. odoo-python #: code:addons/l10n_account_withholding_tax/wizards/account_payment_register.py:0 msgid "The withholding net amount cannot be negative." -msgstr "" +msgstr "El importe neto retenido no puede ser negativo." #. module: l10n_account_withholding_tax #: model:ir.model.fields,help:l10n_account_withholding_tax.field_res_company__withholding_tax_base_account_id #: model:ir.model.fields,help:l10n_account_withholding_tax.field_res_config_settings__withholding_tax_base_account_id msgid "This account will be set on withholding tax base lines." -msgstr "" +msgstr "Esta cuenta se establecerá en las líneas base del impuesto retenido." #. module: l10n_account_withholding_tax #: model:ir.model.fields,help:l10n_account_withholding_tax.field_account_payment_register_withholding_line__withholding_sequence_id @@ -464,58 +470,60 @@ msgid "" "This sequence will be used to generate default numbers on payment " "withholding lines." msgstr "" +"Esta secuencia se utilizará para generar números predeterminados en las " +"líneas de retención de pago." #. module: l10n_account_withholding_tax #: model_terms:ir.ui.view,arch_db:l10n_account_withholding_tax.view_account_payment_form #: model_terms:ir.ui.view,arch_db:l10n_account_withholding_tax.view_account_payment_register_form msgid "Total Withheld Amount" -msgstr "" +msgstr "Importe total retenido" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register_withholding_line__type_tax_use #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_withholding_line__type_tax_use #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_withholding_line__type_tax_use msgid "Type Tax Use" -msgstr "" +msgstr "Uso fiscal del impuesto" #. module: l10n_account_withholding_tax #. odoo-python #: code:addons/l10n_account_withholding_tax/models/account_withholding_line.py:0 msgid "WH Base Counterpart: %(names)s" -msgstr "" +msgstr "Base de retención - Contrapartida: %(names)s" #. module: l10n_account_withholding_tax #. odoo-python #: code:addons/l10n_account_withholding_tax/models/account_withholding_line.py:0 msgid "WH Base: %(names)s" -msgstr "" +msgstr "Base de retención: %(names)s" #. module: l10n_account_withholding_tax #. odoo-python #: code:addons/l10n_account_withholding_tax/models/account_withholding_line.py:0 msgid "WH Tax: %(name)s" -msgstr "" +msgstr "Impuesto retenido: %(name)s" #. module: l10n_account_withholding_tax #: model_terms:ir.ui.view,arch_db:l10n_account_withholding_tax.view_account_payment_form msgid "Withheld Amount" -msgstr "" +msgstr "Importe retenido" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_tax__is_withholding_tax_on_payment msgid "Withhold On Payment" -msgstr "" +msgstr "Retener al pagar" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment__should_withhold_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register__should_withhold_tax msgid "Withhold Tax Amounts" -msgstr "" +msgstr "Retener importes de impuestos" #. module: l10n_account_withholding_tax #: model:ir.model.fields,help:l10n_account_withholding_tax.field_account_payment__should_withhold_tax msgid "Withhold tax amounts from the payment amount." -msgstr "" +msgstr "Retén los importes de impuestos del importe del pago." #. module: l10n_account_withholding_tax #: model_terms:ir.ui.view,arch_db:l10n_account_withholding_tax.view_account_payment_form @@ -527,13 +535,13 @@ msgstr "Retencion" #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment__withholding_hide_tax_base_account #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register__withholding_hide_tax_base_account msgid "Withholding Hide Tax Base Account" -msgstr "" +msgstr "Ocultar cuenta base del impuesto retenido" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment__withholding_line_ids #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register__withholding_line_ids msgid "Withholding Lines" -msgstr "" +msgstr "Líneas de retención" #. module: l10n_account_withholding_tax #. odoo-python @@ -542,6 +550,8 @@ msgid "" "Withholding On Payment taxes cannot use the 'Group of Taxes' or the " "'Percentage Tax Included' computations." msgstr "" +"Los impuestos retenidos al registrar el pago no pueden usar los cálculos de " +"\"Grupo de impuestos\" ni \"Porcentaje de impuesto incluido\"." #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register_withholding_line__withholding_sequence_id @@ -549,34 +559,34 @@ msgstr "" #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_tax__withholding_sequence_id #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_withholding_line__withholding_sequence_id msgid "Withholding Sequence" -msgstr "" +msgstr "Secuencia de retención" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_res_company__withholding_tax_base_account_id #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_res_config_settings__withholding_tax_base_account_id msgid "Withholding Tax Base" -msgstr "" +msgstr "Base del impuesto retenido" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register_withholding_line__amount #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_withholding_line__amount #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_withholding_line__amount msgid "Withholding amount" -msgstr "" +msgstr "Importe retenido" #. module: l10n_account_withholding_tax #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_register_withholding_line__base_amount #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_payment_withholding_line__base_amount #: model:ir.model.fields,field_description:l10n_account_withholding_tax.field_account_withholding_line__base_amount msgid "Withholding base" -msgstr "" +msgstr "Base de retención" #. module: l10n_account_withholding_tax #: model_terms:ir.ui.view,arch_db:l10n_account_withholding_tax.res_config_settings_form msgid "Withholding:" -msgstr "" +msgstr "Retención:" #. module: l10n_account_withholding_tax #: model:ir.model,name:l10n_account_withholding_tax.model_account_withholding_line msgid "withholding line" -msgstr "" +msgstr "línea de retención" diff --git a/addons/l10n_account_withholding_tax/models/account_withholding_line.py b/addons/l10n_account_withholding_tax/models/account_withholding_line.py index cd0d0f97df78b5..d00a28781a4641 100644 --- a/addons/l10n_account_withholding_tax/models/account_withholding_line.py +++ b/addons/l10n_account_withholding_tax/models/account_withholding_line.py @@ -163,7 +163,7 @@ def _compute_original_amounts(self): company = line.company_id date = line.comodel_date comp_curr = line.comodel_company_currency_id - line_curr = line.comodel_currency_id + line_curr = line.comodel_currency_id or comp_curr if not source_curr: rate = 1.0 base_amount = line.base_amount @@ -214,7 +214,7 @@ def _compute_base_amount(self): support installments, early payment discounts,... """ for line in self: - line_curr = line.comodel_currency_id + line_curr = line.comodel_currency_id or line.comodel_company_currency_id if line.source_currency_id: percentage_paid_factor = line.comodel_percentage_paid_factor line.base_amount = line_curr.round(line.original_base_amount * percentage_paid_factor) @@ -226,7 +226,7 @@ def _compute_amount(self): a ratio calculated from the current base amount and the original base amount. """ for line in self: - line_curr = line.comodel_currency_id + line_curr = line.comodel_currency_id or line.comodel_company_currency_id if line.original_base_amount: line.amount = line_curr.round(line.original_tax_amount * line.base_amount / line.original_base_amount) else: diff --git a/addons/l10n_account_withholding_tax/tests/test_account_withholding_flows.py b/addons/l10n_account_withholding_tax/tests/test_account_withholding_flows.py index 416f66e3be82b2..2e81441f523119 100644 --- a/addons/l10n_account_withholding_tax/tests/test_account_withholding_flows.py +++ b/addons/l10n_account_withholding_tax/tests/test_account_withholding_flows.py @@ -1149,3 +1149,21 @@ def test_tax_repartition_on_refund(self): {"balance": 1000.0, "tax_tag_ids": []}, {"balance": -1000.0, "tax_tag_ids": base_tag.ids}, ]) + + def test_payment_register_with_empty_currency(self): + withholding_tax = self.percent_tax( + -1, + is_withholding_tax_on_payment=True, + withholding_sequence_id=self.withholding_sequence.id + ) + + invoice = self._create_invoice_one_line(tax_ids=withholding_tax, price_unit=1000.0) + + payment_register = self.env['account.payment.register']\ + .with_context(active_model='account.move', active_ids=invoice.ids)\ + .create({}) + + payment_register_form = Form(payment_register) + payment_register_form.currency_id = self.env['res.currency'] + + self.assertEqual(payment_register.withholding_line_ids.original_base_amount, 1000.0) diff --git a/addons/l10n_ar/i18n/es_419.po b/addons/l10n_ar/i18n/es_419.po index d0b55dc9455f9f..f1385599fb5bc0 100644 --- a/addons/l10n_ar/i18n/es_419.po +++ b/addons/l10n_ar/i18n/es_419.po @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: Odoo Server 18.0+e\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-04-10 19:14+0000\n" -"PO-Revision-Date: 2026-05-16 09:38+0000\n" +"PO-Revision-Date: 2026-08-18 18:57+0000\n" "Last-Translator: \"Fernanda Alvarez (mfar)\" \n" "Language-Team: Spanish (Latin America) \n" @@ -18,7 +18,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.17\n" +"X-Generator: Weblate 2026.6.1\n" #. module: l10n_ar #: model_terms:ir.ui.view,arch_db:l10n_ar.report_invoice_document @@ -1013,10 +1013,10 @@ msgid "" "taxes are required for the invoices related to this document type, and those " "with \"Zero\" means that only \"VAT Not Applicable\" tax is allowed." msgstr "" -"Genere un error si la factura de vendedor esta mal cargada. \"No Cero\" " -"significa que los impuestos de IVA son necesarios en las facturas " -"relacionadas a este tipo de documento, si aparece \"Cero\" significa que " -"solo está permitido usar \"IVA No corresponde\"." +"Genera un error si una factura de proveedor está mal cargada. \"No Cero\" " +"significa que los impuestos de IVA son obligatorios para las facturas " +"relacionadas con este tipo de documento, mientras que \"Cero\" significa que " +"solo está permitido usar el impuesto \"IVA No corresponde\"." #. module: l10n_ar #: model:ir.ui.menu,name:l10n_ar.menu_afip_responsibility_type diff --git a/addons/l10n_ar/models/account_move.py b/addons/l10n_ar/models/account_move.py index d7ea449676f52f..04105741b6e37b 100644 --- a/addons/l10n_ar/models/account_move.py +++ b/addons/l10n_ar/models/account_move.py @@ -182,7 +182,6 @@ def _onchange_partner_journal(self): if res_code in ['8', '9', '10'] and rec.journal_id.l10n_ar_afip_pos_system not in expo_journals: # if it is a foreign partner and journal is not for expo, we try to change it to an expo journal journal = journal.search(domain + [('l10n_ar_afip_pos_system', 'in', expo_journals)], limit=1) - msg = _('You are trying to create an invoice for foreign partner but you don\'t have an exportation journal') elif res_code not in ['8', '9', '10'] and rec.journal_id.l10n_ar_afip_pos_system in expo_journals: # if it is NOT a foreign partner and journal is for expo, we try to change it to a local journal journal = journal.search(domain + [('l10n_ar_afip_pos_system', 'not in', expo_journals)], limit=1) diff --git a/addons/l10n_ar/tests/test_manual.py b/addons/l10n_ar/tests/test_manual.py index 835d975e859e76..e524f0ebb5dce2 100644 --- a/addons/l10n_ar/tests/test_manual.py +++ b/addons/l10n_ar/tests/test_manual.py @@ -455,3 +455,34 @@ def test_create_debit_note_for_credit_note(self): }) debit_note_wizard.create_debit() self.assertTrue(invoice.reversal_move_ids.debit_note_ids) + + def test_foreign_partner_without_expo_journal(self): + """ Test that if there is no active export journal, creating an invoice for a foreign partner doesn't + block the user but set a regular (non-expo) sales journal and default the document type to Invoice B.""" + + expo_journals = self.env['account.journal'].search([ + ('company_id', '=', self.company_ri.id), + ('type', '=', 'sale'), + ('l10n_ar_afip_pos_system', 'in', ['FEERCEL', 'FEEWS', 'FEERCELP']), + ]) + draft_moves_on_expo_journals = self.env['account.move'].search([ + ('journal_id', 'in', expo_journals.ids), + ('state', '=', 'draft'), + ]) + draft_moves_on_expo_journals.unlink() + expo_journals.write({'active': False}) + + with Form(self.env['account.move'].with_context(default_move_type='out_invoice')) as invoice_form: + invoice_form.partner_id = self.res_partner_barcelona_food + with invoice_form.invoice_line_ids.new() as line_form: + line_form.product_id = self.product_iva_21 + invoice = invoice_form.save() + + self.assertEqual( + invoice.journal_id, self.journal, + 'Journal should stay on a regular (non-expo) sales journal since there is no export journal available', + ) + self.assertEqual( + invoice.l10n_latam_document_type_id, self.document_type['invoice_b'], + 'Document type should default to Invoice B when no export journal is available', + ) diff --git a/addons/l10n_ar_stock/models/stock_picking.py b/addons/l10n_ar_stock/models/stock_picking.py index 76e38a2f36be31..048b1af2642132 100644 --- a/addons/l10n_ar_stock/models/stock_picking.py +++ b/addons/l10n_ar_stock/models/stock_picking.py @@ -46,17 +46,24 @@ def l10n_ar_action_create_delivery_guide(self): if not self.l10n_ar_delivery_guide_number: picking_type = self.picking_type_id + sequence_start = picking_type.l10n_ar_sequence_number_start + sequence_end = picking_type.l10n_ar_sequence_number_end new_sequence_number = picking_type.l10n_ar_next_delivery_number delivery_guide_number = picking_type.l10n_ar_sequence_id.next_by_id() - if not int(picking_type.l10n_ar_sequence_number_start) <= new_sequence_number <= int(picking_type.l10n_ar_sequence_number_end): + if ( + sequence_start + and sequence_end + and picking_type.l10n_ar_cai_authorization_code + and not (int(sequence_start) <= new_sequence_number <= int(sequence_end)) + ): raise UserError(_("The delivery guide number %s exceeds the range specified in the CAI. Please update the range or use a different CAI with a different range.", delivery_guide_number)) self.l10n_ar_delivery_guide_number = delivery_guide_number self.l10n_ar_cai_data = { 'document_type_id': picking_type.l10n_ar_document_type_id.id, 'cai_authorization_code': picking_type.l10n_ar_cai_authorization_code, - 'cai_expiration_date': picking_type.l10n_ar_cai_expiration_date.strftime('%Y-%m-%d'), - 'sequence_number_start': picking_type.l10n_ar_sequence_number_start, - 'sequence_number_end': picking_type.l10n_ar_sequence_number_end, + 'cai_expiration_date': picking_type.l10n_ar_cai_expiration_date and picking_type.l10n_ar_cai_expiration_date.strftime('%Y-%m-%d'), + 'sequence_number_start': sequence_start, + 'sequence_number_end': sequence_end, } def l10n_ar_action_send_delivery_guide(self): diff --git a/addons/l10n_ar_stock/tests/test_l10n_ar_delivery_guide.py b/addons/l10n_ar_stock/tests/test_l10n_ar_delivery_guide.py index 356cbc4b6b3f18..1623b0efae2392 100644 --- a/addons/l10n_ar_stock/tests/test_l10n_ar_delivery_guide.py +++ b/addons/l10n_ar_stock/tests/test_l10n_ar_delivery_guide.py @@ -1,4 +1,5 @@ from odoo import Command +from odoo.exceptions import UserError from odoo.tests import tagged from odoo.addons.l10n_ar.tests.common import TestArCommon @@ -91,3 +92,17 @@ def test_delivery_guide_creation_and_mail(self): self._new_mails.attachment_ids.name, "Email should contain the delivery guide PDF attachment." ) + + def test_delivery_guide_creation_sequence_validation(self): + """Test Sequence numbers are only validated if CAI + authorization code, start and end sequences are provided """ + self.picking_type.l10n_ar_sequence_number_end = '00000002' + with self.assertRaises(UserError): + for sequence in range(int(self.picking_type.l10n_ar_sequence_number_end) + 1): + self.picking_type.invalidate_recordset(['l10n_ar_next_delivery_number']) + self.get_stock_picking().l10n_ar_action_create_delivery_guide() + # Without authorization code we will allow creation of delivery guide. + self.picking_type.l10n_ar_cai_authorization_code = False + for sequence in range(int(self.picking_type.l10n_ar_sequence_number_end) + 1): + self.picking_type.invalidate_recordset(['l10n_ar_next_delivery_number']) + self.get_stock_picking().l10n_ar_action_create_delivery_guide() diff --git a/addons/l10n_ar_stock/views/report_delivery_guide.xml b/addons/l10n_ar_stock/views/report_delivery_guide.xml index a257859e7e3933..0cd6d55d5aeccf 100644 --- a/addons/l10n_ar_stock/views/report_delivery_guide.xml +++ b/addons/l10n_ar_stock/views/report_delivery_guide.xml @@ -51,14 +51,15 @@ Page: /
- Sequence from to +
Sequence from:
+
Sequence to:
-
+
CAI:
-
+
CAI Expiration Date:
diff --git a/addons/l10n_ar_stock/views/stock_picking_type_views.xml b/addons/l10n_ar_stock/views/stock_picking_type_views.xml index 59c1562450cc6c..4b099c69a3455c 100644 --- a/addons/l10n_ar_stock/views/stock_picking_type_views.xml +++ b/addons/l10n_ar_stock/views/stock_picking_type_views.xml @@ -9,25 +9,19 @@ + invisible="not l10n_ar_document_type_id"/> + invisible="not l10n_ar_document_type_id"/> + invisible="not l10n_ar_document_type_id"/> + invisible="not l10n_ar_document_type_id"/> + invisible="not l10n_ar_document_type_id"/> + invisible="not l10n_ar_document_type_id"/> diff --git a/addons/l10n_be/data/template/account.tax-be.csv b/addons/l10n_be/data/template/account.tax-be.csv index d78f1dd505b8b9..c9def765023bea 100644 --- a/addons/l10n_be/data/template/account.tax-be.csv +++ b/addons/l10n_be/data/template/account.tax-be.csv @@ -63,7 +63,7 @@ "","","","","","","","","","","","","tax","invoice","59","a411","","","","","","","","","","","","" "","","","","","","","","","","","","base","refund","81||85","","","","","","","","","","","","","" "","","","","","","","","","","","","tax","refund","63","a411","","","","","","","","","","","","" -"attn_VAT-IN-V81-00","140","Goods for resale, raw materials, and consumables","0%","0% M","0.0","percent","purchase","tax_group_tva_0","merch","True","","base","invoice","81","","","","","","Marchandises, matières premières et matières auxiliaires","Handelsgoederen, grond- en hulpstoffen","Handelsgüter, Roh- und Hilfsstoffe","fiscal_position_template_1,fiscal_position_template_5","","","Z","" +"attn_VAT-IN-V81-00","140","Goods for resale, raw materials, and consumables","0%","0% M","0.0","percent","purchase","tax_group_tva_0","merch","True","","base","invoice","81","","","","","","Marchandises, matières premières et matières auxiliaires","Handelsgoederen, grond- en hulpstoffen","Handelsgüter, Roh- und Hilfsstoffe","fiscal_position_template_1,fiscal_position_template_5,fiscal_position_template_6","","","Z","" "","","","","","","","","","","","","tax","invoice","","","","","","","","","","","","","","" "","","","","","","","","","","","","base","refund","81||85","","","","","","","","","","","","","" "","","","","","","","","","","","","tax","refund","","","","","","","","","","","","","","" @@ -95,11 +95,11 @@ "","","","","","","","","","","","","tax","invoice","59","a411","","","","","","","","","","","","" "","","","","","","","","","","","","base","refund","82||85","","","","","","","","","","","","","" "","","","","","","","","","","","","tax","refund","63","a411","","","","","","","","","","","","" -"attn_VAT-IN-V82-00-S","270","Services: rent and rental charges, transactions relating to the delivery of goods or services exempt from Belgian VAT and for which the supplier or service provider is not required to issue an invoice","0%","0% S","0.0","percent","purchase","tax_group_tva_0","service","True","","base","invoice","82","","","","","","Services : loyer et charges locatives, opérations relatives aux livraisons de biens ou prestations de services exemptées de la TVA belge et pour lesquelles le fournisseur ou le prestataire n'est pas tenu de fournir une facture","Diensten: huur en huurlasten, handelingen met betrekking tot de levering van goederen of diensten die vrijgesteld zijn van Belgische btw en waarvoor de leverancier of dienstverlener geen factuur hoeft op te stellen","Dienstleistungen: Miete und Nebenkosten, Umsätze im Zusammenhang mit der Lieferung von Gütern oder der Erbringung von Dienstleistungen, die von der belgischen Mehrwertsteuer befreit sind und für die der Lieferant oder Dienstleister nicht verpflichtet ist, eine Rechnung auszustellen","fiscal_position_template_1,fiscal_position_template_5","","","O","VATEX-EU-O" +"attn_VAT-IN-V82-00-S","270","Services: rent and rental charges, transactions relating to the delivery of goods or services exempt from Belgian VAT and for which the supplier or service provider is not required to issue an invoice","0%","0% S","0.0","percent","purchase","tax_group_tva_0","service","True","","base","invoice","82","","","","","","Services : loyer et charges locatives, opérations relatives aux livraisons de biens ou prestations de services exemptées de la TVA belge et pour lesquelles le fournisseur ou le prestataire n'est pas tenu de fournir une facture","Diensten: huur en huurlasten, handelingen met betrekking tot de levering van goederen of diensten die vrijgesteld zijn van Belgische btw en waarvoor de leverancier of dienstverlener geen factuur hoeft op te stellen","Dienstleistungen: Miete und Nebenkosten, Umsätze im Zusammenhang mit der Lieferung von Gütern oder der Erbringung von Dienstleistungen, die von der belgischen Mehrwertsteuer befreit sind und für die der Lieferant oder Dienstleister nicht verpflichtet ist, eine Rechnung auszustellen","fiscal_position_template_1,fiscal_position_template_5,fiscal_position_template_6","","","O","VATEX-EU-O" "","","","","","","","","","","","","tax","invoice","","","","","","","","","","","","","","" "","","","","","","","","","","","","base","refund","82||85","","","","","","","","","","","","","" "","","","","","","","","","","","","tax","refund","","","","","","","","","","","","","","" -"attn_VAT-IN-V82-00-G","280","Miscellaneous goods","0%","0% G","0.0","percent","purchase","tax_group_tva_0","consu","True","","base","invoice","82","","","","","","Biens divers","Diverse goederen","Verschiedene Güter","fiscal_position_template_1,fiscal_position_template_5","","","Z","" +"attn_VAT-IN-V82-00-G","280","Miscellaneous goods","0%","0% G","0.0","percent","purchase","tax_group_tva_0","consu","True","","base","invoice","82","","","","","","Biens divers","Diverse goederen","Verschiedene Güter","fiscal_position_template_1,fiscal_position_template_5,fiscal_position_template_6","","","Z","" "","","","","","","","","","","","","tax","invoice","","","","","","","","","","","","","","" "","","","","","","","","","","","","base","refund","82||85","","","","","","","","","","","","","" "","","","","","","","","","","","","tax","refund","","","","","","","","","","","","","","" @@ -115,7 +115,7 @@ "","","","","","","","","","","","","tax","invoice","59","a411","","","","","","","","","","","","" "","","","","","","","","","","","","base","refund","83||85","","","","","","","","","","","","","" "","","","","","","","","","","","","tax","refund","63","a411","","","","","","","","","","","","" -"attn_VAT-IN-V83-00","340","Investment goods","0%","0% IG","0.0","percent","purchase","tax_group_tva_0","invest","True","","base","invoice","83","","","","","","Biens d'investissement","Investeringsgoederen","Investitionsgüter","fiscal_position_template_1,fiscal_position_template_5","","","E","VATEX-EU-F" +"attn_VAT-IN-V83-00","340","Investment goods","0%","0% IG","0.0","percent","purchase","tax_group_tva_0","invest","True","","base","invoice","83","","","","","","Biens d'investissement","Investeringsgoederen","Investitionsgüter","fiscal_position_template_1,fiscal_position_template_5,fiscal_position_template_6","","","E","VATEX-EU-F" "","","","","","","","","","","","","tax","invoice","","","","","","","","","","","","","","" "","","","","","","","","","","","","base","refund","83||85","","","","","","","","","","","","","" "","","","","","","","","","","","","tax","refund","","","","","","","","","","","","","","" diff --git a/addons/l10n_de/models/__init__.py b/addons/l10n_de/models/__init__.py index ac82f3bb8e70b0..d03f1b350c0e8b 100644 --- a/addons/l10n_de/models/__init__.py +++ b/addons/l10n_de/models/__init__.py @@ -10,3 +10,4 @@ from . import res_company from . import template_de_skr03 from . import template_de_skr04 +from . import res_partner diff --git a/addons/l10n_de/models/res_partner.py b/addons/l10n_de/models/res_partner.py new file mode 100644 index 00000000000000..06ce31a1807d51 --- /dev/null +++ b/addons/l10n_de/models/res_partner.py @@ -0,0 +1,10 @@ +from odoo import _, models + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + def _get_company_registry_labels(self): + labels = super()._get_company_registry_labels() + labels['DE'] = _('Commercial Register Number') + return labels diff --git a/addons/l10n_de/views/res_company_views.xml b/addons/l10n_de/views/res_company_views.xml index a4a332a6571f64..175d82e446704b 100644 --- a/addons/l10n_de/views/res_company_views.xml +++ b/addons/l10n_de/views/res_company_views.xml @@ -9,6 +9,12 @@ + + + + + + diff --git a/addons/l10n_din5008/report/din5008_base_document_layout.xml b/addons/l10n_din5008/report/din5008_base_document_layout.xml index 9912f4668a8525..a6495b95b52191 100644 --- a/addons/l10n_din5008/report/din5008_base_document_layout.xml +++ b/addons/l10n_din5008/report/din5008_base_document_layout.xml @@ -11,15 +11,15 @@ Invoice Date: - + Due Date: - + Delivery Date: - + Reference: diff --git a/addons/l10n_din5008/report/din5008_report.xml b/addons/l10n_din5008/report/din5008_report.xml index 94eb49509ea4dd..55aeb7b266f984 100644 --- a/addons/l10n_din5008/report/din5008_report.xml +++ b/addons/l10n_din5008/report/din5008_report.xml @@ -36,11 +36,12 @@
+
- +
@@ -77,18 +78,29 @@

- + + + + + +
- + + +
+ +
+
+ @@ -133,7 +145,7 @@ -
  • HRB Nr:
  • +
  • @@ -190,7 +202,7 @@