[IMP] marketing_campaign: another round of reviewing/improvements:

- better form/search views
- improved labels
- added next sync on segments
- added domain to prevent cross-campaign transitions on activities
- ...

bzr revid: odo@openerp.com-20100913004337-0cvl8qd0qaj3fomc
This commit is contained in:
Olivier Dony 2010-09-13 02:43:37 +02:00
parent 27a92be325
commit 33cb2c8c9a
3 changed files with 89 additions and 77 deletions

View File

@ -135,12 +135,12 @@ Normal - the campaign runs normally and automatically sends all emails and repor
if activity.type != 'email': if activity.type != 'email':
continue continue
if not activity.email_template_id.from_account: if not activity.email_template_id.from_account:
raise osv.except_osv(_("Error"), _("The campaign cannot be started: an email account is missing in the email activity '%s'")%activity.name) raise osv.except_osv(_("Error"), _("The campaign cannot be started: the email account is missing in email activity '%s'")%activity.name)
if activity.email_template_id.from_account.state != 'approved': if activity.email_template_id.from_account.state != 'approved':
raise osv.except_osv(_("Error"), _("The campaign cannot be started: the email account is not approved in the email activity '%s'")%activity.name) raise osv.except_osv(_("Error"), _("The campaign cannot be started: the email account is not approved in email activity '%s'")%activity.name)
if not has_start and not has_signal_without_from: if not has_start and not has_signal_without_from:
raise osv.except_osv(_("Error"), _("The campaign hasn't any starting activity nor any activity with a signal and no previous activity.")) raise osv.except_osv(_("Error"), _("The campaign cannot be started: it doesn't have any starting activity (or any activity with a signal and no previous activity)"))
return self.write(cr, uid, ids, {'state': 'running'}) return self.write(cr, uid, ids, {'state': 'running'})
@ -213,27 +213,31 @@ class marketing_campaign_segment(osv.osv):
_name = "marketing.campaign.segment" _name = "marketing.campaign.segment"
_description = "Campaign Segment" _description = "Campaign Segment"
def _get_next_sync(self, cr, uid, ids, fn, args, context=None):
# next auto sync date is same for all segments
sync_job = self.pool.get('ir.model.data').get_object(cr, uid, 'marketing_campaign', 'ir_cron_marketing_campaign_every_day', context=context)
next_sync = sync_job and sync_job.nextcall or False
return dict.fromkeys(ids, next_sync)
_columns = { _columns = {
'name': fields.char('Name', size=64,required=True), 'name': fields.char('Name', size=64,required=True),
'campaign_id': fields.many2one('marketing.campaign', 'Campaign', 'campaign_id': fields.many2one('marketing.campaign', 'Campaign', required=True, select=1, ondelete="cascade"),
required=True, select=1, ondelete="cascade"), 'object_id': fields.related('campaign_id','object_id', type='many2one', relation='ir.model', string='Resource'),
'object_id': fields.related('campaign_id','object_id', 'ir_filter_id': fields.many2one('ir.filters', 'Filter', help="Filter to select the matching resource records that belong to this segment. New filters can be created and saved using the advanced search on the list view of the Resource. If no filter is set, all records are selected without filtering. The synchronization mode may also add a criterion to the filter."),
type='many2one', relation='ir.model',
string='Object'),
'ir_filter_id': fields.many2one('ir.filters', 'Filter', help="Filter to select the matching resource records that belong to this segment. New filters can be created and saved using the advanced search on the list view of the Resource"),
'sync_last_date': fields.datetime('Last Synchronization', help="Date on which this segment was synchronized last time (automatically or manually)"), 'sync_last_date': fields.datetime('Last Synchronization', help="Date on which this segment was synchronized last time (automatically or manually)"),
'sync_mode': fields.selection([('create_date', 'If record created after last sync'), 'sync_mode': fields.selection([('create_date', 'Only records created after last sync'),
('write_date', 'If record modified after last sync (no duplicates)'), ('write_date', 'Only records modified after last sync (no duplicates)'),
('all', 'All records (no duplicates)')], ('all', 'All records (no duplicates)')],
'Workitem creation mode', 'Synchronization mode',
help="Determines how new campaign workitems are created for resource records matching this segment. This is used when segments are synchronized manually, or automatically via the scheduled job."), help="Determines an additional criterion to add to the filter when selecting new records to inject in the campaign."),
'state': fields.selection([('draft', 'Draft'), 'state': fields.selection([('draft', 'Draft'),
('running', 'Running'), ('running', 'Running'),
('done', 'Done'), ('done', 'Done'),
('cancelled', 'Cancelled')], ('cancelled', 'Cancelled')],
'State',), 'State',),
'date_run': fields.datetime('Launching Date', help="Initial start date of this segment."), 'date_run': fields.datetime('Launch Date', help="Initial start date of this segment."),
'date_done': fields.datetime('End Date', help="Date this segment was last closed or cancelled."), 'date_done': fields.datetime('End Date', help="Date this segment was last closed or cancelled."),
'date_next_sync': fields.function(_get_next_sync, method=True, string='Next Synchronization', type='datetime', help="Next time the synchronization job is scheduled to run automatically"),
} }
_defaults = { _defaults = {
@ -264,7 +268,6 @@ class marketing_campaign_segment(osv.osv):
if model_name: if model_name:
mod_name = model_name[0]['model'] mod_name = model_name[0]['model']
res['domain'] = {'ir_filter_id': [('model_id', '=', mod_name)]} res['domain'] = {'ir_filter_id': [('model_id', '=', mod_name)]}
res['context'] = {'default_model_id': model_name[0]['model']}
else: else:
res['value'] = {'ir_filter_id': False} res['value'] = {'ir_filter_id': False}
return res return res
@ -594,7 +597,7 @@ class marketing_campaign_workitem(osv.osv):
'campaign_id': fields.related('activity_id', 'campaign_id', 'campaign_id': fields.related('activity_id', 'campaign_id',
type='many2one', relation='marketing.campaign', string='Campaign', readonly=True, store=True), type='many2one', relation='marketing.campaign', string='Campaign', readonly=True, store=True),
'object_id': fields.related('activity_id', 'campaign_id', 'object_id', 'object_id': fields.related('activity_id', 'campaign_id', 'object_id',
type='many2one', relation='ir.model', string='Resource', select=1, readonly=True), type='many2one', relation='ir.model', string='Resource', select=1, readonly=True, store=True),
'res_id': fields.integer('Resource ID', select=1, readonly=True), 'res_id': fields.integer('Resource ID', select=1, readonly=True),
'res_name': fields.function(_res_name_get, method=True, string='Resource Name', fnct_search=_resource_search, type="char", size=64), 'res_name': fields.function(_res_name_get, method=True, string='Resource Name', fnct_search=_resource_search, type="char", size=64),
'date': fields.datetime('Execution Date', help='If date is not set, this workitem has to be run manually', readonly=True), 'date': fields.datetime('Execution Date', help='If date is not set, this workitem has to be run manually', readonly=True),

View File

@ -5,7 +5,7 @@
<!-- Cron --> <!-- Cron -->
<record model="ir.cron" id="ir_cron_marketing_campaign_every_hour"> <record model="ir.cron" id="ir_cron_marketing_campaign_every_hour">
<field name="name">Check Marketing Campaign Activities</field> <field name="name">Marketing Campaign: Campaign workitems processing</field>
<field name="interval_number">1</field> <field name="interval_number">1</field>
<field name="interval_type">hours</field> <field name="interval_type">hours</field>
<field name="numbercall">-1</field> <field name="numbercall">-1</field>
@ -14,9 +14,9 @@
<field name="function" eval="'process_all'"/> <field name="function" eval="'process_all'"/>
<field name="args" eval="'()'"/> <field name="args" eval="'()'"/>
</record> </record>
<record model="ir.cron" id="ir_cron_marketing_campaign_every_day"> <record model="ir.cron" id="ir_cron_marketing_campaign_every_day">
<field name="name">Check Segment</field> <field name="name">Marketing Campaign: Segment Sync</field>
<field name="interval_number">1</field> <field name="interval_number">1</field>
<field name="interval_type">days</field><!-- it s every day --> <field name="interval_type">days</field><!-- it s every day -->
<field name="numbercall">-1</field> <field name="numbercall">-1</field>
@ -25,6 +25,6 @@
<field name="function" eval="'process_segment'"/> <field name="function" eval="'process_segment'"/>
<field name="args" eval="'()'"/> <field name="args" eval="'()'"/>
</record> </record>
</data> </data>
</openerp> </openerp>

View File

@ -32,14 +32,14 @@
<field name="fixed_cost"/> <field name="fixed_cost"/>
</group> </group>
</group> </group>
<field name="activity_ids" nolabel = "1" colspan="4" default_get="{'default_object_id': object_id}" /> <field name="activity_ids" nolabel = "1" colspan="4" context="{'default_object_id': object_id, 'default_campaign_id': active_id}" />
<separator string="" colspan="4" /> <separator string="" colspan="4" />
<group col="10" colspan="4"> <group col="10" colspan="4">
<field name="state" readonly="1" /> <field name="state" readonly="1" />
<button name="state_running_set" string="Run" states="draft,done,cancelled" icon="gtk-apply"/> <button name="state_running_set" string="Run" states="draft,done,cancelled" icon="gtk-apply"/>
<button name="state_draft_set" string="Set to Draft" states="done,cancelled" icon="gtk-convert"/> <button name="state_draft_set" string="Set to Draft" states="done,cancelled" icon="gtk-convert"/>
<button name="state_done_set" string="Close" states="running" icon="terp-dialog-close"/> <button name="state_done_set" string="Close" states="running" icon="terp-dialog-close"/>
<button name="state_cancel_set" string="Cancelled" states="running" icon="terp-gtk-stop"/> <button name="state_cancel_set" string="Cancel" states="running" icon="terp-gtk-stop"/>
</group> </group>
</form> </form>
</field> </field>
@ -139,18 +139,19 @@
<separator string="Segment" colspan="4"/> <separator string="Segment" colspan="4"/>
<field name="name"/> <field name="name"/>
<field name="campaign_id" select="1" on_change="onchange_campaign_id(campaign_id)"/> <field name="campaign_id" select="1" on_change="onchange_campaign_id(campaign_id)"/>
<field name="object_id" invisible="1"/>
</group> </group>
<group colspan="2" col="2"> <group colspan="2" col="2">
<separator string="Filter" colspan="4"/> <separator string="Filter" colspan="4"/>
<field name="ir_filter_id" select="1"/> <field name="ir_filter_id" domain="[('model_id', '=', object_id)]"/>
</group> </group>
<newline/> <newline/>
<group colspan="2" col="2"> <group colspan="2" col="4">
<separator string="Synchronization" colspan="4"/> <separator string="Synchronization" colspan="4"/>
<field name="sync_mode" required="True"/> <field name="sync_mode" required="True" colspan="4"/>
<field name="sync_last_date"/> <field name="sync_last_date" colspan="3"/>
<label string="" colspan="1"/> <button string="Synchronize Manually" states="running" name="synchroniz" icon="terp-project" type="object"/>
<button string="Synchronize" states="running" name="synchroniz" icon="terp-project" type="object"/> <field name="date_next_sync" colspan="3"/>
</group> </group>
<group colspan="2" col="2"> <group colspan="2" col="2">
<separator string="History" colspan="2"/> <separator string="History" colspan="2"/>
@ -179,6 +180,7 @@
<field name="campaign_id"/> <field name="campaign_id"/>
<field name="date_run"/> <field name="date_run"/>
<field name="sync_last_date"/> <field name="sync_last_date"/>
<button string="Synchronize Manually" states="running" name="synchroniz" icon="terp-project" type="object"/>
<field name="state" /> <field name="state" />
</tree> </tree>
</field> </field>
@ -193,9 +195,9 @@
<filter icon="terp-check" string="Running" name="running" domain="[('state','=','running')]"/> <filter icon="terp-check" string="Running" name="running" domain="[('state','=','running')]"/>
<filter icon="terp-document-new" string="Draft" domain="[('state','=','draft')]"/> <filter icon="terp-document-new" string="Draft" domain="[('state','=','draft')]"/>
<separator orientation="vertical"/> <separator orientation="vertical"/>
<filter icon="terp-project" string="Newly Created" domain="[('sync_mode','=','create_date')]"/> <filter icon="terp-project" string="Newly Created" help="Sync mode: only records created after last sync" domain="[('sync_mode','=','create_date')]"/>
<filter icon="terp-project" string="Newly Modified" domain="[('sync_mode','=','write_date')]"/> <filter icon="terp-project" string="Newly Modified" help="Sync mode: only records updated after last sync" domain="[('sync_mode','=','write_date')]"/>
<filter icon="terp-project" string="All" domain="[('sync_mode','=','all')]"/> <filter icon="terp-project" string="All" help="Sync mode: all records" domain="[('sync_mode','=','all')]"/>
<separator orientation="vertical"/> <separator orientation="vertical"/>
<field name="name" select="1"/> <field name="name" select="1"/>
<field name="campaign_id" select="1"/> <field name="campaign_id" select="1"/>
@ -207,7 +209,7 @@
<separator orientation="vertical"/> <separator orientation="vertical"/>
<filter string="State" name="State" icon="terp-stock_effects-object-colorize" context="{'group_by':'state'}" /> <filter string="State" name="State" icon="terp-stock_effects-object-colorize" context="{'group_by':'state'}" />
<separator orientation="vertical"/> <separator orientation="vertical"/>
<filter string="Run Date" name="Date Run" icon="terp-go-month" context="{'group_by':'date_run'}" /> <filter string="Launch Date" name="Launch Date" icon="terp-go-month" context="{'group_by':'date_run'}" />
</group> </group>
</search> </search>
</field> </field>
@ -246,6 +248,7 @@
<field name="signal" groups="base.group_extended"/> <field name="signal" groups="base.group_extended"/>
<field name="start"/> <field name="start"/>
<field name="object_id" invisible="1"/> <field name="object_id" invisible="1"/>
<field name="campaign_id" invisible="1"/>
</group> </group>
<group colspan='2' col='2'> <group colspan='2' col='2'>
<separator string="Cost / Revenue" colspan="4"/> <separator string="Cost / Revenue" colspan="4"/>
@ -271,37 +274,35 @@
<field name="server_action_id" attrs="{'required':[('type','=','action')],'invisible':[('type','!=','action')]}" domain="[('model_id','=',object_id)]" /> <field name="server_action_id" attrs="{'required':[('type','=','action')],'invisible':[('type','!=','action')]}" domain="[('model_id','=',object_id)]" />
</group> </group>
</group> </group>
<group colspan="4" col="2"> <separator string="Transitions" colspan="4"/>
<separator string="Transitions" colspan="2"/> <field name="from_ids" nolabel="1" mode="tree,form" context="{'default_activity_to_id': active_id}" colspan="2">
<field name="from_ids" nolabel="1" mode="tree,form" default_get="{'default_activity_to_id': active_id}"> <tree string="Incoming Transitions" editable="bottom">
<tree string="Incoming Transitions" editable="bottom"> <field name="activity_from_id" domain="[('campaign_id', '=', parent.campaign_id)]"/>
<field name="activity_from_id"/> <field name='trigger'/>
<field name='trigger'/> <field name="interval_nbr"/>
<field name="interval_nbr"/> <field name="interval_type"/>
<field name="interval_type"/> </tree>
</tree> <form string="Incoming Transitions">
<form string="Incoming Transitions"> <field name="activity_from_id" domain="[('campaign_id', '=', parent.campaign_id)]"/>
<field name="activity_from_id"/> <field name='trigger'/>
<field name='trigger'/> <field name="interval_nbr"/>
<field name="interval_nbr"/> <field name="interval_type"/>
<field name="interval_type"/> </form>
</form> </field>
</field> <field name="to_ids" nolabel="1" mode="tree,form" context="{'default_activity_from_id': active_id}" colspan="2">
<field name="to_ids" nolabel="1" mode="tree,form" default_get="{'default_activity_from_id': active_id}"> <tree string="Outgoing Transitions" editable="bottom">
<tree string="Outgoing Transitions" editable="bottom"> <field name="activity_to_id" domain="[('campaign_id', '=', parent.campaign_id)]"/>
<field name="activity_to_id"/> <field name='trigger'/>
<field name='trigger'/> <field name="interval_nbr"/>
<field name="interval_nbr"/> <field name="interval_type"/>
<field name="interval_type"/> </tree>
</tree> <form string="Outgoing Transitions">
<form string="Outgoing Transitions"> <field name="activity_to_id" domain="[('campaign_id', '=', parent.campaign_id)]"/>
<field name="activity_to_id"/> <field name='trigger'/>
<field name='trigger'/> <field name="interval_nbr"/>
<field name="interval_nbr"/> <field name="interval_type"/>
<field name="interval_type"/> </form>
</form> </field>
</field>
</group>
</form> </form>
</field> </field>
</record> </record>
@ -333,14 +334,15 @@
<field name="type">tree</field> <field name="type">tree</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree colors="red:state in ('exception');black:state in ('todo');gray:state in ('cancelled')" string="Marketing Campaign Activities"> <tree colors="red:state in ('exception');black:state in ('todo');gray:state in ('cancelled')" string="Marketing Campaign Activities">
<field name="campaign_id"/>
<field name="segment_id"/> <field name="segment_id"/>
<field name="activity_id" /> <field name="activity_id" />
<field name="object_id" invisible="1"/>
<field name="res_id" invisible="1"/>
<field name="res_name" /> <field name="res_name" />
<field name="partner_id"/> <field name="partner_id"/>
<field name="date"/> <field name="date"/>
<field name="state"/> <field name="state"/>
<field name="res_id" invisible="1"/>
<field name="campaign_id" invisible="1"/>
<button string="Preview" states="todo" name="preview" icon="gtk-zoom-fit" type="object"/> <button string="Preview" states="todo" name="preview" icon="gtk-zoom-fit" type="object"/>
<button string="Process" states="todo" name="process" type="object" icon="terp-gtk-go-back-rtl"/> <button string="Process" states="todo" name="process" type="object" icon="terp-gtk-go-back-rtl"/>
<button string="Cancel" states="todo" name="button_cancel" type="object" icon="terp-gtk-stop"/> <button string="Cancel" states="todo" name="button_cancel" type="object" icon="terp-gtk-stop"/>
@ -394,21 +396,28 @@
<field name="type">search</field> <field name="type">search</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search string="Marketing Campaign Activities"> <search string="Marketing Campaign Activities">
<filter icon="terp-go-today" string="Today" name="today" domain="[('date','&lt;', time.strftime('%%Y-%%m-%%d 23:59:59')), ('date','&gt;=', time.strftime('%%Y-%%m-%%d 00:00:00'))]"/> <group colspan="4" col="10">
<filter icon="terp-gtk-go-back-rtl" string="To Do" name="todo" domain="[('state','=','todo')]"/> <filter icon="terp-go-today" string="Today" name="today" domain="[('date','&lt;', time.strftime('%%Y-%%m-%%d 23:59:59')), ('date','&gt;=', time.strftime('%%Y-%%m-%%d 00:00:00'))]"/>
<filter icon="terp-emblem-important" string="Exception" domain="[('state','=','exception')]"/> <filter icon="terp-gtk-go-back-rtl" string="To Do" name="todo" domain="[('state','=','todo')]"/>
<separator orientation="vertical"/> <filter icon="terp-emblem-important" string="Exception" domain="[('state','=','exception')]"/>
<field name="segment_id" select="1"/> <separator orientation="vertical"/>
<field name="res_name" select="1"/> <field name="campaign_id"/>
<field name="res_id" select="1"/> <field name="segment_id"/>
<field name="partner_id" select="1"/> <field name="date"/>
<field name="date" select="1"/> <newline/>
<field name="object_id"/>
<field name="res_name"/>
<field name="res_id"/>
<field name="partner_id"/>
</group>
<newline/> <newline/>
<group expand="0" string="Group By..." colspan="10" col="12"> <group expand="0" string="Group By..." colspan="10" col="12">
<filter string="Campaign" name="campaign" icon="terp-gtk-jump-to-rtl" context="{'group_by':'campaign_id'}" /> <filter string="Campaign" name="campaign" icon="terp-gtk-jump-to-rtl" context="{'group_by':'campaign_id'}" />
<filter string="Segment" name="segment" icon="terp-stock_symbol-selection" context="{'group_by':'segment_id'}" /> <filter string="Segment" name="segment" icon="terp-stock_symbol-selection" context="{'group_by':'segment_id'}" />
<filter string="Activity" name="activity" icon="terp-stock_align_left_24" context="{'group_by':'activity_id'}" /> <filter string="Activity" name="activity" icon="terp-stock_align_left_24" context="{'group_by':'activity_id'}" />
<filter string="Resource" name="resource" icon="terp-accessories-archiver" context="{'group_by':'res_id'}" /> <separator orientation="vertical"/>
<filter string="Resource" name="resource" icon="terp-accessories-archiver" context="{'group_by':'object_id'}" />
<filter string="Resource ID" name="res_id" icon="terp-accessories-archiver" context="{'group_by':'res_id'}" />
<separator orientation="vertical"/> <separator orientation="vertical"/>
<filter string="State" name="State" icon="terp-stock_effects-object-colorize" context="{'group_by':'state'}" /> <filter string="State" name="State" icon="terp-stock_effects-object-colorize" context="{'group_by':'state'}" />
<separator orientation="vertical"/> <separator orientation="vertical"/>
@ -426,7 +435,7 @@
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="view_id" ref="view_marketing_campaign_workitem_tree"/> <field name="view_id" ref="view_marketing_campaign_workitem_tree"/>
<field name="search_view_id" ref="view_marketing_campaign_workitem_search"/> <field name="search_view_id" ref="view_marketing_campaign_workitem_search"/>
<field name="context">{'group_by': [], 'search_default_todo': 1}</field> <field name="context">{'group_by': [], 'search_default_todo': 1, 'search_default_today': 1}</field>
</record> </record>
<menuitem id="menu_action_marketing_campaign_workitem" parent="menu_marketing_campaign" action="action_marketing_campaign_workitem" sequence="30"/> <menuitem id="menu_action_marketing_campaign_workitem" parent="menu_marketing_campaign" action="action_marketing_campaign_workitem" sequence="30"/>
@ -443,7 +452,7 @@
<!-- Campaign Followups --> <!-- Campaign Followups -->
<act_window domain="[('campaign_id', '=', active_id)]" <act_window domain="[('campaign_id', '=', active_id)]"
id="act_marketing_campaing_followup" id="act_marketing_campaing_followup"
name="Campaign Follow-ups" res_model="marketing.campaign.workitem" name="Campaign Follow-up" res_model="marketing.campaign.workitem"
src_model="marketing.campaign" view_mode="tree,form" src_model="marketing.campaign" view_mode="tree,form"
view_type="form" /> view_type="form" />