[REF] event_sale: convert configurator to OWL
And stop relying on dumb event_ok field, when the product_type already holds the information (recently moved from sale_stock to sale). Task-2918791 X-original-commit: 10255e549d071c1ceee5faae7be0e1a0486a3639 Part-of: odoo/odoo#101352
This commit is contained in:
@@ -48,9 +48,6 @@ this event.
|
||||
'web.assets_tests': [
|
||||
'event_sale/static/tests/tours/**/*',
|
||||
],
|
||||
'web.qunit_suite_tests': [
|
||||
'event_sale/static/tests/event_configurator.test.js',
|
||||
],
|
||||
},
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
||||
@@ -21,12 +21,14 @@ class SaleOrder(models.Model):
|
||||
def action_confirm(self):
|
||||
res = super(SaleOrder, self).action_confirm()
|
||||
for so in self:
|
||||
if not any(line.product_type == 'event' for line in so.order_line):
|
||||
continue
|
||||
# confirm registration if it was free (otherwise it will be confirmed once invoice fully paid)
|
||||
so.order_line._update_registrations(confirm=so.amount_total == 0, cancel_to_draft=False)
|
||||
if any(line.event_ok for line in so.order_line):
|
||||
return self.env['ir.actions.act_window'] \
|
||||
.with_context(default_sale_order_id=so.id) \
|
||||
._for_xml_id('event_sale.action_sale_order_event_registration')
|
||||
if len(self) == 1:
|
||||
return self.env['ir.actions.act_window'].with_context(
|
||||
default_sale_order_id=so.id
|
||||
)._for_xml_id('event_sale.action_sale_order_event_registration')
|
||||
return res
|
||||
|
||||
def _action_cancel(self):
|
||||
@@ -68,6 +70,7 @@ class SaleOrderLine(models.Model):
|
||||
'event.event.ticket', string='Event Ticket',
|
||||
compute="_compute_event_ticket_id", store=True, readonly=False, precompute=True,
|
||||
help="Choose an event ticket and it will automatically create a registration for this event ticket.")
|
||||
# TODO in master: remove this field, unused anymore
|
||||
event_ok = fields.Boolean(compute='_compute_event_ok')
|
||||
|
||||
@api.depends('product_id.detailed_type')
|
||||
@@ -89,7 +92,9 @@ class SaleOrderLine(models.Model):
|
||||
RegistrationSudo = self.env['event.registration'].sudo()
|
||||
registrations = RegistrationSudo.search([('sale_order_line_id', 'in', self.ids)])
|
||||
registrations_vals = []
|
||||
for so_line in self.filtered('event_ok'):
|
||||
for so_line in self:
|
||||
if not so_line.product_type == 'event':
|
||||
continue
|
||||
existing_registrations = registrations.filtered(lambda self: self.sale_order_line_id.id == so_line.id)
|
||||
if confirm:
|
||||
existing_registrations.filtered(lambda self: self.state not in ['open', 'cancel']).action_confirm()
|
||||
|
||||
@@ -28,8 +28,8 @@ class EventConfiguratorRecord extends Record {
|
||||
await super.save(options);
|
||||
this.model.action.doAction({type: 'ir.actions.act_window_close', infos: {
|
||||
eventConfiguration: {
|
||||
event_id: {id: this.data.event_id[0]},
|
||||
event_ticket_id: {id: this.data.event_ticket_id[0]}
|
||||
event_id: this.data.event_id,
|
||||
event_ticket_id: this.data.event_ticket_id,
|
||||
}
|
||||
}});
|
||||
}
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
odoo.define('event_sale.product_configurator', function (require) {
|
||||
var ProductConfiguratorWidget = require('sale.product_configurator');
|
||||
|
||||
/**
|
||||
* Extension of the ProductConfiguratorWidget to support event product configuration.
|
||||
* It opens when an event product_product is set.
|
||||
*
|
||||
* The event information include:
|
||||
* - event_id
|
||||
* - event_ticket_id
|
||||
*
|
||||
*/
|
||||
ProductConfiguratorWidget.include({
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_isConfigurableLine: function () {
|
||||
return this.recordData.event_ok || this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {integer} productId
|
||||
* @param {String} dataPointID
|
||||
* @returns {Promise<Boolean>} stopPropagation true if a suitable configurator has been found.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_onProductChange: function (productId, dataPointId) {
|
||||
var self = this;
|
||||
return this._super.apply(this, arguments).then(function (stopPropagation) {
|
||||
if (stopPropagation || productId === undefined) {
|
||||
return Promise.resolve(true);
|
||||
} else {
|
||||
return self._checkForEvent(productId, dataPointId);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* This method will check if the productId needs configuration or not:
|
||||
*
|
||||
* @param {integer} productId
|
||||
* @param {string} dataPointID
|
||||
* @returns {Promise<Boolean>} stopPropagation true if the product is an event ticket.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_checkForEvent: function (productId, dataPointId) {
|
||||
var self = this;
|
||||
return this._rpc({
|
||||
model: 'product.product',
|
||||
method: 'read',
|
||||
args: [productId, ['detailed_type']],
|
||||
}).then(function (result) {
|
||||
if (Array.isArray(result) && result.length && result[0].detailed_type === 'event') {
|
||||
self._openEventConfigurator({
|
||||
default_product_id: productId
|
||||
},
|
||||
dataPointId
|
||||
);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
return Promise.resolve(false);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens the event configurator in 'edit' mode.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_onEditLineConfiguration: function () {
|
||||
if (this.recordData.event_ok) {
|
||||
var defaultValues = {
|
||||
default_product_id: this.recordData.product_id.data.id
|
||||
};
|
||||
|
||||
if (this.recordData.event_id) {
|
||||
defaultValues.default_event_id = this.recordData.event_id.data.id;
|
||||
}
|
||||
|
||||
if (this.recordData.event_ticket_id) {
|
||||
defaultValues.default_event_ticket_id = this.recordData.event_ticket_id.data.id;
|
||||
}
|
||||
|
||||
this._openEventConfigurator(defaultValues, this.dataPointID);
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens the event configurator to allow configuring the SO line with events information.
|
||||
*
|
||||
* When the window is closed, configured values are used to trigger a 'field_changed'
|
||||
* event to modify the current SO line.
|
||||
*
|
||||
* If the window is closed without providing the required values 'event_id' and
|
||||
* 'event_ticket_id', the product_id field is cleaned.
|
||||
*
|
||||
* @param {Object} data various "default_" values
|
||||
* @param {string} dataPointId
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_openEventConfigurator: function (data, dataPointId) {
|
||||
var self = this;
|
||||
this.do_action('event_sale.event_configurator_action', {
|
||||
additional_context: data,
|
||||
on_close: function (result) {
|
||||
if (result && !result.special) {
|
||||
self.trigger_up('field_changed', {
|
||||
dataPointID: dataPointId,
|
||||
changes: result.eventConfiguration,
|
||||
onSuccess: function () {
|
||||
// Call post-init function.
|
||||
self._onLineConfigured();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (!self.recordData.event_id || !self.recordData.event_ticket_id) {
|
||||
self.trigger_up('field_changed', {
|
||||
dataPointID: dataPointId,
|
||||
changes: {
|
||||
product_id: false,
|
||||
name: ''
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return ProductConfiguratorWidget;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { SaleOrderLineProductField } from '@sale/js/sale_product_field';
|
||||
|
||||
|
||||
patch(SaleOrderLineProductField.prototype, 'event_sale', {
|
||||
|
||||
async _onProductUpdate() {
|
||||
this._super(...arguments);
|
||||
if (this.props.record.data.product_type === 'event') {
|
||||
this._openEventConfigurator();
|
||||
}
|
||||
},
|
||||
|
||||
_editLineConfiguration() {
|
||||
this._super(...arguments);
|
||||
if (this.props.record.data.product_type === 'event') {
|
||||
this._openEventConfigurator();
|
||||
}
|
||||
},
|
||||
|
||||
get isConfigurableLine() {
|
||||
return this._super(...arguments) || Boolean(this.props.record.data.event_ticket_id);
|
||||
},
|
||||
|
||||
async _openEventConfigurator() {
|
||||
let actionContext = {
|
||||
'default_product_id': this.props.record.data.product_id[0],
|
||||
};
|
||||
if (this.props.record.data.event_id) {
|
||||
actionContext.default_event_id = this.props.record.data.event_id[0];
|
||||
}
|
||||
if (this.props.record.data.event_ticket_id) {
|
||||
actionContext.default_event_ticket_id = this.props.record.data.event_ticket_id[0];
|
||||
}
|
||||
this.action.doAction(
|
||||
'event_sale.event_configurator_action',
|
||||
{
|
||||
additionalContext: actionContext,
|
||||
onClose: async (closeInfo) => {
|
||||
if (!closeInfo || closeInfo.special) {
|
||||
// wizard popup closed or 'Cancel' button triggered
|
||||
if (!this.props.record.data.event_ticket_id) {
|
||||
// remove product if event configuration was cancelled.
|
||||
this.props.record.update({
|
||||
[this.props.name]: undefined,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const eventConfiguration = closeInfo.eventConfiguration;
|
||||
this.props.record.update({
|
||||
'event_id': eventConfiguration.event_id,
|
||||
'event_ticket_id': eventConfiguration.event_ticket_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -1,140 +0,0 @@
|
||||
odoo.define('event.configurator.tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var FormView = require('web.FormView');
|
||||
var testUtils = require('web.test_utils');
|
||||
var createView = testUtils.createView;
|
||||
|
||||
var getArch = function (){
|
||||
return '<form>' +
|
||||
'<sheet>' +
|
||||
'<field name="sale_order_line" widget="section_and_note_one2many">' +
|
||||
'<tree editable="top"><control>' +
|
||||
'<create string="Add a product"/>' +
|
||||
'<create string="Add a section" context="{\'default_display_type\': \'line_section\'}"/>' +
|
||||
'<create string="Add a note" context="{\'default_display_type\': \'line_note\'}"/>' +
|
||||
'</control>' +
|
||||
'<field name="product_id" widget="product_configurator" />' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</sheet>' +
|
||||
'</form>';
|
||||
};
|
||||
|
||||
QUnit.module('Event Configurator', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
'product.product': {
|
||||
fields: {
|
||||
id: {type: 'integer'},
|
||||
detailed_type: {type: 'selection'},
|
||||
rent_ok: {type: 'boolean'}//sale_rental purposes
|
||||
},
|
||||
records: [{
|
||||
id: 1,
|
||||
display_name: "Customizable Event",
|
||||
detailed_type: 'event',
|
||||
rent_ok: false//sale_rental purposes
|
||||
}, {
|
||||
id: 2,
|
||||
display_name: "Desk",
|
||||
detailed_type: 'service',
|
||||
rent_ok: false//sale_rental purposes
|
||||
}]
|
||||
},
|
||||
sale_order: {
|
||||
fields: {
|
||||
id: {type: 'integer'},
|
||||
sale_order_line: {
|
||||
string: 'lines',
|
||||
type: 'one2many',
|
||||
relation: 'sale_order_line'
|
||||
},
|
||||
}
|
||||
},
|
||||
sale_order_line: {
|
||||
fields: {
|
||||
product_id: {
|
||||
string: 'product',
|
||||
type: 'many2one',
|
||||
relation: 'product.product'
|
||||
},
|
||||
event_ok: {type: 'boolean'},
|
||||
rent_ok: {type: 'boolean'},//sale_rental purposes
|
||||
event_id: {
|
||||
string: 'event',
|
||||
type: 'many2one',
|
||||
relation: 'event'
|
||||
},
|
||||
event_ticket_id: {
|
||||
string: 'event_ticket',
|
||||
type: 'many2one',
|
||||
relation: 'event_ticket'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}, function (){
|
||||
QUnit.test('Select a regular product and verify that the event configurator is not opened', async function (assert) {
|
||||
// When the module event_booth_sale is installed, it will also call the rpc: read/detailed_type when selecting a product.
|
||||
// This means that 2 asserts are done when this module is installed.
|
||||
// Note that: this test will fail if the module event_booth_sale is not installed.
|
||||
assert.expect(2);
|
||||
|
||||
var form = await createView({
|
||||
View: FormView,
|
||||
model: 'sale_order',
|
||||
data: this.data,
|
||||
arch: getArch(),
|
||||
mockRPC: function (route, params) {
|
||||
if (params.method === 'read' && params.args[1][0] === 'detailed_type') {
|
||||
assert.ok(true);
|
||||
return Promise.resolve([{detailed_type: 'service'}]);
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
intercepts: {
|
||||
do_action: function (ev) {
|
||||
if (ev.data.action === 'event_sale.event_configurator_action') {
|
||||
assert.ok(false, "Should not execute the configure action");
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
await testUtils.dom.click(form.$("a:contains('Add a product')"));
|
||||
await testUtils.fields.many2one.searchAndClickItem("product_id", {item: 'Desk'})
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('Select a configurable event and verify that the event configurator is opened', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var form = await createView({
|
||||
View: FormView,
|
||||
model: 'sale_order',
|
||||
data: this.data,
|
||||
arch: getArch(),
|
||||
mockRPC: function (route, params) {
|
||||
if (params.method === 'read' && params.args[1][0] === 'detailed_type') {
|
||||
assert.ok(true);
|
||||
return Promise.resolve([{detailed_type: 'event'}]);
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
intercepts: {
|
||||
do_action: function (ev) {
|
||||
if (ev.data.action === 'event_sale.event_configurator_action') {
|
||||
assert.ok(true);
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
await testUtils.dom.click(form.$("a:contains('Add a product')"));
|
||||
await testUtils.fields.many2one.searchAndClickItem("product_id", {item: 'Customizable Event'});
|
||||
form.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -42,24 +42,23 @@ tour.register('event_configurator_tour', {
|
||||
}, {
|
||||
trigger: '.o_event_sale_js_event_configurator_ok'
|
||||
}, {
|
||||
trigger: 'textarea[name="name"]',
|
||||
run: function () {
|
||||
var $textarea = $('textarea[name="name"]');
|
||||
if ($textarea.val().includes('Design Fair Los Angeles') && $textarea.val().includes('VIP')) {
|
||||
$textarea.addClass('tour_success');
|
||||
}
|
||||
}
|
||||
}, {
|
||||
trigger: 'textarea[name="name"].tour_success',
|
||||
trigger: "td[name='name'][data-tooltip*='VIP']",
|
||||
run: function () {} // check
|
||||
}, {
|
||||
trigger: 'ul.nav a:contains("Order Lines")',
|
||||
run: 'click'
|
||||
}, {
|
||||
content: "search the partner",
|
||||
trigger: 'div[name="partner_id"] input',
|
||||
run: 'text Azure'
|
||||
}, {
|
||||
content: "select the partner",
|
||||
trigger: 'ul.ui-autocomplete > li > a:contains(Azure)',
|
||||
}, {
|
||||
trigger: 'td:contains("Event")',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: '.o_edit_product_configuration'
|
||||
trigger: 'button.fa-pencil'
|
||||
}, {
|
||||
trigger: 'div[name="event_ticket_id"] input',
|
||||
run: 'click'
|
||||
@@ -70,17 +69,9 @@ tour.register('event_configurator_tour', {
|
||||
}, {
|
||||
trigger: '.o_event_sale_js_event_configurator_ok'
|
||||
}, {
|
||||
trigger: 'textarea[name="name"]',
|
||||
run: function () {
|
||||
var $textarea = $('textarea[name="name"]');
|
||||
if ($textarea.val().includes('Standard')) {
|
||||
$textarea.addClass('tour_success_2');
|
||||
}
|
||||
}
|
||||
}, {
|
||||
trigger: 'textarea[name="name"].tour_success_2',
|
||||
trigger: "td[name='name'][data-tooltip*='Standard']",
|
||||
run: function () {} // check
|
||||
}, ...tour.stepUtils.discardForm()
|
||||
}, ...tour.stepUtils.saveForm()
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
('date_end','>=',time.strftime('%Y-%m-%d 00:00:00')),
|
||||
'|', ('company_id', '=', False), ('company_id', '=', parent.company_id)
|
||||
]"
|
||||
attrs="{'invisible': [('event_ok', '=', False)], 'required': [('event_ok', '!=', False)]}"
|
||||
attrs="{'invisible': [('product_type', '!=', 'event')], 'required': [('product_type', '=', 'event')]}"
|
||||
options="{'no_open': True, 'no_create': True}"
|
||||
/>
|
||||
<field
|
||||
@@ -29,15 +29,13 @@
|
||||
'|', ('company_id', '=', False), ('company_id', '=', parent.company_id)
|
||||
]"
|
||||
attrs="{
|
||||
'invisible': ['|', ('event_ok', '=', False), ('event_id', '=', False)],
|
||||
'required': [('event_ok', '!=', False), ('event_id', '!=', False)],
|
||||
'invisible': ['|', ('product_type', '!=', 'event'), ('event_id', '=', False)],
|
||||
'required': [('product_type', '=', 'event'), ('event_id', '!=', False)],
|
||||
}"
|
||||
options="{'no_open': True, 'no_create': True}"
|
||||
/>
|
||||
<field name="event_ok" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='order_line']//tree//field[@name='product_template_id']" position="after">
|
||||
<field name="event_ok" invisible="1" />
|
||||
<field name="event_id" optional="hide" domain="['|', ('company_id', '=', False), ('company_id', '=', parent.company_id)]"/>
|
||||
<field name="event_ticket_id" optional="hide" domain="['|', ('company_id', '=', False), ('company_id', '=', parent.company_id)]"/>
|
||||
</xpath>
|
||||
|
||||
Reference in New Issue
Block a user