summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--addons/account/models/account_journal.py6
-rw-r--r--addons/account/models/account_move.py13
-rw-r--r--addons/account/tests/test_account_move_out_invoice.py41
-rw-r--r--addons/account/wizard/account_secure_entries_wizard.py2
-rw-r--r--addons/hr_holidays/tests/test_expiring_leaves.py2
-rw-r--r--addons/hr_holidays/views/hr_leave_allocation_views.xml4
-rw-r--r--addons/hr_holidays_attendance/i18n/hr_holidays_attendance.pot14
-rw-r--r--addons/hr_holidays_attendance/models/hr_leave_allocation.py4
-rw-r--r--addons/html_editor/static/src/main/media/media_dialog/upload_progress_toast/upload_progress_toast.scss17
-rw-r--r--addons/html_editor/static/src/main/media/media_dialog/upload_progress_toast/upload_progress_toast.xml10
-rw-r--r--addons/point_of_sale/static/src/app/models/related_models.js52
-rw-r--r--addons/point_of_sale/static/src/app/screens/payment_screen/payment_screen.js4
-rw-r--r--addons/point_of_sale/static/src/app/store/pos_store.js6
-rw-r--r--addons/point_of_sale/static/src/app/store/select_lot_popup/edit_list_input/edit_list_input.scss6
-rw-r--r--addons/pos_online_payment/static/src/overrides/pos_overrides/components/payment_screen/payment_screen.js4
-rw-r--r--addons/pos_restaurant/static/tests/tours/pos_restaurant_tour.js13
-rw-r--r--addons/pos_restaurant/tests/test_frontend.py39
-rw-r--r--odoo/addons/base/models/ir_actions.py6
18 files changed, 199 insertions, 44 deletions
diff --git a/addons/account/models/account_journal.py b/addons/account/models/account_journal.py
index c5a039a5529..b3b79dbc079 100644
--- a/addons/account/models/account_journal.py
+++ b/addons/account/models/account_journal.py
@@ -678,9 +678,6 @@ class AccountJournal(models.Model):
for journal in self.filtered(lambda r: r.type == 'bank' and not r.bank_account_id):
journal.set_bank_account(vals.get('bank_acc_number'), vals.get('bank_id'))
- if vals.get('restrict_mode_hash_table'):
- self.env['res.groups']._activate_group_account_secured()
-
return result
def _alias_get_creation_values(self):
@@ -880,9 +877,6 @@ class AccountJournal(models.Model):
if journal.type == 'bank' and not journal.bank_account_id and vals.get('bank_acc_number'):
journal.set_bank_account(vals.get('bank_acc_number'), vals.get('bank_id'))
- if any(journals.mapped('restrict_mode_hash_table')):
- self.env['res.groups']._activate_group_account_secured()
-
return journals
def set_bank_account(self, acc_number, bank_id=None):
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index a33ff499316..a1af8cd8d94 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -3839,11 +3839,17 @@ class AccountMove(models.Model):
def _hash_moves(self, **kwargs):
chains_to_hash = self._get_chains_to_hash(**kwargs)
+ grant_secure_group_access = False
for chain in chains_to_hash:
move_hashes = chain['moves']._calculate_hashes(chain['previous_hash'])
for move, move_hash in move_hashes.items():
move.inalterable_hash = move_hash
+ # If any secured entries belong to journals without 'hash on post', the user should be granted access rights
+ if not chain['journal_restrict_mode']:
+ grant_secure_group_access = True
chain['moves']._message_log_batch(bodies={m.id: self.env._("This journal entry has been secured.") for m in chain['moves']})
+ if grant_secure_group_access:
+ self.env['res.groups']._activate_group_account_secured()
def _get_chain_info(self, force_hash=False, include_pre_last_hash=False, early_stop=False):
"""All records in `self` must belong to the same journal and sequence_prefix
@@ -3928,6 +3934,7 @@ class AccountMove(models.Model):
if not chain_info:
continue
+ chain_info['journal_restrict_mode'] = journal.restrict_mode_hash_table
if early_stop:
return True
@@ -4701,9 +4708,13 @@ class AccountMove(models.Model):
for move, reverse_move in zip(self, reverse_moves):
group = (move.line_ids + reverse_move.line_ids) \
.filtered(lambda l: not l.reconciled) \
+ .sorted(lambda l: l.account_type not in ('asset_receivable', 'liability_payable')) \
.grouped(lambda l: (l.account_id, l.currency_id))
for (account, _currency), lines in group.items():
- if account.reconcile or account.account_type in ('asset_cash', 'liability_credit_card'):
+ if (
+ all(not line.reconciled for line in lines) # if it was reconciled due to a previous group
+ and account.reconcile or account.account_type in ('asset_cash', 'liability_credit_card')
+ ):
lines.with_context(move_reverse_cancel=move_reverse_cancel).reconcile()
return reverse_moves
diff --git a/addons/account/tests/test_account_move_out_invoice.py b/addons/account/tests/test_account_move_out_invoice.py
index d690950a626..d7a3630c162 100644
--- a/addons/account/tests/test_account_move_out_invoice.py
+++ b/addons/account/tests/test_account_move_out_invoice.py
@@ -3662,6 +3662,47 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
]
self.assertRecordValues(caba_move.line_ids, expected_values)
+ def test_out_invoice_caba_on_payment(self):
+ 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 10%',
+ 'type_tax_use': 'sale',
+ 'amount': 10,
+ 'tax_exigibility': 'on_payment',
+ 'cash_basis_transition_account_id': tax_waiting_account.id,
+ })
+ caba_tax.invoice_repartition_line_ids.account_id.reconcile = True
+ invoice = self.env['account.move'].create({
+ 'move_type': 'out_invoice',
+ 'partner_id': self.partner_a.id,
+ 'invoice_line_ids': [
+ Command.create({
+ 'price_unit': 1000.0,
+ 'tax_ids': [Command.set(caba_tax.ids)],
+ })
+ ],
+ })
+ invoice.invoice_line_ids.tax_ids = caba_tax
+ invoice.action_post()
+ credit_note = invoice._reverse_moves()
+ credit_note.action_post()
+ receivable_lines = (invoice + credit_note).line_ids.filtered(lambda l: l.account_id == self.partner_a.property_account_receivable_id)
+ invoice_receivable_matching, refund_receivable_matching = receivable_lines.mapped('matching_number')
+ self.assertEqual(invoice_receivable_matching, refund_receivable_matching)
+ # The tax account should be reconciled with the CABA entries if they exist
+ # But ideally, they shouldn't exist since no cash was involved.
+ tax_lines = (invoice + credit_note).line_ids.filtered(lambda l: l.account_id == tax_waiting_account)
+ invoice_tax_matching, refund_tax_matching = tax_lines.mapped('matching_number')
+ self.assertNotEqual(invoice_tax_matching, refund_tax_matching)
+ self.assertTrue(all([invoice_tax_matching, refund_tax_matching, invoice_receivable_matching, refund_receivable_matching]))
+
+
def test_tax_grid_remove_tax(self):
# Add a tag to tax_sale_a
tax_line_tag = self.env['account.account.tag'].create({
diff --git a/addons/account/wizard/account_secure_entries_wizard.py b/addons/account/wizard/account_secure_entries_wizard.py
index 3f77c571ff2..7c8772c3d47 100644
--- a/addons/account/wizard/account_secure_entries_wizard.py
+++ b/addons/account/wizard/account_secure_entries_wizard.py
@@ -259,8 +259,6 @@ class AccountSecureEntries(models.TransientModel):
if not self.hash_date:
raise UserError(_("Set a date. The moves will be secured up to including this date."))
- self.env['res.groups']._activate_group_account_secured()
-
if not self.move_to_hash_ids:
return
diff --git a/addons/hr_holidays/tests/test_expiring_leaves.py b/addons/hr_holidays/tests/test_expiring_leaves.py
index 639bcf7b8f6..983466dbcb5 100644
--- a/addons/hr_holidays/tests/test_expiring_leaves.py
+++ b/addons/hr_holidays/tests/test_expiring_leaves.py
@@ -468,7 +468,7 @@ class TestExpiringLeaves(HttpCase, TestHrHolidaysCommon):
})
with freeze_time("2024-1-1"):
- self.env['hr.leave.allocation'].sudo()._update_accrual()
+ self.env['hr.leave.allocation'].with_user(self.user_hruser)._update_accrual()
target_date = date(2024, 1, 1)
allocation_data = self.leave_type.get_allocation_data(logged_in_emp, target_date)
diff --git a/addons/hr_holidays/views/hr_leave_allocation_views.xml b/addons/hr_holidays/views/hr_leave_allocation_views.xml
index be40ca564cd..6c998410b81 100644
--- a/addons/hr_holidays/views/hr_leave_allocation_views.xml
+++ b/addons/hr_holidays/views/hr_leave_allocation_views.xml
@@ -154,10 +154,10 @@
<div name="duration_display">
<field name="number_of_days_display" nolabel="1" style="width: 5rem;"
invisible="type_request_unit == 'hour'"
- readonly="0"/>
+ readonly="is_officer != True and state not in ('draft','confirm')"/>
<field name="number_of_hours_display" nolabel="1" style="width: 5rem;"
invisible="type_request_unit != 'hour'"
- readonly="0"/>
+ readonly="is_officer != True and state not in ('draft','confirm')"/>
<span class="ml8" invisible="type_request_unit == 'hour'">Days</span>
<span class="ml8" invisible="type_request_unit != 'hour'">Hours</span>
</div>
diff --git a/addons/hr_holidays_attendance/i18n/hr_holidays_attendance.pot b/addons/hr_holidays_attendance/i18n/hr_holidays_attendance.pot
index 43bca44e311..b509a57f75e 100644
--- a/addons/hr_holidays_attendance/i18n/hr_holidays_attendance.pot
+++ b/addons/hr_holidays_attendance/i18n/hr_holidays_attendance.pot
@@ -4,10 +4,10 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Odoo Server 18.0\n"
+"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-09-26 08:55+0000\n"
-"PO-Revision-Date: 2024-09-26 08:55+0000\n"
+"POT-Creation-Date: 2025-02-06 14:58+0000\n"
+"PO-Revision-Date: 2025-02-06 14:58+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@@ -111,6 +111,14 @@ msgid ""
msgstr ""
#. module: hr_holidays_attendance
+#. odoo-python
+#: code:addons/hr_holidays_attendance/models/hr_leave_allocation.py:0
+msgid ""
+"Only an Officer or Administrator is allowed to edit the allocation duration "
+"in this status."
+msgstr ""
+
+#. module: hr_holidays_attendance
#: model:ir.model.fields,field_description:hr_holidays_attendance.field_hr_leave__overtime_deductible
#: model:ir.model.fields,field_description:hr_holidays_attendance.field_hr_leave_allocation__overtime_deductible
msgid "Overtime Deductible"
diff --git a/addons/hr_holidays_attendance/models/hr_leave_allocation.py b/addons/hr_holidays_attendance/models/hr_leave_allocation.py
index 1b962540e9f..2732fd5e4b8 100644
--- a/addons/hr_holidays_attendance/models/hr_leave_allocation.py
+++ b/addons/hr_holidays_attendance/models/hr_leave_allocation.py
@@ -52,7 +52,9 @@ class HolidaysAllocation(models.Model):
res = super().write(vals)
if 'number_of_days' not in vals:
return res
- for allocation in self.filtered('overtime_id'):
+ if not self.env.user.has_group("hr_holidays.group_hr_holidays_user") and any(allocation.state not in ('draft', 'confirm') for allocation in self):
+ raise ValidationError(_('Only an Officer or Administrator is allowed to edit the allocation duration in this status.'))
+ for allocation in self.sudo().filtered('overtime_id'):
employee = allocation.employee_id
duration = allocation.number_of_hours_display
overtime_duration = allocation.overtime_id.sudo().duration
diff --git a/addons/html_editor/static/src/main/media/media_dialog/upload_progress_toast/upload_progress_toast.scss b/addons/html_editor/static/src/main/media/media_dialog/upload_progress_toast/upload_progress_toast.scss
index 998ffd9ec92..b6344499f74 100644
--- a/addons/html_editor/static/src/main/media/media_dialog/upload_progress_toast/upload_progress_toast.scss
+++ b/addons/html_editor/static/src/main/media/media_dialog/upload_progress_toast/upload_progress_toast.scss
@@ -7,3 +7,20 @@
}
}
}
+
+$o-notification-max-width-sm: calc(400px - 3rem);
+$button-width: 3rem;
+
+.editor_notification_manager {
+ width: calc(100% - #{$button-width});
+ @include media-breakpoint-up(sm) {
+ width: calc(#{$o-notification-max-width-sm} + #{$button-width});
+ }
+}
+
+.editor_notification_body {
+ width: calc(100% - #{$button-width});
+ @include media-breakpoint-up(sm) {
+ width: $o-notification-max-width-sm;
+ }
+}
diff --git a/addons/html_editor/static/src/main/media/media_dialog/upload_progress_toast/upload_progress_toast.xml b/addons/html_editor/static/src/main/media/media_dialog/upload_progress_toast/upload_progress_toast.xml
index 275077d96b7..340567009ab 100644
--- a/addons/html_editor/static/src/main/media/media_dialog/upload_progress_toast/upload_progress_toast.xml
+++ b/addons/html_editor/static/src/main/media/media_dialog/upload_progress_toast/upload_progress_toast.xml
@@ -9,17 +9,16 @@
<span t-if="props.uploaded" class="text-success"><i class="fa fa-check my-1 me-1"/> File has been uploaded</span>
<span t-else="" class="text-danger"><i class="fa fa-times float-start my-1 me-1"/> <span class="o_we_error_text" t-esc="props.errorMessage ? props.errorMessage : 'File could not be saved'"/></span>
</small>
- <div t-else="" class="progress">
+ <div t-else="" class="progress mt-2">
<div class="progress-bar bg-info progress-bar-striped progress-bar-animated" role="progressbar" t-attf-style="width: {{this.progress}}%;" aria-label="Progress bar"><span t-esc="this.progress + '%'"/></div>
</div>
<hr/>
</t>
<t t-name="html_editor.UploadProgressToast">
- <div class="o_notification_manager o_upload_progress_toast">
- <div t-if="state.isVisible" class="o_notification position-relative show fade mb-2 border border-info bg-white" role="alert" aria-live="assertive" aria-atomic="true">
- <button type="button" class="btn btn-close o_notification_close p-2" aria-label="Close" t-on-click="props.close"/>
- <div class="o_notification_body ps-2 pe-4 py-2">
+ <div class="editor_notification_manager o_notification_manager o_upload_progress_toast">
+ <div t-if="state.isVisible" class="o_notification position-relative show fade mb-2 border border-info bg-white d-flex justify-content-between" role="alert" aria-live="assertive" aria-atomic="true">
+ <div class="editor_notification_body o_notification_body ps-2 py-2">
<div class="me-auto o_notification_content">
<div t-foreach="state.files" t-as="file" t-key="file" class="o_we_progressbar">
<ProgressBar progress="file_value.progress"
@@ -31,6 +30,7 @@
</div>
</div>
</div>
+ <button type="button" class="btn btn-close o_notification_close p-2" aria-label="Close" t-on-click="props.close"/>
</div>
</div>
</t>
diff --git a/addons/point_of_sale/static/src/app/models/related_models.js b/addons/point_of_sale/static/src/app/models/related_models.js
index 1d66355cb27..8b0c2885c10 100644
--- a/addons/point_of_sale/static/src/app/models/related_models.js
+++ b/addons/point_of_sale/static/src/app/models/related_models.js
@@ -131,11 +131,12 @@ function processModelDefs(modelDefs) {
}
export class Base {
- constructor({ models, records, model, dynamicModels }) {
+ constructor({ models, records, model, dynamicModels, baseData }) {
this.models = models;
this.records = records;
this.model = model;
this._dynamicModels = dynamicModels;
+ this.baseData = baseData;
}
/**
* Called during instantiation when the instance is fully-populated with field values.
@@ -290,7 +291,7 @@ export class Base {
return this[cacheName];
}
get raw() {
- return this._raw ?? {};
+ return this.baseData[this.id];
}
}
@@ -441,7 +442,13 @@ export function createRelatedModels(modelDefs, modelClasses = {}, opts = {}) {
const Model = modelClasses[model] || Base;
const record = reactive(
- new Model({ models, records, model: models[model], dynamicModels: opts.dynamicModels })
+ new Model({
+ models,
+ records,
+ model: models[model],
+ dynamicModels: opts.dynamicModels,
+ baseData: baseData[model],
+ })
);
const id = vals["id"];
record.id = id;
@@ -454,7 +461,6 @@ export function createRelatedModels(modelDefs, modelClasses = {}, opts = {}) {
baseData[model][id] = vals;
}
- record._raw = baseData[model][id];
records[model].set(id, record);
const fields = getFields(model);
@@ -563,7 +569,6 @@ export function createRelatedModels(modelDefs, modelClasses = {}, opts = {}) {
continue;
} else if (name === "id" && vals[name] !== record.id) {
records[model].delete(record.id);
- delete baseData[model][record.id];
for (const key of indexes[model] || []) {
const keyVal = record.raw[key];
@@ -574,6 +579,8 @@ export function createRelatedModels(modelDefs, modelClasses = {}, opts = {}) {
}
}
+ delete baseData[model][record.id];
+
record.id = vals[name];
records[model].set(record.id, record);
baseData[model][record.id] = vals;
@@ -584,7 +591,7 @@ export function createRelatedModels(modelDefs, modelClasses = {}, opts = {}) {
const field = fields[name];
const comodelName = field.relation;
- if (X2MANY_TYPES.has(field.type)) {
+ if (X2MANY_TYPES.has(field.type) && comodelName in models) {
for (const command of vals[name]) {
const [type, ...items] = command;
if (type === "unlink") {
@@ -613,7 +620,6 @@ export function createRelatedModels(modelDefs, modelClasses = {}, opts = {}) {
const existingRecords = items.filter((record) =>
exists(comodelName, record.id)
);
-
for (const record2 of [...linkedRecs]) {
disconnect(field, record, record2);
}
@@ -622,7 +628,7 @@ export function createRelatedModels(modelDefs, modelClasses = {}, opts = {}) {
}
}
}
- } else if (field.type === "many2one") {
+ } else if (field.type === "many2one" && comodelName in models) {
if (vals[name]) {
const id = vals[name]?.id || vals[name];
const exist = exists(comodelName, id);
@@ -639,7 +645,9 @@ export function createRelatedModels(modelDefs, modelClasses = {}, opts = {}) {
const linkedRec = record[name];
disconnect(field, record, linkedRec);
}
- } else {
+ }
+
+ if (!RELATION_TYPES.has(field.type)) {
record[name] = vals[name];
}
}
@@ -806,7 +814,7 @@ export function createRelatedModels(modelDefs, modelClasses = {}, opts = {}) {
if (field.type === "many2one") {
result[name] = record[name]?.id || record.raw[name] || false;
} else if (X2MANY_TYPES.has(field.type)) {
- const ids = [...record[name]].map((record) => record.id);
+ const ids = [...record[name]].map((record) => record.id).filter(Boolean);
result[name] = ids.length ? ids : (!orm && record.raw[name]) || [];
} else if (typeof record[name] === "object") {
result[name] = JSON.stringify(record[name]);
@@ -946,18 +954,34 @@ export function createRelatedModels(modelDefs, modelClasses = {}, opts = {}) {
if (oldRecord && keepLocalRelation) {
for (const [field, value] of Object.entries(record)) {
- if (field === "id") {
+ const params = getFields(model)[field];
+ if (field === "id" || !params) {
continue;
}
- const params = getFields(model)[field];
- if (params && X2MANY_TYPES.has(params.type)) {
+ if (X2MANY_TYPES.has(params.type)) {
value.push(
...oldRecord[field]
.filter((r) => typeof r.id === "string")
.map((r) => r.id)
);
- record[field] = ["set", value];
+ const existingRecords = value
+ .map((r) => models[params.relation]?.get(r))
+ .filter(Boolean);
+ if (existingRecords.length) {
+ record[field] = [["set", ...existingRecords]];
+ }
+ } else if (
+ params.type === "many2one" &&
+ value &&
+ !exists(params.relation, value)
+ ) {
+ const key = `${params.relation}_${value}`;
+ if (!missingFields[key]) {
+ missingFields[key] = [[oldRecord, params]];
+ } else {
+ missingFields[key].push([oldRecord, params]);
+ }
}
}
diff --git a/addons/point_of_sale/static/src/app/screens/payment_screen/payment_screen.js b/addons/point_of_sale/static/src/app/screens/payment_screen/payment_screen.js
index 6923e762700..aedb9c9f331 100644
--- a/addons/point_of_sale/static/src/app/screens/payment_screen/payment_screen.js
+++ b/addons/point_of_sale/static/src/app/screens/payment_screen/payment_screen.js
@@ -301,8 +301,8 @@ export class PaymentScreen extends Component {
// 2. Invoice.
if (this.shouldDownloadInvoice() && this.currentOrder.is_to_invoice()) {
- if (this.currentOrder.account_move) {
- await this.invoiceService.downloadPdf(this.currentOrder.account_move);
+ if (this.currentOrder.raw.account_move) {
+ await this.invoiceService.downloadPdf(this.currentOrder.raw.account_move);
} else {
throw {
code: 401,
diff --git a/addons/point_of_sale/static/src/app/store/pos_store.js b/addons/point_of_sale/static/src/app/store/pos_store.js
index b3d1ff010d2..e4ce9287b5a 100644
--- a/addons/point_of_sale/static/src/app/store/pos_store.js
+++ b/addons/point_of_sale/static/src/app/store/pos_store.js
@@ -1051,7 +1051,7 @@ export class PosStore extends Reactive {
* @returns {name: string, id: int, role: string}
*/
get_cashier() {
- this.user.role = this.user._raw.role;
+ this.user.role = this.user.raw.role;
return this.user;
}
get_cashier_user_id() {
@@ -1658,7 +1658,6 @@ export class PosStore extends Reactive {
async printChanges(order, orderChange) {
const unsuccedPrints = [];
- const lastChangedLines = order.last_order_preparation_change.lines;
orderChange.new.sort((a, b) => {
const sequenceA = a.pos_categ_sequence;
const sequenceB = b.pos_categ_sequence;
@@ -1674,8 +1673,9 @@ export class PosStore extends Reactive {
printer.config.product_categories_ids,
orderChange
);
+ const anyChangesToPrint = Object.values(changes).some((change) => change.length);
const diningModeUpdate = orderChange.modeUpdate;
- if (diningModeUpdate || !Object.keys(lastChangedLines).length) {
+ if (diningModeUpdate || anyChangesToPrint) {
const printed = await this.printReceipts(
order,
printer,
diff --git a/addons/point_of_sale/static/src/app/store/select_lot_popup/edit_list_input/edit_list_input.scss b/addons/point_of_sale/static/src/app/store/select_lot_popup/edit_list_input/edit_list_input.scss
index 472f56651ee..c3e331b11f8 100644
--- a/addons/point_of_sale/static/src/app/store/select_lot_popup/edit_list_input/edit_list_input.scss
+++ b/addons/point_of_sale/static/src/app/store/select_lot_popup/edit_list_input/edit_list_input.scss
@@ -35,3 +35,9 @@
.pos .edit-list-inputs .options-dropdown .option:hover {
background-color: $gray-200;
}
+
+@media (max-width: 575px) {
+ .pos .edit-list-inputs .options-dropdown {
+ position: static;
+ }
+}
diff --git a/addons/pos_online_payment/static/src/overrides/pos_overrides/components/payment_screen/payment_screen.js b/addons/pos_online_payment/static/src/overrides/pos_overrides/components/payment_screen/payment_screen.js
index 4c9580b9af8..85e0034f693 100644
--- a/addons/pos_online_payment/static/src/overrides/pos_overrides/components/payment_screen/payment_screen.js
+++ b/addons/pos_online_payment/static/src/overrides/pos_overrides/components/payment_screen/payment_screen.js
@@ -230,13 +230,13 @@ patch(PaymentScreen.prototype, {
}
if (isInvoiceRequested) {
- if (!orderJSON[0].account_move) {
+ if (!orderJSON[0].raw.account_move) {
this.dialog.add(AlertDialog, {
title: _t("Invoice could not be generated"),
body: _t("The invoice could not be generated."),
});
} else {
- await this.invoiceService.downloadPdf(orderJSON[0].account_move);
+ await this.invoiceService.downloadPdf(orderJSON[0].raw.account_move);
}
}
diff --git a/addons/pos_restaurant/static/tests/tours/pos_restaurant_tour.js b/addons/pos_restaurant/static/tests/tours/pos_restaurant_tour.js
index a63e588927f..9fb58390614 100644
--- a/addons/pos_restaurant/static/tests/tours/pos_restaurant_tour.js
+++ b/addons/pos_restaurant/static/tests/tours/pos_restaurant_tour.js
@@ -393,3 +393,16 @@ registry.category("web_tour.tours").add("PreparationPrinterContent", {
},
].flat(),
});
+
+registry.category("web_tour.tours").add("MultiPreparationPrinter", {
+ checkDelay: 50,
+ steps: () =>
+ [
+ Chrome.startPoS(),
+ Dialog.confirm("Open Register"),
+ FloorScreen.clickTable("5"),
+ ProductScreen.clickDisplayedProduct("Product 1"),
+ ProductScreen.clickOrderButton(),
+ Dialog.bodyIs("Failed in printing Detailed Receipt changes of the order"),
+ ].flat(),
+});
diff --git a/addons/pos_restaurant/tests/test_frontend.py b/addons/pos_restaurant/tests/test_frontend.py
index 1953cbf9729..af0fd9e8736 100644
--- a/addons/pos_restaurant/tests/test_frontend.py
+++ b/addons/pos_restaurant/tests/test_frontend.py
@@ -399,3 +399,42 @@ class TestFrontend(TestFrontendCommon):
})
self.main_pos_config.with_user(self.pos_user).open_ui()
self.start_tour(f"/pos/ui?config_id={self.main_pos_config.id}", 'PreparationPrinterContent', login="pos_user")
+
+ def test_multiple_preparation_printer(self):
+ """This test make sure that no empty receipt are sent when using multiple printer with different categories
+ The tour will check that we tried did not try to print two receipt. We can achieve that by checking the content
+ of the error message. Because we do not have real printer an error message will be displayed, this will contain
+ all the receipt that failed to print. If it contains more than 1 it means that we tried to print a second receipt
+ and it should not be the case here. The only one we should see is 'Detailed Receipt'
+ """
+ pos_category_1 = self.env['pos.category'].create({'name': 'Category 1'})
+ pos_category_2 = self.env['pos.category'].create({'name': 'Category 2'})
+ printer_1 = self.env['pos.printer'].create({
+ 'name': 'Printer',
+ 'printer_type': 'epson_epos',
+ 'epson_printer_ip': '0.0.0.0',
+ 'product_categories_ids': [Command.set(pos_category_2.ids)],
+ })
+ printer_2 = self.env['pos.printer'].create({
+ 'name': 'Printer',
+ 'printer_type': 'epson_epos',
+ 'epson_printer_ip': '0.0.0.0',
+ 'product_categories_ids': [Command.set(pos_category_1.ids)],
+ })
+
+
+ self.main_pos_config.write({
+ 'is_order_printer' : True,
+ 'printer_ids': [Command.set([printer_1.id, printer_2.id])],
+ })
+
+ self.product_1 = self.env['product.product'].create({
+ 'name': 'Product 1',
+ 'available_in_pos': True,
+ 'list_price': 10,
+ 'pos_categ_ids': [(6, 0, [pos_category_1.id])],
+ 'taxes_id': False,
+ })
+
+ self.main_pos_config.with_user(self.pos_user).open_ui()
+ self.start_tour(f"/pos/ui?config_id={self.main_pos_config.id}", 'MultiPreparationPrinter', login="pos_user")
diff --git a/odoo/addons/base/models/ir_actions.py b/odoo/addons/base/models/ir_actions.py
index 1a75b2e7440..4bc9ebac3ec 100644
--- a/odoo/addons/base/models/ir_actions.py
+++ b/odoo/addons/base/models/ir_actions.py
@@ -1079,9 +1079,11 @@ class IrActionsServer(models.Model):
def copy_data(self, default=None):
default = default or {}
+ vals_list = super().copy_data(default=default)
if not default.get('name'):
- default['name'] = _('%s (copy)', self.name)
- return super().copy_data(default=default)
+ for vals in vals_list:
+ vals['name'] = _('%s (copy)', vals.get('name', ''))
+ return vals_list
class IrActionsTodo(models.Model):
"""