Merge branch 'new/13.0/rma__invoice_warranty_rtv' into 13.0-test

This commit is contained in:
Jared Kipe
2020-09-30 11:29:55 -07:00
19 changed files with 475 additions and 14 deletions

View File

@@ -2,12 +2,13 @@
{
'name': 'Hibou RMAs',
'version': '13.0.1.2.0',
'version': '13.0.1.3.0',
'category': 'Warehouse',
'author': 'Hibou Corp.',
'license': 'OPL-1',
'website': 'https://hibou.io/',
'depends': [
'hibou_professional',
'stock',
'delivery',
],

View File

@@ -42,4 +42,15 @@
<field name="in_procure_method">make_to_stock</field>
<field name="in_require_return" eval="True"/>
</record>
<record id="template_rtv" model="rma.template">
<field name="name">Return To Vendor</field>
<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>
<field name="invoice_done" eval="True"/>
</record>
</odoo>

View File

@@ -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)

View File

@@ -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')

View File

@@ -138,8 +138,8 @@
<t t-foreach="rma.lines" t-as="line">
<div class="row purchases_vertical_align">
<div class="col-lg-3 text-center">
<img t-attf-src="/web/image/product.product/#{line.product_id.id}/image_64"
width="64" alt="Product image"></img>
<img class="mr4 float-left o_portal_product_img" t-if="line.product_id.image_128" t-att-src="image_data_uri(line.product_id.image_128)" alt="Product Image" width="64"/>
<img class="mr4 float-left o_portal_product_img" t-if="not line.product_id.image_128" src="/web/static/src/img/placeholder.png" alt="Product Image" width="64"/>
</div>
<div class="col-lg-7">
<span t-esc="line.product_id.name"/>
@@ -237,8 +237,8 @@
<t t-foreach="picking.move_lines" t-as="line">
<div class="row purchases_vertical_align">
<div class="col-lg-2 text-center">
<img t-attf-src="/web/image/product.product/#{line.product_id.id}/image_64"
width="64" alt="Product image"></img>
<img class="mr4 float-left o_portal_product_img" t-if="line.product_id.image_128" t-att-src="image_data_uri(line.product_id.image_128)" alt="Product Image" width="64"/>
<img class="mr4 float-left o_portal_product_img" t-if="not line.product_id.image_128" src="/web/static/src/img/placeholder.png" alt="Product Image" width="64"/>
</div>
<div class="col-lg-4">
<span t-esc="line.product_id.name"/>

View File

@@ -26,6 +26,7 @@
</div>
<group>
<group>
<field name="parent_id" readonly="1" attrs="{'invisible': [('parent_id', '=', False)]}"/>
<field name="template_usage" invisible="1"/>
<field name="template_create_in_picking" invisible="1"/>
<field name="template_create_out_picking" invisible="1"/>
@@ -35,6 +36,7 @@
<button string="Add lines" type="object" name="action_add_picking_lines" attrs="{'invisible': ['|', ('stock_picking_id', '=', False), ('state', '!=', 'draft')]}"/>
</group>
<group>
<field name="claim_number"/>
<field name="validity_date"/>
<field name="tag_ids" widget="many2many_tags" placeholder="Tags" options="{'no_create': True}"/>
<field name="partner_id" options="{'no_create_edit': True}" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
@@ -137,6 +139,7 @@
<field name="arch" type="xml">
<search string="Search RMA">
<field name="name"/>
<field name="claim_number"/>
<field name="partner_id"/>
<field name="template_id"/>
<field name="stock_picking_id"/>
@@ -162,7 +165,6 @@
<h1>
<field name="name"/>
</h1>
</div>
<group>
<group>
@@ -172,6 +174,7 @@
<field name="company_id" options="{'no_create': True}"/>
<field name="portal_ok"/>
<field name="invoice_done" help="This feature is implemented in specific RMA types automatically when enabled."/>
<field name="next_rma_template_id"/>
</group>
<group>
<field name="responsible_user_ids" domain="[('share', '=', False)]" widget="many2many_tags"/>

View File

@@ -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

View File

