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