diff options
Diffstat (limited to 'addons')
17 files changed, 195 insertions, 42 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") |
