From a22d11f4bf900173af14523413c38ba22f13fcdf Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Thu, 9 Jul 2020 15:22:16 -0700 Subject: [PATCH 01/25] [IMP] attachment_minio: add post migration script to move some views out of attachment storage Every database I've run the search (for s3 attachments for specific fields or models) on has not returned any attachments, but it seems safe enough in general, and presumably happens when migrating from older versions. --- .../migrations/13.0.0.0.1/post-migration.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 attachment_minio/migrations/13.0.0.0.1/post-migration.py diff --git a/attachment_minio/migrations/13.0.0.0.1/post-migration.py b/attachment_minio/migrations/13.0.0.0.1/post-migration.py new file mode 100644 index 00000000..fefe6dbf --- /dev/null +++ b/attachment_minio/migrations/13.0.0.0.1/post-migration.py @@ -0,0 +1,60 @@ +# Copyright 2020 Hibou Corp. +# Copyright 2016-2019 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + +import logging + +from contextlib import closing + +import odoo + +_logger = logging.getLogger(__name__) + + +def migrate(cr, version): + if not version: + return + cr.execute(""" + SELECT value FROM ir_config_parameter + WHERE key = 'ir_attachment.location' + """) + row = cr.fetchone() + + if row[0] == 's3': + uid = odoo.SUPERUSER_ID + registry = odoo.modules.registry.RegistryManager.get(cr.dbname) + new_cr = registry.cursor() + with closing(new_cr): + with odoo.api.Environment.manage(): + env = odoo.api.Environment(new_cr, uid, {}) + store_local = env['ir.attachment'].search( + [('store_fname', '=like', 's3://%'), + '|', ('res_model', '=', 'ir.ui.view'), + ('res_field', 'in', ['image_small', + 'image_medium', + 'web_icon_data']) + ], + ) + + _logger.info( + 'Moving %d attachments from S3 to DB for fast access', + len(store_local) + ) + for attachment_id in store_local.ids: + # force re-storing the document, will move + # it from the object storage to the database + + # This is a trick to avoid having the 'datas' function + # fields computed for every attachment on each + # iteration of the loop. The former issue being that + # it reads the content of the file of ALL the + # attachments on each loop. + try: + env.clear() + attachment = env['ir.attachment'].browse(attachment_id) + _logger.info('Moving attachment %s (id: %s)', + attachment.name, attachment.id) + attachment.write({'datas': attachment.datas}) + new_cr.commit() + except: + new_cr.rollback() From a3f49982aabf999049d21ca1fad5a93a88b119b2 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Thu, 9 Jul 2020 18:46:37 -0700 Subject: [PATCH 02/25] [IMP] hr_commission: remove menus in Sales because we don't depend on `sale` --- hr_commission/tests/test_commission.py | 4 ++++ hr_commission/views/commission_views.xml | 21 --------------------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/hr_commission/tests/test_commission.py b/hr_commission/tests/test_commission.py index 688513d1..f921f30a 100644 --- a/hr_commission/tests/test_commission.py +++ b/hr_commission/tests/test_commission.py @@ -2,6 +2,10 @@ from odoo.tests import common +# TODO Tests won't pass without `sale` +# Tests should be refactored to not build sale orders +# to invoice, just create invoices directly. + class TestCommission(common.TransactionCase): diff --git a/hr_commission/views/commission_views.xml b/hr_commission/views/commission_views.xml index 0e99a965..62a695ca 100644 --- a/hr_commission/views/commission_views.xml +++ b/hr_commission/views/commission_views.xml @@ -116,20 +116,6 @@ tree,form,pivot,graph - - - - tree,form - - Date: Thu, 20 Jun 2019 10:08:00 -0700 Subject: [PATCH 03/25] Initial commit of `sale_credit_limit` for 11.0 --- sale_credit_limit/__init__.py | 1 + sale_credit_limit/__manifest__.py | 29 ++++++++++++++++++++++ sale_credit_limit/data/sale_exceptions.xml | 21 ++++++++++++++++ sale_credit_limit/models/__init__.py | 1 + sale_credit_limit/models/sale.py | 17 +++++++++++++ sale_credit_limit/views/partner_views.xml | 15 +++++++++++ 6 files changed, 84 insertions(+) create mode 100644 sale_credit_limit/__init__.py create mode 100644 sale_credit_limit/__manifest__.py create mode 100644 sale_credit_limit/data/sale_exceptions.xml create mode 100644 sale_credit_limit/models/__init__.py create mode 100644 sale_credit_limit/models/sale.py create mode 100644 sale_credit_limit/views/partner_views.xml diff --git a/sale_credit_limit/__init__.py b/sale_credit_limit/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/sale_credit_limit/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/sale_credit_limit/__manifest__.py b/sale_credit_limit/__manifest__.py new file mode 100644 index 00000000..966faa45 --- /dev/null +++ b/sale_credit_limit/__manifest__.py @@ -0,0 +1,29 @@ +{ + 'name': 'Sale Credit Limit', + 'summary': 'Uses credit limit on Partners to warn salespeople if they are over their limit.', + 'version': '11.0.1.0.0', + 'author': "Hibou Corp.", + 'category': 'Sale', + 'license': 'AGPL-3', + 'complexity': 'expert', + 'images': [], + 'website': "https://hibou.io", + 'description': """ +Uses credit limit on Partners to warn salespeople if they are over their limit. + +When confirming a sale order, the current sale order total will be considered and a Sale Order Exception +will be created if the total would put them over their credit limit. +""", + 'depends': [ + 'sale', + 'account', + 'sale_exception', + ], + 'demo': [], + 'data': [ + 'data/sale_exceptions.xml', + 'views/partner_views.xml', + ], + 'auto_install': False, + 'installable': True, +} diff --git a/sale_credit_limit/data/sale_exceptions.xml b/sale_credit_limit/data/sale_exceptions.xml new file mode 100644 index 00000000..ba593a13 --- /dev/null +++ b/sale_credit_limit/data/sale_exceptions.xml @@ -0,0 +1,21 @@ + + + + + Invoice Partner credit limit exceeded. + The Customer or Invoice Address has a credit limit. + This sale order, or the customer has an outstanding balance that, exceeds their credit limit. + 50 + sale.order + sale + +partner = sale.partner_invoice_id.commercial_partner_id +partner_balance = partner.credit + sale.amount_total +if partner.credit_limit and partner.credit_limit <= partner_balance: + failed = True + + + sale + + + \ No newline at end of file diff --git a/sale_credit_limit/models/__init__.py b/sale_credit_limit/models/__init__.py new file mode 100644 index 00000000..8a0dc04e --- /dev/null +++ b/sale_credit_limit/models/__init__.py @@ -0,0 +1 @@ +from . import sale diff --git a/sale_credit_limit/models/sale.py b/sale_credit_limit/models/sale.py new file mode 100644 index 00000000..ff40a466 --- /dev/null +++ b/sale_credit_limit/models/sale.py @@ -0,0 +1,17 @@ +from odoo import api, models + + +class SaleOrder(models.Model): + _inherit = 'sale.order' + + @api.onchange('partner_invoice_id') + def _onchange_partner_invoice_id(self): + for so in self: + partner = so.partner_invoice_id.commercial_partner_id + if partner.credit_limit and partner.credit_limit <= partner.credit: + m = 'Partner outstanding receivables %0.2f is above their credit limit of %0.2f' \ + % (partner.credit, partner.credit_limit) + return { + 'warning': {'title': 'Sale Credit Limit', + 'message': m} + } diff --git a/sale_credit_limit/views/partner_views.xml b/sale_credit_limit/views/partner_views.xml new file mode 100644 index 00000000..009ee4be --- /dev/null +++ b/sale_credit_limit/views/partner_views.xml @@ -0,0 +1,15 @@ + + + + + res.partner.form.inherit + res.partner + + + + + + + + + \ No newline at end of file From 41dc9405dedb62a4a568df83bd0dc940f0f3fc4a Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Thu, 20 Jun 2019 15:15:54 -0700 Subject: [PATCH 04/25] IMP `sale_credit_limit` Include the Currency and Currency formatting in SO warning. --- sale_credit_limit/models/sale.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sale_credit_limit/models/sale.py b/sale_credit_limit/models/sale.py index ff40a466..5b1f9b89 100644 --- a/sale_credit_limit/models/sale.py +++ b/sale_credit_limit/models/sale.py @@ -1,4 +1,5 @@ from odoo import api, models +from odoo.addons.mail.models.mail_template import format_amount class SaleOrder(models.Model): @@ -9,8 +10,9 @@ class SaleOrder(models.Model): for so in self: partner = so.partner_invoice_id.commercial_partner_id if partner.credit_limit and partner.credit_limit <= partner.credit: - m = 'Partner outstanding receivables %0.2f is above their credit limit of %0.2f' \ - % (partner.credit, partner.credit_limit) + m = 'Partner outstanding receivables %s is above their credit limit of %s' \ + % (format_amount(self.env, partner.credit, so.currency_id), + format_amount(self.env, partner.credit_limit, so.currency_id)) return { 'warning': {'title': 'Sale Credit Limit', 'message': m} From 7e7c7ef488f4814382809a374ac603bf69c7fb39 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Tue, 3 Sep 2019 14:03:09 -0700 Subject: [PATCH 05/25] MIG `sale_credit_limit` for 12.0 --- sale_credit_limit/__manifest__.py | 2 +- sale_credit_limit/views/partner_views.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sale_credit_limit/__manifest__.py b/sale_credit_limit/__manifest__.py index 966faa45..f0291499 100644 --- a/sale_credit_limit/__manifest__.py +++ b/sale_credit_limit/__manifest__.py @@ -1,7 +1,7 @@ { 'name': 'Sale Credit Limit', 'summary': 'Uses credit limit on Partners to warn salespeople if they are over their limit.', - 'version': '11.0.1.0.0', + 'version': '12.0.1.0.0', 'author': "Hibou Corp.", 'category': 'Sale', 'license': 'AGPL-3', diff --git a/sale_credit_limit/views/partner_views.xml b/sale_credit_limit/views/partner_views.xml index 009ee4be..e9dbe0ae 100644 --- a/sale_credit_limit/views/partner_views.xml +++ b/sale_credit_limit/views/partner_views.xml @@ -6,7 +6,7 @@ res.partner - + From aaa65b1c5fc4154e72b70089b876d64f6c5e5311 Mon Sep 17 00:00:00 2001 From: Bhoomi Vaishnani Date: Tue, 14 Jul 2020 12:30:11 -0400 Subject: [PATCH 06/25] [MIG] sale_credit_limit: For Odoo13.0 --- sale_credit_limit/__manifest__.py | 2 +- sale_credit_limit/data/sale_exceptions.xml | 2 -- sale_credit_limit/models/sale.py | 7 ++-- sale_credit_limit/tests/__init__.py | 1 + .../tests/test_sale_credit_exception.py | 32 +++++++++++++++++++ 5 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 sale_credit_limit/tests/__init__.py create mode 100644 sale_credit_limit/tests/test_sale_credit_exception.py diff --git a/sale_credit_limit/__manifest__.py b/sale_credit_limit/__manifest__.py index f0291499..30ec8fe4 100644 --- a/sale_credit_limit/__manifest__.py +++ b/sale_credit_limit/__manifest__.py @@ -1,7 +1,7 @@ { 'name': 'Sale Credit Limit', 'summary': 'Uses credit limit on Partners to warn salespeople if they are over their limit.', - 'version': '12.0.1.0.0', + 'version': '13.0.1.0.0', 'author': "Hibou Corp.", 'category': 'Sale', 'license': 'AGPL-3', diff --git a/sale_credit_limit/data/sale_exceptions.xml b/sale_credit_limit/data/sale_exceptions.xml index ba593a13..b8f6192d 100644 --- a/sale_credit_limit/data/sale_exceptions.xml +++ b/sale_credit_limit/data/sale_exceptions.xml @@ -7,7 +7,6 @@ This sale order, or the customer has an outstanding balance that, exceeds their credit limit. 50 sale.order - sale partner = sale.partner_invoice_id.commercial_partner_id partner_balance = partner.credit + sale.amount_total @@ -15,7 +14,6 @@ if partner.credit_limit and partner.credit_limit <= partner_balance: failed = True - sale \ No newline at end of file diff --git a/sale_credit_limit/models/sale.py b/sale_credit_limit/models/sale.py index 5b1f9b89..5b90514e 100644 --- a/sale_credit_limit/models/sale.py +++ b/sale_credit_limit/models/sale.py @@ -1,5 +1,4 @@ -from odoo import api, models -from odoo.addons.mail.models.mail_template import format_amount +from odoo import api, models, tools class SaleOrder(models.Model): @@ -11,8 +10,8 @@ class SaleOrder(models.Model): partner = so.partner_invoice_id.commercial_partner_id if partner.credit_limit and partner.credit_limit <= partner.credit: m = 'Partner outstanding receivables %s is above their credit limit of %s' \ - % (format_amount(self.env, partner.credit, so.currency_id), - format_amount(self.env, partner.credit_limit, so.currency_id)) + % (tools.format_amount(self.env, partner.credit, so.currency_id), + tools.format_amount(self.env, partner.credit_limit, so.currency_id)) return { 'warning': {'title': 'Sale Credit Limit', 'message': m} diff --git a/sale_credit_limit/tests/__init__.py b/sale_credit_limit/tests/__init__.py new file mode 100644 index 00000000..a2d422d0 --- /dev/null +++ b/sale_credit_limit/tests/__init__.py @@ -0,0 +1 @@ +from . import test_sale_credit_exception diff --git a/sale_credit_limit/tests/test_sale_credit_exception.py b/sale_credit_limit/tests/test_sale_credit_exception.py new file mode 100644 index 00000000..d679661f --- /dev/null +++ b/sale_credit_limit/tests/test_sale_credit_exception.py @@ -0,0 +1,32 @@ + +from odoo.addons.sale_exception.tests.test_sale_exception import TestSaleException + + +class TestSaleCreditException(TestSaleException): + + def setUp(self): + super(TestSaleCreditException, self).setUp() + + def test_sale_order_credit_limit_exception(self): + self.sale_exception_confirm = self.env['sale.exception.confirm'] + exception = self.env.ref('sale_credit_limit.excep_sale_credit_limit') + exception.active = True + partner = self.env.ref('base.res_partner_12') + partner.credit_limit = 100.00 + p = self.env.ref('product.product_product_25_product_template') + so1 = self.env['sale.order'].create({ + 'partner_id': partner.id, + 'partner_invoice_id': partner.id, + 'partner_shipping_id': partner.id, + 'order_line': [(0, 0, {'name': p.name, + 'product_id': p.id, + 'product_uom_qty': 2, + 'product_uom': p.uom_id.id, + 'price_unit': p.list_price})], + 'pricelist_id': self.env.ref('product.list0').id, + }) + + # confirm quotation + so1.action_confirm() + self.assertTrue(so1.state == 'draft') + self.assertFalse(so1.ignore_exception) From d07c542c69a8bd0b262b6c3ab87618c81f129d8a Mon Sep 17 00:00:00 2001 From: Bhoomi Vaishnani Date: Wed, 29 Jul 2020 11:46:06 -0400 Subject: [PATCH 07/25] [IMP] maintenance_usage: Changed access group stock manger to equipment manager and also added product as depend module on mainfest. --- maintenance_usage/__manifest__.py | 1 + maintenance_usage/security/ir.model.access.csv | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/maintenance_usage/__manifest__.py b/maintenance_usage/__manifest__.py index 0687af86..8f0b3554 100644 --- a/maintenance_usage/__manifest__.py +++ b/maintenance_usage/__manifest__.py @@ -16,6 +16,7 @@ Create preventative maintenance requests based on usage. 'website': 'https://hibou.io/', 'depends': [ 'hr_maintenance', + 'product', ], 'data': [ 'security/ir.model.access.csv', diff --git a/maintenance_usage/security/ir.model.access.csv b/maintenance_usage/security/ir.model.access.csv index aea213d8..8c7451e1 100644 --- a/maintenance_usage/security/ir.model.access.csv +++ b/maintenance_usage/security/ir.model.access.csv @@ -1,3 +1,3 @@ "id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" -"manage_maintenance_usage_log","manage maintenance.usage.log","model_maintenance_usage_log","stock.group_stock_manager",1,1,1,1 -"access_maintenance_usage_log","access maintenance.usage.log","model_maintenance_usage_log","base.group_user",1,0,1,0 \ No newline at end of file +"manage_maintenance_usage_log","manage maintenance.usage.log","model_maintenance_usage_log","maintenance.group_equipment_manager",1,1,1,1 +"access_maintenance_usage_log","access maintenance.usage.log","model_maintenance_usage_log","base.group_user",1,0,1,0 From edeb21c8fa45c481d5e0c95db73a8c2e20c45868 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Thu, 27 Aug 2020 09:06:04 -0700 Subject: [PATCH 08/25] [FIX] attachment_minio: Odoo registry class name change --- attachment_minio/migrations/13.0.0.0.1/post-migration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/attachment_minio/migrations/13.0.0.0.1/post-migration.py b/attachment_minio/migrations/13.0.0.0.1/post-migration.py index fefe6dbf..068cdab0 100644 --- a/attachment_minio/migrations/13.0.0.0.1/post-migration.py +++ b/attachment_minio/migrations/13.0.0.0.1/post-migration.py @@ -22,7 +22,7 @@ def migrate(cr, version): if row[0] == 's3': uid = odoo.SUPERUSER_ID - registry = odoo.modules.registry.RegistryManager.get(cr.dbname) + registry = odoo.modules.registry.Registry(cr.dbname) new_cr = registry.cursor() with closing(new_cr): with odoo.api.Environment.manage(): From 216b1db9b579291df8638cda425f9619483c287a Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Wed, 2 Sep 2020 12:17:53 -0700 Subject: [PATCH 09/25] [ADD] logging_json, monitoring_log_requests, monitoring_statsd, monitoring_status: from Camp2camp Cloud --- external/camptocamp-cloud-platform | 2 +- logging_json | 1 + monitoring_log_requests | 1 + monitoring_statsd | 1 + monitoring_status | 1 + 5 files changed, 5 insertions(+), 1 deletion(-) create mode 120000 logging_json create mode 120000 monitoring_log_requests create mode 120000 monitoring_statsd create mode 120000 monitoring_status diff --git a/external/camptocamp-cloud-platform b/external/camptocamp-cloud-platform index 298050ba..af8cee48 160000 --- a/external/camptocamp-cloud-platform +++ b/external/camptocamp-cloud-platform @@ -1 +1 @@ -Subproject commit 298050ba520e8becd1e1ef1dea5f127979ac3795 +Subproject commit af8cee48fb6b9a8a6acf924d40409cc693a2aa0c diff --git a/logging_json b/logging_json new file mode 120000 index 00000000..a7b34d54 --- /dev/null +++ b/logging_json @@ -0,0 +1 @@ +./external/camptocamp-cloud-platform/logging_json \ No newline at end of file diff --git a/monitoring_log_requests b/monitoring_log_requests new file mode 120000 index 00000000..854c2986 --- /dev/null +++ b/monitoring_log_requests @@ -0,0 +1 @@ +./external/camptocamp-cloud-platform/monitoring_log_requests \ No newline at end of file diff --git a/monitoring_statsd b/monitoring_statsd new file mode 120000 index 00000000..f73264ee --- /dev/null +++ b/monitoring_statsd @@ -0,0 +1 @@ +./external/camptocamp-cloud-platform/monitoring_statsd \ No newline at end of file diff --git a/monitoring_status b/monitoring_status new file mode 120000 index 00000000..db6885a8 --- /dev/null +++ b/monitoring_status @@ -0,0 +1 @@ +./external/camptocamp-cloud-platform/monitoring_status \ No newline at end of file From 202ce5bc845879af742a36e48f44890743c1fd91 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Wed, 2 Sep 2020 17:12:00 -0700 Subject: [PATCH 10/25] [ADD] hr_payroll_batch_error_skip: for Odoo 13.0 --- hr_payroll_batch_error_skip/__init__.py | 1 + hr_payroll_batch_error_skip/__manifest__.py | 17 +++++++++++++++++ hr_payroll_batch_error_skip/wizard/__init__.py | 1 + .../wizard/hr_payroll_payslips_by_employees.py | 16 ++++++++++++++++ 4 files changed, 35 insertions(+) create mode 100755 hr_payroll_batch_error_skip/__init__.py create mode 100755 hr_payroll_batch_error_skip/__manifest__.py create mode 100644 hr_payroll_batch_error_skip/wizard/__init__.py create mode 100644 hr_payroll_batch_error_skip/wizard/hr_payroll_payslips_by_employees.py diff --git a/hr_payroll_batch_error_skip/__init__.py b/hr_payroll_batch_error_skip/__init__.py new file mode 100755 index 00000000..40272379 --- /dev/null +++ b/hr_payroll_batch_error_skip/__init__.py @@ -0,0 +1 @@ +from . import wizard diff --git a/hr_payroll_batch_error_skip/__manifest__.py b/hr_payroll_batch_error_skip/__manifest__.py new file mode 100755 index 00000000..cd4cc810 --- /dev/null +++ b/hr_payroll_batch_error_skip/__manifest__.py @@ -0,0 +1,17 @@ +{ + 'name': 'Payroll Batch Work Entry Errork SKIP', + 'description': 'This module bypasses a blocking error on payroll batch runs. ' + 'If your business does not depend on the stock functionality ' + '(e.g. you use Timesheet and salary but not the stock work schedule ' + 'calculations), this will alleviate your blocking issues.', + 'version': '13.0.1.0.0', + 'website': 'https://hibou.io/', + 'author': 'Hibou Corp. ', + 'license': 'AGPL-3', + 'category': 'Human Resources', + 'data': [ + ], + 'depends': [ + 'hr_payroll', + ], +} diff --git a/hr_payroll_batch_error_skip/wizard/__init__.py b/hr_payroll_batch_error_skip/wizard/__init__.py new file mode 100644 index 00000000..8d73dcf7 --- /dev/null +++ b/hr_payroll_batch_error_skip/wizard/__init__.py @@ -0,0 +1 @@ +from . import hr_payroll_payslips_by_employees diff --git a/hr_payroll_batch_error_skip/wizard/hr_payroll_payslips_by_employees.py b/hr_payroll_batch_error_skip/wizard/hr_payroll_payslips_by_employees.py new file mode 100644 index 00000000..93672ef4 --- /dev/null +++ b/hr_payroll_batch_error_skip/wizard/hr_payroll_payslips_by_employees.py @@ -0,0 +1,16 @@ +import logging +from odoo import models +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + + +class HrPayslipEmployees(models.TransientModel): + _inherit = 'hr.payslip.employees' + + def _check_undefined_slots(self, work_entries, payslip_run): + try: + super()._check_undefined_slots(work_entries, payslip_run) + except UserError as e: + _logger.info('Caught user error when checking for undefined slots: ' + str(e)) + From 178cfbf0ae68cc43093b7a7c2225f617d58a4264 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Thu, 3 Sep 2020 13:05:00 -0700 Subject: [PATCH 11/25] [IMP] attachment_minio: Handle migrated `image.mixin` field `image_128` --- attachment_minio/migrations/13.0.0.0.1/post-migration.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/attachment_minio/migrations/13.0.0.0.1/post-migration.py b/attachment_minio/migrations/13.0.0.0.1/post-migration.py index 068cdab0..b8a62e3a 100644 --- a/attachment_minio/migrations/13.0.0.0.1/post-migration.py +++ b/attachment_minio/migrations/13.0.0.0.1/post-migration.py @@ -32,7 +32,14 @@ def migrate(cr, version): '|', ('res_model', '=', 'ir.ui.view'), ('res_field', 'in', ['image_small', 'image_medium', - 'web_icon_data']) + 'web_icon_data', + # image.mixin sizes + # image_128 is essentially image_medium + 'image_128', + # depending on use case, these may need migrated/moved + # 'image_256', + # 'image_512', + ]) ], ) From 998085a6ab5956020d3821bfba227592feb83ea5 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Fri, 4 Sep 2020 11:48:52 -0700 Subject: [PATCH 12/25] [MIG] shipbox: for Odoo 13.0 --- .gitmodules | 3 +++ external/hibou-shipbox | 1 + shipbox | 1 + 3 files changed, 5 insertions(+) create mode 160000 external/hibou-shipbox create mode 120000 shipbox diff --git a/.gitmodules b/.gitmodules index 0f12a611..07a15ee9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "external/camptocamp-cloud-platform"] path = external/camptocamp-cloud-platform url = https://github.com/hibou-io/camptocamp-cloud-platform +[submodule "external/hibou-shipbox"] + path = external/hibou-shipbox + url = https://gitlab.com/hibou-io/hibou-odoo/shipbox.git diff --git a/external/hibou-shipbox b/external/hibou-shipbox new file mode 160000 index 00000000..e0a0f3e9 --- /dev/null +++ b/external/hibou-shipbox @@ -0,0 +1 @@ +Subproject commit e0a0f3e9c6664c8e5e46cc77ff9c89dccb514fee diff --git a/shipbox b/shipbox new file mode 120000 index 00000000..e077c55d --- /dev/null +++ b/shipbox @@ -0,0 +1 @@ +./external/hibou-shipbox/shipbox \ No newline at end of file From 23a1edfd163fb31260c78b500f21224801e74827 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Fri, 11 Sep 2020 14:22:02 -0700 Subject: [PATCH 13/25] [IMP] rma: Release 13.0.1.3.0! Add wizard and invoicing functionality for RTV, chained 'next RMA' etc. --- rma/__manifest__.py | 2 +- rma/demo/rma_demo.xml | 11 ++++ rma/models/rma.py | 90 +++++++++++++++++++++++++++++++ rma/tests/test_rma.py | 62 +++++++++++++++++++++ rma/views/portal_templates.xml | 8 +-- rma/views/rma_views.xml | 5 +- rma/wizard/__init__.py | 1 + rma/wizard/rma_make_rtv.py | 90 +++++++++++++++++++++++++++++++ rma/wizard/rma_make_rtv_views.xml | 41 ++++++++++++++ 9 files changed, 304 insertions(+), 6 deletions(-) create mode 100644 rma/wizard/rma_make_rtv.py create mode 100644 rma/wizard/rma_make_rtv_views.xml diff --git a/rma/__manifest__.py b/rma/__manifest__.py index a1de4a07..ef12b21a 100644 --- a/rma/__manifest__.py +++ b/rma/__manifest__.py @@ -2,7 +2,7 @@ { 'name': 'Hibou RMAs', - 'version': '13.0.1.2.0', + 'version': '13.0.1.3.0', 'category': 'Warehouse', 'author': 'Hibou Corp.', 'license': 'OPL-1', diff --git a/rma/demo/rma_demo.xml b/rma/demo/rma_demo.xml index 17f405e0..94f78e70 100644 --- a/rma/demo/rma_demo.xml +++ b/rma/demo/rma_demo.xml @@ -42,4 +42,15 @@ make_to_stock + + + Return To Vendor + + + + + make_to_stock + + + \ No newline at end of file diff --git a/rma/models/rma.py b/rma/models/rma.py index d2e9847e..cdce249c 100644 --- a/rma/models/rma.py +++ b/rma/models/rma.py @@ -52,6 +52,7 @@ class RMATemplate(models.Model): company_id = fields.Many2one('res.company', 'Company') responsible_user_ids = fields.Many2many('res.users', string='Responsible Users', help='Users that get activities when creating RMA.') + next_rma_template_id = fields.Many2one('rma.template', string='Next RMA Template') def _portal_try_create(self, request_user, res_id, **kw): if self.usage == 'stock_picking': @@ -201,6 +202,7 @@ class RMA(models.Model): ('cancel', 'Cancelled'), ], string='State', default='draft', copy=False) company_id = fields.Many2one('res.company', 'Company') + parent_id = fields.Many2one('rma.rma') template_id = fields.Many2one('rma.template', string='Type', required=True) template_create_in_picking = fields.Boolean(related='template_id.create_in_picking') template_create_out_picking = fields.Boolean(related='template_id.create_out_picking') @@ -215,6 +217,7 @@ class RMA(models.Model): customer_description = fields.Html(string='Customer Instructions', related='template_id.customer_description') template_usage = fields.Selection(string='Template Usage', related='template_id.usage') validity_date = fields.Datetime(string='Expiration Date') + claim_number = fields.Char(string='Claim Number') invoice_ids = fields.Many2many('account.move', 'rma_invoice_rel', 'rma_id', @@ -363,6 +366,26 @@ class RMA(models.Model): 'in_picking_id': in_picking_id.id if in_picking_id else False, 'out_picking_id': out_picking_id.id if out_picking_id else False}) + def _next_rma_values(self): + return { + 'template_id': self.template_id.next_rma_template_id.id, + # Partners should be set when confirming or using the RTV wizard + # 'partner_id': self.partner_id.id, + # 'partner_shipping_id': self.partner_shipping_id.id, + 'parent_id': self.id, + 'lines': [(0, 0, { + 'product_id': l.product_id.id, + 'product_uom_id': l.product_uom_id.id, + 'product_uom_qty': l.product_uom_qty, + }) for l in self.lines] + } + + def _next_rma(self): + if self.template_id.next_rma_template_id: + # currently we do not want to automatically confirm them + # this is because we want to mass confirm and set picking to one partner/vendor + _ = self.create(self._next_rma_values()) + def action_done(self): for rma in self: if rma.in_picking_id and rma.in_picking_id.state not in ('done', 'cancel'): @@ -370,6 +393,67 @@ class RMA(models.Model): if rma.out_picking_id and rma.out_picking_id.state not in ('done', 'cancel'): raise UserError(_('Outbound picking not complete or cancelled.')) self.write({'state': 'done'}) + self._done_invoice() + self._next_rma() + + def _done_invoice(self): + for rma in self.filtered(lambda r: r.template_id.invoice_done): + # If you do NOT want to take part in the default invoicing functionality + # then your usage method (e.g. _invoice_values_sale_order) should be + # defined, and return nothing or extend _invoice_values to do the same + usage = rma.template_usage or '' + if hasattr(rma, '_invoice_values_' + usage): + values = getattr(rma, '_invoice_values_' + usage)() + else: + values = rma._invoice_values() + if values: + if hasattr(rma, '_invoice_' + usage): + getattr(rma, '_invoice_' + usage)(values) + else: + rma._invoice(values) + + def _invoice(self, invoice_values): + self.invoice_ids += self.env['account.move'].with_context(default_type=invoice_values['type']).create( + invoice_values) + + def _invoice_values(self): + self.ensure_one() + # special case for vendor return + supplier = self._context.get('rma_supplier') + if supplier is None and self.out_picking_id and self.out_picking_id.location_dest_id.usage == 'supplier': + supplier = True + + fiscal_position_id = self.env['account.fiscal.position'].get_fiscal_position( + self.partner_id.id, delivery_id=self.partner_shipping_id.id) + + invoice_values = { + 'type': 'in_refund' if supplier else 'out_refund', + 'partner_id': self.partner_id.id, + 'fiscal_position_id': fiscal_position_id, + } + + line_commands = [] + for rma_line in self.lines: + product = rma_line.product_id + accounts = product.product_tmpl_id.get_product_accounts() + account = accounts['expense'] if supplier else accounts['income'] + qty = rma_line.product_uom_qty + uom = rma_line.product_uom_id + price = product.standard_price if supplier else product.lst_price + if uom != product.uom_id: + price = product.uom_id._compute_price(price, uom) + line_commands.append((0, 0, { + 'product_id': product.id, + 'product_uom_id': uom.id, + 'name': product.name, + 'price_unit': price, + 'quantity': qty, + 'account_id': account.id, + 'tax_ids': [(6, 0, product.taxes_id.ids)], + })) + if line_commands: + invoice_values['invoice_line_ids'] = line_commands + return invoice_values def action_cancel(self): for rma in self: @@ -382,12 +466,18 @@ class RMA(models.Model): 'state': 'draft', 'in_picking_id': False, 'out_picking_id': False}) def _create_in_picking(self): + if self._context.get('rma_in_picking_id'): + # allow passing/setting by context to allow many RMA's to include the same pickings + return self.env['stock.picking'].browse(self._context.get('rma_in_picking_id')) if self.template_usage and hasattr(self, '_create_in_picking_' + self.template_usage): return getattr(self, '_create_in_picking_' + self.template_usage)() values = self.template_id._values_for_in_picking(self) return self.env['stock.picking'].sudo().create(values) def _create_out_picking(self): + if self._context.get('rma_out_picking_id'): + # allow passing/setting by context to allow many RMA's to include the same pickings + return self.env['stock.picking'].browse(self._context.get('rma_out_picking_id')) if self.template_usage and hasattr(self, '_create_out_picking_' + self.template_usage): return getattr(self, '_create_out_picking_' + self.template_usage)() values = self.template_id._values_for_out_picking(self) diff --git a/rma/tests/test_rma.py b/rma/tests/test_rma.py index 588cce36..ec70c3ac 100644 --- a/rma/tests/test_rma.py +++ b/rma/tests/test_rma.py @@ -14,8 +14,11 @@ class TestRMA(common.TransactionCase): self.product1 = self.env.ref('product.product_product_24') self.template_missing = self.env.ref('rma.template_missing_item') self.template_return = self.env.ref('rma.template_picking_return') + self.template_rtv = self.env.ref('rma.template_rtv') self.partner1 = self.env.ref('base.res_partner_2') self.user1 = self.env.ref('base.user_demo') + # Additional partner in tests or vendor in Return To Vendor + self.partner2 = self.env.ref('base.res_partner_12') def test_00_basic_rma(self): self.template_missing.responsible_user_ids += self.user1 @@ -244,3 +247,62 @@ class TestRMA(common.TransactionCase): # RMA cannot be completed because the inbound picking state is confirmed with self.assertRaises(UserError): rma2.action_done() + + def test_30_next_rma_rtv(self): + self.template_return.usage = False + self.template_return.in_require_return = False + self.template_return.next_rma_template_id = self.template_rtv + rma = self.env['rma.rma'].create({ + 'template_id': self.template_return.id, + 'partner_id': self.partner1.id, + 'partner_shipping_id': self.partner1.id, + }) + self.assertEqual(rma.state, 'draft') + rma_line = self.env['rma.line'].create({ + 'rma_id': rma.id, + 'product_id': self.product1.id, + 'product_uom_id': self.product1.uom_id.id, + 'product_uom_qty': 2.0, + }) + rma.action_confirm() + # Should have made pickings + self.assertEqual(rma.state, 'confirmed') + + # No outbound picking + self.assertFalse(rma.out_picking_id) + # Good inbound picking + self.assertTrue(rma.in_picking_id) + self.assertEqual(rma_line.product_id, rma.in_picking_id.move_lines.product_id) + self.assertEqual(rma_line.product_uom_qty, rma.in_picking_id.move_lines.product_uom_qty) + + with self.assertRaises(UserError): + rma.action_done() + + rma.in_picking_id.move_lines.quantity_done = 2.0 + rma.in_picking_id.action_done() + rma.action_done() + self.assertEqual(rma.state, 'done') + + # RTV RMA + rma_rtv = self.env['rma.rma'].search([('parent_id', '=', rma.id)]) + self.assertTrue(rma_rtv) + self.assertEqual(rma_rtv.state, 'draft') + + wiz = self.env['rma.make.rtv'].with_context(active_model='rma.rma', active_ids=rma_rtv.ids).create({}) + self.assertTrue(wiz.rma_line_ids) + wiz.partner_id = self.partner2 + wiz.create_batch() + self.assertTrue(rma_rtv.out_picking_id) + self.assertEqual(rma_rtv.out_picking_id.partner_id, self.partner2) + self.assertEqual(rma_rtv.state, 'confirmed') + + # ship and finish + rma_rtv.out_picking_id.move_lines.quantity_done = 2.0 + rma_rtv.out_picking_id.action_done() + rma_rtv.action_done() + self.assertEqual(rma_rtv.state, 'done') + + # ensure invoice and type + rtv_invoice = rma_rtv.invoice_ids + self.assertTrue(rtv_invoice) + self.assertEqual(rtv_invoice.type, 'in_refund') diff --git a/rma/views/portal_templates.xml b/rma/views/portal_templates.xml index abd1728e..0692be42 100644 --- a/rma/views/portal_templates.xml +++ b/rma/views/portal_templates.xml @@ -138,8 +138,8 @@
- Product image + Product Image + Product Image
@@ -237,8 +237,8 @@
- Product image + Product Image + Product Image
diff --git a/rma/views/rma_views.xml b/rma/views/rma_views.xml index f636ba54..42ee959a 100644 --- a/rma/views/rma_views.xml +++ b/rma/views/rma_views.xml @@ -26,6 +26,7 @@
+ @@ -35,6 +36,7 @@
@@ -172,6 +174,7 @@ + diff --git a/rma/wizard/__init__.py b/rma/wizard/__init__.py index 1cbabc08..38eeb3bd 100644 --- a/rma/wizard/__init__.py +++ b/rma/wizard/__init__.py @@ -1,3 +1,4 @@ # Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. from . import rma_lines +from . import rma_make_rtv diff --git a/rma/wizard/rma_make_rtv.py b/rma/wizard/rma_make_rtv.py new file mode 100644 index 00000000..728e3905 --- /dev/null +++ b/rma/wizard/rma_make_rtv.py @@ -0,0 +1,90 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from odoo import api, fields, models +from odoo.exceptions import UserError + + +class RMAMakeRTV(models.TransientModel): + _name = 'rma.make.rtv' + _description = 'Make RTV Batch' + + partner_id = fields.Many2one('res.partner', string='Vendor') + partner_shipping_id = fields.Many2one('res.partner', string='Shipping Address') + rma_line_ids = fields.One2many('rma.make.rtv.line', 'rma_make_rtv_id', string='Lines') + + @api.model + def default_get(self, fields): + result = super().default_get(fields) + if 'rma_line_ids' in fields and self._context.get('active_model') == 'rma.rma' and self._context.get('active_ids'): + active_ids = self._context.get('active_ids') + rmas = self.env['rma.rma'].browse(active_ids) + result['rma_line_ids'] = [(0, 0, { + 'rma_id': r.id, + 'rma_state': r.state, + 'rma_claim_number': r.claim_number, + }) for r in rmas] + rma_partner = rmas.mapped('partner_id') + if rma_partner: + result['partner_id'] = rma_partner[0].id + return result + + def create_batch(self): + self.ensure_one() + if self.rma_line_ids.filtered(lambda rl: rl.rma_id.state != 'draft'): + raise UserError('All RMAs must be in the draft state.') + rma_partner = self.rma_line_ids.mapped('rma_id.partner_id') + if rma_partner and len(rma_partner) != 1: + raise UserError('All RMAs must be for the same partner.') + elif not rma_partner and not self.partner_id: + raise UserError('Please select a Vendor') + elif not rma_partner: + rma_partner = self.partner_id + rma_partner_shipping = self.partner_shipping_id or rma_partner + # update all RMA's to the currently selected vendor + self.rma_line_ids.mapped('rma_id').write({ + 'partner_id': rma_partner.id, + 'partner_shipping_id': rma_partner_shipping.id, + }) + if len(self.rma_line_ids.mapped('rma_id.template_id')) != 1: + raise UserError('All RMAs must be of the same template.') + + in_values = None + out_values = None + for rma in self.rma_line_ids.mapped('rma_id'): + if rma.template_id.create_in_picking: + if not in_values: + in_values = rma.template_id._values_for_in_picking(rma) + in_values['origin'] = [in_values['origin']] + else: + other_in_values = rma.template_id._values_for_in_picking(rma) + in_values['move_lines'] += other_in_values['move_lines'] + if rma.template_id.create_out_picking: + if not out_values: + out_values = rma.template_id._values_for_out_picking(rma) + out_values['origin'] = [out_values['origin']] + else: + other_out_values = rma.template_id._values_for_out_picking(rma) + out_values['move_lines'] += other_out_values['move_lines'] + in_picking_id = False + out_picking_id = False + if in_values: + in_values['origin'] = ', '.join(in_values['origin']) + in_picking = self.env['stock.picking'].sudo().create(in_values) + in_picking_id = in_picking.id + if out_values: + out_values['origin'] = ', '.join(out_values['origin']) + out_picking = self.env['stock.picking'].sudo().create(out_values) + out_picking_id = out_picking.id + rmas = self.rma_line_ids.mapped('rma_id').with_context(rma_in_picking_id=in_picking_id, rma_out_picking_id=out_picking_id) + # action_confirm known to be multi-aware and makes only one context + rmas.action_confirm() + + +class RMAMakeRTVLine(models.TransientModel): + _name = 'rma.make.rtv.line' + _description = 'Make RTV Batch RMA' + + rma_make_rtv_id = fields.Many2one('rma.make.rtv') + rma_id = fields.Many2one('rma.rma') + rma_state = fields.Selection(related='rma_id.state') + rma_claim_number = fields.Char(related='rma_id.claim_number', readonly=False) diff --git a/rma/wizard/rma_make_rtv_views.xml b/rma/wizard/rma_make_rtv_views.xml new file mode 100644 index 00000000..b7da2d82 --- /dev/null +++ b/rma/wizard/rma_make_rtv_views.xml @@ -0,0 +1,41 @@ + + + + + Return To Vendor + rma.make.rtv + +
+

