From 0042fdf5c65a1c3e61cb2932bb4044174cebe696 Mon Sep 17 00:00:00 2001 From: "rch(OpenERP)" <> Date: Fri, 2 Jul 2010 17:56:30 +0530 Subject: [PATCH 1/5] [IMP]:purchase:improved purchase dashboard. bzr revid: apa@apa-20100702122630-vh0jz3sugn396fe8 --- addons/purchase/board_purchase_view.xml | 9 +- addons/purchase/report/purchase_report.py | 18 ++-- .../purchase/report/purchase_report_view.xml | 92 +++++++++++++++++++ 3 files changed, 105 insertions(+), 14 deletions(-) diff --git a/addons/purchase/board_purchase_view.xml b/addons/purchase/board_purchase_view.xml index b1822519cf0..10e35e0e04c 100644 --- a/addons/purchase/board_purchase_view.xml +++ b/addons/purchase/board_purchase_view.xml @@ -35,12 +35,13 @@
- - + + - - + + +
diff --git a/addons/purchase/report/purchase_report.py b/addons/purchase/report/purchase_report.py index d5688e5bd22..e85561f28b6 100644 --- a/addons/purchase/report/purchase_report.py +++ b/addons/purchase/report/purchase_report.py @@ -34,16 +34,14 @@ class purchase_report(osv.osv): 'date': fields.date('Order Date', readonly=True), 'name': fields.char('Year',size=64,required=False, readonly=True), 'day': fields.char('Day', size=128, readonly=True), - 'state': fields.selection([ - ('draft','Quotation'), - ('waiting_date','Waiting Schedule'), - ('manual','Manual in progress'), - ('progress','In progress'), - ('shipping_except','Shipping Exception'), - ('invoice_except','Invoice Exception'), - ('done','Done'), - ('cancel','Cancel') - ], 'Order State', readonly=True), + 'state': fields.selection([('draft', 'Request for Quotation'), + ('wait', 'Waiting'), + ('confirmed', 'Waiting Supplier Ack'), + ('approved', 'Approved'), + ('except_picking', 'Shipping Exception'), + ('except_invoice', 'Invoice Exception'), + ('done', 'Done'), + ('cancel', 'Cancelled')],'Order State', readonly=True), 'product_id':fields.many2one('product.product', 'Product', readonly=True), 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', readonly=True), 'location_id': fields.many2one('stock.location', 'Destination', readonly=True), diff --git a/addons/purchase/report/purchase_report_view.xml b/addons/purchase/report/purchase_report_view.xml index 6b27a0ec6df..82478da3156 100644 --- a/addons/purchase/report/purchase_report_view.xml +++ b/addons/purchase/report/purchase_report_view.xml @@ -176,6 +176,98 @@ + + purchase.by.supplier.graph + purchase.report + tree + + + + + + + + + Purchase by supplier + purchase.report + form + tree + [('state','=','done'),('date','>',(datetime.date.today()-datetime.timedelta(days=30)).strftime('%Y-%m-%d'))] + {'group_by_no_leaf':1,'group_by':['partner_id']} + + + + total.price.by.product.by.state.graph + purchase.report + tree + + + + + + + + + + Total price by product by state + purchase.report + form + tree,graph + {'group_by_no_leaf':1,'group_by':['product_id','state']} + + + + + qty.per.product.graph + purchase.report + tree + + + + + + + + + Qty. per product + purchase.report + form + tree + [('state','=','done')] + {'group_by_no_leaf':1,'group_by':['product_id']} + + + + + qty.per.product.tree + purchase.report + tree + + + + + + + + + + Purchase order per month + purchase.report + form + tree + [('date','>',time.strftime('%Y-01-01 00:00:00')),('date','<',time.strftime('%Y-12-31 23:59:59')),('state','=','done')] + {'group_by_no_leaf':1,'group_by':['month','product_id']} + + + + + Current purchases + purchase.order + form + tree,form + [('state','in',('draft','confirmed'))] + + From 0f03d60fdf6a48b1188bbcfb17fb01c17e1c420d Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 2 Jul 2010 10:57:19 +0200 Subject: [PATCH 2/5] [FIX] marketing_campaign: auto transitions are also directly processed in manual mode bzr revid: chs@openerp.com-20100702085719-isca5a7nmwa0j31i --- addons/marketing_campaign/marketing_campaign.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/addons/marketing_campaign/marketing_campaign.py b/addons/marketing_campaign/marketing_campaign.py index ad48622bd27..3261d58686b 100644 --- a/addons/marketing_campaign/marketing_campaign.py +++ b/addons/marketing_campaign/marketing_campaign.py @@ -587,13 +587,12 @@ class marketing_campaign_workitem(osv.osv): # test test_realtime manual normal (active) # time Y N N N # signal N N N N - # auto Y Y N Y + # auto Y Y Y Y # - run = False - if transition.trigger != 'signal' and campaign_mode != 'manual': - if transition.trigger == 'auto' or campaign_mode == 'test': - run = True + run = transition.trigger == 'auto' \ + or (transition.trigger == 'time' \ + and campaign_mode == 'test') if run: new_wi = self.browse(cr, uid, wi_id, context) self._process_one(cr, uid, new_wi, context) From ee23522ca871a4e039a2256d1b1020988d5dd159 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 2 Jul 2010 11:23:19 +0200 Subject: [PATCH 3/5] [IMP] marketing_campaign: workitems of a campaign on partners are linked to this partner bzr revid: chs@openerp.com-20100702092319-u94h7j64kdbjlwey --- addons/marketing_campaign/marketing_campaign.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/addons/marketing_campaign/marketing_campaign.py b/addons/marketing_campaign/marketing_campaign.py index 3261d58686b..241edc08627 100644 --- a/addons/marketing_campaign/marketing_campaign.py +++ b/addons/marketing_campaign/marketing_campaign.py @@ -84,12 +84,12 @@ class marketing_campaign(osv.osv): _columns = { 'name': fields.char('Name', size=64, required=True), - 'object_id': fields.many2one('ir.model', 'Object', required=True, - help="Choose the Object on which you want \ + 'object_id': fields.many2one('ir.model', 'Model', required=True, + help="Choose the model on which you want \ this campaign to be run"), 'partner_field_id': fields.many2one('ir.model.fields', 'Partner Field', domain="[('model_id', '=', object_id), ('ttype', '=', 'many2one'), ('relation', '=', 'res.partner')]", - help="The generated workitems will be linked to the partner related to the record"), + help="The generated workitems will be linked to the partner related to the record. If the record is the partner itself left this field empty."), 'mode':fields.selection([('test', 'Test Directly'), ('test_realtime', 'Test in Realtime'), ('manual', 'With Manual Confirmation'), @@ -265,10 +265,14 @@ class marketing_campaign_segment(osv.osv): 'state': 'todo', 'res_id': o_ids.id } + + partner = None if partner_field: partner = getattr(o_ids, partner_field) - if partner: - wi_vals['partner_id'] = partner.id + elif model_obj._name == 'res.partner': + partner = o_ids + if partner: + wi_vals['partner_id'] = partner.id for act_id in act_ids: wi_vals['activity_id'] = act_id From 23d6634faa8cf19f31c8aef7dd60f6e814535dd6 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 2 Jul 2010 15:18:49 +0200 Subject: [PATCH 4/5] [IMP] marketing_campaign: change the way that the differents activity types are processed. [FIX] marketing_campaign: deactivate the subcampaign activity type because it is not implemented at all. bzr revid: chs@openerp.com-20100702131849-961q3075l5wvrorn --- .../marketing_campaign/marketing_campaign.py | 67 ++++++++++++------- .../marketing_campaign_view.xml | 3 +- .../marketing_campaign_mailchimp.py | 21 +++--- 3 files changed, 51 insertions(+), 40 deletions(-) diff --git a/addons/marketing_campaign/marketing_campaign.py b/addons/marketing_campaign/marketing_campaign.py index 241edc08627..dbb547af410 100644 --- a/addons/marketing_campaign/marketing_campaign.py +++ b/addons/marketing_campaign/marketing_campaign.py @@ -115,6 +115,7 @@ you required for the campaign"), } def state_running_set(self, cr, uid, ids, *args): + # TODO check that all subcampaigns are running campaign = self.browse(cr, uid, ids[0]) if not campaign.activity_ids : raise osv.except_osv("Error", "There is no activitity in the campaign") @@ -135,6 +136,7 @@ you required for the campaign"), return True def state_done_set(self, cr, uid, ids, *args): + # TODO check that this campaign is not a subcampaign in running mode. segment_ids = self.pool.get('marketing.campaign.segment').search(cr, uid, [('campaign_id', 'in', ids), ('state', '=', 'running')]) @@ -144,6 +146,7 @@ you required for the campaign"), return True def state_cancel_set(self, cr, uid, ids, *args): + # TODO check that this campaign is not a subcampaign in running mode. self.write(cr, uid, ids, {'state': 'cancelled'}) return True @@ -288,8 +291,18 @@ class marketing_campaign_activity(osv.osv): _name = "marketing.campaign.activity" _description = "Campaign Activity" - _actions_type = [('email', 'E-mail'), ('paper', 'Paper'), ('action', 'Action'), - ('subcampaign', 'Sub-Campaign')] + _action_types = [ + ('email', 'E-mail'), + ('paper', 'Paper'), + ('action', 'Action'), + # TODO implement the subcampaigns. The segment_id on woritems must be + # optional (thus campaign_id must be a real many2one, and a constraint + # must be set). + # TODO implement the subcampaign out. disallow out transitions from + # subcampaign activities ? + #('subcampaign', 'Sub-Campaign'), + ] + _columns = { 'name': fields.char('Name', size=128, required=True), 'campaign_id': fields.many2one('marketing.campaign', 'Campaign', @@ -300,8 +313,7 @@ class marketing_campaign_activity(osv.osv): 'start': fields.boolean('Start',help= "This activity is launched when the campaign starts."), 'condition': fields.char('Condition', size=256, required=True, help="Python condition to know if the activity can be launched"), - 'type': fields.selection(_actions_type, - 'Type', required=True, + 'type': fields.selection(_action_types, 'Type', required=True, help="Describe type of action to be performed on the Activity.Eg : Send email,Send paper.."), 'email_template_id': fields.many2one('email.template','Email Template'), 'report_id': fields.many2one('ir.actions.report.xml', 'Reports', ), @@ -316,9 +328,8 @@ class marketing_campaign_activity(osv.osv): 'from_ids': fields.one2many('marketing.campaign.transition', 'activity_to_id', 'Previous Activities'), - 'subcampaign_id': fields.many2one('marketing.campaign', 'Sub-Campaign'), - 'subcampaign_segment_id': fields.many2one('marketing.campaign.segment', - 'Sub Campaign Segment'), + #'subcampaign_id': fields.many2one('marketing.campaign', 'Sub-Campaign', + # domain="[('object_id', '=', object_id)]"), 'variable_cost': fields.float('Variable Cost'), 'revenue': fields.float('Revenue'), 'signal': fields.char('Signal', size=128, @@ -345,14 +356,6 @@ class marketing_campaign_activity(osv.osv): ), ] - def __init__(self, *args): - # FIXME use self._process_wi_ - self._actions = {'paper' : self.process_wi_report, - 'email' : self.process_wi_email, - 'action' : self.process_wi_action, - } - return super(marketing_campaign_activity, self).__init__(*args) - def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): if context == None: @@ -367,7 +370,7 @@ class marketing_campaign_activity(osv.osv): return super(marketing_campaign_activity, self).search(cr, uid, args, offset, limit, order, context, count) - def process_wi_report(self, cr, uid, activity, workitem, context={}): + def _process_wi_paper(self, cr, uid, activity, workitem, context=None): service = netsvc.LocalService('report.%s'%activity.report_id.report_name) (report_data, format) = service.create(cr, uid, [], {}, {}) attach_vals = { @@ -382,24 +385,36 @@ class marketing_campaign_activity(osv.osv): self.pool.get('ir.attachment').create(cr, uid, attach_vals) return True - def process_wi_email(self, cr, uid, activity, workitem, context=None): + def _process_wi_email(self, cr, uid, activity, workitem, context=None): return self.pool.get('email.template').generate_mail(cr, uid, activity.email_template_id.id, [workitem.res_id], context=context) - def process_wi_action(self, cr, uid, activity, workitem, context={}): - context = {} + def _process_wi_action(self, cr, uid, activity, workitem, context=None): + if context is None: + context = {} server_obj = self.pool.get('ir.actions.server') - context['active_id'] = workitem.res_id - res = server_obj.run(cr, uid, [activity.server_action_id.id], context) - #server action return False if the action is perfomed except client_action,other and python code - return res==False and True or res - def process(self, cr, uid, act_id, wi_id, context={}): - activity = self.browse(cr, uid, act_id) + action_context = dict(context, + active_id=workitem.res_id, + active_ids=[workitem.res_id], + active_model=workitem.object_id.model) + res = server_obj.run(cr, uid, [activity.server_action_id.id], + context=action_context) + # server action return False if the action is perfomed + # 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)) + workitem_obj = self.pool.get('marketing.campaign.workitem') workitem = workitem_obj.browse(cr, uid, wi_id, context=context) - return self._actions[activity.type](cr, uid, activity, workitem, context) + return action(cr, uid, activity, workitem, context) marketing_campaign_activity() diff --git a/addons/marketing_campaign/marketing_campaign_view.xml b/addons/marketing_campaign/marketing_campaign_view.xml index ee681b9e332..4ac70292c32 100644 --- a/addons/marketing_campaign/marketing_campaign_view.xml +++ b/addons/marketing_campaign/marketing_campaign_view.xml @@ -241,10 +241,11 @@ + diff --git a/addons/marketing_campaign_mailchimp/marketing_campaign_mailchimp.py b/addons/marketing_campaign_mailchimp/marketing_campaign_mailchimp.py index f3d8386623e..566bf497fd7 100644 --- a/addons/marketing_campaign_mailchimp/marketing_campaign_mailchimp.py +++ b/addons/marketing_campaign_mailchimp/marketing_campaign_mailchimp.py @@ -22,11 +22,9 @@ import urllib import urllib2 import simplejson as json import time -from datetime import datetime - from osv import fields, osv -from marketing_campaign.marketing_campaign import _intervalTypes + mailchimp_url = 'http://%s.api.mailchimp.com/1.2' class mailchimp_account(osv.osv): @@ -150,7 +148,7 @@ class marketing_campaign_activity(osv.osv): return {} return {'value':{}} - def process_wi_mailchimp(self, cr, uid, activity, workitem, context={}): + def _process_wi_mailchimp(self, cr, uid, activity, workitem, context=None): mailchimp_account_id = activity.mailchimp_account_id.id list_name = activity.mailchimp_list mc_acc_obj = self.pool.get('mailchimp.account') @@ -193,18 +191,15 @@ class marketing_campaign_activity(osv.osv): 'merge_vars[address][city]' : model_obj.city and model_obj.city or '', 'merge_vars[phone]' : model_obj.phone and model_obj.phone or '', }) - res = mc_acc_obj.get_response(cr, uid, mailchimp_account_id, 'listSubscribe', params) - # handle mailchimp error + mc_acc_obj.get_response(cr, uid, mailchimp_account_id, 'listSubscribe', params) + # TODO handle mailchimp error return True - else : + else : return {'error_msg' : "Invalid Email Address"} - + def __init__(self, *args): - res = super(marketing_campaign_activity, self).__init__(*args) - self._actions.update({'mailchimp' : self.process_wi_mailchimp}) - self._actions_type.append(('mailchimp', 'Mailchimp')) - return res - + super(marketing_campaign_activity, self).__init__(*args) + self._action_types.append(('mailchimp', 'Mailchimp')) marketing_campaign_activity() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From b8d5278f2a6d4493154fdd01329da19d8d118520 Mon Sep 17 00:00:00 2001 From: "rpa (Open ERP)" Date: Fri, 2 Jul 2010 19:00:13 +0530 Subject: [PATCH 5/5] [IMP]: base_calendar: Improvement for trigger alarm for recurrent events + code improvement bzr revid: rpa@tinyerp.com-20100702133013-58u9hrvfauqdlqzl --- addons/base_calendar/base_calendar.py | 151 ++++++++++++++++---------- 1 file changed, 95 insertions(+), 56 deletions(-) diff --git a/addons/base_calendar/base_calendar.py b/addons/base_calendar/base_calendar.py index d7cbabc6283..4f6001a83d5 100644 --- a/addons/base_calendar/base_calendar.py +++ b/addons/base_calendar/base_calendar.py @@ -50,15 +50,17 @@ def get_recurrent_dates(rrulestring, exdate, startdate=None, exrule=None): if not startdate: startdate = datetime.now() - rset1 = rrule.rrulestr(rrulestring, dtstart=startdate, forceset=True) + if not exdate: + exdate = [] + rset1 = rrule.rrulestr(str(rrulestring), dtstart=startdate, forceset=True) for date in exdate: datetime_obj = todate(date) rset1._exdate.append(datetime_obj) if exrule: rset1.exrule(rrule.rrulestr(str(exrule), dtstart=startdate)) - re_dates = map(lambda x:x.strftime('%Y-%m-%d %H:%M:%S'), rset1._iter()) - return re_dates + + return rset1._iter() def base_calendar_id2real_id(base_calendar_id=None, with_date=False): """ @@ -791,61 +793,98 @@ class calendar_alarm(osv.osv): @param use_new_cursor: False or the dbname @param context: A standard dictionary for contextual values """ - if not context: context = {} - current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - cr.execute("select alarm.id as id \ - from calendar_alarm alarm \ - where alarm.state = %s and alarm.trigger_date <= %s", ('run', current_datetime)) - res = cr.dictfetchall() - alarm_ids = map(lambda x: x['id'], res) - #attendee_obj = self.pool.get('calendar.attendee') + current_datetime = datetime.now() request_obj = self.pool.get('res.request') + alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context) + mail_to = [] - for alarm in self.browse(cr, uid, alarm_ids): - if alarm.action == 'display': - value = { - 'name': alarm.name, - 'act_from': alarm.user_id.id, - 'act_to': alarm.user_id.id, - 'body': alarm.description, - 'trigger_date': alarm.trigger_date, - 'ref_doc1': '%s,%s' % (alarm.model_id.model, alarm.res_id) - } - request_id = request_obj.create(cr, uid, value) - request_ids = [request_id] - for attendee in alarm.attendee_ids: - if attendee.user_id: - value['act_to'] = attendee.user_id.id - request_id = request_obj.create(cr, uid, value) - request_ids.append(request_id) - request_obj.request_send(cr, uid, request_ids) - if alarm.action == 'email': - sub = '[Openobject Remainder] %s' % (alarm.name) - body = """ - Name: %s - Date: %s - Description: %s + for alarm in self.browse(cr, uid, alarm_ids, context=context): + next_trigger_date = None + update_vals = {} + model_obj = self.pool.get(alarm.model_id.model) + res_obj = model_obj.browse(cr, uid, alarm.res_id, context=context) + re_dates = [] - From: - %s - %s + if res_obj.rrule: + event_date = datetime.strptime(res_obj.date, '%Y-%m-%d %H:%M:%S') + recurrent_dates = get_recurrent_dates(res_obj.rrule, res_obj.exdate, event_date, res_obj.exrule) - """ % (alarm.name, alarm.trigger_date, alarm.description, \ - alarm.user_id.name, alarm.user_id.signature) - mail_to = [alarm.user_id.address_id.email] - for att in alarm.attendee_ids: - mail_to.append(att.user_id.address_id.email) - if mail_to: - tools.email_send( - tools.config.get('email_from', False), - mail_to, - sub, - body - ) - self.write(cr, uid, [alarm.id], {'state':'done'}) + trigger_interval = alarm.trigger_interval + if trigger_interval == 'days': + delta = timedelta(days=alarm.trigger_duration) + if trigger_interval == 'hours': + delta = timedelta(hours=alarm.trigger_duration) + if trigger_interval == 'minutes': + delta = timedelta(minutes=alarm.trigger_duration) + delta = alarm.trigger_occurs == 'after' and delta or -delta + + for rdate in recurrent_dates: + if rdate + delta <= current_datetime: + re_dates.append(rdate.strftime("%Y-%m-%d %H:%M:%S")) + rest_dates = recurrent_dates[len(re_dates):] + next_trigger_date = rest_dates and rest_dates[0] or None + + else: + re_dates = [alarm.trigger_date] + + for r_date in re_dates: + ref = alarm.model_id.model + ',' + str(alarm.res_id) + + # search for alreay sent requests + if request_obj.search(cr, uid, [('trigger_date', '=', r_date), ('ref_doc1', '=', ref)], context=context): + continue + + if alarm.action == 'display': + value = { + 'name': alarm.name, + 'act_from': alarm.user_id.id, + 'act_to': alarm.user_id.id, + 'body': alarm.description, + 'trigger_date': r_date, + 'ref_doc1': ref + } + request_id = request_obj.create(cr, uid, value) + request_ids = [request_id] + for attendee in res_obj.attendee_ids: + if attendee.user_id: + value['act_to'] = attendee.user_id.id + request_id = request_obj.create(cr, uid, value) + request_ids.append(request_id) + request_obj.request_send(cr, uid, request_ids) + + if alarm.action == 'email': + sub = '[Openobject Reminder] %s' % (alarm.name) + body = """ +Event: %s +Event Date: %s +Description: %s + +From: + %s + +---- +%s + +""" % (alarm.name, alarm.trigger_date, alarm.description, \ + alarm.user_id.name, alarm.user_id.signature) + mail_to = [alarm.user_id.address_id.email] + for att in alarm.attendee_ids: + mail_to.append(att.user_id.address_id.email) + if mail_to: + tools.email_send( + tools.config.get('email_from', False), + mail_to, + sub, + body + ) + if next_trigger_date: + update_vals.update({'trigger_date': next_trigger_date}) + else: + update_vals.update({'state': 'done'}) + self.write(cr, uid, [alarm.id], update_vals) return True calendar_alarm() @@ -1273,13 +1312,12 @@ true, it will allow you to hide the event alarm information without removing it. new_rrule_str.append(new_rule) new_rrule_str = ';'.join(new_rrule_str) rdates = get_recurrent_dates(str(new_rrule_str), exdate, start_date, data['exrule']) - for rdate in rdates: - r_date = datetime.strptime(rdate, "%Y-%m-%d %H:%M:%S") + for r_date in rdates: if start_date and r_date < start_date: continue if until_date and r_date > until_date: continue - idval = real_id2base_calendar_id(data['id'], rdate) + idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S")) result.append(idval) count += 1 if result: @@ -1413,9 +1451,10 @@ true, it will allow you to hide the event alarm information without removing it. new_ids.append(event_id) res = super(calendar_event, self).write(cr, uid, new_ids, vals, context=context) - if vals.has_key('alarm_id') or vals.has_key('base_calendar_alarm_id'): + if (vals.has_key('alarm_id') or vals.has_key('base_calendar_alarm_id'))\ + or (vals.has_key('date') or vals.has_key('duration') or vals.has_key('date_deadline')): + # change alarm details alarm_obj = self.pool.get('res.alarm') - context.update({'alarm_id': vals.get('alarm_id')}) alarm_obj.do_alarm_create(cr, uid, new_ids, self._name, 'date', \ context=context) return res