2010-05-14 14:36:02 +00:00
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
2010-06-10 20:27:51 +00:00
# Copyright (C) 2004-2010 OpenERP SA (<http://openerp.com>).
2010-05-14 14:36:02 +00:00
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
2010-06-10 20:27:51 +00:00
2010-05-14 14:36:02 +00:00
import time
2010-05-21 15:50:09 +00:00
import base64
2011-03-12 00:07:05 +00:00
import itertools
2010-05-14 14:36:02 +00:00
from datetime import datetime
from dateutil . relativedelta import relativedelta
2010-06-30 14:07:34 +00:00
from operator import itemgetter
2010-07-01 16:35:49 +00:00
from traceback import format_exception
from sys import exc_info
2010-07-16 09:05:13 +00:00
from tools . safe_eval import safe_eval as eval
2010-08-18 15:55:29 +00:00
import re
2010-10-05 12:29:35 +00:00
from decimal_precision import decimal_precision as dp
2010-05-14 14:36:02 +00:00
from osv import fields , osv
2010-05-21 15:50:09 +00:00
import netsvc
2010-06-15 06:04:21 +00:00
from tools . translate import _
2010-05-14 14:36:02 +00:00
_intervalTypes = {
' hours ' : lambda interval : relativedelta ( hours = interval ) ,
' days ' : lambda interval : relativedelta ( days = interval ) ,
' months ' : lambda interval : relativedelta ( months = interval ) ,
' years ' : lambda interval : relativedelta ( years = interval ) ,
}
2010-07-01 11:53:55 +00:00
DT_FMT = ' % Y- % m- %d % H: % M: % S '
2010-06-30 14:07:34 +00:00
def dict_map ( f , d ) :
return dict ( ( k , f ( v ) ) for k , v in d . items ( ) )
def _find_fieldname ( model , field ) :
inherit_columns = dict_map ( itemgetter ( 2 ) , model . _inherit_fields )
all_columns = dict ( inherit_columns , * * model . _columns )
for fn in all_columns :
if all_columns [ fn ] is field :
return fn
raise ValueError ( ' field not found: %r ' % ( field , ) )
class selection_converter ( object ) :
""" Format the selection in the browse record objects """
def __init__ ( self , value ) :
self . _value = value
self . _str = value
def set_value ( self , cr , uid , _self_again , record , field , lang ) :
# this design is terrible
# search fieldname from the field
fieldname = _find_fieldname ( record . _table , field )
context = dict ( lang = lang . code )
fg = record . _table . fields_get ( cr , uid , [ fieldname ] , context = context )
selection = dict ( fg [ fieldname ] [ ' selection ' ] )
self . _str = selection [ self . value ]
@property
def value ( self ) :
return self . _value
def __str__ ( self ) :
return self . _str
translate_selections = {
' selection ' : selection_converter ,
}
2010-06-10 20:27:51 +00:00
class marketing_campaign ( osv . osv ) :
2010-05-14 14:36:02 +00:00
_name = " marketing.campaign "
2010-05-19 18:32:32 +00:00
_description = " Marketing Campaign "
2010-06-10 08:02:54 +00:00
2010-05-14 14:36:02 +00:00
_columns = {
2010-05-18 10:42:08 +00:00
' name ' : fields . char ( ' Name ' , size = 64 , required = True ) ,
2010-08-10 09:52:11 +00:00
' object_id ' : fields . many2one ( ' ir.model ' , ' Resource ' , required = True ,
2010-12-20 11:38:20 +00:00
help = " Choose the resource on which you want \
2010-06-10 12:55:38 +00:00
this campaign to be run " ),
2010-06-29 16:37:28 +00:00
' partner_field_id ' : fields . many2one ( ' ir.model.fields ' , ' Partner Field ' ,
domain = " [( ' model_id ' , ' = ' , object_id), ( ' ttype ' , ' = ' , ' many2one ' ), ( ' relation ' , ' = ' , ' res.partner ' )] " ,
2011-04-07 11:41:45 +00:00
help = " The generated workitems will be linked to the partner related to the record. " \
" If the record is the partner itself leave this field empty. " \
" This is useful for reporting purposes, via the Campaign Analysis or Campaign Follow-up views. " ) ,
' unique_field_id ' : fields . many2one ( ' ir.model.fields ' , ' Unique Field ' ,
domain = " [( ' model_id ' , ' = ' , object_id), ( ' ttype ' , ' in ' , [ ' char ' , ' int ' , ' many2one ' , ' text ' , ' selection ' ])] " ,
help = ' If set, this field will help segments that work in " no duplicates " mode to avoid ' \
' selecting similar records twice. Similar records are records that have the same value for ' \
' this unique field. For example by choosing the " email_from " field for CRM Leads you would prevent ' \
' sending the same campaign to the same email address again. If not set, the " no duplicates " segments ' \
" will only avoid selecting the same record again if it entered the campaign previously. " \
" Only easily comparable fields like textfields, integers, selections or single relationships may be used. " ) ,
2010-07-16 09:05:13 +00:00
' mode ' : fields . selection ( [ ( ' test ' , ' Test Directly ' ) ,
2010-06-10 20:27:51 +00:00
( ' test_realtime ' , ' Test in Realtime ' ) ,
( ' manual ' , ' With Manual Confirmation ' ) ,
( ' active ' , ' Normal ' ) ] ,
' Mode ' , required = True , help = \
2010-07-16 09:05:13 +00:00
""" Test - It creates and process all the activities directly (without waiting for the delay on transitions) but does not send emails or produce reports.
Test in Realtime - It creates and processes all the activities directly but does not send emails or produce reports .
2010-06-10 20:27:51 +00:00
With Manual Confirmation - the campaigns runs normally , but the user has to validate all workitem manually .
2010-07-16 09:05:13 +00:00
Normal - the campaign runs normally and automatically sends all emails and reports ( be very careful with this mode , you ' re live!) " " " ),
2011-09-14 13:11:11 +00:00
' state ' : fields . selection ( [ ( ' draft ' , ' New ' ) ,
2010-05-14 14:36:02 +00:00
( ' running ' , ' Running ' ) ,
( ' done ' , ' Done ' ) ,
( ' cancelled ' , ' Cancelled ' ) , ] ,
2012-05-04 11:57:48 +00:00
' Status ' , ) ,
2010-06-10 08:02:54 +00:00
' activity_ids ' : fields . one2many ( ' marketing.campaign.activity ' ,
2010-05-17 09:41:49 +00:00
' campaign_id ' , ' Activities ' ) ,
2010-10-05 12:29:35 +00:00
' fixed_cost ' : fields . float ( ' Fixed Cost ' , help = " Fixed cost for running this campaign. You may also specify variable cost and revenue on each campaign activity. Cost and Revenue statistics are included in Campaign Reporting. " , digits_compute = dp . get_precision ( ' Purchase Price ' ) ) ,
2010-05-14 14:36:02 +00:00
}
2010-08-13 12:20:05 +00:00
2010-05-21 09:53:53 +00:00
_defaults = {
' state ' : lambda * a : ' draft ' ,
2010-06-10 08:02:54 +00:00
' mode ' : lambda * a : ' test ' ,
2010-05-21 09:53:53 +00:00
}
2010-05-25 14:26:20 +00:00
2010-05-21 09:53:53 +00:00
def state_running_set ( self , cr , uid , ids , * args ) :
2010-07-02 13:18:49 +00:00
# TODO check that all subcampaigns are running
2010-05-21 09:53:53 +00:00
campaign = self . browse ( cr , uid , ids [ 0 ] )
2010-07-19 14:08:30 +00:00
if not campaign . activity_ids :
2011-12-31 07:57:20 +00:00
raise osv . except_osv ( _ ( " Error " ) , _ ( " The campaign cannot be started: there are no activities in it. " ) )
2010-07-19 14:08:30 +00:00
has_start = False
has_signal_without_from = False
for activity in campaign . activity_ids :
if activity . start :
has_start = True
if activity . signal and len ( activity . from_ids ) == 0 :
has_signal_without_from = True
if not has_start and not has_signal_without_from :
2011-12-31 07:57:20 +00:00
raise osv . except_osv ( _ ( " Error " ) , _ ( " The campaign cannot be started: it doesn ' t have any starting activity. Modify campaign ' s activities to mark one as the starting point. " ) )
2010-07-19 14:08:30 +00:00
return self . write ( cr , uid , ids , { ' state ' : ' running ' } )
2010-06-10 08:02:54 +00:00
2010-05-21 09:53:53 +00:00
def state_done_set ( self , cr , uid , ids , * args ) :
2010-07-02 13:18:49 +00:00
# TODO check that this campaign is not a subcampaign in running mode.
2010-05-21 09:53:53 +00:00
segment_ids = self . pool . get ( ' marketing.campaign.segment ' ) . search ( cr , uid ,
[ ( ' campaign_id ' , ' in ' , ids ) ,
( ' state ' , ' = ' , ' running ' ) ] )
if segment_ids :
2011-12-31 07:57:20 +00:00
raise osv . except_osv ( _ ( " Error " ) , _ ( " The campaign cannot be marked as done before all segments are closed. " ) )
2010-05-21 09:53:53 +00:00
self . write ( cr , uid , ids , { ' state ' : ' done ' } )
return True
2010-06-10 08:02:54 +00:00
2010-05-21 09:53:53 +00:00
def state_cancel_set ( self , cr , uid , ids , * args ) :
2010-07-02 13:18:49 +00:00
# TODO check that this campaign is not a subcampaign in running mode.
2010-05-21 09:53:53 +00:00
self . write ( cr , uid , ids , { ' state ' : ' cancelled ' } )
return True
2010-07-01 11:53:55 +00:00
2011-12-13 05:22:38 +00:00
# dead code
2010-08-06 09:42:02 +00:00
def signal ( self , cr , uid , model , res_id , signal , run_existing = True , context = None ) :
2010-07-14 08:42:15 +00:00
record = self . pool . get ( model ) . browse ( cr , uid , res_id , context )
2010-08-06 09:42:02 +00:00
return self . _signal ( cr , uid , record , signal , run_existing , context )
2010-07-14 08:42:15 +00:00
2011-12-13 05:22:38 +00:00
#dead code
2010-08-06 09:42:02 +00:00
def _signal ( self , cr , uid , record , signal , run_existing = True , context = None ) :
2010-07-01 11:53:55 +00:00
if not signal :
raise ValueError ( ' signal cannot be False ' )
Workitems = self . pool . get ( ' marketing.campaign.workitem ' )
2010-07-14 08:42:15 +00:00
domain = [ ( ' object_id.model ' , ' = ' , record . _table . _name ) ,
2010-07-01 11:53:55 +00:00
( ' state ' , ' = ' , ' running ' ) ]
campaign_ids = self . search ( cr , uid , domain , context = context )
2010-11-19 13:48:01 +00:00
for campaign in self . browse ( cr , uid , campaign_ids , context = context ) :
2010-07-01 11:53:55 +00:00
for activity in campaign . activity_ids :
if activity . signal != signal :
continue
2010-07-14 08:42:15 +00:00
data = dict ( activity_id = activity . id ,
res_id = record . id ,
state = ' todo ' )
wi_domain = [ ( k , ' = ' , v ) for k , v in data . items ( ) ]
2010-07-01 11:53:55 +00:00
wi_ids = Workitems . search ( cr , uid , wi_domain , context = context )
2010-08-06 09:42:02 +00:00
if wi_ids :
if not run_existing :
continue
else :
2010-07-14 08:42:15 +00:00
partner = self . _get_partner_for ( campaign , record )
if partner :
data [ ' partner_id ' ] = partner . id
wi_id = Workitems . create ( cr , uid , data , context = context )
wi_ids = [ wi_id ]
2010-07-01 11:53:55 +00:00
Workitems . process ( cr , uid , wi_ids , context = context )
2010-07-01 16:35:49 +00:00
return True
2010-07-01 11:53:55 +00:00
2010-07-14 08:42:15 +00:00
def _get_partner_for ( self , campaign , record ) :
partner_field = campaign . partner_field_id . name
if partner_field :
return getattr ( record , partner_field )
2010-07-16 09:05:13 +00:00
elif campaign . object_id . model == ' res.partner ' :
2010-07-14 08:42:15 +00:00
return record
return None
2010-07-01 11:53:55 +00:00
2010-08-20 11:43:37 +00:00
# prevent duplication until the server properly duplicates several levels of nested o2m
def copy ( self , cr , uid , id , default = None , context = None ) :
2011-12-31 07:57:20 +00:00
raise osv . except_osv ( _ ( " Operation not supported " ) , _ ( " You can not duplicate a campaign, it ' s not supported yet. " ) )
2010-08-20 11:43:37 +00:00
2011-04-07 11:41:45 +00:00
def _find_duplicate_workitems ( self , cr , uid , record , campaign_rec , context = None ) :
""" Finds possible duplicates workitems for a record in this campaign, based on a uniqueness
field .
: param record : browse_record to find duplicates workitems for .
: param campaign_rec : browse_record of campaign
"""
Workitems = self . pool . get ( ' marketing.campaign.workitem ' )
duplicate_workitem_domain = [ ( ' res_id ' , ' = ' , record . id ) ,
( ' campaign_id ' , ' = ' , campaign_rec . id ) ]
unique_field = campaign_rec . unique_field_id
if unique_field :
unique_value = getattr ( record , unique_field . name , None )
if unique_value :
if unique_field . ttype == ' many2one ' :
unique_value = unique_value . id
similar_res_ids = self . pool . get ( campaign_rec . object_id . model ) . search ( cr , uid ,
[ ( unique_field . name , ' = ' , unique_value ) ] , context = context )
if similar_res_ids :
duplicate_workitem_domain = [ ( ' res_id ' , ' in ' , similar_res_ids ) ,
( ' campaign_id ' , ' = ' , campaign_rec . id ) ]
return Workitems . search ( cr , uid , duplicate_workitem_domain , context = context )
2010-06-10 20:27:51 +00:00
marketing_campaign ( )
2010-05-14 14:36:02 +00:00
2010-06-10 20:27:51 +00:00
class marketing_campaign_segment ( osv . osv ) :
2010-05-14 14:36:02 +00:00
_name = " marketing.campaign.segment "
2010-05-19 18:32:32 +00:00
_description = " Campaign Segment "
2011-04-07 11:41:45 +00:00
_order = " name "
2010-05-14 14:36:02 +00:00
2010-09-13 00:43:37 +00:00
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 )
2010-05-14 14:36:02 +00:00
_columns = {
2010-05-18 10:42:08 +00:00
' name ' : fields . char ( ' Name ' , size = 64 , required = True ) ,
2010-09-13 00:43:37 +00:00
' campaign_id ' : fields . many2one ( ' marketing.campaign ' , ' Campaign ' , required = True , select = 1 , ondelete = " cascade " ) ,
' object_id ' : fields . related ( ' campaign_id ' , ' object_id ' , type = ' many2one ' , relation = ' ir.model ' , string = ' Resource ' ) ,
2010-09-16 15:26:24 +00:00
' ir_filter_id ' : fields . many2one ( ' ir.filters ' , ' Filter ' , ondelete = " restrict " ,
2011-04-07 11:41:45 +00:00
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. " ) ,
2010-08-25 13:31:53 +00:00
' sync_last_date ' : fields . datetime ( ' Last Synchronization ' , help = " Date on which this segment was synchronized last time (automatically or manually) " ) ,
2010-09-13 00:43:37 +00:00
' sync_mode ' : fields . selection ( [ ( ' create_date ' , ' Only records created after last sync ' ) ,
( ' write_date ' , ' Only records modified after last sync (no duplicates) ' ) ,
2010-08-18 15:55:29 +00:00
( ' all ' , ' All records (no duplicates) ' ) ] ,
2010-09-13 00:43:37 +00:00
' Synchronization mode ' ,
2011-04-07 11:41:45 +00:00
help = " Determines an additional criterion to add to the filter when selecting new records to inject in the campaign. " \
' " No duplicates " prevents selecting records which have already entered the campaign previously. ' \
' If the campaign has a " unique field " set, " no duplicates " will also prevent selecting records which have ' \
' the same value for the unique field as other records that already entered the campaign. ' ) ,
2011-09-14 13:11:11 +00:00
' state ' : fields . selection ( [ ( ' draft ' , ' New ' ) ,
2010-05-14 14:36:02 +00:00
( ' running ' , ' Running ' ) ,
( ' done ' , ' Done ' ) ,
( ' cancelled ' , ' Cancelled ' ) ] ,
2012-05-04 11:57:48 +00:00
' Status ' , ) ,
2010-09-13 00:43:37 +00:00
' date_run ' : fields . datetime ( ' Launch Date ' , help = " Initial start date of this segment. " ) ,
2010-08-25 13:31:53 +00:00
' date_done ' : fields . datetime ( ' End Date ' , help = " Date this segment was last closed or cancelled. " ) ,
2011-07-01 23:41:24 +00:00
' date_next_sync ' : fields . function ( _get_next_sync , string = ' Next Synchronization ' , type = ' datetime ' , help = " Next time the synchronization job is scheduled to run automatically " ) ,
2010-05-14 14:36:02 +00:00
}
2010-06-10 08:02:54 +00:00
2010-05-21 09:53:53 +00:00
_defaults = {
' state ' : lambda * a : ' draft ' ,
' sync_mode ' : lambda * a : ' create_date ' ,
}
2010-06-10 08:02:54 +00:00
2010-08-26 05:29:37 +00:00
def _check_model ( self , cr , uid , ids , context = None ) :
for obj in self . browse ( cr , uid , ids , context = context ) :
2010-08-26 06:09:04 +00:00
if not obj . ir_filter_id :
return True
2010-08-26 05:29:37 +00:00
if obj . campaign_id . object_id . model != obj . ir_filter_id . model_id :
return False
return True
_constraints = [
2010-10-28 11:40:19 +00:00
( _check_model , ' Model of filter must be same as resource model of Campaign ' , [ ' ir_filter_id,campaign_id ' ] ) ,
2010-08-26 05:29:37 +00:00
]
2010-08-26 06:09:04 +00:00
def onchange_campaign_id ( self , cr , uid , ids , campaign_id ) :
res = { ' domain ' : { ' ir_filter_id ' : [ ] } }
campaign_pool = self . pool . get ( ' marketing.campaign ' )
if campaign_id :
campaign = campaign_pool . browse ( cr , uid , campaign_id )
model_name = self . pool . get ( ' ir.model ' ) . read ( cr , uid , [ campaign . object_id . id ] , [ ' model ' ] )
if model_name :
mod_name = model_name [ 0 ] [ ' model ' ]
res [ ' domain ' ] = { ' ir_filter_id ' : [ ( ' model_id ' , ' = ' , mod_name ) ] }
else :
res [ ' value ' ] = { ' ir_filter_id ' : False }
return res
2010-05-21 09:53:53 +00:00
def state_running_set ( self , cr , uid , ids , * args ) :
segment = self . browse ( cr , uid , ids [ 0 ] )
2010-05-25 14:26:20 +00:00
vals = { ' state ' : ' running ' }
2010-05-21 09:53:53 +00:00
if not segment . date_run :
2010-06-10 08:02:54 +00:00
vals [ ' date_run ' ] = time . strftime ( ' % Y- % m- %d % H: % M: % S ' )
2010-05-25 14:26:20 +00:00
self . write ( cr , uid , ids , vals )
2010-05-21 09:53:53 +00:00
return True
2010-06-10 08:02:54 +00:00
2010-05-21 09:53:53 +00:00
def state_done_set ( self , cr , uid , ids , * args ) :
wi_ids = self . pool . get ( " marketing.campaign.workitem " ) . search ( cr , uid ,
2010-06-10 20:27:51 +00:00
[ ( ' state ' , ' = ' , ' todo ' ) , ( ' segment_id ' , ' in ' , ids ) ] )
self . pool . get ( " marketing.campaign.workitem " ) . write ( cr , uid , wi_ids , { ' state ' : ' cancelled ' } )
self . write ( cr , uid , ids , { ' state ' : ' done ' , ' date_done ' : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) } )
2010-05-21 09:53:53 +00:00
return True
2010-06-10 08:02:54 +00:00
2010-05-21 09:53:53 +00:00
def state_cancel_set ( self , cr , uid , ids , * args ) :
2010-06-10 20:27:51 +00:00
wi_ids = self . pool . get ( " marketing.campaign.workitem " ) . search ( cr , uid ,
[ ( ' state ' , ' = ' , ' todo ' ) , ( ' segment_id ' , ' in ' , ids ) ] )
self . pool . get ( " marketing.campaign.workitem " ) . write ( cr , uid , wi_ids , { ' state ' : ' cancelled ' } )
self . write ( cr , uid , ids , { ' state ' : ' cancelled ' , ' date_done ' : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) } )
2010-05-21 09:53:53 +00:00
return True
2010-06-10 20:27:51 +00:00
2010-06-10 12:55:38 +00:00
def synchroniz ( self , cr , uid , ids , * args ) :
2010-06-10 20:27:51 +00:00
self . process_segment ( cr , uid , ids )
2010-06-10 12:55:38 +00:00
return True
2010-06-29 16:37:28 +00:00
def process_segment ( self , cr , uid , segment_ids = None , context = None ) :
Workitems = self . pool . get ( ' marketing.campaign.workitem ' )
2011-04-07 11:41:45 +00:00
Campaigns = self . pool . get ( ' marketing.campaign ' )
2010-06-10 20:27:51 +00:00
if not segment_ids :
segment_ids = self . search ( cr , uid , [ ( ' state ' , ' = ' , ' running ' ) ] , context = context )
2010-05-21 09:53:53 +00:00
action_date = time . strftime ( ' % Y- % m- %d % H: % M: % S ' )
2010-06-29 16:37:28 +00:00
campaigns = set ( )
2010-06-10 20:27:51 +00:00
for segment in self . browse ( cr , uid , segment_ids , context = context ) :
2010-08-18 15:55:29 +00:00
if segment . campaign_id . state != ' running ' :
continue
2010-10-28 11:36:41 +00:00
2010-06-29 16:37:28 +00:00
campaigns . add ( segment . campaign_id . id )
2010-06-10 08:02:54 +00:00
act_ids = self . pool . get ( ' marketing.campaign.activity ' ) . search ( cr ,
2010-06-29 16:37:28 +00:00
uid , [ ( ' start ' , ' = ' , True ) , ( ' campaign_id ' , ' = ' , segment . campaign_id . id ) ] , context = context )
2010-06-10 20:27:51 +00:00
model_obj = self . pool . get ( segment . object_id . model )
criteria = [ ]
2010-08-18 15:55:29 +00:00
if segment . sync_last_date and segment . sync_mode != ' all ' :
2010-06-10 20:27:51 +00:00
criteria + = [ ( segment . sync_mode , ' > ' , segment . sync_last_date ) ]
if segment . ir_filter_id :
2010-06-10 20:50:45 +00:00
criteria + = eval ( segment . ir_filter_id . domain )
object_ids = model_obj . search ( cr , uid , criteria , context = context )
2010-06-10 20:27:51 +00:00
2010-07-16 09:05:13 +00:00
# XXX TODO: rewrite this loop more efficiently without doing 1 search per record!
2011-04-07 11:41:45 +00:00
for record in model_obj . browse ( cr , uid , object_ids , context = context ) :
# avoid duplicate workitem for the same resource
2010-08-18 15:55:29 +00:00
if segment . sync_mode in ( ' write_date ' , ' all ' ) :
2011-04-07 11:41:45 +00:00
if Campaigns . _find_duplicate_workitems ( cr , uid , record , segment . campaign_id , context = context ) :
2010-06-10 20:41:26 +00:00
continue
2010-06-29 16:37:28 +00:00
wi_vals = {
' segment_id ' : segment . id ,
' date ' : action_date ,
' state ' : ' todo ' ,
2011-04-07 11:41:45 +00:00
' res_id ' : record . id
2010-06-29 16:37:28 +00:00
}
2010-07-02 09:23:19 +00:00
2011-04-07 11:41:45 +00:00
partner = self . pool . get ( ' marketing.campaign ' ) . _get_partner_for ( segment . campaign_id , record )
2010-07-02 09:23:19 +00:00
if partner :
wi_vals [ ' partner_id ' ] = partner . id
2010-06-29 16:37:28 +00:00
2010-06-10 20:27:51 +00:00
for act_id in act_ids :
2010-06-29 16:37:28 +00:00
wi_vals [ ' activity_id ' ] = act_id
Workitems . create ( cr , uid , wi_vals , context = context )
self . write ( cr , uid , segment . id , { ' sync_last_date ' : action_date } , context = context )
Workitems . process_all ( cr , uid , list ( campaigns ) , context = context )
2010-05-21 09:53:53 +00:00
return True
2010-05-14 14:36:02 +00:00
2010-06-10 20:27:51 +00:00
marketing_campaign_segment ( )
2010-05-14 14:36:02 +00:00
2010-06-10 20:27:51 +00:00
class marketing_campaign_activity ( osv . osv ) :
2010-05-14 14:36:02 +00:00
_name = " marketing.campaign.activity "
2011-04-07 11:41:45 +00:00
_order = " name "
2010-05-19 18:32:32 +00:00
_description = " Campaign Activity "
2010-07-01 11:53:55 +00:00
2010-07-02 13:18:49 +00:00
_action_types = [
( ' email ' , ' E-mail ' ) ,
2010-08-25 12:12:47 +00:00
( ' report ' , ' Report ' ) ,
( ' action ' , ' Custom Action ' ) ,
2010-07-14 08:42:15 +00:00
# TODO implement the subcampaigns.
2010-07-02 13:18:49 +00:00
# TODO implement the subcampaign out. disallow out transitions from
# subcampaign activities ?
#('subcampaign', 'Sub-Campaign'),
]
2010-05-14 14:36:02 +00:00
_columns = {
2010-06-02 14:39:28 +00:00
' name ' : fields . char ( ' Name ' , size = 128 , required = True ) ,
2010-06-10 08:02:54 +00:00
' campaign_id ' : fields . many2one ( ' marketing.campaign ' , ' Campaign ' ,
2012-01-10 11:04:18 +00:00
required = True , ondelete = ' cascade ' , select = 1 ) ,
2010-05-14 14:36:02 +00:00
' object_id ' : fields . related ( ' campaign_id ' , ' object_id ' ,
type = ' many2one ' , relation = ' ir.model ' ,
2010-06-30 14:07:34 +00:00
string = ' Object ' , readonly = True ) ,
2010-07-16 09:05:13 +00:00
' start ' : fields . boolean ( ' Start ' , help = " This activity is launched when the campaign starts. " , select = True ) ,
2010-08-18 15:55:29 +00:00
' condition ' : fields . text ( ' Condition ' , size = 256 , required = True ,
help = " Python expression to decide whether the activity can be executed, otherwise it will be deleted or cancelled. "
" The expression may use the following [browsable] variables: \n "
" - activity: the campaign activity \n "
2010-10-28 11:36:41 +00:00
" - workitem: the campaign workitem \n "
2010-08-25 12:12:47 +00:00
" - resource: the resource object this campaign item represents \n "
2010-08-18 15:55:29 +00:00
" - transitions: list of campaign transitions outgoing from this activity \n "
" ...- re: Python regular expression module " ) ,
2010-07-02 13:18:49 +00:00
' type ' : fields . selection ( _action_types , ' Type ' , required = True ,
2010-08-25 12:12:47 +00:00
help = """ The type of action to execute when an item enters this activity, such as:
- Email : send an email using a predefined email template
- Report : print an existing Report defined on the resource item and save it into a specific directory
- Custom Action : execute a predefined action , e . g . to modify the fields of the resource record
""" ),
' email_template_id ' : fields . many2one ( ' email.template ' , " Email Template " , help = ' The e-mail to send when this activity is activated ' ) ,
' report_id ' : fields . many2one ( ' ir.actions.report.xml ' , " Report " , help = ' The report to generate when this activity is activated ' , ) ,
2010-05-26 08:23:28 +00:00
' report_directory_id ' : fields . many2one ( ' document.directory ' , ' Directory ' ,
2010-08-18 15:55:29 +00:00
help = " This folder is used to store the generated reports " ) ,
2010-05-26 08:23:28 +00:00
' server_action_id ' : fields . many2one ( ' ir.actions.server ' , string = ' Action ' ,
2010-08-18 15:55:29 +00:00
help = " The action to perform when this activity is activated " ) ,
2010-05-14 14:36:02 +00:00
' to_ids ' : fields . one2many ( ' marketing.campaign.transition ' ,
2010-05-25 14:26:20 +00:00
' activity_from_id ' ,
2010-05-17 09:41:49 +00:00
' Next Activities ' ) ,
' from_ids ' : fields . one2many ( ' marketing.campaign.transition ' ,
2010-05-25 14:26:20 +00:00
' activity_to_id ' ,
2010-06-10 08:02:54 +00:00
' Previous Activities ' ) ,
2010-10-05 12:29:35 +00:00
' variable_cost ' : fields . float ( ' Variable Cost ' , help = " Set a variable cost if you consider that every campaign item that has reached this point has entailed a certain cost. You can get cost statistics in the Reporting section " , digits_compute = dp . get_precision ( ' Purchase Price ' ) ) ,
' revenue ' : fields . float ( ' Revenue ' , help = " Set an expected revenue if you consider that every campaign item that has reached this point has generated a certain revenue. You can get revenue statistics in the Reporting section " , digits_compute = dp . get_precision ( ' Sale Price ' ) ) ,
2010-07-01 11:53:55 +00:00
' signal ' : fields . char ( ' Signal ' , size = 128 ,
2010-07-16 09:05:13 +00:00
help = ' An activity with a signal can be called programmatically. Be careful, the workitem is always created when a signal is sent ' ) ,
2010-08-25 12:32:08 +00:00
' keep_if_condition_not_met ' : fields . boolean ( " Don ' t delete workitems " ,
2010-07-16 09:05:13 +00:00
help = " By activating this option, workitems that aren ' t executed because the condition is not met are marked as cancelled instead of being deleted. " )
2010-06-10 20:27:51 +00:00
}
2010-05-21 09:53:53 +00:00
2010-05-25 14:26:20 +00:00
_defaults = {
' type ' : lambda * a : ' email ' ,
' condition ' : lambda * a : ' True ' ,
}
2010-07-01 11:53:55 +00:00
2010-06-10 08:02:54 +00:00
def search ( self , cr , uid , args , offset = 0 , limit = None , order = None ,
2010-05-18 10:42:08 +00:00
context = None , count = False ) :
if context == None :
context = { }
if ' segment_id ' in context and context [ ' segment_id ' ] :
segment_obj = self . pool . get ( ' marketing.campaign.segment ' ) . browse ( cr ,
uid , context [ ' segment_id ' ] )
act_ids = [ ]
for activity in segment_obj . campaign_id . activity_ids :
act_ids . append ( activity . id )
return act_ids
2010-06-10 08:02:54 +00:00
return super ( marketing_campaign_activity , self ) . search ( cr , uid , args ,
2010-05-18 10:42:08 +00:00
offset , limit , order , context , count )
2010-05-21 15:50:09 +00:00
2011-12-13 05:22:38 +00:00
#dead code
2010-08-27 13:37:49 +00:00
def _process_wi_report ( self , cr , uid , activity , workitem , context = None ) :
2010-06-10 12:55:38 +00:00
service = netsvc . LocalService ( ' report. %s ' % activity . report_id . report_name )
( report_data , format ) = service . create ( cr , uid , [ ] , { } , { } )
attach_vals = {
2010-08-13 12:20:05 +00:00
' name ' : ' %s _ %s _ %s ' % ( activity . report_id . report_name ,
activity . name , workitem . partner_id . name ) ,
' datas_fname ' : ' %s . %s ' % ( activity . report_id . report_name ,
activity . report_id . report_type ) ,
' parent_id ' : activity . report_directory_id . id ,
' datas ' : base64 . encodestring ( report_data ) ,
' file_type ' : format
}
2010-06-10 12:55:38 +00:00
self . pool . get ( ' ir.attachment ' ) . create ( cr , uid , attach_vals )
return True
2010-06-10 08:02:54 +00:00
2010-07-02 13:18:49 +00:00
def _process_wi_email ( self , cr , uid , activity , workitem , context = None ) :
2011-08-23 17:58:09 +00:00
return self . pool . get ( ' email.template ' ) . send_mail ( cr , uid ,
2010-07-01 13:19:50 +00:00
activity . email_template_id . id ,
2011-09-12 18:00:25 +00:00
workitem . res_id , context = context )
2010-06-11 07:44:19 +00:00
2011-12-13 05:22:38 +00:00
#dead code
2010-07-02 13:18:49 +00:00
def _process_wi_action ( self , cr , uid , activity , workitem , context = None ) :
if context is None :
context = { }
2010-06-10 12:55:38 +00:00
server_obj = self . pool . get ( ' ir.actions.server ' )
2010-07-02 13:18:49 +00:00
action_context = dict ( context ,
active_id = workitem . res_id ,
active_ids = [ workitem . res_id ] ,
2011-04-07 11:41:45 +00:00
active_model = workitem . object_id . model ,
workitem = workitem )
2010-07-02 13:18:49 +00:00
res = server_obj . run ( cr , uid , [ activity . server_action_id . id ] ,
context = action_context )
2011-04-07 11:41:45 +00:00
# server action return False if the action is performed
2010-07-02 13:18:49 +00:00
# except client_action, other and python code
return res == False and True or res
def process ( self , cr , uid , act_id , wi_id , context = None ) :
activity = self . browse ( cr , uid , act_id , context = context )
method = ' _process_wi_ %s ' % ( activity . type , )
action = getattr ( self , method , None )
if not action :
raise NotImplementedError ( ' method %r in not implemented on %r object ' % ( method , self ) )
2010-06-10 12:55:38 +00:00
workitem_obj = self . pool . get ( ' marketing.campaign.workitem ' )
2010-06-11 07:44:19 +00:00
workitem = workitem_obj . browse ( cr , uid , wi_id , context = context )
2010-11-19 13:48:01 +00:00
return action ( cr , uid , activity , workitem , context = context )
2010-06-10 12:55:38 +00:00
2010-06-10 20:27:51 +00:00
marketing_campaign_activity ( )
2010-05-14 14:36:02 +00:00
2010-06-10 20:27:51 +00:00
class marketing_campaign_transition ( osv . osv ) :
2010-05-14 14:36:02 +00:00
_name = " marketing.campaign.transition "
2010-05-19 18:32:32 +00:00
_description = " Campaign Transition "
2010-06-30 14:07:34 +00:00
2010-08-13 12:20:05 +00:00
_interval_units = [
( ' hours ' , ' Hour(s) ' ) , ( ' days ' , ' Day(s) ' ) ,
( ' months ' , ' Month(s) ' ) , ( ' years ' , ' Year(s) ' )
]
2010-06-30 14:07:34 +00:00
def _get_name ( self , cr , uid , ids , fn , args , context = None ) :
result = dict . fromkeys ( ids , False )
2010-07-01 11:53:55 +00:00
formatters = {
' auto ' : _ ( ' Automatic transition ' ) ,
' time ' : _ ( ' After %(interval_nbr)d %(interval_type)s ' ) ,
2010-07-14 08:42:15 +00:00
' cosmetic ' : _ ( ' Cosmetic ' ) ,
2010-07-01 11:53:55 +00:00
}
for tr in self . browse ( cr , uid , ids , context = context ,
fields_process = translate_selections ) :
result [ tr . id ] = formatters [ tr . trigger . value ] % tr
2010-06-30 14:07:34 +00:00
return result
2010-05-14 14:36:02 +00:00
2010-07-01 11:53:55 +00:00
def _delta ( self , cr , uid , ids , context = None ) :
assert len ( ids ) == 1
2010-11-19 13:48:01 +00:00
transition = self . browse ( cr , uid , ids [ 0 ] , context = context )
2010-07-01 11:53:55 +00:00
if transition . trigger != ' time ' :
raise ValueError ( ' Delta is only relevant for timed transiton ' )
2010-08-19 18:31:05 +00:00
return relativedelta ( * * { str ( transition . interval_type ) : transition . interval_nbr } )
2010-07-01 11:53:55 +00:00
2010-05-14 14:36:02 +00:00
_columns = {
2011-07-01 23:41:24 +00:00
' name ' : fields . function ( _get_name , string = ' Name ' ,
2010-06-30 14:07:34 +00:00
type = ' char ' , size = 128 ) ,
2010-05-14 14:36:02 +00:00
' activity_from_id ' : fields . many2one ( ' marketing.campaign.activity ' ,
2010-08-10 09:52:11 +00:00
' Previous Activity ' , select = 1 ,
2010-08-18 15:55:29 +00:00
required = True , ondelete = " cascade " ) ,
2010-06-10 08:02:54 +00:00
' activity_to_id ' : fields . many2one ( ' marketing.campaign.activity ' ,
2010-08-10 09:52:11 +00:00
' Next Activity ' ,
2010-08-18 15:55:29 +00:00
required = True , ondelete = " cascade " ) ,
2010-06-30 14:07:34 +00:00
' interval_nbr ' : fields . integer ( ' Interval Value ' , required = True ) ,
' interval_type ' : fields . selection ( _interval_units , ' Interval Unit ' ,
required = True ) ,
2010-07-01 11:53:55 +00:00
' trigger ' : fields . selection ( [ ( ' auto ' , ' Automatic ' ) ,
( ' time ' , ' Time ' ) ,
2010-07-14 08:42:15 +00:00
( ' cosmetic ' , ' Cosmetic ' ) , # fake plastic transition
] ,
2010-07-01 11:53:55 +00:00
' Trigger ' , required = True ,
2010-07-16 09:05:13 +00:00
help = " How is the destination workitem triggered " ) ,
2010-06-10 20:57:52 +00:00
}
2010-05-19 06:44:15 +00:00
2010-06-30 14:08:33 +00:00
_defaults = {
' interval_nbr ' : 1 ,
' interval_type ' : ' days ' ,
2010-07-01 11:53:55 +00:00
' trigger ' : ' time ' ,
2010-06-30 14:08:33 +00:00
}
2010-08-31 13:09:26 +00:00
def _check_campaign ( self , cr , uid , ids , context = None ) :
for obj in self . browse ( cr , uid , ids , context = context ) :
if obj . activity_from_id . campaign_id != obj . activity_to_id . campaign_id :
return False
return True
2010-06-30 14:08:33 +00:00
2010-08-31 13:09:26 +00:00
_constraints = [
2010-10-28 11:40:19 +00:00
( _check_campaign , ' The To/From Activity of transition must be of the same Campaign ' , [ ' activity_from_id,activity_to_id ' ] ) ,
2010-08-31 13:09:26 +00:00
]
2010-10-28 11:36:41 +00:00
2010-06-30 14:08:33 +00:00
_sql_constraints = [
( ' interval_positive ' , ' CHECK(interval_nbr >= 0) ' , ' The interval must be positive or zero ' )
]
2010-06-10 20:27:51 +00:00
marketing_campaign_transition ( )
2010-05-14 14:36:02 +00:00
2010-06-10 20:27:51 +00:00
class marketing_campaign_workitem ( osv . osv ) :
2010-05-14 14:36:02 +00:00
_name = " marketing.campaign.workitem "
2010-05-19 18:32:32 +00:00
_description = " Campaign Workitem "
2010-05-14 14:36:02 +00:00
2010-06-10 08:02:54 +00:00
def _res_name_get ( self , cr , uid , ids , field_name , arg , context = None ) :
2010-07-01 13:19:50 +00:00
res = dict . fromkeys ( ids , ' / ' )
for wi in self . browse ( cr , uid , ids , context = context ) :
if not wi . res_id :
continue
proxy = self . pool . get ( wi . object_id . model )
2011-04-07 11:41:45 +00:00
if not proxy . exists ( cr , uid , [ wi . res_id ] ) :
continue
2010-07-01 13:19:50 +00:00
ng = proxy . name_get ( cr , uid , [ wi . res_id ] , context = context )
if ng :
res [ wi . id ] = ng [ 0 ] [ 1 ]
2010-06-10 08:02:54 +00:00
return res
2010-08-26 09:35:40 +00:00
def _resource_search ( self , cr , uid , obj , name , args , domain = None , context = None ) :
""" Returns id of workitem whose resource_name matches with the given name """
if not len ( args ) :
return [ ]
2011-04-07 11:41:45 +00:00
condition_name = None
for domain_item in args :
# we only use the first domain criterion and ignore all the rest including operators
if isinstance ( domain_item , ( list , tuple ) ) and len ( domain_item ) == 3 and domain_item [ 0 ] == ' res_name ' :
condition_name = [ None , domain_item [ 1 ] , domain_item [ 2 ] ]
break
assert condition_name , " Invalid search domain for marketing_campaign_workitem.res_name. It should use ' res_name ' "
2010-08-26 09:35:40 +00:00
cr . execute ( """ select w.id, w.res_id, m.model \
from marketing_campaign_workitem w \
left join marketing_campaign_activity a on ( a . id = w . activity_id ) \
left join marketing_campaign c on ( c . id = a . campaign_id ) \
left join ir_model m on ( m . id = c . object_id )
""" )
res = cr . fetchall ( )
2011-04-07 11:41:45 +00:00
workitem_map = { }
matching_workitems = [ ]
2010-08-26 09:35:40 +00:00
for id , res_id , model in res :
2011-04-07 11:41:45 +00:00
workitem_map . setdefault ( model , { } ) . setdefault ( res_id , set ( ) ) . add ( id )
for model , id_map in workitem_map . iteritems ( ) :
2010-08-26 09:35:40 +00:00
model_pool = self . pool . get ( model )
2011-04-07 11:41:45 +00:00
condition_name [ 0 ] = model_pool . _rec_name
condition = [ ( ' id ' , ' in ' , id_map . keys ( ) ) , condition_name ]
for res_id in model_pool . search ( cr , uid , condition , context = context ) :
matching_workitems . extend ( id_map [ res_id ] )
return [ ( ' id ' , ' in ' , list ( set ( matching_workitems ) ) ) ]
2010-08-26 09:35:40 +00:00
2010-05-14 14:36:02 +00:00
_columns = {
2010-08-26 06:42:23 +00:00
' segment_id ' : fields . many2one ( ' marketing.campaign.segment ' , ' Segment ' , readonly = True ) ,
2010-05-18 10:42:08 +00:00
' activity_id ' : fields . many2one ( ' marketing.campaign.activity ' , ' Activity ' ,
2010-08-26 06:42:23 +00:00
required = True , readonly = True ) ,
2010-07-14 08:42:15 +00:00
' campaign_id ' : fields . related ( ' activity_id ' , ' campaign_id ' ,
2010-08-31 13:23:31 +00:00
type = ' many2one ' , relation = ' marketing.campaign ' , string = ' Campaign ' , readonly = True , store = True ) ,
2010-07-14 08:42:15 +00:00
' object_id ' : fields . related ( ' activity_id ' , ' campaign_id ' , ' object_id ' ,
2010-09-13 00:43:37 +00:00
type = ' many2one ' , relation = ' ir.model ' , string = ' Resource ' , select = 1 , readonly = True , store = True ) ,
2010-08-26 06:42:23 +00:00
' res_id ' : fields . integer ( ' Resource ID ' , select = 1 , readonly = True ) ,
2011-07-01 23:41:24 +00:00
' res_name ' : fields . function ( _res_name_get , string = ' Resource Name ' , fnct_search = _resource_search , type = " char " , size = 64 ) ,
2010-08-26 06:42:23 +00:00
' date ' : fields . datetime ( ' Execution Date ' , help = ' If date is not set, this workitem has to be run manually ' , readonly = True ) ,
' partner_id ' : fields . many2one ( ' res.partner ' , ' Partner ' , select = 1 , readonly = True ) ,
2010-07-01 11:53:55 +00:00
' state ' : fields . selection ( [ ( ' todo ' , ' To Do ' ) ,
2010-05-19 07:33:24 +00:00
( ' exception ' , ' Exception ' ) , ( ' done ' , ' Done ' ) ,
2012-05-04 11:57:48 +00:00
( ' cancelled ' , ' Cancelled ' ) ] , ' Status ' , readonly = True ) ,
2010-05-25 14:26:20 +00:00
2010-08-26 06:42:23 +00:00
' error_msg ' : fields . text ( ' Error Message ' , readonly = True )
2010-06-10 08:02:54 +00:00
}
2010-05-21 09:53:53 +00:00
_defaults = {
2010-06-10 08:02:54 +00:00
' state ' : lambda * a : ' todo ' ,
2010-07-01 11:53:55 +00:00
' date ' : False ,
2010-05-21 09:53:53 +00:00
}
2010-05-14 14:36:02 +00:00
2010-11-19 13:48:01 +00:00
def button_draft ( self , cr , uid , workitem_ids , context = None ) :
2010-06-11 07:51:45 +00:00
for wi in self . browse ( cr , uid , workitem_ids , context = context ) :
2010-07-16 09:05:13 +00:00
if wi . state in ( ' exception ' , ' cancelled ' ) :
2010-06-11 07:51:45 +00:00
self . write ( cr , uid , [ wi . id ] , { ' state ' : ' todo ' } , context = context )
return True
2010-11-19 13:48:01 +00:00
def button_cancel ( self , cr , uid , workitem_ids , context = None ) :
2010-06-11 07:51:45 +00:00
for wi in self . browse ( cr , uid , workitem_ids , context = context ) :
2010-06-10 08:02:54 +00:00
if wi . state in ( ' todo ' , ' exception ' ) :
self . write ( cr , uid , [ wi . id ] , { ' state ' : ' cancelled ' } , context = context )
return True
2010-06-10 20:27:51 +00:00
2010-07-01 11:53:55 +00:00
def _process_one ( self , cr , uid , workitem , context = None ) :
if workitem . state != ' todo ' :
2011-12-02 11:01:18 +00:00
return False
2010-07-01 11:53:55 +00:00
activity = workitem . activity_id
2010-07-16 09:05:13 +00:00
proxy = self . pool . get ( workitem . object_id . model )
object_id = proxy . browse ( cr , uid , workitem . res_id , context = context )
2010-07-01 11:53:55 +00:00
eval_context = {
2010-07-16 09:05:13 +00:00
' activity ' : activity ,
' workitem ' : workitem ,
' object ' : object_id ,
2010-10-28 11:36:41 +00:00
' resource ' : object_id ,
2010-08-18 15:55:29 +00:00
' transitions ' : activity . to_ids ,
' re ' : re ,
2010-07-01 11:53:55 +00:00
}
try :
condition = activity . condition
campaign_mode = workitem . campaign_id . mode
if condition :
if not eval ( condition , eval_context ) :
2010-07-14 10:14:58 +00:00
if activity . keep_if_condition_not_met :
workitem . write ( { ' state ' : ' cancelled ' } , context = context )
else :
workitem . unlink ( context = context )
2010-07-01 11:53:55 +00:00
return
result = True
if campaign_mode in ( ' manual ' , ' active ' ) :
Activities = self . pool . get ( ' marketing.campaign.activity ' )
2010-07-01 16:35:49 +00:00
result = Activities . process ( cr , uid , activity . id , workitem . id ,
2010-07-01 11:53:55 +00:00
context = context )
values = dict ( state = ' done ' )
if not workitem . date :
values [ ' date ' ] = datetime . now ( ) . strftime ( DT_FMT )
workitem . write ( values , context = context )
if result :
# process _chain
2011-11-08 11:20:23 +00:00
workitem = workitem . browse ( context = context ) [ 0 ] # reload
2010-08-17 11:27:52 +00:00
date = datetime . strptime ( workitem . date , DT_FMT )
2010-07-01 11:53:55 +00:00
for transition in activity . to_ids :
2010-07-14 08:42:15 +00:00
if transition . trigger == ' cosmetic ' :
continue
2010-07-01 11:53:55 +00:00
launch_date = False
if transition . trigger == ' auto ' :
2010-08-17 11:27:52 +00:00
launch_date = date
2010-07-01 11:53:55 +00:00
elif transition . trigger == ' time ' :
2010-08-17 11:27:52 +00:00
launch_date = date + transition . _delta ( )
2010-07-01 11:53:55 +00:00
if launch_date :
launch_date = launch_date . strftime ( DT_FMT )
values = {
' date ' : launch_date ,
' segment_id ' : workitem . segment_id . id ,
' activity_id ' : transition . activity_to_id . id ,
' partner_id ' : workitem . partner_id . id ,
' res_id ' : workitem . res_id ,
' state ' : ' todo ' ,
}
wi_id = self . create ( cr , uid , values , context = context )
2010-08-18 15:55:29 +00:00
# Now, depending on the trigger and the campaign mode
# we know whether we must run the newly created workitem.
2010-07-01 11:53:55 +00:00
#
# rows = transition trigger \ colums = campaign mode
#
# test test_realtime manual normal (active)
# time Y N N N
2010-07-14 08:42:15 +00:00
# cosmetic N N N N
2010-08-18 15:55:29 +00:00
# auto Y Y N Y
2010-08-13 12:20:05 +00:00
#
2010-07-01 11:53:55 +00:00
2010-08-18 15:55:29 +00:00
run = ( transition . trigger == ' auto ' \
and campaign_mode != ' manual ' ) \
2010-07-02 08:57:19 +00:00
or ( transition . trigger == ' time ' \
and campaign_mode == ' test ' )
2010-07-01 11:53:55 +00:00
if run :
new_wi = self . browse ( cr , uid , wi_id , context )
self . _process_one ( cr , uid , new_wi , context )
2010-07-01 16:35:49 +00:00
except Exception :
tb = " " . join ( format_exception ( * exc_info ( ) ) )
workitem . write ( { ' state ' : ' exception ' , ' error_msg ' : tb } ,
2010-07-01 11:53:55 +00:00
context = context )
def process ( self , cr , uid , workitem_ids , context = None ) :
2010-11-19 13:48:01 +00:00
for wi in self . browse ( cr , uid , workitem_ids , context = context ) :
self . _process_one ( cr , uid , wi , context = context )
2010-05-14 14:36:02 +00:00
return True
2010-06-10 08:02:54 +00:00
2010-07-01 11:53:55 +00:00
def process_all ( self , cr , uid , camp_ids = None , context = None ) :
2010-06-10 20:27:51 +00:00
camp_obj = self . pool . get ( ' marketing.campaign ' )
2010-07-01 11:53:55 +00:00
if camp_ids is None :
2010-06-10 20:57:52 +00:00
camp_ids = camp_obj . search ( cr , uid , [ ( ' state ' , ' = ' , ' running ' ) ] , context = context )
2010-06-10 20:27:51 +00:00
for camp in camp_obj . browse ( cr , uid , camp_ids , context = context ) :
2010-07-01 11:53:55 +00:00
if camp . mode == ' manual ' :
# manual states are not processed automatically
continue
2010-06-23 15:01:35 +00:00
while True :
2011-04-07 11:41:45 +00:00
domain = [ ( ' campaign_id ' , ' = ' , camp . id ) , ( ' state ' , ' = ' , ' todo ' ) , ( ' date ' , ' != ' , False ) ]
2010-07-01 11:53:55 +00:00
if camp . mode in ( ' test_realtime ' , ' active ' ) :
domain + = [ ( ' date ' , ' <= ' , time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ) ]
workitem_ids = self . search ( cr , uid , domain , context = context )
if not workitem_ids :
2010-06-23 15:01:35 +00:00
break
2010-06-10 08:02:54 +00:00
2010-11-19 13:48:01 +00:00
self . process ( cr , uid , workitem_ids , context = context )
2010-07-01 16:35:49 +00:00
return True
2010-07-01 11:53:55 +00:00
2010-11-19 13:48:01 +00:00
def preview ( self , cr , uid , ids , context = None ) :
2010-06-15 06:04:21 +00:00
res = { }
2010-11-19 13:48:01 +00:00
wi_obj = self . browse ( cr , uid , ids [ 0 ] , context = context )
2010-06-15 06:04:21 +00:00
if wi_obj . activity_id . type == ' email ' :
2011-11-11 09:41:42 +00:00
view_id = self . pool . get ( ' ir.model.data ' ) . get_object_reference ( cr , uid , ' email_template ' , ' email_template_preview_form ' )
2010-06-15 06:04:21 +00:00
res = {
' name ' : _ ( ' Email Preview ' ) ,
' view_type ' : ' form ' ,
' view_mode ' : ' form,tree ' ,
' res_model ' : ' email_template.preview ' ,
' view_id ' : False ,
' context ' : context ,
2011-11-11 09:41:42 +00:00
' views ' : [ ( view_id and view_id [ 1 ] or 0 , ' form ' ) ] ,
2010-06-15 06:04:21 +00:00
' type ' : ' ir.actions.act_window ' ,
' target ' : ' new ' ,
' nodestroy ' : True ,
2011-09-12 18:00:25 +00:00
' context ' : " { ' template_id ' : %d , ' default_res_id ' : %d } " %
2010-06-15 06:04:21 +00:00
( wi_obj . activity_id . email_template_id . id ,
wi_obj . res_id )
}
2010-08-25 12:12:47 +00:00
elif wi_obj . activity_id . type == ' report ' :
2010-08-13 12:20:05 +00:00
datas = {
' ids ' : [ wi_obj . res_id ] ,
' model ' : wi_obj . object_id . model
}
2010-07-01 13:19:50 +00:00
res = {
2010-06-15 06:04:21 +00:00
' type ' : ' ir.actions.report.xml ' ,
' report_name ' : wi_obj . activity_id . report_id . report_name ,
' datas ' : datas ,
2010-07-01 13:19:50 +00:00
}
2010-07-16 18:28:55 +00:00
else :
raise osv . except_osv ( _ ( ' No preview ' ) , _ ( ' The current step for this item has no email or report to preview. ' ) )
2010-06-15 06:04:21 +00:00
return res
2010-06-10 20:27:51 +00:00
marketing_campaign_workitem ( )
2010-05-25 14:26:20 +00:00
2010-06-02 14:39:28 +00:00
class email_template ( osv . osv ) :
_inherit = " email.template "
2010-05-25 14:26:20 +00:00
_defaults = {
2011-02-15 07:31:50 +00:00
' model_id ' : lambda obj , cr , uid , context : context . get ( ' object_id ' , False ) ,
2010-05-25 14:26:20 +00:00
}
2010-08-13 12:20:05 +00:00
2010-07-16 09:05:13 +00:00
# TODO: add constraint to prevent disabling / disapproving an email account used in a running campaign
2010-08-13 12:20:05 +00:00
2010-06-10 08:02:54 +00:00
email_template ( )
2010-07-01 13:19:50 +00:00
2010-05-25 14:26:20 +00:00
class report_xml ( osv . osv ) :
_inherit = ' ir.actions.report.xml '
def search ( self , cr , uid , args , offset = 0 , limit = None , order = None , context = None , count = False ) :
2010-07-01 13:19:50 +00:00
if context is None :
2010-05-25 14:26:20 +00:00
context = { }
2010-07-01 13:19:50 +00:00
object_id = context . get ( ' object_id ' )
if object_id :
2010-11-19 13:48:01 +00:00
model = self . pool . get ( ' ir.model ' ) . browse ( cr , uid , object_id , context = context ) . model
2010-05-26 08:23:28 +00:00
args . append ( ( ' model ' , ' = ' , model ) )
2010-05-25 14:26:20 +00:00
return super ( report_xml , self ) . search ( cr , uid , args , offset , limit , order , context , count )
2010-06-10 08:02:54 +00:00
report_xml ( )
2010-05-14 14:36:02 +00:00
2011-11-22 08:51:38 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: