From 36da699f9279a8a4ed09a52c5661d9717c098d91 Mon Sep 17 00:00:00 2001 From: Ronald Portier Date: Sat, 16 Feb 2019 01:00:40 +0100 Subject: [PATCH] [IMP] contract_publication. New module to manage publications and distribution. --- contract_publication/README.rst | 66 +++++ contract_publication/__init__.py | 3 + contract_publication/__manifest__.py | 25 ++ .../i18n/contract_publication.pot | 279 +++++++++++++++++ contract_publication/i18n/nl.po | 280 ++++++++++++++++++ contract_publication/models/__init__.py | 6 + .../models/account_analytic_invoice_line.py | 33 +++ .../models/product_template.py | 42 +++ .../models/publication_distribution_list.py | 180 +++++++++++ contract_publication/models/res_partner.py | 18 ++ .../security/ir.model.access.csv | 3 + contract_publication/tests/__init__.py | 3 + .../tests/test_publication.py | 163 ++++++++++ .../views/distribution_list.xml | 98 ++++++ contract_publication/views/menu.xml | 13 + .../views/product_template.xml | 75 +++++ contract_publication/views/res_partner.xml | 73 +++++ 17 files changed, 1360 insertions(+) create mode 100644 contract_publication/README.rst create mode 100644 contract_publication/__init__.py create mode 100644 contract_publication/__manifest__.py create mode 100644 contract_publication/i18n/contract_publication.pot create mode 100644 contract_publication/i18n/nl.po create mode 100644 contract_publication/models/__init__.py create mode 100644 contract_publication/models/account_analytic_invoice_line.py create mode 100644 contract_publication/models/product_template.py create mode 100644 contract_publication/models/publication_distribution_list.py create mode 100644 contract_publication/models/res_partner.py create mode 100644 contract_publication/security/ir.model.access.csv create mode 100644 contract_publication/tests/__init__.py create mode 100644 contract_publication/tests/test_publication.py create mode 100644 contract_publication/views/distribution_list.xml create mode 100644 contract_publication/views/menu.xml create mode 100644 contract_publication/views/product_template.xml create mode 100644 contract_publication/views/res_partner.xml diff --git a/contract_publication/README.rst b/contract_publication/README.rst new file mode 100644 index 00000000..e344ad8b --- /dev/null +++ b/contract_publication/README.rst @@ -0,0 +1,66 @@ +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: https://www.gnu.org/licenses/agpl + :alt: License: AGPL-3 + +==================== +Contract Publication +==================== + +This module allows to register publications that a partner can receive. + +Publications can be of multiple kind: printed, electronic, +and free or paid for. + +Paid publications can be linked to a contract (recurring analytic account). + +For printed publications, you can setup a distribution list, in order that +one partner can have a subscription contract, but other partners actually +receive the publication. Think about organisations subscribing to +a publication and having them send out to their members. + +Usage +===== + +To use this module, you need to add publications as products. Then you can +have contract lines for those publications. In the partner form you will be +able to see the publications the partner has subscribed to and the partners +actually receiving the publications. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/110/10.0 + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smash it by providing detailed and welcomed feedback. + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Ronald Portier (https://therp.nl) + +Do not contact contributors directly about support or help with technical issues. + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/contract_publication/__init__.py b/contract_publication/__init__.py new file mode 100644 index 00000000..ded86ea6 --- /dev/null +++ b/contract_publication/__init__.py @@ -0,0 +1,3 @@ +#-*- coding: utf-8 -*- +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import models diff --git a/contract_publication/__manifest__.py b/contract_publication/__manifest__.py new file mode 100644 index 00000000..706e5853 --- /dev/null +++ b/contract_publication/__manifest__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Copyright 2014-2019 Therp BV . +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + 'name': 'Publication', + 'version': '10.0.1.0.0', + 'category': 'Contract Management', + 'author': 'Therp BV, ' + 'Odoo Community Association (OCA)', + 'website': 'https://github.com/oca/contract', + 'license': 'AGPL-3', + 'summary': 'Maintain electronic or print publications for your relations.', + 'depends': [ + 'contract', + ], + 'data': [ + 'views/product_template.xml', + 'views/distribution_list.xml', + 'views/res_partner.xml', + 'views/menu.xml', + 'security/ir.model.access.csv', + ], + 'auto_install': False, + 'installable': True, +} diff --git a/contract_publication/i18n/contract_publication.pot b/contract_publication/i18n/contract_publication.pot new file mode 100644 index 00000000..12d3c7ea --- /dev/null +++ b/contract_publication/i18n/contract_publication.pot @@ -0,0 +1,279 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * contract_publication +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-12-01 10:39+0000\n" +"PO-Revision-Date: 2017-12-01 10:39+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"Language: en\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_active +msgid "Active" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,help:publication.field_product_product_publishing_frequency_type +#: model:ir.model.fields,help:publication.field_product_template_publishing_frequency_type +msgid "At what intervals publication is published" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_contract_partner_id +#: model:ir.ui.view,arch_db:publication.subscription_search +msgid "Contract Partner" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_create_uid +msgid "Created by" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_create_date +msgid "Created on" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_date_end +msgid "Date end" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_date_start +msgid "Date start" +msgstr "" + +#. module: contract_publication +#: selection:product.template,publishing_frequency_type:0 +msgid "Day(s)" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_display_name +msgid "Display Name" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_product_product_distribution_type +#: model:ir.model.fields,field_description:publication.field_product_template_distribution_type +msgid "Distribution" +msgstr "" + +#. module: contract_publication +#: model:ir.actions.act_window,name:publication.action_distribution_list +#: model:ir.ui.menu,name:publication.menu_distribution_list +msgid "Distribution List" +msgstr "" + +#. module: contract_publication +#: selection:product.template,distribution_type:0 +msgid "Electronic" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,help:publication.field_distribution_list_date_end +msgid "End date is exclusive." +msgstr "" + +#. module: contract_publication +#: model:ir.ui.view,arch_db:publication.subscription_search +msgid "Group By..." +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_id +msgid "ID" +msgstr "" + +#. module: contract_publication +#: selection:product.template,publishing_frequency_type:0 +msgid "Irregular" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list___last_update +msgid "Last Modified on" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_write_uid +msgid "Last Updated by" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_write_date +msgid "Last Updated on" +msgstr "" + +#. module: contract_publication +#: selection:product.template,publishing_frequency_type:0 +msgid "Month(s)" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_name +msgid "Name" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_assigned_count +msgid "Quantity already assigned" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_copies +msgid "Number of copies" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_available_count +msgid "Quantity still available" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_contract_count +msgid "Quantity to distribute" +msgstr "" + +#. module: contract_publication +#: model:ir.model,name:publication.model_res_partner +#: model:ir.model.fields,field_description:publication.field_account_analytic_contract_line_partner_id +#: model:ir.model.fields,field_description:publication.field_account_analytic_invoice_line_partner_id +msgid "Partner" +msgstr "" + +#. module: contract_publication +#: selection:product.template,distribution_type:0 +msgid "Print" +msgstr "" + +#. module: contract_publication +#: model:ir.model,name:publication.model_product_template +msgid "Product Template" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_product_product_publication +#: model:ir.model.fields,field_description:publication.field_product_template_publication +msgid "Product is a publication?" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_product_id +#: model:ir.ui.view,arch_db:publication.product_template_only_form_view +#: model:ir.ui.view,arch_db:publication.subscription_search +msgid "Publication" +msgstr "" + +#. module: contract_publication +#: model:ir.actions.act_window,name:publication.action_publication +#: model:ir.ui.menu,name:publication.menu_publication +msgid "Publications" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_res_partner_distribution_list_ids +#: model:ir.model.fields,field_description:publication.field_res_users_distribution_list_ids +msgid "Publications received" +msgstr "" + +#. module: contract_publication +#: model:ir.ui.view,arch_db:publication.view_partner_form +msgid "Publications to receive" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_product_product_publishing_frequency_interval +#: model:ir.model.fields,field_description:publication.field_product_template_publishing_frequency_interval +msgid "Published Every" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,help:publication.field_product_product_publishing_frequency_interval +#: model:ir.model.fields,help:publication.field_product_template_publishing_frequency_interval +msgid "Published every (Days/Week/Month/Quarter/Year)" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_product_product_publishing_frequency_type +#: model:ir.model.fields,field_description:publication.field_product_template_publishing_frequency_type +msgid "Publishing frequency" +msgstr "" + +#. module: contract_publication +#: selection:product.template,publishing_frequency_type:0 +msgid "Quarterly" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_partner_id +#: model:ir.ui.view,arch_db:publication.subscription_search +msgid "Receiving Partner" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_display_address +msgid "Receiving address" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,help:publication.field_product_product_distribution_type +#: model:ir.model.fields,help:publication.field_product_template_distribution_type +msgid "Required if product is publication" +msgstr "" + +#. module: contract_publication +#: model:ir.ui.view,arch_db:publication.subscription_form +msgid "Subscription Address" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_res_partner_subscription_ids +#: model:ir.model.fields,field_description:publication.field_res_users_subscription_ids +#: model:ir.ui.view,arch_db:publication.view_partner_form +msgid "Subscription contract lines" +msgstr "" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_account_analytic_contract_line_publication +#: model:ir.model.fields,field_description:publication.field_account_analytic_invoice_line_publication +msgid "Subscription product line" +msgstr "" + +#. module: contract_publication +#: model:ir.ui.view,arch_db:publication.view_partner_form +#: model:ir.ui.view,arch_db:publication.subscription_search +#: model:ir.ui.view,arch_db:publication.subscription_tree +msgid "Subscriptions" +msgstr "" + +#. module: contract_publication +#: selection:product.template,publishing_frequency_type:0 +msgid "Week(s)" +msgstr "" + +#. module: contract_publication +#: selection:product.template,publishing_frequency_type:0 +msgid "Year(s)" +msgstr "" + +#. module: contract_publication +#: model:ir.model,name:publication.model_account_analytic_invoice_line +msgid "account.analytic.invoice.line" +msgstr "" + +#. module: contract_publication +#: model:ir.model,name:publication.model_distribution_list +msgid "distribution.list" +msgstr "" + diff --git a/contract_publication/i18n/nl.po b/contract_publication/i18n/nl.po new file mode 100644 index 00000000..e5e0cb61 --- /dev/null +++ b/contract_publication/i18n/nl.po @@ -0,0 +1,280 @@ +# Translation of OpenERP Server. +# This file contains the translation of the following modules: +# * contract_publication +# Ronald Portier , 2014, 2015. +# +msgid "" +msgstr "" +"Project-Id-Version: OpenERP Server 7.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-12-01 10:39+0000\n" +"PO-Revision-Date: 2015-02-09 12:54+0100\n" +"Last-Translator: Ronald Portier \n" +"Language-Team: Nederlands <>\n" +"Language: nl_NL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Gtranslator 2.91.6\n" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_active +msgid "Active" +msgstr "Actief" + +#. module: contract_publication +#: model:ir.model.fields,help:publication.field_product_product_publishing_frequency_type +#: model:ir.model.fields,help:publication.field_product_template_publishing_frequency_type +msgid "At what intervals publication is published" +msgstr "Met wat voor frequentie de publicatie uitkomt" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_contract_partner_id +#: model:ir.ui.view,arch_db:publication.subscription_search +msgid "Contract Partner" +msgstr "Contract informatie" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_create_uid +msgid "Created by" +msgstr "Aangemaakt door" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_create_date +msgid "Created on" +msgstr "Aangemaakt op" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_date_end +msgid "Date end" +msgstr "Einddatum" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_date_start +msgid "Date start" +msgstr "Startdatum" + +#. module: contract_publication +#: selection:product.template,publishing_frequency_type:0 +msgid "Day(s)" +msgstr "Dagen" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_display_name +msgid "Display Name" +msgstr "Weergave naam" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_product_product_distribution_type +#: model:ir.model.fields,field_description:publication.field_product_template_distribution_type +msgid "Distribution" +msgstr "Voorkeur voor email of drukwerk" + +#. module: contract_publication +#: model:ir.actions.act_window,name:publication.action_distribution_list +#: model:ir.ui.menu,name:publication.menu_distribution_list +msgid "Distribution List" +msgstr "Verzendlijst" + +#. module: contract_publication +#: selection:product.template,distribution_type:0 +msgid "Electronic" +msgstr "Elektronisch" + +#. module: contract_publication +#: model:ir.model.fields,help:publication.field_distribution_list_date_end +msgid "End date is exclusive." +msgstr "Einddatum it tot datum" + +#. module: contract_publication +#: model:ir.ui.view,arch_db:publication.subscription_search +msgid "Group By..." +msgstr "Groepeer op" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_id +msgid "ID" +msgstr "ID" + +#. module: contract_publication +#: selection:product.template,publishing_frequency_type:0 +msgid "Irregular" +msgstr "Onregelmatig" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list___last_update +msgid "Last Modified on" +msgstr "Laatst gewijzigd op" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_write_uid +msgid "Last Updated by" +msgstr "Laatst gewijzigd door" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_write_date +msgid "Last Updated on" +msgstr "Laatst bijgewerkt op" + +#. module: contract_publication +#: selection:product.template,publishing_frequency_type:0 +msgid "Month(s)" +msgstr "Maanden" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_name +msgid "Name" +msgstr "Naam" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_assigned_count +msgid "Quantity already assigned" +msgstr "Aantal al toegekend" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_copies +msgid "Number of copies" +msgstr "Aantal exemplaren" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_available_count +msgid "Quantity still available" +msgstr "Aantal nog beschikbaar" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_contract_count +msgid "Quantity to distribute" +msgstr "Aantal exemplaren" + +#. module: contract_publication +#: model:ir.model,name:publication.model_res_partner +#: model:ir.model.fields,field_description:publication.field_account_analytic_contract_line_partner_id +#: model:ir.model.fields,field_description:publication.field_account_analytic_invoice_line_partner_id +msgid "Partner" +msgstr "Relatie" + +#. module: contract_publication +#: selection:product.template,distribution_type:0 +msgid "Print" +msgstr "Drukwerk" + +#. module: contract_publication +#: model:ir.model,name:publication.model_product_template +msgid "Product Template" +msgstr "Product" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_product_product_publication +#: model:ir.model.fields,field_description:publication.field_product_template_publication +msgid "Product is a publication?" +msgstr "Product is een publicatie" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_product_id +#: model:ir.ui.view,arch_db:publication.product_template_only_form_view +#: model:ir.ui.view,arch_db:publication.subscription_search +msgid "Publication" +msgstr "Publicatie" + +#. module: contract_publication +#: model:ir.actions.act_window,name:publication.action_publication +#: model:ir.ui.menu,name:publication.menu_publication +msgid "Publications" +msgstr "Publicaties" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_res_partner_distribution_list_ids +#: model:ir.model.fields,field_description:publication.field_res_users_distribution_list_ids +msgid "Publications received" +msgstr "Publicaties" + +#. module: contract_publication +#: model:ir.ui.view,arch_db:publication.view_partner_form +msgid "Publications to receive" +msgstr "Publicaties te ontvangen" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_product_product_publishing_frequency_interval +#: model:ir.model.fields,field_description:publication.field_product_template_publishing_frequency_interval +msgid "Published Every" +msgstr "Verschijnt iedere" + +#. module: contract_publication +#: model:ir.model.fields,help:publication.field_product_product_publishing_frequency_interval +#: model:ir.model.fields,help:publication.field_product_template_publishing_frequency_interval +msgid "Published every (Days/Week/Month/Quarter/Year)" +msgstr "Herhaal elke (dagen/weken/maanden/jaren)" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_product_product_publishing_frequency_type +#: model:ir.model.fields,field_description:publication.field_product_template_publishing_frequency_type +msgid "Publishing frequency" +msgstr "Frequentie van verschijnen" + +#. module: contract_publication +#: selection:product.template,publishing_frequency_type:0 +msgid "Quarterly" +msgstr "Kwartaal" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_partner_id +#: model:ir.ui.view,arch_db:publication.subscription_search +msgid "Receiving Partner" +msgstr "Ontvangende relatie" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_distribution_list_display_address +msgid "Receiving address" +msgstr "Verzendadres" + +#. module: contract_publication +#: model:ir.model.fields,help:publication.field_product_product_distribution_type +#: model:ir.model.fields,help:publication.field_product_template_distribution_type +msgid "Required if product is publication" +msgstr "Verplicht indien product een publicatie is" + +#. module: contract_publication +#: model:ir.ui.view,arch_db:publication.subscription_form +msgid "Subscription Address" +msgstr "Verzendadres" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_res_partner_subscription_ids +#: model:ir.model.fields,field_description:publication.field_res_users_subscription_ids +#: model:ir.ui.view,arch_db:publication.view_partner_form +msgid "Subscription contract lines" +msgstr "Abonnementen" + +#. module: contract_publication +#: model:ir.model.fields,field_description:publication.field_account_analytic_contract_line_publication +#: model:ir.model.fields,field_description:publication.field_account_analytic_invoice_line_publication +msgid "Subscription product line" +msgstr "Contractregel voor abonnement" + +#. module: contract_publication +#: model:ir.ui.view,arch_db:publication.view_partner_form +#: model:ir.ui.view,arch_db:publication.subscription_search +#: model:ir.ui.view,arch_db:publication.subscription_tree +msgid "Subscriptions" +msgstr "Abonnementen" + +#. module: contract_publication +#: selection:product.template,publishing_frequency_type:0 +msgid "Week(s)" +msgstr "Week(en)" + +#. module: contract_publication +#: selection:product.template,publishing_frequency_type:0 +msgid "Year(s)" +msgstr "Jaar(en)" + +#. module: contract_publication +#: model:ir.model,name:publication.model_account_analytic_invoice_line +msgid "account.analytic.invoice.line" +msgstr "" + +#. module: contract_publication +#: model:ir.model,name:publication.model_distribution_list +msgid "distribution.list" +msgstr "" diff --git a/contract_publication/models/__init__.py b/contract_publication/models/__init__.py new file mode 100644 index 00000000..b6c58706 --- /dev/null +++ b/contract_publication/models/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import product_template +from . import account_analytic_invoice_line +from . import publication_distribution_list +from . import res_partner diff --git a/contract_publication/models/account_analytic_invoice_line.py b/contract_publication/models/account_analytic_invoice_line.py new file mode 100644 index 00000000..b5ee0169 --- /dev/null +++ b/contract_publication/models/account_analytic_invoice_line.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Copyright 2014-2019 Therp BV . +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +# pylint: disable=no-member,too-few-public-methods +from odoo import api, fields, models + + +class AccountAnalyticInvoiceLine(models.Model): + _inherit = 'account.analytic.invoice.line' + + partner_id = fields.Many2one( + related='analytic_account_id.partner_id', + store=True, + readonly=True) + publication = fields.Boolean( + string='Subscription product line', + related='product_id.publication', + store=True) + + @api.multi + def action_distribution_list(self): + self.ensure_one() + action = self.env.ref( + 'publication.action_distribution_list').read()[0] + action['context'] = { + 'default_product_id': self.product_id.id, + 'default_contract_partner_id': self.partner_id.id} + action['domain'] = [ + ('contract_partner_id', '=', self.partner_id.id), + ('product_id', '=', self.product_id.id)] + action['view_mode'] = 'form' + action['target'] = 'current' + return action diff --git a/contract_publication/models/product_template.py b/contract_publication/models/product_template.py new file mode 100644 index 00000000..17ac2a23 --- /dev/null +++ b/contract_publication/models/product_template.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Copyright 2014-2019 Therp BV . +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import api, fields, models + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + publication = fields.Boolean(string='Product is a publication?') + distribution_type = fields.Selection( + selection=[('email', 'Electronic'), ('print', 'Print')], + string='Distribution', + help="Required if product is publication") + publishing_frequency_type = fields.Selection( + selection=[ + ('irregular', 'Irregular'), + ('daily', 'Day(s)'), + ('weekly', 'Week(s)'), + ('monthly', 'Month(s)'), + ('quarterly', 'Quarterly'), + ('yearly', 'Year(s)')], + default='monthly', + string='Publishing frequency', + help="At what intervals publication is published") + publishing_frequency_interval = fields.Integer( + string='Published Every', + default=1, + help="Published every (Days/Week/Month/Quarter/Year)") + + @api.multi + def action_distribution_list(self): + self.ensure_one() + action = self.env.ref( + 'publication.action_distribution_list').read()[0] + action['context'] = { + 'default_product_id': self.product_variant_ids[0].id} + action['domain'] = [ + ('product_id', '=', self.product_variant_ids[0].id)] + action['view_mode'] = 'form' + action['target'] = 'current' + return action diff --git a/contract_publication/models/publication_distribution_list.py b/contract_publication/models/publication_distribution_list.py new file mode 100644 index 00000000..5778e44b --- /dev/null +++ b/contract_publication/models/publication_distribution_list.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- +# Copyright 2014-2019 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +SQL_CONTRACT_COUNT = """ +SELECT COALESCE(SUM(ROUND(l.quantity)::integer), 0) as quantity + FROM account_analytic_invoice_line l + JOIN account_analytic_account c + ON l.analytic_account_id = c.id + WHERE l.product_id = %s + AND c.partner_id = %s""" + +SQL_ASSIGNED_COUNT = """ +SELECT COALESCE(SUM(copies), 0) as copies + FROM publication_distribution_list + WHERE product_id = %s + AND contract_partner_id = %s""" + + +class PublicationDistributionList(models.Model): + _name = 'publication.distribution.list' + _order = 'name' + + @api.model + def get_product_contract_count(self, product_id, contract_partner_id): + self.env.cr.execute( + SQL_CONTRACT_COUNT, + (product_id, contract_partner_id)) + return self.env.cr.fetchone()[0] + + @api.model + def get_product_contract_assigned_count( + self, product_id, contract_partner_id): + self.env.cr.execute( + SQL_ASSIGNED_COUNT, + (product_id, contract_partner_id)) + return self.env.cr.fetchone()[0] + + @api.depends('product_id', 'partner_id') + def _compute_name_address(self): + """Create subscription name from publication and partner.""" + partner_model = self.env['res.partner'] + for this in self: + if not this.product_id or not this.partner_id: + this.name = False + this.contact_address = False + continue + this.name = ' - '.join( + [this.product_id.name, this.partner_id.name]) + if this.product_id.distribution_type == 'email': + this.contact_address = this.partner_id.email + else: + delivery_id = this.partner_id.address_get( + ['delivery'])['delivery'] + this.contact_address = partner_model.browse( + delivery_id).contact_address + + @api.multi + @api.depends('product_id', 'contract_partner_id', 'copies') + def _compute_counts(self): + """Used to check how many addresses can still be added.""" + for this in self: + if not self.product_id or not self.contract_partner_id: + continue + contract_count = self.get_product_contract_count( + this.product_id.id, this.contract_partner_id.id) + assigned_count = self.get_product_contract_assigned_count( + this.product_id.id, this.contract_partner_id.id) + available_count = contract_count - assigned_count + this.contract_count = contract_count + this.assigned_count = assigned_count + this.available_count = available_count + + product_id = fields.Many2one( + comodel_name='product.product', + string='Publication', + domain=[('publication', '=', True)], + required=True) + distribution_type = fields.Selection( + string='Type of publication', + related='product_id.distribution_type', + store=True) + partner_id = fields.Many2one( + comodel_name='res.partner', + string='Receiving Partner', + required=True) + contract_partner_id = fields.Many2one( + comodel_name='res.partner', + string='Contract Partner', + required=True) + date_start = fields.Date( + string='Date start', + default=fields.Date.today(), + required=True) + date_end = fields.Date( + string='Date end') + copies = fields.Integer(string='Number of copies', default=1) + name = fields.Char( + compute='_compute_name_address', + string='Name') + contact_address = fields.Char( + compute='_compute_name_address', + string='Receiving address') + contract_count = fields.Integer( + string="Quantity to distribute", + compute='_compute_counts') + assigned_count = fields.Integer( + string="Quantity already assigned", + compute='_compute_counts') + available_count = fields.Integer( + string="Quantity still available", + compute='_compute_counts') + + @api.onchange('product_id', 'contract_partner_id') + def _onchange_keyfields(self): + """Sets the proper domain for contract_partner. + + Also enforces first selecting the publication. + """ + self.ensure_one() + if self.contract_partner_id and not self.partner_id: + self.partner_id = self.contract_partner_id + if self.contract_partner_id and not self.product_id: + raise ValidationError(_( + "You must select a publication before selecting" + " the contract partner.")) + if not self.product_id: + return + valid_partners = [] + line_model = self.env['account.analytic.invoice.line'] + lines = line_model.search([('product_id', '=', self.product_id.id)]) + for line in lines: + if line.analytic_account_id.partner_id.id not in valid_partners: + valid_partners.append(line.analytic_account_id.partner_id.id) + if not valid_partners: + raise ValidationError(_( + "There are no active subscriptions for this publication.")) + partner_domain = [('id', 'in', valid_partners)] + return {'domain': {'contract_partner_id': partner_domain}} + + @api.model + def create(self, vals): + result = super(PublicationDistributionList, self).create(vals) + result._limit_count() + return result + + @api.multi + def write(self, vals): + result = super(PublicationDistributionList, self).write(vals) + self._limit_count() + return result + + @api.multi + def _limit_count(self): + """Limit number of copies send to amount set in contract lines. + + It should be possible to make a constrains method of this function, + but for inexplicable reasons this does not work. + """ + for this in self: + # Do not use counts from 'this' as they probably have not + # been updated yet. + product = this.product_id + contract_partner = this.contract_partner_id + contract_count = self.get_product_contract_count( + product.id, contract_partner.id) + assigned_count = self.get_product_contract_assigned_count( + product.id, contract_partner.id) + available_count = contract_count - assigned_count + if available_count < 0: + raise ValidationError(_( + "Number of copies sent %d can not exceed contracted" + " number %d for partner %s and product %s" % ( + assigned_count, + contract_count, + contract_partner.display_name, + product.display_name))) diff --git a/contract_publication/models/res_partner.py b/contract_publication/models/res_partner.py new file mode 100644 index 00000000..f14e1bbb --- /dev/null +++ b/contract_publication/models/res_partner.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Copyright 2014-2019 Therp BV . +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + subscription_ids = fields.One2many( + comodel_name='account.analytic.invoice.line', + inverse_name='partner_id', + domain=[('publication', '=', True)], + string='Subscription contract lines') + distribution_list_ids = fields.One2many( + comodel_name='publication.distribution.list', + inverse_name='partner_id', + string='Publications received') diff --git a/contract_publication/security/ir.model.access.csv b/contract_publication/security/ir.model.access.csv new file mode 100644 index 00000000..8888bd03 --- /dev/null +++ b/contract_publication/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_distribution_list_manager,CRUD access for distribution.list,model_publication_distribution_list,base.group_partner_manager,1,1,1,1 +access_distribution_list_reader,READ access for distribution.list,model_publication_distribution_list,base.group_user,1,0,0,0 diff --git a/contract_publication/tests/__init__.py b/contract_publication/tests/__init__.py new file mode 100644 index 00000000..3d76c693 --- /dev/null +++ b/contract_publication/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import test_publication diff --git a/contract_publication/tests/test_publication.py b/contract_publication/tests/test_publication.py new file mode 100644 index 00000000..b2670432 --- /dev/null +++ b/contract_publication/tests/test_publication.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +# Copyright 2018-2019 Therp BV . +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo.tests import common +from odoo.exceptions import ValidationError + + +class TestPublication(common.SavepointCase): + + post_install = True + at_install = False + + @classmethod + def setUpClass(cls): + super(TestPublication, cls).setUpClass() + # Create some test partners + partner_model = cls.env['res.partner'] + cls.partner_jan = partner_jan = partner_model.create({ + 'name': 'Jan', + 'city': 'Amsterdam'}) + cls.partner_piet = partner_piet = partner_model.create({ + 'name': 'Piet', + 'city': 'Rotterdam'}) + cls.partner_joris = partner_model.create({ + 'name': 'Joris', + 'city': 'Den Haag'}) + cls.partner_corneel = partner_model.create({ + 'name': 'Corneel', + 'city': 'Dordrecht'}) + cls.partner_corneel_delivery = partner_model.create({ + 'name': 'Corneels store', + 'parent_id': cls.partner_corneel.id, + 'type': 'delivery', + 'street': 'Wijnstraat 35', + 'city': 'Dordrecht'}) + # Make sure a sale journal is present for tests + sequence_model = cls.env['ir.sequence'] + contract_sequence = sequence_model.create({ + 'company_id': cls.env.user.company_id.id, + 'code': 'contract', + 'name': 'contract sequence', + 'number_next': 1, + 'implementation': 'standard', + 'padding': 3, + 'number_increment': 1}) + journal_model = cls.env['account.journal'] + journal_model.create({ + 'company_id': cls.env.user.company_id.id, + 'code': 'contract', + 'name': 'contract journal', + 'sequence_id': contract_sequence.id, + 'type': 'sale'}) + # Create products: + cls.uom_unit = cls.env.ref('product.product_uom_unit') + product_model = cls.env['product.product'] + cls.product_book = product_book = product_model.create({ + 'name': 'Test yearbook', + 'type': 'consu', + 'uom_id': cls.uom_unit.id, + 'uom_po_id': cls.uom_unit.id}) + cls.product_newsletter = product_newsletter = product_model.create({ + 'name': 'Test newsletter', + 'type': 'consu', + 'publication': True, + 'distribution_type': 'print', + 'publishing_frequency_type': 'weekly', + 'uom_id': cls.uom_unit.id, + 'uom_po_id': cls.uom_unit.id}) + # Create contract that will be subscription from the beginning: + contract_model = cls.env['account.analytic.account'] + line_model = cls.env['account.analytic.invoice.line'] + cls.contract_jan = contract_jan = contract_model.create({ + 'name': 'Test Contract Jan', + 'partner_id': partner_jan.id, + 'pricelist_id': partner_jan.property_product_pricelist.id, + 'recurring_invoices': True, + 'date_start': '2016-02-15', + 'recurring_next_date': '2016-02-29'}) + cls.line_newsletter = line_model.create({ + 'analytic_account_id': contract_jan.id, + 'product_id': product_newsletter.id, + 'name': 'Newsletter subscription', + 'quantity': 40, + 'uom_id': product_newsletter.uom_id.id, + 'price_unit': 100, + 'discount': 50}) + # Create contract for yearbook. product will be changed to publication: + cls.contract_piet = contract_piet = contract_model.create({ + 'name': 'Test Contract Piet', + 'partner_id': partner_piet.id, + 'pricelist_id': partner_piet.property_product_pricelist.id, + 'recurring_invoices': True, + 'date_start': '2016-02-15', + 'recurring_next_date': '2016-02-29'}) + cls.line_book = line_model.create({ + 'analytic_account_id': contract_piet.id, + 'product_id': product_book.id, + 'name': 'Newsletter subscription', + 'quantity': 1, + 'uom_id': product_book.uom_id.id, + 'price_unit': 100, + 'discount': 50}) + + def test_contract(self): + """Test creation of contract line for publication.""" + line_newsletter = self.line_newsletter + partner_jan = self.partner_jan + self.assertTrue(line_newsletter.publication) + self.assertEqual(partner_jan.subscription_ids[0], line_newsletter) + + def test_product_change(self): + """Test change of product to publication product.""" + product_book = self.product_book + line_book = self.line_book + partner_piet = self.partner_piet + self.assertFalse(line_book.publication) + self.assertFalse(bool(partner_piet.subscription_ids)) + product_book.write({ + 'publication': True, + 'distribution_type': 'print', + 'publishing_frequency_type': 'yearly'}) + self.assertTrue(line_book.publication) + self.assertEqual(partner_piet.subscription_ids[0], line_book) + + def test_distribution_list(self): + """Test adding recipients to distribution list.""" + distribution_model = self.env['publication.distribution.list'] + product_newsletter = self.product_newsletter + partner_jan = self.partner_jan + partner_joris = self.partner_joris + partner_corneel = self.partner_corneel + product_count = distribution_model.get_product_contract_count( + product_newsletter.id, partner_jan.id) + self.assertEqual(product_count, 40) + assigned_count = \ + distribution_model.get_product_contract_assigned_count( + product_newsletter.id, partner_jan.id) + self.assertEqual(assigned_count, 0) + subscription01 = distribution_model.create({ + 'product_id': product_newsletter.id, + 'contract_partner_id': partner_jan.id, + 'partner_id': partner_joris.id, + 'copies': 10}) + self.assertEqual(subscription01.contract_count, 40) + self.assertEqual(subscription01.assigned_count, 10) + self.assertEqual(subscription01.available_count, 30) + subscription02 = distribution_model.create({ + 'product_id': product_newsletter.id, + 'contract_partner_id': partner_jan.id, + 'partner_id': partner_corneel.id, + 'copies': 10}) + self.assertEqual(subscription02.contract_count, 40) + # Old entry should also be changed + self.assertEqual(subscription01.assigned_count, 20) + self.assertEqual(subscription01.available_count, 20) + with self.assertRaises(ValidationError): + distribution_model.create({ + 'product_id': product_newsletter.id, + 'contract_partner_id': partner_jan.id, + 'partner_id': partner_jan.id, + 'copies': 30}) + # Delivery for Corneel should be to his store. + self.assertIn('Wijnstraat', subscription02.contact_address) diff --git a/contract_publication/views/distribution_list.xml b/contract_publication/views/distribution_list.xml new file mode 100644 index 00000000..c51e5c61 --- /dev/null +++ b/contract_publication/views/distribution_list.xml @@ -0,0 +1,98 @@ + + + + + Search view for subscriptions + publication.distribution.list + + + + + + + + + + + + + + + + subscription.tree + publication.distribution.list + tree + + + + + + + + + + + + + subscription.form + publication.distribution.list + form + +
+ + + + + + + + + + + + + + + + + +
+
+
+ +
diff --git a/contract_publication/views/menu.xml b/contract_publication/views/menu.xml new file mode 100644 index 00000000..6ce5216f --- /dev/null +++ b/contract_publication/views/menu.xml @@ -0,0 +1,13 @@ + + + + + tree,form + publication.distribution.list + Distribution List + + + diff --git a/contract_publication/views/product_template.xml b/contract_publication/views/product_template.xml new file mode 100644 index 00000000..9300be49 --- /dev/null +++ b/contract_publication/views/product_template.xml @@ -0,0 +1,75 @@ + + + + + product.template.product.form.publication + product.template + + form + +
+ +
+ + + + + + + + + + + + +
+
+ + + product.template.search + product.template + + + + + + + + +
diff --git a/contract_publication/views/res_partner.xml b/contract_publication/views/res_partner.xml new file mode 100644 index 00000000..aaddbe07 --- /dev/null +++ b/contract_publication/views/res_partner.xml @@ -0,0 +1,73 @@ + + + + + res.partner.publication.form + res.partner + form + + + + + + + + + + + + + + + + /> + + + + + + + + + + + + + + + +