@@ -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)

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_rma_make_rtv" model="ir.ui.view">
<field name="name">Return To Vendor</field>
<field name="model">rma.make.rtv</field>
<field name="arch" type="xml">
<form string="RMA Make RTV">
<p class="oe_grey">
RMAs will be batched to pick simultaneously.
</p>
<group>
<field name="partner_id"/>
<field name="partner_shipping_id"/>
<field name="rma_line_ids" nolabel="1" colspan="4">
<tree decoration-warning="rma_state != 'draft'" editable="bottom">
<field name="rma_id" readonly="1" force_save="1"/>
<field name="rma_state" invisible="1"/>
<field name="rma_claim_number"/>
</tree>
</field>
</group>
<footer>
<button name="create_batch" string="Confirm" type="object" class="btn-primary"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_view_rma_make_rtv" model="ir.actions.act_window">
<field name="name">RMA Make RTV</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">rma.make.rtv</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="binding_model_id" ref="rma.model_rma_rma" />
<field name="binding_view_types">list</field>
</record>
</odoo>

View File

@@ -3,7 +3,7 @@
{
'name': 'RMA - Product Cores',
'author': 'Hibou Corp. <hello@hibou.io>',
'version': '13.0.1.0.0',
'version': '13.0.1.0.1',
'license': 'OPL-1',
'category': 'Tools',
'summary': 'RMA Product Cores',
@@ -12,6 +12,7 @@ RMA Product Cores - Return core products from customers.
""",
'website': 'https://hibou.io/',
'depends': [
'hibou_professional',
'product_cores',
'rma_sale',
],

View File

@@ -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

View File

@@ -42,8 +42,8 @@
<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>
<img class="mr4 float-left o_portal_product_img" t-if="product.image_128" t-att-src="image_data_uri(product.image_128)" alt="Product Image" width="64"/>
<img class="mr4 float-left o_portal_product_img" t-if="not product.image_128" src="/web/static/src/img/placeholder.png" alt="Product Image" width="64"/>
</div>
<div class="col-lg-4">
<span t-esc="product.name"/>

View File

@@ -2,12 +2,13 @@
{
'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',
'website': 'https://hibou.io/',
'depends': [
'hibou_professional',
'rma',
'sale',
'sales_team',

View File

@@ -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.')

View File

@@ -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:

View File

@@ -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)

View File

@@ -57,8 +57,8 @@
<t t-set="validity" t-value="rma_template._rma_sale_line_validity(line)"/>
<div class="row" t-attf-class="row #{'' if validity == 'valid' else 'text-muted'}">
<div class="col-lg-1 text-center">
<img t-attf-src="/web/image/product.product/#{line.product_id.id}/image_64"
width="64" alt="Product image"></img>
<img class="mr4 float-left o_portal_product_img" t-if="line.product_id.image_128" t-att-src="image_data_uri(line.product_id.image_128)" alt="Product Image" width="64"/>
<img class="mr4 float-left o_portal_product_img" t-if="not line.product_id.image_128" src="/web/static/src/img/placeholder.png" alt="Product Image" width="64"/>
</div>
<div class="col-lg-3">
<span t-esc="line.product_id.name"/>

View File

@@ -9,6 +9,7 @@
<xpath expr="//page[@name='sales']/group[@name='sale']" position="inside">
<group name="rma_sale" string="RMA Sales">
<field name="rma_sale_validity" string="Eligible Days"/>
<field name="rma_sale_warranty_validity" string="Warranty Eligible Days"/>
</group>
</xpath>
</field>
@@ -22,6 +23,7 @@
<xpath expr="//page[@name='sales']/group[@name='sale']" position="inside">
<group name="rma_sale" string="RMA Sales">
<field name="rma_sale_validity" string="Eligible Days"/>
<field name="rma_sale_warranty_validity" string="Warranty Eligible Days"/>
</group>
</xpath>
</field>

View File

@@ -9,6 +9,7 @@
<field name="arch" type="xml">
<xpath expr="//field[@name='usage']" position="after">
<field name="so_decrement_order_qty" string="Decrement Ordered Qty" attrs="{'invisible': [('usage', '!=', 'sale_order')]}"/>
<field name="sale_order_warranty" string="Warranty" attrs="{'invisible': [('usage', '!=', 'sale_order')]}"/>
</xpath>
</field>
</record>