+ RMAs will be batched to pick simultaneously. +

+ + + + + + + + + + + +
+
+
+
+
+ + + RMA Make RTV + ir.actions.act_window + rma.make.rtv + form + new + + list + + +
From 2fb8ae254d55923c26b6b5fc562d6e9dde19dfed Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Fri, 11 Sep 2020 14:24:39 -0700 Subject: [PATCH 14/25] [IMP] rma_product_cores: Add hook to prevent rma invoicing due to upgrade. --- rma_product_cores/__manifest__.py | 2 +- rma_product_cores/models/rma.py | 4 ++++ rma_product_cores/views/portal_templates.xml | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/rma_product_cores/__manifest__.py b/rma_product_cores/__manifest__.py index 1d02720e..810c31f3 100755 --- a/rma_product_cores/__manifest__.py +++ b/rma_product_cores/__manifest__.py @@ -3,7 +3,7 @@ { 'name': 'RMA - Product Cores', 'author': 'Hibou Corp. ', - 'version': '13.0.1.0.0', + 'version': '13.0.1.0.1', 'license': 'OPL-1', 'category': 'Tools', 'summary': 'RMA Product Cores', diff --git a/rma_product_cores/models/rma.py b/rma_product_cores/models/rma.py index 7cda5acf..1d52747c 100644 --- a/rma_product_cores/models/rma.py +++ b/rma_product_cores/models/rma.py @@ -120,6 +120,10 @@ class RMA(models.Model): return res2 return res + def _invoice_values_product_core_sale(self): + # the RMA invoice API will not be used as invoicing will happen at the SO level + return False + def _get_dirty_core_from_service_line(self, line): original_product_line = line.core_line_id return original_product_line.product_id.product_core_id diff --git a/rma_product_cores/views/portal_templates.xml b/rma_product_cores/views/portal_templates.xml index 84c02b56..ff1818d8 100644 --- a/rma_product_cores/views/portal_templates.xml +++ b/rma_product_cores/views/portal_templates.xml @@ -42,8 +42,8 @@
- Product image + Product Image + Product Image
From cb47eecd9f06f74f8fd4c66b35940a2939661fb2 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Fri, 11 Sep 2020 14:25:40 -0700 Subject: [PATCH 15/25] [IMP] rma_sale: Release 13.0.1.2.0! Add additional support for Sale Warranty return/exchange. --- rma_sale/__manifest__.py | 2 +- rma_sale/models/product.py | 10 +++ rma_sale/models/rma.py | 12 ++- rma_sale/tests/test_rma.py | 135 +++++++++++++++++++++++++++- rma_sale/views/portal_templates.xml | 4 +- rma_sale/views/product_views.xml | 2 + rma_sale/views/rma_views.xml | 1 + 7 files changed, 161 insertions(+), 5 deletions(-) diff --git a/rma_sale/__manifest__.py b/rma_sale/__manifest__.py index 2cbf748b..e9fa3f5b 100644 --- a/rma_sale/__manifest__.py +++ b/rma_sale/__manifest__.py @@ -2,7 +2,7 @@ { 'name': 'Hibou RMAs for Sale Orders', - 'version': '13.0.1.1.0', + 'version': '13.0.1.2.0', 'category': 'Sale', 'author': 'Hibou Corp.', 'license': 'OPL-1', diff --git a/rma_sale/models/product.py b/rma_sale/models/product.py index 2e338a39..06308b76 100644 --- a/rma_sale/models/product.py +++ b/rma_sale/models/product.py @@ -14,3 +14,13 @@ class ProductTemplate(models.Model): 'A positive number will allow the product to be ' 'returned up to that number of days. A negative ' 'number prevents the return of the product.') + + rma_sale_warranty_validity = fields.Integer(string='RMA Eligible Days (Sale Warranty)', + help='Determines the number of days from the time ' + 'of the sale that the product is eligible to ' + 'be returned for warranty claims. ' + '0 (default) will allow the product to be ' + 'returned for an indefinite period of time. ' + 'A positive number will allow the product to be ' + 'returned up to that number of days. A negative ' + 'number prevents the return of the product.') diff --git a/rma_sale/models/rma.py b/rma_sale/models/rma.py index 03344398..fb800915 100644 --- a/rma_sale/models/rma.py +++ b/rma_sale/models/rma.py @@ -20,6 +20,9 @@ class RMATemplate(models.Model): _inherit = 'rma.template' usage = fields.Selection(selection_add=[('sale_order', 'Sale Order')]) + sale_order_warranty = fields.Boolean(string='Sale Order Warranty', + help='Determines if the regular return validity or ' + 'Warranty validity is used.') so_decrement_order_qty = fields.Boolean(string='SO Decrement Ordered Qty.', help='When completing the RMA, the Ordered Quantity will be decremented by ' 'the RMA qty.') @@ -87,7 +90,10 @@ class RMATemplate(models.Model): return super(RMATemplate, self)._portal_values(request_user, res_id=res_id) def _rma_sale_line_validity(self, so_line): - validity_days = so_line.product_id.rma_sale_validity + if self.sale_order_warranty: + validity_days = so_line.product_id.rma_sale_warranty_validity + else: + validity_days = so_line.product_id.rma_sale_validity if validity_days < 0: return '' elif validity_days > 0: @@ -195,6 +201,10 @@ class RMA(models.Model): pass return sale_orders.mapped('invoice_ids') - original_invoices + def _invoice_values_sale_order(self): + # the RMA invoice API will not be used as invoicing will happen at the SO level + return False + def action_add_so_lines(self): make_line_obj = self.env['rma.sale.make.lines'] for rma in self: diff --git a/rma_sale/tests/test_rma.py b/rma_sale/tests/test_rma.py index 446260d6..7cd4d329 100644 --- a/rma_sale/tests/test_rma.py +++ b/rma_sale/tests/test_rma.py @@ -167,7 +167,140 @@ class TestRMASale(TestRMA): # Existing lot cannot be re-used. with self.assertRaises(ValidationError): rma2.in_picking_id.action_done() - + # RMA cannot be completed because the inbound picking state is confirmed with self.assertRaises(UserError): rma2.action_done() + + def test_30_product_sale_return_warranty(self): + self.template_sale_return.write({ + 'usage': 'sale_order', + 'invoice_done': True, + 'sale_order_warranty': True, + 'in_to_refund': True, + 'so_decrement_order_qty': False, # invoice on decremented delivered not decremented order + 'next_rma_template_id': self.template_rtv.id, + }) + + validity = 100 # eligible for 100 days + warranty_validity = validity + 100 # eligible for 200 days + + self.product1.write({ + 'rma_sale_validity': validity, + 'rma_sale_warranty_validity': warranty_validity, + 'type': 'product', + 'invoice_policy': 'delivery', + 'tracking': 'serial', + 'standard_price': 1.5, + }) + + order = self.env['sale.order'].create({ + 'partner_id': self.partner1.id, + 'partner_invoice_id': self.partner1.id, + 'partner_shipping_id': self.partner1.id, + 'user_id': self.user1.id, + 'order_line': [(0, 0, { + 'product_id': self.product1.id, + 'product_uom_qty': 1.0, + 'product_uom': self.product1.uom_id.id, + 'price_unit': 10.0, + })] + }) + order.action_confirm() + self.assertTrue(order.state in ('sale', 'done')) + self.assertEqual(len(order.picking_ids), 1, 'Tests only run with single stage delivery.') + + # Try to RMA item not delivered yet + rma = self.env['rma.rma'].create({ + 'template_id': self.template_sale_return.id, + 'partner_id': self.partner1.id, + 'partner_shipping_id': self.partner1.id, + 'sale_order_id': order.id, + }) + self.assertEqual(rma.state, 'draft') + # Do not allow warranty return. + self.product1.rma_sale_warranty_validity = -1 + wizard = self.env['rma.sale.make.lines'].with_user(self.user1).create({'rma_id': rma.id}) + self.assertEqual(wizard.line_ids.qty_delivered, 0.0) + wizard.line_ids.product_uom_qty = 1.0 + + with self.assertRaises(UserError): + wizard.add_lines() + + # Allows returns, but not forever + self.product1.rma_sale_warranty_validity = warranty_validity + original_date_order = order.date_order + order.write({'date_order': original_date_order - timedelta(days=warranty_validity+1)}) + wizard = self.env['rma.sale.make.lines'].with_user(self.user1).create({'rma_id': rma.id}) + self.assertEqual(wizard.line_ids.qty_delivered, 0.0) + wizard.line_ids.product_uom_qty = 1.0 + with self.assertRaises(UserError): + wizard.add_lines() + + # Allows returns due to date, due to warranty option + order.write({'date_order': original_date_order - timedelta(days=validity+1)}) + wizard = self.env['rma.sale.make.lines'].with_user(self.user1).create({'rma_id': rma.id}) + self.assertEqual(wizard.line_ids.qty_delivered, 0.0) + wizard.line_ids.product_uom_qty = 1.0 + wizard.add_lines() + + # finish outbound so that we can invoice. + order.picking_ids.action_assign() + pack_opt = order.picking_ids.move_line_ids[0] + lot = self.env['stock.production.lot'].create({ + 'product_id': self.product1.id, + 'name': 'X100', + 'product_uom_id': self.product1.uom_id.id, + 'company_id': self.env.user.company_id.id, + }) + pack_opt.qty_done = 1.0 + pack_opt.lot_id = lot + order.picking_ids.button_validate() + self.assertEqual(order.picking_ids.state, 'done') + self.assertEqual(order.order_line.qty_delivered, 1.0) + + # Invoice the order so that only the core product is invoiced at the end... + self.assertFalse(order.invoice_ids) + wiz = self.env['sale.advance.payment.inv'].with_context(active_ids=order.ids).create({}) + wiz.create_invoices() + order.flush() + self.assertTrue(order.invoice_ids) + order_invoice = order.invoice_ids + + self.assertEqual(rma.lines.product_id, self.product1) + rma.action_confirm() + self.assertTrue(rma.in_picking_id) + self.assertEqual(rma.in_picking_id.state, 'assigned') + pack_opt = rma.in_picking_id.move_line_ids[0] + pack_opt.lot_id = lot.id + pack_opt.qty_done = 1.0 + rma.in_picking_id.button_validate() + self.assertEqual(rma.in_picking_id.state, 'done') + order.flush() + # self.assertEqual(order.order_line.qty_delivered, 0.0) + rma.action_done() + self.assertEqual(rma.state, 'done') + order.flush() + + rma_invoice = rma.invoice_ids + self.assertTrue(rma_invoice) + sale_line = rma_invoice.invoice_line_ids.filtered(lambda l: l.sale_line_ids) + so_line = sale_line.sale_line_ids + self.assertTrue(sale_line) + self.assertEqual(sale_line.price_unit, so_line.price_unit) + + # Invoices do not have their anglo-saxon cost lines until they post + order_invoice.post() + rma_invoice.post() + + # Find the return to vendor RMA + rtv_rma = self.env['rma.rma'].search([('parent_id', '=', rma.id)]) + self.assertTrue(rtv_rma) + self.assertFalse(rtv_rma.out_picking_id) + + wiz = self.env['rma.make.rtv'].with_context(active_model='rma.rma', active_ids=rtv_rma.ids).create({}) + self.assertTrue(wiz.rma_line_ids) + wiz.partner_id = self.partner2 + wiz.create_batch() + self.assertTrue(rtv_rma.out_picking_id) + self.assertEqual(rtv_rma.out_picking_id.partner_id, self.partner2) diff --git a/rma_sale/views/portal_templates.xml b/rma_sale/views/portal_templates.xml index 6340352d..bb97b9b7 100644 --- a/rma_sale/views/portal_templates.xml +++ b/rma_sale/views/portal_templates.xml @@ -57,8 +57,8 @@
- Product image + Product Image + Product Image
diff --git a/rma_sale/views/product_views.xml b/rma_sale/views/product_views.xml index 4fa17000..41ea3e62 100644 --- a/rma_sale/views/product_views.xml +++ b/rma_sale/views/product_views.xml @@ -9,6 +9,7 @@ + @@ -22,6 +23,7 @@ + diff --git a/rma_sale/views/rma_views.xml b/rma_sale/views/rma_views.xml index f97ce8a8..9c39bf9b 100644 --- a/rma_sale/views/rma_views.xml +++ b/rma_sale/views/rma_views.xml @@ -9,6 +9,7 @@ + From ea5dea0aacfdfb417ec104b69d73cc60016686d9 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Tue, 29 Sep 2020 15:12:33 -0700 Subject: [PATCH 16/25] [FIX] timesheet_description: portal view position and semantics --- timesheet_description/views/project_templates.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/timesheet_description/views/project_templates.xml b/timesheet_description/views/project_templates.xml index e2d78d0b..c9e84a23 100644 --- a/timesheet_description/views/project_templates.xml +++ b/timesheet_description/views/project_templates.xml @@ -1,9 +1,8 @@ \ No newline at end of file From 05d265a365169676d37c2c0bf0aef31500a8e0ba Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Tue, 29 Sep 2020 15:36:11 -0700 Subject: [PATCH 17/25] [FIX] timesheet_description: portal view position and semantics --- timesheet_description/views/project_templates.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/timesheet_description/views/project_templates.xml b/timesheet_description/views/project_templates.xml index c9e84a23..d691233c 100644 --- a/timesheet_description/views/project_templates.xml +++ b/timesheet_description/views/project_templates.xml @@ -1,8 +1,9 @@ -