diff --git a/subscription/__init__.py b/subscription/__init__.py
index 81fc81d2..0650744f 100644
--- a/subscription/__init__.py
+++ b/subscription/__init__.py
@@ -1,4 +1 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-import models
+from . import models
diff --git a/subscription/__manifest__.py b/subscription/__manifest__.py
index 030c705d..d27d55c6 100644
--- a/subscription/__manifest__.py
+++ b/subscription/__manifest__.py
@@ -1,26 +1,19 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
+# Copyright Odoo S.A. (https://www.odoo.com/)
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl)
{
'name': 'Recurring Documents',
+ 'version': '12.0.1.0.0',
'category': 'Extra Tools',
- 'description': """
-Create recurring documents.
-===========================
-
-This module allows to create new documents and add subscriptions on that document.
-
-e.g. To have an invoice generated automatically periodically:
--------------------------------------------------------------
- * Define a document type based on Invoice object
- * Define a subscription whose source document is the document defined as
- above. Specify the interval information and partner to be invoiced.
- """,
- 'depends': ['base'],
+ 'summary': 'Generate recurring invoices, sale orders, purchase orders, etc.',
+ 'license': 'LGPL-3',
+ 'author': 'Odoo SA, Odoo Community Association (OCA)',
+ 'website': 'https://github.com/OCA/server-ux',
+ 'depends': ['mail'],
'data': [
'security/ir.model.access.csv',
- 'views/subscription_view.xml'
+ 'views/subscription.xml',
],
- 'demo': ['data/subscription_demo.xml'],
+ 'demo': ['demo/subscription_demo.xml'],
+ 'installable': True,
}
diff --git a/subscription/data/subscription_demo.xml b/subscription/data/subscription_demo.xml
deleted file mode 100644
index bb302b34..00000000
--- a/subscription/data/subscription_demo.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
- Partner
-
-
-
- false
-
-
-
-
-
-
-
diff --git a/subscription/demo/subscription_demo.xml b/subscription/demo/subscription_demo.xml
new file mode 100644
index 00000000..2f3aa954
--- /dev/null
+++ b/subscription/demo/subscription_demo.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ Partner
+
+
+
+
+ false
+
+
+
+
+
+
+
+
+
diff --git a/subscription/models/__init__.py b/subscription/models/__init__.py
index f3e4a281..6bbf63b8 100644
--- a/subscription/models/__init__.py
+++ b/subscription/models/__init__.py
@@ -1,4 +1 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-import subscription
+from . import subscription
diff --git a/subscription/models/subscription.py b/subscription/models/subscription.py
index 3cf18f64..38435f90 100644
--- a/subscription/models/subscription.py
+++ b/subscription/models/subscription.py
@@ -1,5 +1,5 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
+# Copyright Odoo S.A. (https://www.odoo.com/)
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl)
# TODO:
# Error treatment: exception, request, ... -> send request to user_id
@@ -9,7 +9,8 @@ from odoo.exceptions import UserError
def _get_document_types(self):
- return [(doc.model.model, doc.name) for doc in self.env['subscription.document'].search([], order='name')]
+ return [(doc.model.model, doc.name) for doc
+ in self.env['subscription.document'].search([], order='name')]
class SubscriptionDocument(models.Model):
@@ -17,9 +18,14 @@ class SubscriptionDocument(models.Model):
_description = "Subscription Document"
name = fields.Char(required=True)
- active = fields.Boolean(help="If the active field is set to False, it will allow you to hide the subscription document without removing it.", default=True)
+ active = fields.Boolean(
+ default=True,
+ help="If the active field is set to False, it will allow you to "
+ "hide the subscription document without removing it.")
model = fields.Many2one('ir.model', string="Object", required=True)
- field_ids = fields.One2many('subscription.document.fields', 'document_id', string='Fields', copy=True)
+ field_ids = fields.One2many(
+ 'subscription.document.fields', 'document_id', string='Fields',
+ copy=True)
class SubscriptionDocumentFields(models.Model):
@@ -27,44 +33,96 @@ class SubscriptionDocumentFields(models.Model):
_description = "Subscription Document Fields"
_rec_name = 'field'
- field = fields.Many2one('ir.model.fields', domain="[('model_id', '=', parent.model)]", required=True)
- value = fields.Selection([('false', 'False'), ('date', 'Current Date')], string='Default Value', help="Default value is considered for field when new document is generated.")
- document_id = fields.Many2one('subscription.document', string='Subscription Document', ondelete='cascade')
+ field = fields.Many2one(
+ 'ir.model.fields', domain="[('model_id', '=', parent.model)]",
+ required=True)
+ value = fields.Selection([
+ ('false', 'False'),
+ ('date', 'Current Date')],
+ string='Default Value',
+ help="Default value is considered for field when new document "
+ "is generated.")
+ document_id = fields.Many2one(
+ 'subscription.document', string='Subscription Document',
+ ondelete='cascade')
class Subscription(models.Model):
_name = "subscription.subscription"
_description = "Subscription"
-
- name = fields.Char(required=True)
- active = fields.Boolean(help="If the active field is set to False, it will allow you to hide the subscription without removing it.", default=True)
- partner_id = fields.Many2one('res.partner', string='Partner')
+ _inherit = ['mail.thread', 'mail.activity.mixin']
+
+ name = fields.Char(required=True, track_visibility='onchange')
+ active = fields.Boolean(
+ default=True, track_visibility='onchange',
+ states={'running': [('readonly', True)]},
+ help="If the active field is set to False, it will allow you to hide "
+ "the subscription without removing it.")
+ partner_id = fields.Many2one(
+ 'res.partner', string='Partner', ondelete='restrict',
+ track_visibility='onchange')
notes = fields.Text(string='Internal Notes')
- user_id = fields.Many2one('res.users', string='User', required=True, default=lambda self: self.env.user)
- interval_number = fields.Integer(string='Internal Qty', default=1)
- interval_type = fields.Selection([('days', 'Days'), ('weeks', 'Weeks'), ('months', 'Months')], string='Interval Unit', default='months')
- exec_init = fields.Integer(string='Number of Documents')
- date_init = fields.Datetime(string='First Date', default=fields.Datetime.now)
- state = fields.Selection([('draft', 'Draft'), ('running', 'Running'), ('done', 'Done')], string='Status', copy=False, default='draft')
- doc_source = fields.Reference(selection=_get_document_types, string='Source Document', required=True, help="User can choose the source document on which he wants to create documents")
- doc_lines = fields.One2many('subscription.subscription.history', 'subscription_id', string='Documents created', readonly=True)
- cron_id = fields.Many2one('ir.cron', string='Cron Job', help="Scheduler which runs on subscription", states={'running': [('readonly', True)], 'done': [('readonly', True)]})
- note = fields.Text(string='Notes', help="Description or Summary of Subscription")
+ user_id = fields.Many2one(
+ 'res.users', string='User', required=True,
+ default=lambda self: self.env.user, track_visibility='onchange')
+ interval_number = fields.Integer(
+ readonly=True, states={'draft': [('readonly', False)]},
+ string='Internal Qty', default=1, track_visibility='onchange')
+ interval_type = fields.Selection([
+ ('days', 'Days'),
+ ('weeks', 'Weeks'),
+ ('months', 'Months')],
+ string='Interval Unit', default='months', track_visibility='onchange',
+ readonly=True, states={'draft': [('readonly', False)]})
+ exec_init = fields.Integer(
+ string='Number of Documents', track_visibility='onchange',
+ default=-1)
+ date_init = fields.Datetime(
+ string='First Date', default=fields.Datetime.now,
+ track_visibility='onchange')
+ state = fields.Selection([
+ ('draft', 'Draft'),
+ ('running', 'Running'),
+ ('done', 'Done')],
+ string='Status', copy=False, default='draft',
+ track_visibility='onchange')
+ doc_source = fields.Reference(
+ selection=_get_document_types, string='Source Document',
+ required=True,
+ help="User can choose the source document on which he wants to create "
+ "documents")
+ doc_lines = fields.One2many(
+ 'subscription.subscription.history', 'subscription_id',
+ string='Documents created', readonly=True)
+ cron_id = fields.Many2one(
+ 'ir.cron', string='Cron Job',
+ help="Scheduler which runs on subscription",
+ states={'running': [('readonly', True)], 'done': [('readonly', True)]})
+ nextcall = fields.Datetime(
+ related='cron_id.nextcall',
+ help='Next call of the Odoo scheduler for this subscription.')
@api.model
def _auto_end(self):
super(Subscription, self)._auto_end()
- # drop the FK from subscription to ir.cron, as it would cause deadlocks
- # during cron job execution. When model_copy() tries to write() on the subscription,
- # it has to wait for an ExclusiveLock on the cron job record, but the latter
- # is locked by the cron system for the duration of the job!
- # FIXME: the subscription module should be reviewed to simplify the scheduling process
- # and to use a unique cron job for all subscriptions, so that it never needs to
- # be updated during its execution.
- self.env.cr.execute("ALTER TABLE %s DROP CONSTRAINT %s" % (self._table, '%s_cron_id_fkey' % self._table))
-
- @api.multi
+ # drop the FK from subscription to ir.cron, as it would cause
+ # deadlocks during cron job execution. When model_copy()
+ # tries to write() on the subscription, it has to wait for an
+ # ExclusiveLock on the cron job record, but the latter is locked
+ # by the cron system for the duration of the job!
+
+ # FIXME: the subscription module should be reviewed to simplify the
+ # scheduling process and to use a unique cron job for all
+ # subscriptions, so that it never needs to be updated during
+ # its execution.
+ self.env.cr.execute(
+ "ALTER TABLE %s DROP CONSTRAINT %s" % (
+ self._table, '%s_cron_id_fkey' % self._table))
+
def set_process(self):
+ sub_model = self.env['ir.model'].search([('model', '=', self._name)])
+ assert len(sub_model) == 1
+
for subscription in self:
cron_data = {
'name': subscription.name,
@@ -72,9 +130,9 @@ class Subscription(models.Model):
'interval_type': subscription.interval_type,
'numbercall': subscription.exec_init,
'nextcall': subscription.date_init,
- 'model': self._name,
- 'args': repr([[subscription.id]]),
- 'function': '_cron_model_copy',
+ 'model_id': sub_model.id,
+ 'state': 'code',
+ 'code': 'model._cron_model_copy(%d)' % subscription.id,
'priority': 6,
'user_id': subscription.user_id.id
}
@@ -85,16 +143,19 @@ class Subscription(models.Model):
def _cron_model_copy(self, ids):
self.browse(ids).model_copy()
- @api.multi
def model_copy(self):
for subscription in self.filtered(lambda sub: sub.cron_id):
if not subscription.doc_source.exists():
- raise UserError(_('Please provide another source document.\nThis one does not exist!'))
+ raise UserError(_(
+ 'Please provide another source document.\n'
+ 'This one does not exist!'))
default = {'state': 'draft'}
- documents = self.env['subscription.document'].search([('model.model', '=', subscription.doc_source._name)], limit=1)
- fieldnames = dict((f.field.name, f.value == 'date' and fields.Date.today() or False)
- for f in documents.field_ids)
+ documents = self.env['subscription.document'].search(
+ [('model.model', '=', subscription.doc_source._name)], limit=1)
+ fieldnames = dict(
+ (f.field.name, f.value == 'date' and fields.Date.today() or
+ False) for f in documents.field_ids)
default.update(fieldnames)
# if there was only one remaining document to generate
@@ -107,20 +168,18 @@ class Subscription(models.Model):
self.env['subscription.subscription.history'].create({
'subscription_id': subscription.id,
'date': fields.Datetime.now(),
- 'document_id': '%s,%s' % (subscription.doc_source._name, copied_doc.id)})
+ 'document_id': '%s,%s' % (subscription.doc_source._name,
+ copied_doc.id)})
- @api.multi
def unlink(self):
if any(self.filtered(lambda s: s.state == "running")):
raise UserError(_('You cannot delete an active subscription!'))
return super(Subscription, self).unlink()
- @api.multi
def set_done(self):
self.mapped('cron_id').write({'active': False})
self.write({'state': 'done'})
- @api.multi
def set_draft(self):
self.write({'state': 'draft'})
@@ -131,5 +190,7 @@ class SubscriptionHistory(models.Model):
_rec_name = 'date'
date = fields.Datetime()
- subscription_id = fields.Many2one('subscription.subscription', string='Subscription', ondelete='cascade')
- document_id = fields.Reference(selection=_get_document_types, string='Source Document', required=True)
+ subscription_id = fields.Many2one(
+ 'subscription.subscription', string='Subscription', ondelete='cascade')
+ document_id = fields.Reference(
+ selection=_get_document_types, string='Source Document', required=True)
diff --git a/subscription/readme/CONTRIBUTORS.rst b/subscription/readme/CONTRIBUTORS.rst
new file mode 100644
index 00000000..7a988bec
--- /dev/null
+++ b/subscription/readme/CONTRIBUTORS.rst
@@ -0,0 +1,2 @@
+* Odoo S.A. (https://www.odoo.com/)
+* Alexis de Lattre
diff --git a/subscription/readme/DESCRIPTION.rst b/subscription/readme/DESCRIPTION.rst
new file mode 100644
index 00000000..35ab7b2c
--- /dev/null
+++ b/subscription/readme/DESCRIPTION.rst
@@ -0,0 +1,3 @@
+This module allows you to automate the periodic duplication of a document (sale order, purchase order, invoice or any other document). It is useful to generate recurring invoices, recurring orders, etc. Note that you can also use the *contract* module from `the OCA contract project `_ to manage recurring invoices; the *contract* module has many more features that this *subscription* module, but the *subscription* module can generate any kind of documents, not only invoices.
+
+This module was part of the official addons up to Odoo Community v10 and was removed from Odoo Community in v11. This is a port of the module from Odoo Community v10.
diff --git a/subscription/readme/USAGE.rst b/subscription/readme/USAGE.rst
new file mode 100644
index 00000000..755cd9c5
--- /dev/null
+++ b/subscription/readme/USAGE.rst
@@ -0,0 +1,4 @@
+For example, to automate the regular creation of an invoice:
+
+* Go to the menu *Settings > Technical > Automation > Recurring Types* and define a new document type based on the *Invoice* object.
+* Go to the menu *Settings > Technical > Automation > Recurring Documents* and create a subscriptionlinked to the new document type and set the parameters of the subscription (frequency, number of documents, etc.). Then click on the *Process* button: Odoo will generate a new scheduled action (ir.cron) linked to that subscription. If you want to stop this subscription before its end or if you have set an unlimited number of documents and you want to stop or suspend it, click on the *Stop* button: Odoo will disable the related automated action.
diff --git a/subscription/security/ir.model.access.csv b/subscription/security/ir.model.access.csv
index a40962b4..988b6a98 100644
--- a/subscription/security/ir.model.access.csv
+++ b/subscription/security/ir.model.access.csv
@@ -3,5 +3,4 @@ access_subscription_document_fields,subscription.document.fields,model_subscript
access_subscription_subscription_user,subscription.subscription user,model_subscription_subscription,base.group_user,1,1,1,1
access_subscription_subscription_history_user,subscription.subscription.history user,model_subscription_subscription_history,base.group_user,1,1,1,1
access_subscription_document_user,subscription.document user,model_subscription_document,base.group_user,1,1,1,1
-access_res_partner_user,res.partner.user,base.model_res_partner,base.group_user,1,1,1,1
access_ir_cron_user,ir.cron.user,base.model_ir_cron,base.group_user,1,0,0,0
diff --git a/subscription/views/subscription_view.xml b/subscription/views/subscription.xml
similarity index 54%
rename from subscription/views/subscription_view.xml
rename to subscription/views/subscription.xml
index b0704957..32a6373c 100644
--- a/subscription/views/subscription_view.xml
+++ b/subscription/views/subscription.xml
@@ -1,57 +1,60 @@
-
-
+
+
subscription.subscription.form
subscription.subscription
-
+
subscription.subscription.tree
subscription.subscription
+
@@ -59,36 +62,35 @@
-
+
subscription.subscription.filter
subscription.subscription
-
+
-
-
-
-
+
+
+
+
-
+
Recurring Documents
- ir.actions.act_window
subscription.subscription
- form
-
- {'search_default_User':1}
-
+ tree,form
-
+
+
subscription.subscription.history.tree
@@ -96,6 +98,7 @@
+
@@ -105,7 +108,7 @@
subscription.subscription.history
-
+
subscription.document.form
subscription.document
@@ -128,7 +131,7 @@
-
+
subscription.document.tree
subscription.document
@@ -140,7 +143,7 @@
-
+
subscription.document.filter
subscription.document
@@ -151,12 +154,12 @@
-
+
subscription.document.fields.form
subscription.document.fields
-
-
+
subscription.document.fields.tree
subscription.document.fields
@@ -175,14 +178,13 @@
-
+
Recurring Types
- ir.actions.act_window
subscription.document
- form
-
-
+ tree,form
-
+
+