mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
[MOV] rma_product_cores: from Hibou Suite Enterprise for 13.0
This commit is contained in:
4
rma_product_cores/__init__.py
Normal file
4
rma_product_cores/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
from . import wizard
|
||||||
29
rma_product_cores/__manifest__.py
Executable file
29
rma_product_cores/__manifest__.py
Executable file
@@ -0,0 +1,29 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'RMA - Product Cores',
|
||||||
|
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||||
|
'version': '13.0.1.0.0',
|
||||||
|
'license': 'OPL-1',
|
||||||
|
'category': 'Tools',
|
||||||
|
'summary': 'RMA Product Cores',
|
||||||
|
'description': """
|
||||||
|
RMA Product Cores - Return core products from customers.
|
||||||
|
""",
|
||||||
|
'website': 'https://hibou.io/',
|
||||||
|
'depends': [
|
||||||
|
'product_cores',
|
||||||
|
'rma_sale',
|
||||||
|
],
|
||||||
|
'data': [
|
||||||
|
'views/portal_templates.xml',
|
||||||
|
'views/rma_views.xml',
|
||||||
|
'wizard/rma_lines_views.xml',
|
||||||
|
],
|
||||||
|
'demo': [
|
||||||
|
'data/rma_demo.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
'application': False,
|
||||||
|
'auto_install': True,
|
||||||
|
}
|
||||||
27
rma_product_cores/data/rma_demo.xml
Normal file
27
rma_product_cores/data/rma_demo.xml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<record id="template_product_core_sale_return" model="rma.template">
|
||||||
|
<field name="name">Core Sale Return</field>
|
||||||
|
<field name="usage">product_core_sale</field>
|
||||||
|
<field name="valid_days" eval="10"/>
|
||||||
|
<field name="create_in_picking" eval="True"/>
|
||||||
|
<field name="in_type_id" ref="stock.picking_type_in"/>
|
||||||
|
<field name="in_location_id" ref="stock.stock_location_customers"/>
|
||||||
|
<field name="in_location_dest_id" ref="stock.stock_location_stock"/>
|
||||||
|
<field name="in_procure_method">make_to_stock</field>
|
||||||
|
<field name="in_to_refund" eval="True"/>
|
||||||
|
<field name="in_require_return" eval="False"/>
|
||||||
|
<field name="so_decrement_order_qty" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="template_product_core_purchase_return" model="rma.template">
|
||||||
|
<field name="name">Core Purchase Return</field>
|
||||||
|
<field name="usage">product_core_purchase</field>
|
||||||
|
<field name="valid_days" eval="10"/>
|
||||||
|
<field name="create_out_picking" eval="True"/>
|
||||||
|
<field name="out_type_id" ref="stock.picking_type_out"/>
|
||||||
|
<field name="out_location_id" ref="stock.stock_location_stock"/>
|
||||||
|
<field name="out_location_dest_id" ref="stock.stock_location_suppliers"/>
|
||||||
|
<field name="out_procure_method">make_to_stock</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
3
rma_product_cores/models/__init__.py
Normal file
3
rma_product_cores/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from . import rma
|
||||||
235
rma_product_cores/models/rma.py
Normal file
235
rma_product_cores/models/rma.py
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
from odoo import api, fields, models, _
|
||||||
|
from odoo.exceptions import UserError, ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
class RMATemplate(models.Model):
|
||||||
|
_inherit = 'rma.template'
|
||||||
|
|
||||||
|
usage = fields.Selection(selection_add=[
|
||||||
|
('product_core_sale', 'Product Core Sale'),
|
||||||
|
('product_core_purchase', 'Product Core Purchase'),
|
||||||
|
])
|
||||||
|
|
||||||
|
# Portal Methods
|
||||||
|
def _portal_try_create(self, request_user, res_id, **kw):
|
||||||
|
if self.usage == 'product_core_sale':
|
||||||
|
prefix = 'product_'
|
||||||
|
product_map = {int(key[len(prefix):]): float(kw[key]) for key in kw if key.find(prefix) == 0 and kw[key]}
|
||||||
|
if product_map:
|
||||||
|
service_lines = self._get_product_core_sale_service_lines(request_user.partner_id)
|
||||||
|
eligible_service_lines = self._product_core_eligible_service_lines(service_lines)
|
||||||
|
eligible_lines = self._rma_product_core_eligible_data(service_lines, eligible_service_lines)
|
||||||
|
lines = []
|
||||||
|
for product_id, qty in product_map.items():
|
||||||
|
product_f = filter(lambda key_product: key_product.id == product_id, eligible_lines)
|
||||||
|
if product_f:
|
||||||
|
product = next(product_f)
|
||||||
|
product_data = eligible_lines[product]
|
||||||
|
if not qty:
|
||||||
|
continue
|
||||||
|
if qty < 0.0 or product_data['qty_delivered'] < qty:
|
||||||
|
raise ValidationError('Invalid quantity.')
|
||||||
|
lines.append((0, 0, {
|
||||||
|
'product_id': product.id,
|
||||||
|
'product_uom_id': product.uom_id.id,
|
||||||
|
'product_uom_qty': qty,
|
||||||
|
}))
|
||||||
|
if not lines:
|
||||||
|
raise ValidationError('Missing product quantity.')
|
||||||
|
rma = self.env['rma.rma'].create({
|
||||||
|
'name': _('New'),
|
||||||
|
'template_id': self.id,
|
||||||
|
'partner_id': request_user.partner_id.id,
|
||||||
|
'partner_shipping_id': request_user.partner_id.id,
|
||||||
|
'lines': lines,
|
||||||
|
})
|
||||||
|
return rma
|
||||||
|
return super(RMATemplate, self)._portal_try_create(request_user, res_id, **kw)
|
||||||
|
|
||||||
|
def _portal_template(self, res_id=None):
|
||||||
|
if self.usage == 'product_core_sale':
|
||||||
|
return 'rma_product_cores.portal_new'
|
||||||
|
return super(RMATemplate, self)._portal_template(res_id=res_id)
|
||||||
|
|
||||||
|
def _portal_values(self, request_user, res_id=None):
|
||||||
|
if self.usage == 'product_core_sale':
|
||||||
|
service_lines = self._get_product_core_sale_service_lines(request_user.partner_id)
|
||||||
|
eligible_service_lines = self._product_core_eligible_service_lines(service_lines)
|
||||||
|
eligible_lines = self._rma_product_core_eligible_data(service_lines, eligible_service_lines)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'rma_template': self,
|
||||||
|
'rma_product_core_lines': eligible_lines,
|
||||||
|
}
|
||||||
|
return super(RMATemplate, self)._portal_values(request_user, res_id=res_id)
|
||||||
|
|
||||||
|
# Product Cores
|
||||||
|
def _get_product_core_sale_service_lines(self, partner):
|
||||||
|
return partner.sale_order_ids.mapped('order_line').filtered(lambda l: l.core_line_id)\
|
||||||
|
.sorted(key=lambda r: r.id)
|
||||||
|
|
||||||
|
def _product_core_eligible_service_lines(self, service_lines, date=None):
|
||||||
|
if not date:
|
||||||
|
date = fields.Datetime.now()
|
||||||
|
lines = service_lines.browse()
|
||||||
|
for line in service_lines:
|
||||||
|
validity = line.core_line_id.product_id.product_core_validity
|
||||||
|
partition_date = date - relativedelta(days=validity)
|
||||||
|
if line.order_id.date_order >= partition_date:
|
||||||
|
lines += line
|
||||||
|
return lines
|
||||||
|
|
||||||
|
def _rma_product_core_eligible_data(self, service_lines, eligible_service_lines):
|
||||||
|
rma_model = self.env['rma.rma']
|
||||||
|
eligible_lines = defaultdict(lambda: {
|
||||||
|
'qty_ordered': 0.0,
|
||||||
|
'qty_delivered': 0.0,
|
||||||
|
'qty_invoiced': 0.0,
|
||||||
|
'lines': self.env['sale.order.line'].browse()})
|
||||||
|
|
||||||
|
for line in service_lines:
|
||||||
|
product = rma_model._get_dirty_core_from_service_line(line)
|
||||||
|
if product:
|
||||||
|
eligible_line = eligible_lines[product]
|
||||||
|
eligible_line['lines'] += line
|
||||||
|
if line in eligible_service_lines:
|
||||||
|
eligible_line['qty_ordered'] += line.product_uom_qty
|
||||||
|
eligible_line['qty_delivered'] += line.qty_delivered
|
||||||
|
eligible_line['qty_invoiced'] += line.qty_invoiced
|
||||||
|
return eligible_lines
|
||||||
|
|
||||||
|
|
||||||
|
class RMA(models.Model):
|
||||||
|
_inherit = 'rma.rma'
|
||||||
|
|
||||||
|
def action_done(self):
|
||||||
|
res = super(RMA, self).action_done()
|
||||||
|
res2 = self._product_core_action_done()
|
||||||
|
if isinstance(res, dict) and isinstance(res2, dict):
|
||||||
|
if 'warning' in res and 'warning' in res2:
|
||||||
|
res['warning'] = '\n'.join([res['warning'], res2['warning']])
|
||||||
|
return res
|
||||||
|
if 'warning' in res2:
|
||||||
|
res['warning'] = res2['warning']
|
||||||
|
return res
|
||||||
|
elif isinstance(res2, dict):
|
||||||
|
return res2
|
||||||
|
return res
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def _product_core_action_done(self):
|
||||||
|
for rma in self.filtered(lambda r: r.template_usage in ('product_core_sale', 'product_core_purchase')):
|
||||||
|
if rma.template_usage == 'product_core_sale':
|
||||||
|
service_lines = rma.template_id._get_product_core_sale_service_lines(rma.partner_id)
|
||||||
|
eligible_service_lines = rma.template_id._product_core_eligible_service_lines(service_lines, date=rma.create_date)
|
||||||
|
|
||||||
|
# collect the to reduce qty by product id
|
||||||
|
qty_to_reduce = defaultdict(float)
|
||||||
|
for line in rma.lines:
|
||||||
|
qty_to_reduce[line.product_id.id] += line.product_uom_qty
|
||||||
|
|
||||||
|
# iterate over all service_lines to see if the qty_delivered can be reduced.
|
||||||
|
sale_orders = self.env['sale.order'].browse()
|
||||||
|
for line in eligible_service_lines:
|
||||||
|
product = self._get_dirty_core_from_service_line(line)
|
||||||
|
pid = product.id
|
||||||
|
if qty_to_reduce[pid] > 0.0:
|
||||||
|
if line.qty_delivered > 0.0:
|
||||||
|
sale_orders += line.order_id
|
||||||
|
if qty_to_reduce[pid] > line.qty_delivered:
|
||||||
|
# can reduce this whole line
|
||||||
|
qty_to_reduce[pid] -= line.qty_delivered
|
||||||
|
line.write({'qty_delivered': 0.0})
|
||||||
|
else:
|
||||||
|
# can reduce some of this line, but there are no more to reduce
|
||||||
|
line.write({'qty_delivered': line.qty_delivered - qty_to_reduce[pid]})
|
||||||
|
qty_to_reduce[pid] = 0.0
|
||||||
|
|
||||||
|
# if there are more qty to reduce, then we have an error.
|
||||||
|
if any(qty_to_reduce.values()):
|
||||||
|
raise UserError(_('Cannot complete RMA as there are not enough service lines to reduce. (Maybe a duplicate)'))
|
||||||
|
|
||||||
|
# Try to invoice if we don't already have an invoice (e.g. from resetting to draft)
|
||||||
|
if sale_orders and rma.template_id.invoice_done and not rma.invoice_ids:
|
||||||
|
rma.invoice_ids += rma._product_core_sale_invoice_done(sale_orders)
|
||||||
|
else:
|
||||||
|
raise UserError(_('not ready for purchase rma'))
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _product_core_sale_invoice_done(self, sale_orders):
|
||||||
|
return self._sale_invoice_done(sale_orders)
|
||||||
|
|
||||||
|
def action_add_product_core_lines(self):
|
||||||
|
make_line_obj = self.env['rma.product_cores.make.lines']
|
||||||
|
for rma in self:
|
||||||
|
lines = make_line_obj.create({
|
||||||
|
'rma_id': rma.id,
|
||||||
|
})
|
||||||
|
action = self.env.ref('rma_product_cores.action_rma_add_lines').read()[0]
|
||||||
|
action['res_id'] = lines.id
|
||||||
|
return action
|
||||||
|
|
||||||
|
def _product_cores_create_make_lines(self, wizard, rma_line_model):
|
||||||
|
"""
|
||||||
|
Called from the wizard, as this model "owns" the eligibility and qty on lines.
|
||||||
|
:param wizard:
|
||||||
|
:param line_model:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if self.partner_id:
|
||||||
|
if self.template_usage == 'product_core_sale':
|
||||||
|
service_lines = self.template_id._get_product_core_sale_service_lines(self.partner_id)
|
||||||
|
eligible_service_lines = self.template_id._product_core_eligible_service_lines(service_lines, date=self.create_date)
|
||||||
|
rma_lines = rma_line_model.browse()
|
||||||
|
for line in service_lines:
|
||||||
|
product = self._get_dirty_core_from_service_line(line)
|
||||||
|
if product:
|
||||||
|
rma_line = rma_lines.filtered(lambda l: l.product_id == product)
|
||||||
|
if not rma_line:
|
||||||
|
rma_line = rma_line_model.create({
|
||||||
|
'rma_make_lines_id': wizard.id,
|
||||||
|
'product_id': product.id,
|
||||||
|
'product_uom_id': product.uom_id.id,
|
||||||
|
})
|
||||||
|
rma_lines += rma_line
|
||||||
|
if line in eligible_service_lines:
|
||||||
|
rma_line.update({
|
||||||
|
'qty_ordered': rma_line.qty_ordered + line.product_uom_qty,
|
||||||
|
'qty_delivered': rma_line.qty_delivered + line.qty_delivered,
|
||||||
|
'qty_invoiced': rma_line.qty_invoiced + line.qty_invoiced,
|
||||||
|
})
|
||||||
|
elif self.template_usage == 'product_core_purchase':
|
||||||
|
raise UserError('not ready for purchase rma')
|
||||||
|
|
||||||
|
def _product_core_field_check(self):
|
||||||
|
if not self.partner_shipping_id:
|
||||||
|
raise UserError(_('You must have a shipping address selected for this RMA.'))
|
||||||
|
if not self.partner_id:
|
||||||
|
raise UserError(_('You must have a partner selected for this RMA.'))
|
||||||
|
|
||||||
|
def _create_in_picking_product_core_sale(self):
|
||||||
|
self._product_core_field_check()
|
||||||
|
values = self.template_id._values_for_in_picking(self)
|
||||||
|
return self._picking_from_values(values, {}, {})
|
||||||
|
|
||||||
|
def _create_out_picking_product_core_sale(self):
|
||||||
|
self._product_core_field_check()
|
||||||
|
values = self.template_id._values_for_out_picking(self)
|
||||||
|
return self._picking_from_values(values, {}, {})
|
||||||
|
|
||||||
|
def _create_in_picking_product_core_purchase(self):
|
||||||
|
self._product_core_field_check()
|
||||||
|
values = self.template_id._values_for_in_picking(self)
|
||||||
|
return self._picking_from_values(values, {}, {})
|
||||||
|
|
||||||
|
def _create_out_picking_product_core_purchase(self):
|
||||||
|
self._product_core_field_check()
|
||||||
|
values = self.template_id._values_for_out_picking(self)
|
||||||
|
return self._picking_from_values(values, {}, {})
|
||||||
3
rma_product_cores/tests/__init__.py
Normal file
3
rma_product_cores/tests/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from . import test_rma
|
||||||
144
rma_product_cores/tests/test_rma.py
Normal file
144
rma_product_cores/tests/test_rma.py
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo.addons.rma.tests.test_rma import TestRMA
|
||||||
|
from odoo.exceptions import UserError, ValidationError
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
|
|
||||||
|
class TestRMACore(TestRMA):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestRMACore, self).setUp()
|
||||||
|
self.template_sale_return = self.env.ref('rma_product_cores.template_product_core_sale_return')
|
||||||
|
self.template_purchase_return = self.env.ref('rma_product_cores.template_product_core_purchase_return')
|
||||||
|
self.product_core_service = self.env['product.product'].create({
|
||||||
|
'name': 'Turbo Core Deposit',
|
||||||
|
'type': 'service',
|
||||||
|
'categ_id': self.env.ref('product.product_category_all').id,
|
||||||
|
'core_ok': True,
|
||||||
|
'invoice_policy': 'delivery',
|
||||||
|
})
|
||||||
|
self.product_core = self.env['product.product'].create({
|
||||||
|
'name': 'Turbo Core',
|
||||||
|
'type': 'product',
|
||||||
|
'categ_id': self.env.ref('product.product_category_all').id,
|
||||||
|
'core_ok': True,
|
||||||
|
'tracking': 'serial',
|
||||||
|
'invoice_policy': 'delivery',
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_30_product_core_sale_return(self):
|
||||||
|
# Initialize template
|
||||||
|
self.template_sale_return.usage = 'product_core_sale'
|
||||||
|
self.template_sale_return.invoice_done = True
|
||||||
|
|
||||||
|
self.product1.tracking = 'serial'
|
||||||
|
self.product1.product_core_id = self.product_core
|
||||||
|
self.product1.product_core_service_id = self.product_core_service
|
||||||
|
self.product1.product_core_validity = 30 # eligible for 30 days
|
||||||
|
|
||||||
|
order = self.env['sale.order'].create({
|
||||||
|
'partner_id': self.partner1.id,
|
||||||
|
'partner_invoice_id': self.partner1.id,
|
||||||
|
'partner_shipping_id': self.partner1.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()
|
||||||
|
original_date_order = order.date_order
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
self.assertEqual(rma.state, 'draft')
|
||||||
|
wizard = self.env['rma.product_cores.make.lines'].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):
|
||||||
|
# Prevents adding if the qty_delivered on the line is not >= product_uom_qty
|
||||||
|
wizard.add_lines()
|
||||||
|
|
||||||
|
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.filtered(lambda l: l.product_id == self.product1).qty_delivered,
|
||||||
|
1.0)
|
||||||
|
self.assertEqual(order.order_line.filtered(lambda l: l.product_id == self.product_core_service).qty_delivered,
|
||||||
|
1.0)
|
||||||
|
|
||||||
|
# ensure that we have a qty_delivered
|
||||||
|
wizard = self.env['rma.product_cores.make.lines'].create({
|
||||||
|
'rma_id': rma.id,
|
||||||
|
})
|
||||||
|
self.assertEqual(wizard.line_ids.qty_delivered, 1.0)
|
||||||
|
|
||||||
|
# set the date back and ensure that we have 0 again.
|
||||||
|
order.date_order = order.date_order - relativedelta(days=31)
|
||||||
|
wizard = self.env['rma.product_cores.make.lines'].create({
|
||||||
|
'rma_id': rma.id,
|
||||||
|
})
|
||||||
|
self.assertEqual(wizard.line_ids.qty_delivered, 0.0)
|
||||||
|
|
||||||
|
# Reset Date and process RMA
|
||||||
|
order.date_order = original_date_order
|
||||||
|
wizard = self.env['rma.product_cores.make.lines'].create({
|
||||||
|
'rma_id': rma.id,
|
||||||
|
})
|
||||||
|
self.assertEqual(wizard.line_ids.qty_delivered, 1.0)
|
||||||
|
wizard.line_ids.product_uom_qty = 1.0
|
||||||
|
wizard.add_lines()
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# The added product should be the 'dirty core' for the RMA's `core_product_id`
|
||||||
|
self.assertEqual(rma.lines.product_id, self.product1.product_core_id)
|
||||||
|
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 = self.env['stock.production.lot'].create({
|
||||||
|
'product_id': pack_opt.product_id.id,
|
||||||
|
'name': 'TESTDIRTYLOT1',
|
||||||
|
'company_id': self.env.user.company_id.id,
|
||||||
|
})
|
||||||
|
pack_opt.qty_done = 1.0
|
||||||
|
rma.in_picking_id.button_validate()
|
||||||
|
rma.action_done()
|
||||||
|
self.assertEqual(rma.state, 'done')
|
||||||
|
|
||||||
|
# Finishing the RMA should have made an invoice
|
||||||
|
self.assertTrue(rma.invoice_ids, 'Finishing RMA did not create an invoice(s).')
|
||||||
|
self.assertEqual(rma.invoice_ids.invoice_line_ids.product_id, self.product_core_service)
|
||||||
|
|
||||||
|
# Make sure the delivered qty of the Core Service was decremented.
|
||||||
|
self.assertEqual(order.order_line.filtered(lambda l: l.product_id == self.product1).qty_delivered,
|
||||||
|
1.0)
|
||||||
|
# This is known to work in practice, but no amount of magic ORM flushing seems to make it work in test.
|
||||||
|
# self.assertEqual(order.order_line.filtered(lambda l: l.product_id == self.product_core_service).qty_delivered,
|
||||||
|
# 0.0)
|
||||||
99
rma_product_cores/views/portal_templates.xml
Normal file
99
rma_product_cores/views/portal_templates.xml
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<!-- New -->
|
||||||
|
<template id="portal_new" name="New Product Core RMA">
|
||||||
|
<t t-call="portal.portal_layout">
|
||||||
|
<div id="optional_placeholder"></div>
|
||||||
|
<div class="container">
|
||||||
|
<t t-call="rma.portal_rma_error"/>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<h4>
|
||||||
|
<span t-esc="rma_template.name"/>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p t-if="not rma_product_core_lines">No product cores in your purchase history.</p>
|
||||||
|
<form t-if="rma_product_core_lines" method="post" t-attf-action="/my/rma/new/#{rma_template.id}/res/1">
|
||||||
|
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-5">
|
||||||
|
<strong>Product</strong>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-2 text-right">
|
||||||
|
<strong>Qty. Ordered</strong>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-2 text-right">
|
||||||
|
<strong>Qty. Invoiced</strong>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-2 text-right">
|
||||||
|
<strong>Qty. Delivered</strong>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-1 text-right">
|
||||||
|
<strong>Qty. to Return</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<t t-foreach="rma_product_core_lines" t-as="product">
|
||||||
|
<t t-set="core_lines" t-value="rma_product_core_lines[product]"/>
|
||||||
|
<hr/>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-1 text-center">
|
||||||
|
<img t-attf-src="/web/image/product.product/#{product.id}/image_64"
|
||||||
|
width="64" alt="Product image"></img>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<span t-esc="product.name"/>
|
||||||
|
<button class="btn btn-secondary btn-sm float-right" type="button" data-toggle="collapse" t-attf-data-target="#product_detail_#{product.id}" aria-expanded="false" aria-controls="collapseExample">
|
||||||
|
<span class="fa fa-info-circle"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-2 text-right">
|
||||||
|
<span t-esc="core_lines['qty_ordered']"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-2 text-right">
|
||||||
|
<span t-esc="core_lines['qty_invoiced']"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-2 text-right">
|
||||||
|
<span t-esc="core_lines['qty_delivered']"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-1 text-right">
|
||||||
|
<input type="text" t-attf-name="product_#{product.id}" class="form-control"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="collapse text-muted" t-attf-id="product_detail_#{product.id}">
|
||||||
|
<div class="row" t-foreach="core_lines['lines']" t-as="l">
|
||||||
|
<t t-set="validity" t-value="float(l.core_line_id.product_id.product_core_validity)"/>
|
||||||
|
<t t-set="partition_date" t-value="current_date - relativedelta(days=validity)"/>
|
||||||
|
<div class="col-lg-3">
|
||||||
|
<span t-field="l.order_id"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-2">
|
||||||
|
<span t-field="l.order_id.date_order"
|
||||||
|
t-attf-class="#{'text-danger' if l.order_id.date_order < partition_date else 'text-success'}"
|
||||||
|
t-options='{"widget": "date"}'/>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-2 text-right">
|
||||||
|
<span t-esc="l.product_uom_qty"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-2 text-right">
|
||||||
|
<span t-esc="l.qty_invoiced"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-2 text-right">
|
||||||
|
<span t-esc="l.qty_delivered"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
<input type="submit" class="btn btn-primary mt16 float-right" name="submit"/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="oe_structure mb32"/>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
||||||
17
rma_product_cores/views/rma_views.xml
Normal file
17
rma_product_cores/views/rma_views.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- RMA -->
|
||||||
|
<record id="view_rma_rma_form_product_cores" model="ir.ui.view">
|
||||||
|
<field name="name">rma.rma.form.product_cores</field>
|
||||||
|
<field name="model">rma.rma</field>
|
||||||
|
<field name="inherit_id" ref="rma.view_rma_rma_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='template_id']" position="after">
|
||||||
|
<br/>
|
||||||
|
<button string="Add lines" type="object" name="action_add_product_core_lines" attrs="{'invisible': ['|', ('template_usage', 'not in', ('product_core_sale')), ('state', '!=', 'draft')]}"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
3
rma_product_cores/wizard/__init__.py
Normal file
3
rma_product_cores/wizard/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from . import rma_lines
|
||||||
43
rma_product_cores/wizard/rma_lines.py
Normal file
43
rma_product_cores/wizard/rma_lines.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# 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 RMAProductCoresMakeLines(models.TransientModel):
|
||||||
|
_name = 'rma.product_cores.make.lines'
|
||||||
|
_description = 'Add Product Core Lines'
|
||||||
|
|
||||||
|
rma_id = fields.Many2one('rma.rma', string='RMA')
|
||||||
|
line_ids = fields.One2many('rma.product_cores.make.lines.line', 'rma_make_lines_id', string='Lines')
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self, vals):
|
||||||
|
maker = super(RMAProductCoresMakeLines, self).create(vals)
|
||||||
|
maker._create_lines()
|
||||||
|
return maker
|
||||||
|
|
||||||
|
def _create_lines(self):
|
||||||
|
make_lines_obj = self.env['rma.product_cores.make.lines.line']
|
||||||
|
self.rma_id._product_cores_create_make_lines(self, make_lines_obj)
|
||||||
|
|
||||||
|
def add_lines(self):
|
||||||
|
rma_line_obj = self.env['rma.line']
|
||||||
|
for o in self:
|
||||||
|
lines = o.line_ids.filtered(lambda l: l.product_uom_qty > 0.0)
|
||||||
|
for l in lines:
|
||||||
|
if l.qty_delivered < l.product_uom_qty:
|
||||||
|
raise UserError('You cannot return more than the eligible qty of this product.')
|
||||||
|
rma_line_obj.create({
|
||||||
|
'rma_id': o.rma_id.id,
|
||||||
|
'product_id': l.product_id.id,
|
||||||
|
'product_uom_id': l.product_uom_id.id,
|
||||||
|
'product_uom_qty': l.product_uom_qty,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class RMAProductCoresMakeLinesLine(models.TransientModel):
|
||||||
|
_name = 'rma.product_cores.make.lines.line'
|
||||||
|
_inherit = 'rma.sale.make.lines.line'
|
||||||
|
|
||||||
|
rma_make_lines_id = fields.Many2one('rma.product_cores.make.lines')
|
||||||
41
rma_product_cores/wizard/rma_lines_views.xml
Normal file
41
rma_product_cores/wizard/rma_lines_views.xml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="view_rma_add_lines_form" model="ir.ui.view">
|
||||||
|
<field name="name">view.rma.add.lines.form</field>
|
||||||
|
<field name="model">rma.product_cores.make.lines</field>
|
||||||
|
<field name="type">form</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<field name="line_ids">
|
||||||
|
<tree editable="top" create="false" delete="false">
|
||||||
|
<field name="product_id" readonly="1"/>
|
||||||
|
<field name="qty_ordered" readonly="1"/>
|
||||||
|
<field name="qty_delivered" readonly="1"/>
|
||||||
|
<field name="qty_invoiced" readonly="1"/>
|
||||||
|
<field name="product_uom_qty"/>
|
||||||
|
<field name="product_uom_id" readonly="1"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
<footer>
|
||||||
|
<button class="oe_highlight"
|
||||||
|
name="add_lines"
|
||||||
|
type="object"
|
||||||
|
string="Add" />
|
||||||
|
<button class="oe_link"
|
||||||
|
special="cancel"
|
||||||
|
string="Cancel" />
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_rma_add_lines" model="ir.actions.act_window">
|
||||||
|
<field name="name">Add RMA Lines</field>
|
||||||
|
<field name="res_model">rma.product_cores.make.lines</field>
|
||||||
|
<field name="view_mode">form</field>
|
||||||
|
<field name="view_id" ref="view_rma_add_lines_form" />
|
||||||
|
<field name="target">new</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
Reference in New Issue
Block a user