From a202690c16f187909dfa0500dc5554d51485d80d Mon Sep 17 00:00:00 2001 From: Kitti U Date: Wed, 12 Jun 2019 22:05:11 +0700 Subject: [PATCH 01/67] [12.0][ADD] purchase_deposit --- purchase_deposit/__init__.py | 6 + purchase_deposit/__manifest__.py | 23 +++ purchase_deposit/models/__init__.py | 7 + purchase_deposit/models/invoice.py | 16 ++ purchase_deposit/models/purchase.py | 28 +++ .../models/res_config_settings.py | 17 ++ purchase_deposit/readme/CONFIGURATION.rst | 7 + purchase_deposit/readme/CONTRIBUTORS.rst | 2 + purchase_deposit/readme/DESCRIPTION.rst | 1 + purchase_deposit/readme/USAGE.rst | 9 + purchase_deposit/tests/__init__.py | 5 + .../tests/test_purchase_deposit.py | 134 ++++++++++++ purchase_deposit/views/purchase_view.xml | 17 ++ .../views/res_config_settings_views.xml | 27 +++ purchase_deposit/wizard/__init__.py | 5 + .../wizard/purchase_make_invoice_advance.py | 192 ++++++++++++++++++ .../purchase_make_invoice_advance_views.xml | 41 ++++ 17 files changed, 537 insertions(+) create mode 100644 purchase_deposit/__init__.py create mode 100644 purchase_deposit/__manifest__.py create mode 100644 purchase_deposit/models/__init__.py create mode 100644 purchase_deposit/models/invoice.py create mode 100644 purchase_deposit/models/purchase.py create mode 100644 purchase_deposit/models/res_config_settings.py create mode 100644 purchase_deposit/readme/CONFIGURATION.rst create mode 100644 purchase_deposit/readme/CONTRIBUTORS.rst create mode 100644 purchase_deposit/readme/DESCRIPTION.rst create mode 100644 purchase_deposit/readme/USAGE.rst create mode 100644 purchase_deposit/tests/__init__.py create mode 100644 purchase_deposit/tests/test_purchase_deposit.py create mode 100644 purchase_deposit/views/purchase_view.xml create mode 100644 purchase_deposit/views/res_config_settings_views.xml create mode 100644 purchase_deposit/wizard/__init__.py create mode 100644 purchase_deposit/wizard/purchase_make_invoice_advance.py create mode 100644 purchase_deposit/wizard/purchase_make_invoice_advance_views.xml diff --git a/purchase_deposit/__init__.py b/purchase_deposit/__init__.py new file mode 100644 index 00000000000..3a8c5973678 --- /dev/null +++ b/purchase_deposit/__init__.py @@ -0,0 +1,6 @@ +# Copyright 2019 Elico Corp, Dominique K. +# Copyright 2019 Ecosoft Co., Ltd., Kitti U. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import models +from . import wizard diff --git a/purchase_deposit/__manifest__.py b/purchase_deposit/__manifest__.py new file mode 100644 index 00000000000..2103cddd870 --- /dev/null +++ b/purchase_deposit/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2019 Elico Corp, Dominique K. +# Copyright 2019 Ecosoft Co., Ltd., Kitti U. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + 'name': 'Purchase Deposit', + 'version': '12.0.1.0.0', + 'summary': 'Option to create deposit from purchase order', + 'author': 'Elico Corp, Ecosoft, Odoo Community Association (OCA)', + 'website': 'https://github.com/OCA/purchase-workflow', + 'category': 'Purchase Management', + 'license': 'AGPL-3', + 'depends': [ + 'purchase', + ], + 'data': [ + 'wizard/purchase_make_invoice_advance_views.xml', + 'views/res_config_settings_views.xml', + 'views/purchase_view.xml', + ], + 'installable': True, + 'auto_install': False, +} diff --git a/purchase_deposit/models/__init__.py b/purchase_deposit/models/__init__.py new file mode 100644 index 00000000000..4cfb36fd4df --- /dev/null +++ b/purchase_deposit/models/__init__.py @@ -0,0 +1,7 @@ +# Copyright 2019 Elico Corp, Dominique K. +# Copyright 2019 Ecosoft Co., Ltd., Kitti U. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import res_config_settings +from . import purchase +from . import invoice diff --git a/purchase_deposit/models/invoice.py b/purchase_deposit/models/invoice.py new file mode 100644 index 00000000000..22da25bd8e5 --- /dev/null +++ b/purchase_deposit/models/invoice.py @@ -0,0 +1,16 @@ +# Copyright 2019 Elico Corp, Dominique K. +# Copyright 2019 Ecosoft Co., Ltd., Kitti U. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import models + + +class AccountInvoice(models.Model): + _inherit = 'account.invoice' + + def _prepare_invoice_line_from_po_line(self, line): + res = super(AccountInvoice, self).\ + _prepare_invoice_line_from_po_line(line) + if line.is_deposit: + res['quantity'] = -1 * line.qty_invoiced + return res diff --git a/purchase_deposit/models/purchase.py b/purchase_deposit/models/purchase.py new file mode 100644 index 00000000000..c31c4a41890 --- /dev/null +++ b/purchase_deposit/models/purchase.py @@ -0,0 +1,28 @@ +# Copyright 2019 Elico Corp, Dominique K. +# Copyright 2019 Ecosoft Co., Ltd., Kitti U. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class PurchaseOrder(models.Model): + _inherit = 'purchase.order' + + @api.multi + def copy_data(self, default=None): + if default is None: + default = {} + default['order_line'] = \ + [(0, 0, line.copy_data()[0]) + for line in self.order_line.filtered(lambda l: not l.is_deposit)] + return super(PurchaseOrder, self).copy_data(default) + + +class PurchaseOrderLine(models.Model): + _inherit = 'purchase.order.line' + + is_deposit = fields.Boolean( + string='Is a deposit payment', + help="Deposit payments are made when creating invoices from a purhcase" + " order. They are not copied when duplicating a purchase order.", + ) diff --git a/purchase_deposit/models/res_config_settings.py b/purchase_deposit/models/res_config_settings.py new file mode 100644 index 00000000000..7628530117b --- /dev/null +++ b/purchase_deposit/models/res_config_settings.py @@ -0,0 +1,17 @@ +# Copyright 2019 Elico Corp, Dominique K. +# Copyright 2019 Ecosoft Co., Ltd., Kitti U. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import models, fields + + +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + default_purchase_deposit_product_id = fields.Many2one( + comodel_name='product.product', + string='Purchase Deposit Product', + default_model='purchase.advance.payment.inv', + domain=[('type', '=', 'service')], + help="Default product used for payment advances.", + ) diff --git a/purchase_deposit/readme/CONFIGURATION.rst b/purchase_deposit/readme/CONFIGURATION.rst new file mode 100644 index 00000000000..ebdedd23334 --- /dev/null +++ b/purchase_deposit/readme/CONFIGURATION.rst @@ -0,0 +1,7 @@ +After install this module, you need to select the product "Purchase Deposit" in +configuration window to be used as default product when create deposit invoice. + +Go to Purchase > Configuration > Settings, then select product "Purchase Deposit" + +Note: If this is not done, by using "Register Deposit" for the first time will +also create product "Purchase Deposit" for you automatically. diff --git a/purchase_deposit/readme/CONTRIBUTORS.rst b/purchase_deposit/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..5673dd01dc8 --- /dev/null +++ b/purchase_deposit/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Dominique K. +* Kitti Upariphutthiphong diff --git a/purchase_deposit/readme/DESCRIPTION.rst b/purchase_deposit/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..6c3209c6d2b --- /dev/null +++ b/purchase_deposit/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module allow purchase order to register deposit similar to that in sales order diff --git a/purchase_deposit/readme/USAGE.rst b/purchase_deposit/readme/USAGE.rst new file mode 100644 index 00000000000..74ee733d7de --- /dev/null +++ b/purchase_deposit/readme/USAGE.rst @@ -0,0 +1,9 @@ +When purchase order is confirmed, a new button "Register Deposit" will appear. +Normally, deposit will be used to create the 1st invoice (as deposit). + +#. On confirmed purchase order, click Register Deposit button, wizard will open +#. 2 type of deposit invoice can be create 1) Down Payment (percentage) 2) Deposit Payment (fixed amount) +#. Fill in the value and click Create Invoice button. + +As deposit is created, when user click button "Create Bill" again in purchase order, +the Vendor Bill will be created with deposit amount deducted. diff --git a/purchase_deposit/tests/__init__.py b/purchase_deposit/tests/__init__.py new file mode 100644 index 00000000000..78727aba356 --- /dev/null +++ b/purchase_deposit/tests/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2019 Elico Corp, Dominique K. +# Copyright 2019 Ecosoft Co., Ltd., Kitti U. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import test_purchase_deposit diff --git a/purchase_deposit/tests/test_purchase_deposit.py b/purchase_deposit/tests/test_purchase_deposit.py new file mode 100644 index 00000000000..0f9a48afd91 --- /dev/null +++ b/purchase_deposit/tests/test_purchase_deposit.py @@ -0,0 +1,134 @@ +# Copyright 2019 Elico Corp, Dominique K. +# Copyright 2019 Ecosoft Co., Ltd., Kitti U. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields +from odoo.tests.common import TransactionCase +from odoo.exceptions import UserError +from odoo.tests.common import Form + + +class TestPurchaseDeposit(TransactionCase): + + def setUp(self): + super(TestPurchaseDeposit, self).setUp() + self.product_model = self.env['product.product'] + self.account_model = self.env['account.account'] + self.invoice_model = self.env['account.invoice'] + self.default_model = self.env['ir.default'] + + # Create Deposit Account + self.account_deposit = self.account_model.create({ + 'name': 'Purchase Deposit', + 'code': '11620', + 'user_type_id': self.env.ref( + 'account.data_account_type_current_assets').id, + }) + # Create products: + p1 = self.product1 = self.product_model.create({ + 'name': 'Test Product 1', + 'type': 'service', + 'default_code': 'PROD1', + 'purchase_method': 'purchase', + }) + + self.po = self.env['purchase.order'].create({ + 'partner_id': self.ref('base.res_partner_3'), + 'order_line': [ + (0, 0, {'product_id': p1.id, + 'product_uom': p1.uom_id.id, + 'name': p1.name, + 'price_unit': 100.0, + 'date_planned': fields.Datetime.now(), + 'product_qty': 42.0})]}) + + def test_create_deposit_invoice(self): + self.assertEqual(len(self.po.order_line), 1) + # We create invoice from expense + ctx = {'active_id': self.po.id, + 'active_ids': [self.po.id], + 'active_model': 'purchase.order'} + CreateDeposit = self.env['purchase.advance.payment.inv'] + self.po.button_confirm() + with Form(CreateDeposit.with_context(ctx)) as f: + f.advance_payment_method = 'percentage' + f.deposit_account_id = self.account_deposit + wizard = f.save() + wizard.amount = 10.0 # 10% + wizard.create_invoices() + # New Purchase Deposit is created automatically + deposit_id = self.default_model.get('purchase.advance.payment.inv', + 'purchase_deposit_product_id') + deposit = self.product_model.browse(deposit_id) + self.assertEqual(deposit.name, 'Purchase Deposit') + # 1 Deposit Invoice is created + self.assertRecordValues(self.po.invoice_ids.invoice_line_ids, [ + {'product_id': deposit.id, + 'price_unit': 420.0, 'name': 'Deposit Payment', }, + ]) + # On Purchase Order, there will be new deposit line create + self.assertRecordValues(self.po.order_line, [ + {'product_id': self.product1.id, + 'price_unit': 100.0, 'is_deposit': False}, + {'product_id': deposit.id, + 'price_unit': 420.0, 'is_deposit': True}, + ]) + # On Purchase Order, create normal billing + res = self.po.with_context(create_bill=True).action_view_invoice() + ctx = res.get('context') + f = Form(self.invoice_model.with_context(ctx), + view='account.invoice_supplier_form') + invoice = f.save() + self.assertRecordValues(invoice.invoice_line_ids, [ + {'product_id': self.product1.id, + 'price_unit': 100.0, 'quantity': 42}, + {'product_id': deposit.id, + 'price_unit': 420.0, 'quantity': -1}, + ]) + + def test_create_deposit_invoice_exception(self): + """This test focus on exception cases, when create deposit invoice, + 1. This action is allowed only in Purchase Order sate + 2. The value of the deposit must be positive + 3. For type percentage, The percentage of the deposit must <= 100 + 4. Purchase Deposit Product's purchase_method != purchase + 5. Purchase Deposit Product's type != service + """ + self.assertEqual(len(self.po.order_line), 1) + # We create invoice from expense + ctx = {'active_id': self.po.id, + 'active_ids': [self.po.id], + 'active_model': 'purchase.order'} + CreateDeposit = self.env['purchase.advance.payment.inv'] + # 1. This action is allowed only in Purchase Order sate + with self.assertRaises(UserError): + Form(CreateDeposit.with_context(ctx)) # Initi wizard + self.po.button_confirm() + self.assertEqual(self.po.state, 'purchase') + # 2. The value of the deposit must be positive + f = Form(CreateDeposit.with_context(ctx)) + f.advance_payment_method = 'fixed' + f.amount = 0.0 + f.deposit_account_id = self.account_deposit + wizard = f.save() + with self.assertRaises(UserError): + wizard.create_invoices() + # 3. For type percentage, The percentage of the deposit must <= 100 + wizard.advance_payment_method = 'percentage' + wizard.amount = 101.0 + with self.assertRaises(UserError): + wizard.create_invoices() + wizard.amount = 10 + # 4. Purchase Deposit Product's purchase_method != purchase + deposit_id = self.default_model.get('purchase.advance.payment.inv', + 'purchase_deposit_product_id') + deposit = self.product_model.browse(deposit_id) + deposit.purchase_method = 'receive' + wizard.purchase_deposit_product_id = deposit + with self.assertRaises(UserError): + wizard.create_invoices() + deposit.purchase_method = 'purchase' + # 5. Purchase Deposit Product's type != service + deposit.type = 'consu' + with self.assertRaises(UserError): + wizard.create_invoices() diff --git a/purchase_deposit/views/purchase_view.xml b/purchase_deposit/views/purchase_view.xml new file mode 100644 index 00000000000..55f59408d94 --- /dev/null +++ b/purchase_deposit/views/purchase_view.xml @@ -0,0 +1,17 @@ + + + + + view.purchase.order.inherit + purchase.order + form + + + +