Browse Source

[MIG] subscription from 10 to 12 + OCA coding standards

pull/357/head
Alexis de Lattre 5 years ago
parent
commit
aa30b4ff9b
  1. 5
      subscription/__init__.py
  2. 29
      subscription/__manifest__.py
  3. 17
      subscription/data/subscription_demo.xml
  4. 20
      subscription/demo/subscription_demo.xml
  5. 5
      subscription/models/__init__.py
  6. 153
      subscription/models/subscription.py
  7. 2
      subscription/readme/CONTRIBUTORS.rst
  8. 3
      subscription/readme/DESCRIPTION.rst
  9. 4
      subscription/readme/USAGE.rst
  10. 1
      subscription/security/ir.model.access.csv
  11. 130
      subscription/views/subscription.xml

5
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

29
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,
}

17
subscription/data/subscription_demo.xml

@ -1,17 +0,0 @@
<?xml version="1.0" ?>
<odoo>
<record id="subscription_document_partner0" model="subscription.document">
<field eval="1" name="active"/>
<field name="model" ref="base.model_res_partner"/>
<field name="name">Partner</field>
</record>
<record id="subscription_document_fields_2" model="subscription.document.fields">
<field name="field" ref="base.field_res_partner_name"/>
<field name="value">false</field>
<field model="subscription.document" name="document_id" search="[('name', '=', u'Partner')]"/>
</record>
<record id="subscription_document_fields_3" model="subscription.document.fields">
<field name="field" ref="base.field_res_partner_child_ids"/>
<field model="subscription.document" name="document_id" search="[('name', '=', u'Partner')]"/>
</record>
</odoo>

20
subscription/demo/subscription_demo.xml

@ -0,0 +1,20 @@
<?xml version="1.0" ?>
<odoo noupdate="1">
<record id="subscription_document_partner0" model="subscription.document">
<field name="model" ref="base.model_res_partner"/>
<field name="name">Partner</field>
</record>
<record id="subscription_document_fields_2" model="subscription.document.fields">
<field name="field" ref="base.field_res_partner_name"/>
<field name="value">false</field>
<field model="subscription.document" name="document_id" search="[('name', '=', u'Partner')]"/>
</record>
<record id="subscription_document_fields_3" model="subscription.document.fields">
<field name="field" ref="base.field_res_partner_child_ids"/>
<field model="subscription.document" name="document_id" search="[('name', '=', u'Partner')]"/>
</record>
</odoo>

5
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

153
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)

2
subscription/readme/CONTRIBUTORS.rst

