forked from OCA/contract
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
460 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg | ||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html | ||
:alt: License: AGPL-3 | ||
|
||
============================= | ||
Contracts for recurrent sales | ||
============================= | ||
|
||
This module extends functionality of contracts to be able to generate sales | ||
orders instead of invoices. | ||
|
||
Usage | ||
===== | ||
|
||
To use this module, you need to: | ||
|
||
#. Go to Accounting -> Contracts and select or create a new contract. | ||
#. Check *Generate recurring invoices automatically*. | ||
#. Fill fields for selecting the recurrency and invoice parameters: | ||
|
||
* Type defines document that contract will generate, can be "Sales" or "Invoices" | ||
* Sale Autoconfirm, validate Sales Orders if type is "Sales" | ||
|
||
.. 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 | ||
<https://github.com/OCA/contract/issues>`_. In case of trouble, please | ||
check there if your issue has already been reported. If you spotted it first, | ||
help us smashing it by providing a detailed and welcomed feedback. | ||
|
||
Credits | ||
======= | ||
|
||
Contributors | ||
------------ | ||
|
||
* Angel Moya <[email protected]> | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# -*- coding: utf-8 -*- | ||
from . import models |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# -*- coding: utf-8 -*- | ||
# Copyright 2017 Pesol (<http://pesol.es>) | ||
# Copyright 2017 Angel Moya <[email protected]> | ||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) | ||
|
||
|
||
{ | ||
'name': 'Contracts Management - Recurring Sales', | ||
'version': '10.0.1.0.0', | ||
'category': 'Contract Management', | ||
'license': 'AGPL-3', | ||
'author': "PESOL, " | ||
"Odoo Community Association (OCA)", | ||
'website': 'https://github.com/oca/contract', | ||
'depends': ['contract', 'sale'], | ||
'data': [ | ||
'views/account_analytic_account_view.xml', | ||
'views/account_analytic_contract_view.xml', | ||
'views/sale_view.xml', | ||
], | ||
'installable': True, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# -*- coding: utf-8 -*- | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
from . import account_analytic_contract | ||
from . import account_analytic_account |
80 changes: 80 additions & 0 deletions
80
contract_sale_generation/models/account_analytic_account.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
# -*- coding: utf-8 -*- | ||
# © 2004-2010 OpenERP SA | ||
# © 2014 Angel Moya <[email protected]> | ||
# © 2015 Pedro M. Baeza <[email protected]> | ||
# © 2016 Carlos Dauden <[email protected]> | ||
# Copyright 2016-2017 LasLabs Inc. | ||
# Copyright 2017 Pesol (<http://pesol.es>) | ||
# Copyright 2017 Angel Moya <[email protected]> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
from odoo import api, models | ||
from odoo.exceptions import ValidationError | ||
from odoo.tools.translate import _ | ||
|
||
|
||
class AccountAnalyticAccount(models.Model): | ||
_inherit = 'account.analytic.account' | ||
|
||
@api.model | ||
def _prepare_sale_line(self, line, order_id): | ||
sale_line = self.env['sale.order.line'].new({ | ||
'order_id': order_id, | ||
'product_id': line.product_id.id, | ||
'proudct_uom_qty': line.quantity, | ||
'proudct_uom_id': line.uom_id.id, | ||
}) | ||
# Get other invoice line values from product onchange | ||
sale_line.product_id_change() | ||
sale_line_vals = sale_line._convert_to_write(sale_line._cache) | ||
# Insert markers | ||
name = line.name | ||
contract = line.analytic_account_id | ||
if 'old_date' in self.env.context and 'next_date' in self.env.context: | ||
lang_obj = self.env['res.lang'] | ||
lang = lang_obj.search( | ||
[('code', '=', contract.partner_id.lang)]) | ||
date_format = lang.date_format or '%m/%d/%Y' | ||
name = self._insert_markers( | ||
line, self.env.context['old_date'], | ||
self.env.context['next_date'], date_format) | ||
sale_line_vals.update({ | ||
'name': name, | ||
'discount': line.discount, | ||
'price_unit': line.price_unit, | ||
}) | ||
return sale_line_vals | ||
|
||
@api.multi | ||
def _prepare_sale(self): | ||
self.ensure_one() | ||
if not self.partner_id: | ||
raise ValidationError( | ||
_("You must first select a Customer for Contract %s!") % | ||
self.name) | ||
sale = self.env['sale.order'].new({ | ||
'partner_id': self.partner_id, | ||
'date_order': self.recurring_next_date, | ||
'origin': self.name, | ||
'company_id': self.company_id.id, | ||
'user_id': self.partner_id.user_id.id, | ||
'project_id': self.id | ||
}) | ||
# Get other invoice values from partner onchange | ||
sale.onchange_partner_id() | ||
return sale._convert_to_write(sale._cache) | ||
|
||
@api.multi | ||
def _create_invoice(self): | ||
self.ensure_one() | ||
if self.type == 'invoice': | ||
return super(AccountAnalyticAccount, self)._create_invoice() | ||
else: | ||
sale_vals = self._prepare_sale() | ||
sale = self.env['sale.order'].create(sale_vals) | ||
for line in self.recurring_invoice_line_ids: | ||
sale_line_vals = self._prepare_sale_line(line, sale.id) | ||
self.env['sale.order.line'].create(sale_line_vals) | ||
if self.sale_autoconfirm: | ||
sale.action_confirm() | ||
return sale |
20 changes: 20 additions & 0 deletions
20
contract_sale_generation/models/account_analytic_contract.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# -*- coding: utf-8 -*- | ||
# Copyright 2017 Pesol (<http://pesol.es>) | ||
# Copyright 2017 Angel Moya <[email protected]> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
from odoo import fields, models | ||
|
||
|
||
class AccountAnalyticContract(models.Model): | ||
_inherit = 'account.analytic.contract' | ||
|
||
type = fields.Selection( | ||
string='Type', | ||
selection=[('invoice', 'Invoice'), | ||
('sale', 'Sale')], | ||
default='invoice', | ||
required=True, | ||
) | ||
sale_autoconfirm = fields.Boolean( | ||
string='Sale autoconfirm') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# -*- coding: utf-8 -*- | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
from . import test_contract_invoice | ||
from . import test_contract_sale |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
# -*- coding: utf-8 -*- | ||
# © 2016 Carlos Dauden <[email protected]> | ||
# Copyright 2017 Pesol (<http://pesol.es>) | ||
# Copyright 2017 Angel Moya <[email protected]> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
from odoo.exceptions import ValidationError | ||
from odoo.tests.common import TransactionCase | ||
|
||
|
||
class TestContractInvoice(TransactionCase): | ||
# Use case : Prepare some data for current test case | ||
|
||
def setUp(self): | ||
super(TestContractInvoice, self).setUp() | ||
self.partner = self.env.ref('base.res_partner_2') | ||
self.product = self.env.ref('product.product_product_2') | ||
self.product.taxes_id += self.env['account.tax'].search( | ||
[('type_tax_use', '=', 'sale')], limit=1) | ||
self.product.description_sale = 'Test description sale' | ||
self.template_vals = { | ||
'recurring_rule_type': 'yearly', | ||
'recurring_interval': 1, | ||
'name': 'Test Contract Template', | ||
'type': 'invoice' | ||
} | ||
self.template = self.env['account.analytic.contract'].create( | ||
self.template_vals, | ||
) | ||
self.contract = self.env['account.analytic.account'].create({ | ||
'name': 'Test Contract', | ||
'partner_id': self.partner.id, | ||
'pricelist_id': self.partner.property_product_pricelist.id, | ||
'recurring_invoices': True, | ||
'date_start': '2016-02-15', | ||
'recurring_next_date': '2016-02-29', | ||
}) | ||
self.contract.contract_template_id = self.template | ||
self.contract._onchange_contract_template_id() | ||
self.contract_line = self.env['account.analytic.invoice.line'].create({ | ||
'analytic_account_id': self.contract.id, | ||
'product_id': self.product.id, | ||
'name': 'Services from #START# to #END#', | ||
'quantity': 1, | ||
'uom_id': self.product.uom_id.id, | ||
'price_unit': 100, | ||
'discount': 50, | ||
}) | ||
|
||
def test_check_discount(self): | ||
with self.assertRaises(ValidationError): | ||
self.contract_line.write({'discount': 120}) | ||
|
||
def test_contract(self): | ||
self.assertAlmostEqual(self.contract_line.price_subtotal, 50.0) | ||
res = self.contract_line._onchange_product_id() | ||
self.assertIn('uom_id', res['domain']) | ||
self.contract_line.price_unit = 100.0 | ||
|
||
self.contract.partner_id = False | ||
with self.assertRaises(ValidationError): | ||
self.contract.recurring_create_invoice() | ||
self.contract.partner_id = self.partner.id | ||
|
||
self.contract.recurring_create_invoice() | ||
self.invoice_monthly = self.env['account.invoice'].search( | ||
[('contract_id', '=', self.contract.id)]) | ||
self.assertTrue(self.invoice_monthly) | ||
self.assertEqual(self.contract.recurring_next_date, '2017-02-28') | ||
|
||
self.inv_line = self.invoice_monthly.invoice_line_ids[0] | ||
self.assertTrue(self.inv_line.invoice_line_tax_ids) | ||
self.assertAlmostEqual(self.inv_line.price_subtotal, 50.0) | ||
self.assertEqual(self.contract.partner_id.user_id, | ||
self.invoice_monthly.user_id) | ||
|
||
def test_onchange_contract_template_id(self): | ||
""" It should change the contract values to match the template. """ | ||
self.contract.contract_template_id = self.template | ||
self.contract._onchange_contract_template_id() | ||
res = { | ||
'recurring_rule_type': self.contract.recurring_rule_type, | ||
'recurring_interval': self.contract.recurring_interval, | ||
'type': 'invoice' | ||
} | ||
del self.template_vals['name'] | ||
self.assertDictEqual(res, self.template_vals) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
# -*- coding: utf-8 -*- | ||
# © 2016 Carlos Dauden <[email protected]> | ||
# Copyright 2017 Pesol (<http://pesol.es>) | ||
# Copyright 2017 Angel Moya <[email protected]> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
from odoo.exceptions import ValidationError | ||
from odoo.tests.common import TransactionCase | ||
|
||
|
||
class TestContractSale(TransactionCase): | ||
# Use case : Prepare some data for current test case | ||
|
||
def setUp(self): | ||
super(TestContractSale, self).setUp() | ||
self.partner = self.env.ref('base.res_partner_2') | ||
self.product = self.env.ref('product.product_product_2') | ||
self.product.taxes_id += self.env['account.tax'].search( | ||
[('type_tax_use', '=', 'sale')], limit=1) | ||
self.product.description_sale = 'Test description sale' | ||
self.template_vals = { | ||
'recurring_rule_type': 'yearly', | ||
'recurring_interval': 1, | ||
'name': 'Test Contract Template', | ||
'type': 'sale', | ||
'sale_autoconfirm': False | ||
} | ||
self.template = self.env['account.analytic.contract'].create( | ||
self.template_vals, | ||
) | ||
self.contract = self.env['account.analytic.account'].create({ | ||
'name': 'Test Contract', | ||
'partner_id': self.partner.id, | ||
'pricelist_id': self.partner.property_product_pricelist.id, | ||
'recurring_invoices': True, | ||
'date_start': '2016-02-15', | ||
'recurring_next_date': '2016-02-29', | ||
}) | ||
self.contract.contract_template_id = self.template | ||
self.contract._onchange_contract_template_id() | ||
self.contract_line = self.env['account.analytic.invoice.line'].create({ | ||
'analytic_account_id': self.contract.id, | ||
'product_id': self.product.id, | ||
'name': 'Services from #START# to #END#', | ||
'quantity': 1, | ||
'uom_id': self.product.uom_id.id, | ||
'price_unit': 100, | ||
'discount': 50, | ||
}) | ||
|
||
def test_check_discount(self): | ||
with self.assertRaises(ValidationError): | ||
self.contract_line.write({'discount': 120}) | ||
|
||
def test_contract(self): | ||
self.assertAlmostEqual(self.contract_line.price_subtotal, 50.0) | ||
res = self.contract_line._onchange_product_id() | ||
self.assertIn('uom_id', res['domain']) | ||
self.contract_line.price_unit = 100.0 | ||
|
||
self.contract.partner_id = False | ||
with self.assertRaises(ValidationError): | ||
self.contract.recurring_create_invoice() | ||
self.contract.partner_id = self.partner.id | ||
|
||
self.contract.recurring_create_invoice() | ||
self.sale_monthly = self.env['sale.order'].search( | ||
[('project_id', '=', self.contract.id), | ||
('state', '=', 'draft')]) | ||
self.assertTrue(self.sale_monthly) | ||
self.assertEqual(self.contract.recurring_next_date, '2017-02-28') | ||
|
||
self.sale_line = self.sale_monthly.order_line[0] | ||
self.assertAlmostEqual(self.sale_line.price_subtotal, 50.0) | ||
self.assertEqual(self.contract.partner_id.user_id, | ||
self.sale_monthly.user_id) | ||
|
||
def test_contract_autoconfirm(self): | ||
self.contract.sale_autoconfirm = True | ||
self.assertAlmostEqual(self.contract_line.price_subtotal, 50.0) | ||
res = self.contract_line._onchange_product_id() | ||
self.assertIn('uom_id', res['domain']) | ||
self.contract_line.price_unit = 100.0 | ||
|
||
self.contract.partner_id = False | ||
with self.assertRaises(ValidationError): | ||
self.contract.recurring_create_invoice() | ||
self.contract.partner_id = self.partner.id | ||
|
||
self.contract.recurring_create_invoice() | ||
self.sale_monthly = self.env['sale.order'].search( | ||
[('project_id', '=', self.contract.id), | ||
('state', '=', 'sale')]) | ||
self.assertTrue(self.sale_monthly) | ||
self.assertEqual(self.contract.recurring_next_date, '2017-02-28') | ||
|
||
self.sale_line = self.sale_monthly.order_line[0] | ||
self.assertAlmostEqual(self.sale_line.price_subtotal, 50.0) | ||
self.assertEqual(self.contract.partner_id.user_id, | ||
self.sale_monthly.user_id) | ||
|
||
def test_onchange_contract_template_id(self): | ||
""" It should change the contract values to match the template. """ | ||
self.contract.contract_template_id = self.template | ||
self.contract._onchange_contract_template_id() | ||
res = { | ||
'recurring_rule_type': self.contract.recurring_rule_type, | ||
'recurring_interval': self.contract.recurring_interval, | ||
'type': 'sale', | ||
'sale_autoconfirm': False | ||
} | ||
del self.template_vals['name'] | ||
self.assertDictEqual(res, self.template_vals) |
Oops, something went wrong.