[MERGE] crm: enforce lost / restore / won flow instead of active / inactive
Leads should not be active / inactive. Purpose of this merge is to use lost / restore flow and hide all active-based filters and buttons. See separate commits for more details. This merge is linked to task ID 1880051 and closes PR #26977 .
This commit is contained in:
@@ -66,7 +66,7 @@ class Lead(models.Model):
|
||||
name = fields.Char('Opportunity', required=True, index=True)
|
||||
partner_id = fields.Many2one('res.partner', string='Customer', track_visibility='onchange', track_sequence=1, index=True,
|
||||
help="Linked partner (optional). Usually created when converting the lead.")
|
||||
active = fields.Boolean('Active', default=True)
|
||||
active = fields.Boolean('Active', default=True, track_visibility=True)
|
||||
date_action_last = fields.Datetime('Last Action', readonly=True)
|
||||
email_from = fields.Char('Email', help="Email address of the contact", track_visibility='onchange', track_sequence=4, index=True)
|
||||
website = fields.Char('Website', index=True, help="Website of the contact")
|
||||
@@ -354,14 +354,6 @@ class Lead(models.Model):
|
||||
""" Lost semantic: probability = 0, active = False """
|
||||
return self.write({'probability': 0, 'active': False})
|
||||
|
||||
@api.multi
|
||||
def action_set_active(self):
|
||||
return self.write({'active': True})
|
||||
|
||||
@api.multi
|
||||
def action_set_unactive(self):
|
||||
return self.write({'active': False})
|
||||
|
||||
@api.multi
|
||||
def action_set_won(self):
|
||||
""" Won semantic: probability = 100 (active untouched) """
|
||||
@@ -459,6 +451,14 @@ class Lead(models.Model):
|
||||
'context': {'default_type': 'opportunity'}
|
||||
}
|
||||
|
||||
def toggle_active(self):
|
||||
""" When re-activating leads and opportunities set their probability
|
||||
to the default stage one. """
|
||||
res = super(Lead, self).toggle_active()
|
||||
for lead in self.filtered(lambda lead: lead.active and lead.stage_id.probability):
|
||||
lead.probability = lead.stage_id.probability
|
||||
return res
|
||||
|
||||
# ----------------------------------------
|
||||
# Business Methods
|
||||
# ----------------------------------------
|
||||
|
||||
@@ -75,9 +75,6 @@
|
||||
<filter string="Creation Date" name="filter_create_date" date="create_date"/>
|
||||
<filter string="Expected Closing" name="filter_date_deadline" date="date_deadline"/>
|
||||
<filter string="Date Closed" name="date_closed_filter" date="date_closed"/>
|
||||
<separator/>
|
||||
<filter string="Current" name="current" domain="[('active', '=', True)]"/>
|
||||
<filter string="Archived" name="archived" domain="[('active', '=', False)]"/>
|
||||
<group expand="0" string="Extended Filters">
|
||||
<field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
|
||||
<field name="stage_id" widget="selection" domain="['|', ('team_id', '=', False), ('team_id', '=', 'team_id')]"/>
|
||||
|
||||
@@ -45,14 +45,21 @@
|
||||
<header>
|
||||
<button name="%(crm.action_crm_lead2opportunity_partner)d" string="Convert to Opportunity" type="action"
|
||||
help="Convert to Opportunity" class="oe_highlight" attrs="{'invisible': [('type', '=', 'opportunity')]}"/>
|
||||
<button name="toggle_active"
|
||||
string="Restore"
|
||||
type="object"
|
||||
attrs="{'invisible': ['|', ('probability', '>', 0), ('active', '=', True)]}"/>
|
||||
<button name="action_set_lost"
|
||||
string="Mark as Lost"
|
||||
type="object"
|
||||
attrs="{'invisible': ['&', ('probability', '=', 0), ('active', '=', False)]}"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<field name="active" invisible="1"/>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="badge badge-success float-right" attrs="{'invisible': [('probability', '<', 100)]}">Won</div>
|
||||
<div class="badge-pill badge-danger float-right" attrs="{'invisible': ['|', ('probability', '>', 0), ('active', '=', True)]}">Lost</div>
|
||||
<div class="badge-pill badge-success float-right" attrs="{'invisible': [('probability', '<', 100)]}">Won</div>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only" string="Lead"/>
|
||||
<h1><field name="name" placeholder="Describe the lead..."/></h1>
|
||||
@@ -169,7 +176,7 @@
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="priority" eval="100"/>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_kanban_mobile">
|
||||
<kanban class="o_kanban_mobile" archivable="false">
|
||||
<field name="name"/>
|
||||
<field name="contact_name"/>
|
||||
<field name="priority"/>
|
||||
@@ -306,7 +313,8 @@
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="priority" eval="1"/>
|
||||
<field name="arch" type="xml">
|
||||
<kanban default_group_by="stage_id" class="o_kanban_small_column o_opportunity_kanban" on_create="quick_create" quick_create_view="crm.quick_create_opportunity_form">
|
||||
<kanban default_group_by="stage_id" class="o_kanban_small_column o_opportunity_kanban" on_create="quick_create" quick_create_view="crm.quick_create_opportunity_form"
|
||||
archivable="false">
|
||||
<field name="stage_id" options='{"group_by_tooltip": {"requirements": "Description", "legend_priority": "Use of stars"}}'/>
|
||||
<field name="color"/>
|
||||
<field name="priority"/>
|
||||
@@ -335,8 +343,6 @@
|
||||
<div class="dropdown-menu" role="menu">
|
||||
<t t-if="widget.editable"><a role="menuitem" type="edit" class="dropdown-item">Edit</a></t>
|
||||
<t t-if="widget.deletable"><a role="menuitem" type="delete" class="dropdown-item">Delete</a></t>
|
||||
<a t-if="! record.active.value" role="menuitem" name="action_set_active" type="object" class="dropdown-item">Restore</a>
|
||||
<a t-if="record.active.value" role="menuitem" name="action_set_unactive" type="object" class="dropdown-item">Archive</a>
|
||||
<ul class="oe_kanban_colorpicker" data-field="color"/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -400,7 +406,7 @@
|
||||
<filter string="Won" name="won"
|
||||
domain="[('probability', '=', 100)]"/>
|
||||
<filter string="Lost" name="lost"
|
||||
domain="[('probability', '=', 0), ('active', '=', False)]"/>
|
||||
domain="['&', ('probability', '=', 0), ('active', '=', False)]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Salesperson" name="salesperson" context="{'group_by':'user_id'}"/>
|
||||
<filter string="Sales Channel" name="saleschannel" context="{'group_by':'team_id'}"/>
|
||||
@@ -439,12 +445,17 @@
|
||||
type="action" class="oe_highlight"
|
||||
context="{'default_lead_id': active_id}"
|
||||
attrs="{'invisible': [('active', '=', False),('probability', '<', 100)]}"/>
|
||||
<button name="toggle_active"
|
||||
string="Restore"
|
||||
type="object"
|
||||
attrs="{'invisible': ['|', ('probability', '>', 0), ('active', '=', True)]}"/>
|
||||
<field name="stage_id" widget="statusbar"
|
||||
options="{'clickable': '1', 'fold_field': 'fold'}"
|
||||
domain="['|', ('team_id', '=', team_id), ('team_id', '=', False)]"
|
||||
attrs="{'invisible': [('active', '=', False)]}"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<field name="active" invisible="1"/>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button class="oe_stat_button" type="object"
|
||||
context="{'partner_id': partner_id}"
|
||||
@@ -455,13 +466,9 @@
|
||||
<span class="o_stat_text" attrs="{'invisible': [('meeting_count', '>', 1)]}"> Meeting</span>
|
||||
</div>
|
||||
</button>
|
||||
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive"
|
||||
attrs="{'invisible': [('active', '=', True), ('probability', '<', 100)]}">
|
||||
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="badge badge-danger float-right" attrs="{'invisible': ['|', ('probability', '>', 0), ('active', '=', True)]}">Lost</div>
|
||||
<div class="badge badge-success float-right" attrs="{'invisible': [('probability', '<', 100)]}">Won</div>
|
||||
<div class="badge-pill badge-danger float-right" attrs="{'invisible': ['|', ('probability', '>', 0), ('active', '=', True)]}">Lost</div>
|
||||
<div class="badge-pill badge-success float-right" attrs="{'invisible': [('probability', '<', 100)]}">Won</div>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name" placeholder="e.g. Product Pricing"/></h1>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="inherit_id" ref="crm.crm_case_form_view_leads" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[@name='toggle_active']" position="before">
|
||||
<xpath expr="//div[@name='button_box']" position="inside">
|
||||
<button class="oe_stat_button" type="action" name="%(crm_lead_convert2task_action)d" icon="fa-bug" help="Convert to Task">
|
||||
<div class="o_field_widget o_stat_info">
|
||||
<span class="o_stat_text">Convert To</span>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[@name='action_set_won_rainbowman']" position="before">
|
||||
<button string="New Quotation" name="%(sale_action_quotations_new)d" type="action" class="oe_highlight"
|
||||
attrs="{'invisible': ['&', ('probability', '=', 0), ('active', '=', False)]}"
|
||||
context="{'search_default_partner_id': partner_id,
|
||||
'default_partner_id': partner_id,
|
||||
'default_team_id': team_id,
|
||||
@@ -16,7 +17,7 @@
|
||||
'default_origin': name,
|
||||
'default_source_id': source_id}"/>
|
||||
</xpath>
|
||||
<button name="toggle_active" position="before">
|
||||
<button name="action_schedule_meeting" position="after">
|
||||
<button class="oe_stat_button" type="action"
|
||||
name="%(sale_action_quotations)d" icon="fa-pencil-square-o"
|
||||
context="{'default_partner_id': partner_id, 'search_default_draft': 1}">
|
||||
|
||||
@@ -53,6 +53,7 @@ var KanbanColumn = Widget.extend({
|
||||
this.grouped_by_m2o = options.grouped_by_m2o;
|
||||
this.editable = options.editable;
|
||||
this.deletable = options.deletable;
|
||||
this.archivable = options.archivable;
|
||||
this.draggable = options.draggable;
|
||||
this.KanbanRecord = options.KanbanRecord || KanbanRecord; // the KanbanRecord class to use
|
||||
this.records_editable = options.records_editable;
|
||||
|
||||
@@ -61,6 +61,7 @@ var KanbanView = BasicView.extend({
|
||||
this.rendererParams.column_options = {
|
||||
editable: activeActions.group_edit,
|
||||
deletable: activeActions.group_delete,
|
||||
archivable: this.arch.attrs.archivable ? JSON.parse(this.arch.attrs.archivable) : true,
|
||||
group_creatable: activeActions.group_create && !config.device.isMobile,
|
||||
quick_create: params.isQuickCreateEnabled || this._isQuickCreateEnabled(),
|
||||
quickCreateView: this.arch.attrs.quick_create_view || null,
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<a t-if="widget.editable and widget.id" role="menuitem" class="dropdown-item o_column_edit" href="#">Edit Stage</a>
|
||||
<a t-if="widget.deletable and widget.id" role="menuitem" class="dropdown-item o_column_delete" href="#">Delete</a>
|
||||
</t>
|
||||
<t t-if="widget.has_active_field">
|
||||
<t t-if="widget.has_active_field and widget.archivable">
|
||||
<a href="#" class="dropdown-item o_column_archive_records">Archive All</a>
|
||||
<a href="#" class="dropdown-item o_column_unarchive_records">Unarchive All</a>
|
||||
</t>
|
||||
|
||||
@@ -148,7 +148,7 @@ QUnit.module('Views', {
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('basic grouped rendering with active field', function (assert) {
|
||||
QUnit.test('basic grouped rendering with active field (archivable by default)', function (assert) {
|
||||
assert.expect(9);
|
||||
|
||||
// add active field on partner model and make all records active
|
||||
@@ -193,6 +193,75 @@ QUnit.module('Views', {
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('basic grouped rendering with active field and archive enabled (archivable true)', function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
// add active field on partner model and make all records active
|
||||
this.data.partner.fields.active = {string: 'Active', type: 'char', default: true};
|
||||
|
||||
var envIDs = [1, 2, 3, 4]; // the ids that should be in the environment during this test
|
||||
var kanban = createView({
|
||||
View: KanbanView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<kanban class="o_kanban_test" archivable="true">' +
|
||||
'<field name="active"/>' +
|
||||
'<field name="bar"/>' +
|
||||
'<templates><t t-name="kanban-box">' +
|
||||
'<div><field name="foo"/></div>' +
|
||||
'</t></templates></kanban>',
|
||||
groupBy: ['bar'],
|
||||
});
|
||||
|
||||
// check archive/restore all actions in kanban header's config dropdown
|
||||
assert.ok(kanban.$('.o_kanban_header:first .o_kanban_config .o_column_archive_records').length, "should be able to archive all the records");
|
||||
assert.ok(kanban.$('.o_kanban_header:first .o_kanban_config .o_column_unarchive_records').length, "should be able to restore all the records");
|
||||
|
||||
// archive the records of the first column
|
||||
assert.strictEqual(kanban.$('.o_kanban_group:last .o_kanban_record').length, 3,
|
||||
"last column should contain 3 records");
|
||||
envIDs = [4];
|
||||
kanban.$('.o_kanban_group:last .o_column_archive_records').click(); // Click on 'Archive All'
|
||||
assert.ok($('.modal').length, 'a confirm modal should be displayed');
|
||||
$('.modal-footer .btn-secondary').click(); // Click on 'Cancel'
|
||||
assert.strictEqual(kanban.$('.o_kanban_group:last .o_kanban_record').length, 3, "still last column should contain 3 records");
|
||||
kanban.$('.o_kanban_group:last .o_column_archive_records').click();
|
||||
assert.ok($('.modal').length, 'a confirm modal should be displayed');
|
||||
$('.modal-footer .btn-primary').click(); // Click on 'Ok'
|
||||
assert.strictEqual(kanban.$('.o_kanban_group:last .o_kanban_record').length, 0, "last column should not contain any records");
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('basic grouped rendering with active field and hidden archive buttons (archivable false)', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
// add active field on partner model and make all records active
|
||||
this.data.partner.fields.active = {string: 'Active', type: 'char', default: true};
|
||||
|
||||
var envIDs = [1, 2, 3, 4]; // the ids that should be in the environment during this test
|
||||
var kanban = createView({
|
||||
View: KanbanView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<kanban class="o_kanban_test" archivable="false">' +
|
||||
'<field name="active"/>' +
|
||||
'<field name="bar"/>' +
|
||||
'<templates><t t-name="kanban-box">' +
|
||||
'<div><field name="foo"/></div>' +
|
||||
'</t></templates></kanban>',
|
||||
groupBy: ['bar'],
|
||||
});
|
||||
|
||||
// check archive/restore all actions in kanban header's config dropdown
|
||||
assert.strictEqual(
|
||||
kanban.$('.o_kanban_header:first .o_kanban_config .o_column_archive_records').length, 0,
|
||||
"should not be able to archive all the records");
|
||||
assert.strictEqual(
|
||||
kanban.$('.o_kanban_header:first .o_kanban_config .o_column_unarchive_records').length, 0,
|
||||
"should not be able to restore all the records");
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('pager should be hidden in grouped mode', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
|
||||
@@ -998,6 +998,9 @@ attributes:
|
||||
whether groups can be deleted via the context menu. Default: true.
|
||||
``group_edit``
|
||||
whether groups can be edited via the context menu. Default: true.
|
||||
``archivable``
|
||||
whether records belonging to a column can be archived / restored if an
|
||||
``active`` field is defined on the model. Default: true.
|
||||
``quick_create``
|
||||
whether it should be possible to create records without switching to the
|
||||
form view. By default, ``quick_create`` is enabled when the Kanban view is
|
||||
|
||||
Reference in New Issue
Block a user