[IMP] Rounding should be done on move immediately to default UoM and quants should...
[odoo/odoo.git] / addons / subscription / subscription.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #    
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.     
19 #
20 ##############################################################################
21
22 # TODO:
23 #   Error treatment: exception, request, ... -> send request to user_id
24
25 import time
26 from openerp.osv import fields,osv
27 from openerp.tools.translate import _
28
29 class subscription_document(osv.osv):
30     _name = "subscription.document"
31     _description = "Subscription Document"
32     _columns = {
33         'name': fields.char('Name', required=True),
34         'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the subscription document without removing it."),
35         'model': fields.many2one('ir.model', 'Object', required=True),
36         'field_ids': fields.one2many('subscription.document.fields', 'document_id', 'Fields', copy=True)
37     }
38     _defaults = {
39         'active' : lambda *a: True,
40     }
41
42 class subscription_document_fields(osv.osv):
43     _name = "subscription.document.fields"
44     _description = "Subscription Document Fields"
45     _rec_name = 'field'
46     _columns = {
47         'field': fields.many2one('ir.model.fields', 'Field', domain="[('model_id', '=', parent.model)]", required=True),
48         'value': fields.selection([('false','False'),('date','Current Date')], 'Default Value', size=40, help="Default value is considered for field when new document is generated."),
49         'document_id': fields.many2one('subscription.document', 'Subscription Document', ondelete='cascade'),
50     }
51     _defaults = {}
52
53 def _get_document_types(self, cr, uid, context=None):
54     cr.execute('select m.model, s.name from subscription_document s, ir_model m WHERE s.model = m.id order by s.name')
55     return cr.fetchall()
56
57 class subscription_subscription(osv.osv):
58     _name = "subscription.subscription"
59     _description = "Subscription"
60     _columns = {
61         'name': fields.char('Name', required=True),
62         'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the subscription without removing it."),
63         'partner_id': fields.many2one('res.partner', 'Partner'),
64         'notes': fields.text('Internal Notes'),
65         'user_id': fields.many2one('res.users', 'User', required=True),
66         'interval_number': fields.integer('Interval Qty'),
67         'interval_type': fields.selection([('days', 'Days'), ('weeks', 'Weeks'), ('months', 'Months')], 'Interval Unit'),
68         'exec_init': fields.integer('Number of documents'),
69         'date_init': fields.datetime('First Date'),
70         'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'Status', copy=False),
71         'doc_source': fields.reference('Source Document', required=True, selection=_get_document_types, size=128, help="User can choose the source document on which he wants to create documents"),
72         'doc_lines': fields.one2many('subscription.subscription.history', 'subscription_id', 'Documents created', readonly=True),
73         'cron_id': fields.many2one('ir.cron', 'Cron Job', help="Scheduler which runs on subscription", states={'running':[('readonly',True)], 'done':[('readonly',True)]}),
74         'note': fields.text('Notes', help="Description or Summary of Subscription"),
75     }
76     _defaults = {
77         'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
78         'user_id': lambda obj,cr,uid,context: uid,
79         'active': lambda *a: True,
80         'interval_number': lambda *a: 1,
81         'interval_type': lambda *a: 'months',
82         'doc_source': lambda *a: False,
83         'state': lambda *a: 'draft'
84     }
85
86     def _auto_end(self, cr, context=None):    
87         super(subscription_subscription, self)._auto_end(cr, context=context)
88         # drop the FK from subscription to ir.cron, as it would cause deadlocks
89         # during cron job execution. When model_copy() tries to write() on the subscription,
90         # it has to wait for an ExclusiveLock on the cron job record, but the latter 
91         # is locked by the cron system for the duration of the job!
92         # FIXME: the subscription module should be reviewed to simplify the scheduling process
93         #        and to use a unique cron job for all subscriptions, so that it never needs to
94         #        be updated during its execution. 
95         cr.execute("ALTER TABLE %s DROP CONSTRAINT %s" % (self._table, '%s_cron_id_fkey' % self._table))
96
97     def set_process(self, cr, uid, ids, context=None):
98         for row in self.read(cr, uid, ids, context=context):
99             mapping = {'name':'name','interval_number':'interval_number','interval_type':'interval_type','exec_init':'numbercall','date_init':'nextcall'}
100             res = {'model':'subscription.subscription', 'args': repr([[row['id']]]), 'function':'model_copy', 'priority':6, 'user_id':row['user_id'] and row['user_id'][0]}
101             for key,value in mapping.items():
102                 res[value] = row[key]
103             id = self.pool.get('ir.cron').create(cr, uid, res)
104             self.write(cr, uid, [row['id']], {'cron_id':id, 'state':'running'})
105         return True
106
107     def model_copy(self, cr, uid, ids, context=None):
108         for row in self.read(cr, uid, ids, context=context):
109             if not row.get('cron_id',False):
110                 continue
111             cron_ids = [row['cron_id'][0]]
112             remaining = self.pool.get('ir.cron').read(cr, uid, cron_ids, ['numbercall'])[0]['numbercall']
113             try:
114                 (model_name, id) = row['doc_source'].split(',')
115                 id = int(id)
116                 model = self.pool[model_name]
117             except:
118                 raise osv.except_osv(_('Wrong Source Document!'), _('Please provide another source document.\nThis one does not exist!'))
119
120             default = {'state':'draft'}
121             doc_obj = self.pool.get('subscription.document')
122             document_ids = doc_obj.search(cr, uid, [('model.model','=',model_name)])
123             doc = doc_obj.browse(cr, uid, document_ids)[0]
124             for f in doc.field_ids:
125                 if f.value=='date':
126                     value = time.strftime('%Y-%m-%d')
127                 else:
128                     value = False
129                 default[f.field.name] = value
130
131             state = 'running'
132
133             # if there was only one remaining document to generate
134             # the subscription is over and we mark it as being done
135             if remaining == 1:
136                 state = 'done'
137             id = self.pool[model_name].copy(cr, uid, id, default, context)
138             self.pool.get('subscription.subscription.history').create(cr, uid, {'subscription_id': row['id'], 'date':time.strftime('%Y-%m-%d %H:%M:%S'), 'document_id': model_name+','+str(id)})
139             self.write(cr, uid, [row['id']], {'state':state})
140         return True
141
142     def unlink(self, cr, uid, ids, context=None):
143         for record in self.browse(cr, uid, ids, context or {}):
144             if record.state=="running":
145                 raise osv.except_osv(_('Error!'),_('You cannot delete an active subscription!'))
146         return super(subscription_subscription, self).unlink(cr, uid, ids, context)
147
148     def set_done(self, cr, uid, ids, context=None):
149         res = self.read(cr,uid, ids, ['cron_id'])
150         ids2 = [x['cron_id'][0] for x in res if x['id']]
151         self.pool.get('ir.cron').write(cr, uid, ids2, {'active':False})
152         self.write(cr, uid, ids, {'state':'done'})
153         return True
154
155     def set_draft(self, cr, uid, ids, context=None):
156         self.write(cr, uid, ids, {'state':'draft'})
157         return True
158
159 class subscription_subscription_history(osv.osv):
160     _name = "subscription.subscription.history"
161     _description = "Subscription history"
162     _rec_name = 'date'
163     _columns = {
164         'date': fields.datetime('Date'),
165         'subscription_id': fields.many2one('subscription.subscription', 'Subscription', ondelete='cascade'),
166         'document_id': fields.reference('Source Document', required=True, selection=_get_document_types, size=128),
167     }
168
169
170 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
171