@ -0,0 +1,2 @@
* Odoo S.A. (https://www.odoo.com/)
* Alexis de Lattre <alexis.delattre@akretion.com>

3
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 <https://github.com/OCA/contract>`_ 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.

4
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.

1
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

130
subscription/views/subscription_view.xml → subscription/views/subscription.xml

@ -1,57 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Subscription Views -->
<record id="view_subscription_form" model="ir.ui.view">
<!-- Subscription -->
<record id="subscription_subscription_form" model="ir.ui.view">
<field name="name">subscription.subscription.form</field>
<field name="model">subscription.subscription</field>
<field name="arch" type="xml">
<form string="Subscriptions">
<header>
<button name="set_process" states="draft" string="Process" type="object" class="oe_highlight"/>
<button name="set_done" states="running" string="Stop" type="object" class="oe_highlight"/>
<button name="set_draft" states="done" string="Set to Draft" type="object"/>
<field name="state" widget="statusbar" statusbar_visible="draft,running"/>
</header>
<sheet>
<group col="4">
<field name="name"/>
<field name="partner_id"/>
<field name="user_id"/>
<field name="active"/>
</group>
<notebook>
<page string="Subscription Data">
<group col="4">
<field name="interval_number"/>
<field name="interval_type"/>
<field name="exec_init"/>
<field name="date_init"/>
<field name="doc_source" widget="reference"/>
<field name="cron_id"/>
</group>
<separator string="Internal Notes"/>
<field name="notes"/>
</page>
<page string="Documents created">
<field name="doc_lines" widget="one2many_list"/>
</page>
<page string="Notes">
<field name="note"/>
</page>
</notebook>
<header>
<button name="set_process" states="draft" string="Process" type="object" class="btn-primary"/>
<button name="set_done" states="running" string="Stop" type="object" class="btn-primary"/>
<button name="set_draft" states="done" string="Set to Draft" type="object"/>
<field name="state" widget="statusbar" statusbar_visible="draft,running"/>
</header>
<sheet>
<group col="4" name="top">
<field name="name"/>
<field name="partner_id"/>
<field name="user_id"/>
<field name="active"/>
</group>
<notebook>
<page string="Subscription Data" name="subscription_data">
<group col="4">
<field name="interval_number"/>
<field name="interval_type"/>
<field name="exec_init"/>
<field name="date_init"/>
<field name="doc_source" widget="reference"/>
<field name="cron_id"/>
<field name="nextcall"/>
</group>
<separator string="Internal Notes"/>
<field name="notes"/>
</page>
<page string="Documents created" name="history">
<field name="doc_lines" widget="one2many_list"/>
</page>
</notebook>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</sheet>
</form>
</field>
</record>
<record id="view_subscription_tree" model="ir.ui.view">
<record id="subscription_subscription_tree" model="ir.ui.view">
<field name="name">subscription.subscription.tree</field>
<field name="model">subscription.subscription</field>
<field name="arch" type="xml">
<tree string="Subscriptions">
<field name="name"/>
<field name="partner_id"/>
<field name="nextcall"/>
<field name="active"/>
<field name="user_id"/>
<field name="state"/>
@ -59,36 +62,35 @@
</field>
</record>
<record id="view_subscription_filter" model="ir.ui.view">
<record id="subscription_subscription_filter" model="ir.ui.view">
<field name="name">subscription.subscription.filter</field>
<field name="model">subscription.subscription</field>
<field name="arch" type="xml">
<search string="Search Subscription">
<field name="name" string="Subscription"/>
<field name="date_init"/>
<filter string="Running"
domain="[('state','=','running')]"/>
<filter string="Running" name="running"
domain="[('state', '=', 'running')]"/>
<field name="user_id"/>
<field name="partner_id"/>
<group expand="0" string="Group By">
<filter string="User" name="User" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Partner" name="Partner" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Status" domain="[]" context="{'group_by':'state'}"/>
<group name="groupby" string="Group By">
<filter string="User" name="user_groupby" context="{'group_by': 'user_id'}"/>
<filter string="Partner" name="partner_groupby" context="{'group_by': 'partner_id'}"/>
<filter string="Status" name="state_groupby" context="{'group_by': 'state'}"/>
</group>
</search>
</field>
</record>
<record id="action_subscription_form" model="ir.actions.act_window">
<record id="subscription_subscription_action" model="ir.actions.act_window">
<field name="name">Recurring Documents</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">subscription.subscription</field>
<field name="view_type">form</field>
<field name="view_id" ref="view_subscription_tree"/>
<field name="context">{'search_default_User':1}</field>
<field name="search_view_id" ref="view_subscription_filter"/>
<field name="view_mode">tree,form</field>
</record>
<menuitem action="action_subscription_form" id="menu_action_subscription_form" parent="base.menu_automation"/>
<menuitem id="subscription_subscription_menu"
action="subscription_subscription_action"
parent="base.menu_automation"/>
<record id="subscription_subscription_history_tree" model="ir.ui.view">
<field name="name">subscription.subscription.history.tree</field>
@ -96,6 +98,7 @@
<field name="arch" type="xml">
<tree string="Subscription History">
<field name="date"/>
<field name="document_id"/>
</tree>
</field>
</record>
@ -105,7 +108,7 @@
<field name="model">subscription.subscription.history</field>
<field name="arch" type="xml">
<form string="Subscription History">
<group col="4">
<group name="main">
<field name="date"/>
<field name="document_id"/>
</group>
@ -113,7 +116,7 @@
</field>
</record>
<record id="document_form" model="ir.ui.view">
<record id="subscription_document_form" model="ir.ui.view">
<field name="name">subscription.document.form</field>
<field name="model">subscription.document</field>
<field name="arch" type="xml">
@ -128,7 +131,7 @@
</field>
</record>
<record id="document_tree" model="ir.ui.view">
<record id="subscription_document_tree" model="ir.ui.view">
<field name="name">subscription.document.tree</field>
<field name="model">subscription.document</field>
<field name="arch" type="xml">
@ -140,7 +143,7 @@
</field>
</record>
<record id="view_subscription_document_filter" model="ir.ui.view">
<record id="subscription_document_filter" model="ir.ui.view">
<field name="name">subscription.document.filter</field>
<field name="model">subscription.document</field>
<field name="arch" type="xml">
@ -151,12 +154,12 @@
</field>
</record>
<record id="document_fields_form" model="ir.ui.view">
<record id="subscription_document_fields_form" model="ir.ui.view">
<field name="name">subscription.document.fields.form</field>
<field name="model">subscription.document.fields</field>
<field name="arch" type="xml">
<form string="Subscription Document Fields">
<group>
<form string="Subscription Document Field">
<group name="main">
<field name="field"/>
<field name="value"/>
</group>
@ -164,7 +167,7 @@
</field>
</record>
<record id="document_fields_tree" model="ir.ui.view">
<record id="subscription_document_fields_tree" model="ir.ui.view">
<field name="name">subscription.document.fields.tree</field>
<field name="model">subscription.document.fields</field>
<field name="arch" type="xml">
@ -175,14 +178,13 @@
</field>
</record>
<record id="action_document_form" model="ir.actions.act_window">
<record id="subscription_document_action" model="ir.actions.act_window">
<field name="name">Recurring Types</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">subscription.document</field>
<field name="view_type">form</field>
<field name="view_id" ref="document_tree"/>
<field name="search_view_id" ref="view_subscription_document_filter"/>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="menu_action_document_form" action="action_document_form" parent="base.menu_automation"/>
<menuitem id="subscription_document_menu" action="subscription_document_action"
parent="base.menu_automation"/>
</odoo>
Loading…
Cancel
Save