Master so/po riddhi
This commit is contained in:
parent
a216e549e6
commit
f27882fa3c
4
addons/blanket_so_po/__init__.py
Executable file
4
addons/blanket_so_po/__init__.py
Executable file
@ -0,0 +1,4 @@
|
||||
# Part of Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import wizard
|
||||
from . import models
|
26
addons/blanket_so_po/__manifest__.py
Normal file
26
addons/blanket_so_po/__manifest__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Part of Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Blanket Sale Order/Purchase Order',
|
||||
'version': "1.0",
|
||||
'category': 'Sales and Purchase Management',
|
||||
'summary': 'A Blanket Sales/Purchase Order clearly lays out the terms '
|
||||
'and conditions of a Sales/Purchase including quantities '
|
||||
'required and when they are to be delivered.',
|
||||
"author": "Flectra",
|
||||
'website': 'https://flectrahq.com',
|
||||
'depends': ['purchase', 'sale_stock'],
|
||||
'data': [
|
||||
'wizard/transfer_so_products_view.xml',
|
||||
'wizard/transfer_po_products_view.xml',
|
||||
'views/sale_view.xml',
|
||||
'views/purchase_view.xml',
|
||||
],
|
||||
'demo': [
|
||||
'demo/blanket_sale_demo.xml',
|
||||
'demo/blanket_purchase_demo.xml'
|
||||
],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'auto_install': False,
|
||||
}
|
41
addons/blanket_so_po/demo/blanket_purchase_demo.xml
Normal file
41
addons/blanket_so_po/demo/blanket_purchase_demo.xml
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<flectra noupdate="1">
|
||||
|
||||
<record id="blanket_purchase_order" model="purchase.order">
|
||||
<field name="partner_id" ref="base.res_partner_4"/>
|
||||
</record>
|
||||
|
||||
<record id="blanket_purchase_order_line_1" model="purchase.order.line">
|
||||
<field name="order_id" ref="blanket_purchase_order"/>
|
||||
<field name="name">[RAM-SR5] RAM DDR SR5</field>
|
||||
<field name="date_planned" eval="time.strftime('%Y/%m/10')"/>
|
||||
<field name="product_id" ref="product.product_product_6"/>
|
||||
<field name="product_uom" ref="product.product_uom_unit"/>
|
||||
<field name="price_unit">6000</field>
|
||||
<field name="product_qty">10</field>
|
||||
<field name="blanket_po_line" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="blanket_purchase_order_line_2" model="purchase.order.line">
|
||||
<field name="order_id" ref="blanket_purchase_order"/>
|
||||
<field name="name">[M-Wir] Mouse, Wireless]</field>
|
||||
<field name="date_planned" eval="time.strftime('%Y/%m/10')"/>
|
||||
<field name="product_id" ref="product.product_product_7"/>
|
||||
<field name="product_uom" ref="product.product_uom_unit"/>
|
||||
<field name="price_unit">200</field>
|
||||
<field name="product_qty">5</field>
|
||||
<field name="blanket_po_line" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="blanket_purchase_order_line_3" model="purchase.order.line">
|
||||
<field name="order_id" ref="blanket_purchase_order"/>
|
||||
<field name="name">[MBi9] Motherboard I9P57</field>
|
||||
<field name="date_planned" eval="time.strftime('%Y/%m/10')"/>
|
||||
<field name="product_id" ref="product.product_product_8"/>
|
||||
<field name="product_uom" ref="product.product_uom_unit"/>
|
||||
<field name="price_unit">3000</field>
|
||||
<field name="product_qty">8</field>
|
||||
<field name="blanket_po_line" eval="False"/>
|
||||
</record>
|
||||
|
||||
</flectra>
|
45
addons/blanket_so_po/demo/blanket_sale_demo.xml
Normal file
45
addons/blanket_so_po/demo/blanket_sale_demo.xml
Normal file
@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<flectra noupdate="1">
|
||||
|
||||
<record id="sale_order_blanket1" model="sale.order">
|
||||
<field name="partner_id" ref="base.res_partner_4"/>
|
||||
<field name="partner_invoice_id" ref="base.res_partner_address_13"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_address_13"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.team_sales_department"/>
|
||||
<field name="date_order"
|
||||
eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
</record>
|
||||
|
||||
<record id="sale_order_line_blanket_1" model="sale.order.line">
|
||||
<field name="order_id" ref="sale_order_blanket1"/>
|
||||
<field name="name">Laptop E5023</field>
|
||||
<field name="product_id" ref="product.product_product_25"/>
|
||||
<field name="product_uom_qty">10</field>
|
||||
<field name="product_uom" ref="product.product_uom_unit"/>
|
||||
<field name="price_unit">2950.00</field>
|
||||
<field name="blanket_so_line" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="sale_order_line_blanket_2" model="sale.order.line">
|
||||
<field name="order_id" ref="sale_order_blanket1"/>
|
||||
<field name="name">Headset USB</field>
|
||||
<field name="product_id" ref="product.product_delivery_01"/>
|
||||
<field name="product_uom_qty">3</field>
|
||||
<field name="product_uom" ref="product.product_uom_unit"/>
|
||||
<field name="price_unit">2950.00</field>
|
||||
<field name="blanket_so_line" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="sale_order_line_blanket_3" model="sale.order.line">
|
||||
<field name="order_id" ref="sale_order_blanket1"/>
|
||||
<field name="name">Webcamv</field>
|
||||
<field name="product_id" ref="product.product_delivery_02"/>
|
||||
<field name="product_uom_qty">10</field>
|
||||
<field name="product_uom" ref="product.product_uom_unit"/>
|
||||
<field name="price_unit">45.00</field>
|
||||
<field name="blanket_so_line" eval="False"/>
|
||||
</record>
|
||||
|
||||
</flectra>
|
5
addons/blanket_so_po/models/__init__.py
Normal file
5
addons/blanket_so_po/models/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
# Part of Flectra. See LICENSE file for full copyright and licensing
|
||||
# details.
|
||||
|
||||
from . import sale
|
||||
from . import purchase
|
66
addons/blanket_so_po/models/purchase.py
Normal file
66
addons/blanket_so_po/models/purchase.py
Normal file
@ -0,0 +1,66 @@
|
||||
# Part of Flectra. See LICENSE file for full copyright and licensing
|
||||
# details.
|
||||
|
||||
from flectra import api, fields, models, _
|
||||
from flectra.exceptions import Warning
|
||||
|
||||
|
||||
class PurchaseOrder(models.Model):
|
||||
_inherit = "purchase.order"
|
||||
|
||||
@api.multi
|
||||
def button_cancel(self):
|
||||
res = super(PurchaseOrder, self).button_cancel()
|
||||
if self.order_line.filtered(
|
||||
lambda l: l.blanket_po_line):
|
||||
raise Warning(
|
||||
_('Sorry, You can not cancel blanket line based PO.'))
|
||||
self.write({'state': 'cancel'})
|
||||
return res
|
||||
|
||||
|
||||
class PurchaseOrderLine(models.Model):
|
||||
_inherit = 'purchase.order.line'
|
||||
|
||||
@api.multi
|
||||
def _prepare_stock_moves(self, picking):
|
||||
res = super(PurchaseOrderLine, self)._prepare_stock_moves(picking)
|
||||
self.ensure_one()
|
||||
context = dict(self._context)
|
||||
if self.product_id.type not in ['product',
|
||||
'consu'] or self.blanket_po_line \
|
||||
and not context.get('blanket'):
|
||||
return []
|
||||
qty = 0.0
|
||||
for move in self.move_ids.filtered(
|
||||
lambda x: x.state != 'cancel' and not
|
||||
x.location_dest_id.usage == "supplier"):
|
||||
qty += move.product_uom._compute_quantity(
|
||||
move.product_uom_qty, self.product_uom,
|
||||
rounding_method='HALF-UP')
|
||||
for re in res:
|
||||
if self.blanket_po_line and context.get('transfer_qty'):
|
||||
re['product_uom_qty'] = context.get('transfer_qty')
|
||||
else:
|
||||
re['product_uom_qty'] = self.product_qty - qty
|
||||
return res
|
||||
|
||||
blanket_po_line = fields.Boolean(string="Blanket Order", copy=False)
|
||||
remaining_to_po_transfer = fields.Float(string="Remaining to Transfer",
|
||||
copy=False)
|
||||
|
||||
@api.multi
|
||||
def create(self, vals):
|
||||
if vals.get('product_qty') and vals.get('blanket_po_line'):
|
||||
vals.update(
|
||||
{'remaining_to_po_transfer': vals.get('product_qty')})
|
||||
res = super(PurchaseOrderLine, self).create(vals)
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def write(self, values):
|
||||
result = super(PurchaseOrderLine, self).write(values)
|
||||
for line in self:
|
||||
if 'product_qty' and 'blanket_po_line' in values:
|
||||
line.remaining_to_po_transfer = line.product_qty
|
||||
return result
|
114
addons/blanket_so_po/models/sale.py
Normal file
114
addons/blanket_so_po/models/sale.py
Normal file
@ -0,0 +1,114 @@
|
||||
# Part of Flectra. See LICENSE file for full copyright and licensing
|
||||
# details.
|
||||
|
||||
from flectra import api, models, fields
|
||||
from flectra.exceptions import UserError
|
||||
from flectra.tools import float_compare
|
||||
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = "sale.order"
|
||||
|
||||
|
||||
class SaleOrderLine(models.Model):
|
||||
_inherit = "sale.order.line"
|
||||
_rec_name = 'product_id'
|
||||
|
||||
blanket_so_line = fields.Boolean(string="Blanket Order", copy=False)
|
||||
remaining_to_so_transfer = fields.Float(string="Remaining to Transfer",
|
||||
copy=False)
|
||||
|
||||
@api.multi
|
||||
def _action_launch_procurement_rule(self):
|
||||
"""
|
||||
Launch procurement group run method with required/custom fields
|
||||
genrated by a
|
||||
sale order line. procurement group will launch '_run_move',
|
||||
'_run_buy' or '_run_manufacture'
|
||||
depending on the sale order line product rule.
|
||||
"""
|
||||
precision = self.env['decimal.precision'].precision_get(
|
||||
'Product Unit of Measure')
|
||||
errors = []
|
||||
context = dict(self._context)
|
||||
for line in self:
|
||||
if line.state != 'sale' or not line.product_id.type in (
|
||||
'consu', 'product') or line.blanket_so_line and \
|
||||
not context.get('blanket'):
|
||||
continue
|
||||
qty = 0.0
|
||||
for move in line.move_ids.filtered(lambda r: r.state != 'cancel'):
|
||||
qty += move.product_uom._compute_quantity(move.product_uom_qty,
|
||||
line.product_uom,
|
||||
rounding_method='HALF-UP')
|
||||
if float_compare(qty, line.product_uom_qty,
|
||||
precision_digits=precision) >= 0:
|
||||
continue
|
||||
|
||||
group_id = line.order_id.procurement_group_id
|
||||
if not group_id:
|
||||
group_id = self.env['procurement.group'].create({
|
||||
'name': line.order_id.name,
|
||||
'move_type': line.order_id.picking_policy,
|
||||
'sale_id': line.order_id.id,
|
||||
'partner_id': line.order_id.partner_shipping_id.id,
|
||||
})
|
||||
line.order_id.procurement_group_id = group_id
|
||||
else:
|
||||
# In case the procurement group is already created and the
|
||||
# order was
|
||||
# cancelled, we need to update certain values of the group.
|
||||
updated_vals = {}
|
||||
if group_id.partner_id != line.order_id.partner_shipping_id:
|
||||
updated_vals.update(
|
||||
{'partner_id': line.order_id.partner_shipping_id.id})
|
||||
if group_id.move_type != line.order_id.picking_policy:
|
||||
updated_vals.update(
|
||||
{'move_type': line.order_id.picking_policy})
|
||||
if updated_vals:
|
||||
group_id.write(updated_vals)
|
||||
|
||||
values = line._prepare_procurement_values(group_id=group_id)
|
||||
|
||||
if line.blanket_so_line and context.get('blanket'):
|
||||
product_qty = context.get('transfer_qty')
|
||||
else:
|
||||
product_qty = line.product_uom_qty - qty
|
||||
|
||||
procurement_uom = line.product_uom
|
||||
quant_uom = line.product_id.uom_id
|
||||
get_param = self.env['ir.config_parameter'].sudo().get_param
|
||||
if procurement_uom.id != quant_uom.id and get_param(
|
||||
'stock.propagate_uom') != '1':
|
||||
product_qty = line.product_uom._compute_quantity(product_qty,
|
||||
quant_uom,
|
||||
rounding_method='HALF-UP')
|
||||
procurement_uom = quant_uom
|
||||
|
||||
try:
|
||||
self.env['procurement.group'].run(line.product_id, product_qty,
|
||||
procurement_uom,
|
||||
line.order_id.partner_shipping_id.property_stock_customer,
|
||||
line.name,
|
||||
line.order_id.name, values)
|
||||
except UserError as error:
|
||||
errors.append(error.name)
|
||||
if errors:
|
||||
raise UserError('\n'.join(errors))
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def create(self, vals):
|
||||
if vals.get('product_uom_qty') and vals.get('blanket_so_line'):
|
||||
vals.update(
|
||||
{'remaining_to_so_transfer': vals.get('product_uom_qty')})
|
||||
res = super(SaleOrderLine, self).create(vals)
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def write(self, values):
|
||||
result = super(SaleOrderLine, self).write(values)
|
||||
for line in self:
|
||||
if 'product_uom_qty' and 'blanket_so_line' in values:
|
||||
line.remaining_to_so_transfer = line.product_uom_qty
|
||||
return result
|
BIN
addons/blanket_so_po/static/description/icon.png
Normal file
BIN
addons/blanket_so_po/static/description/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.9 KiB |
4
addons/blanket_so_po/tests/__init__.py
Normal file
4
addons/blanket_so_po/tests/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
# Part of Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_purchase_to_invoice_and_delivery
|
||||
from . import test_sale_to_invoice_and_delivery
|
@ -0,0 +1,175 @@
|
||||
# Part of Flectra. See LICENSE file for full copyright and licensing
|
||||
# details.
|
||||
|
||||
import logging
|
||||
|
||||
from flectra.exceptions import Warning
|
||||
from flectra.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestPurchaseOrder(TransactionCase):
|
||||
def setUp(self):
|
||||
super(TestPurchaseOrder, self).setUp()
|
||||
self.stock_move = self.env['stock.move']
|
||||
self.inv_obj = self.env['account.invoice']
|
||||
self.purchase_wizard = self.env['purchase.transfer.products']
|
||||
self.po1 = self.env.ref('blanket_so_po.blanket_purchase_order')
|
||||
self.po_line_with_blanket = self.env.ref(
|
||||
'blanket_so_po.blanket_purchase_order_line_1')
|
||||
self.po_line_without_blanket = self.env.ref(
|
||||
'blanket_so_po.blanket_purchase_order_line_2')
|
||||
self.po_line_without_blanket2 = self.env.ref(
|
||||
'blanket_so_po.blanket_purchase_order_line_3')
|
||||
|
||||
def test_2_purchase_with_blanket(self):
|
||||
self.assertTrue(self.po1, 'Purchase: no purchase order created')
|
||||
self.po1.button_confirm()
|
||||
self.assertEqual(self.po1.state, 'purchase',
|
||||
'Purchase: PO state should be "Purchase"')
|
||||
self.assertTrue(self.po1.picking_ids, "Picking should be created.")
|
||||
logging.info('Test Cases for Blanket Purchase order')
|
||||
logging.info('Purchase Order - %s' % (self.po1.name))
|
||||
logging.info(
|
||||
'============================================================='
|
||||
'==================+=====')
|
||||
logging.info(
|
||||
' | Blanket Po Line | Product | Ordered Qty | Remaining '
|
||||
'to transfer | Received Qty |')
|
||||
for line in self.po1.order_line:
|
||||
logging.info(
|
||||
' %s | %s | %d | %d '
|
||||
' | %d ' % (
|
||||
line.blanket_po_line, line.product_id.name,
|
||||
line.product_qty,
|
||||
line.remaining_to_po_transfer, line.qty_received))
|
||||
logging.info(
|
||||
'========================================================='
|
||||
'========================')
|
||||
#
|
||||
blanket_lines_po = self.po1.order_line.search(
|
||||
[('order_id', '=', self.po1.id),
|
||||
('blanket_po_line', '=', True)])
|
||||
uom_qty = self.po_line_with_blanket.product_qty
|
||||
transfer_qty = 2
|
||||
remaining_qty = 0.0
|
||||
|
||||
len_blanket_lines = len(blanket_lines_po)
|
||||
total_po_line = len(self.po1.order_line)
|
||||
move_lines = len(self.po1.picking_ids.move_lines)
|
||||
logging.info(
|
||||
'*****************************************************')
|
||||
logging.info('Delivery Order for Purchase Order- %s' % (
|
||||
self.po1.picking_ids.name))
|
||||
logging.info(
|
||||
'============================================================'
|
||||
'===================+=====')
|
||||
logging.info(
|
||||
' | Product | Initial Demand | Reserved | Done |')
|
||||
for move in self.po1.picking_ids.move_lines:
|
||||
logging.info('| %s | %d |%d | %d' % (
|
||||
move.product_id.name, move.product_uom_qty,
|
||||
move.reserved_availability, move.quantity_done))
|
||||
logging.info(
|
||||
'========================================================'
|
||||
'=========================')
|
||||
self.assertEqual(move_lines, total_po_line - len_blanket_lines,
|
||||
'There is no equal number of move lines in move')
|
||||
|
||||
self.assertTrue(self.po_line_with_blanket.blanket_po_line,
|
||||
'Purchase: There is a Blanket po line')
|
||||
|
||||
remaining_qty = uom_qty - transfer_qty
|
||||
transfer_wizard = self.purchase_wizard.create(
|
||||
{'ref_id': self.po_line_with_blanket.id,
|
||||
'transfer_qty': transfer_qty})
|
||||
transfer_wizard.split_qty_wt_newline_po()
|
||||
self.assertEqual(remaining_qty, uom_qty - transfer_qty,
|
||||
'Remaining to transfer qty is different')
|
||||
|
||||
total_po_line = len(self.po1.order_line)
|
||||
|
||||
transfer_qty += 5
|
||||
remaining_qty = uom_qty - transfer_qty
|
||||
transfer_wizard = self.purchase_wizard.create(
|
||||
{'ref_id': self.po_line_with_blanket.id, 'transfer_qty': 5})
|
||||
transfer_wizard.split_qty_wt_newline_po()
|
||||
self.assertEqual(remaining_qty, uom_qty - transfer_qty,
|
||||
'Remaining to transfer qty is different')
|
||||
|
||||
logging.info(
|
||||
'*****************************************************')
|
||||
logging.info(
|
||||
'Purchase Order after Blanket Split lines- %s' % (
|
||||
self.po1.name))
|
||||
logging.info(
|
||||
'============================================================'
|
||||
'===================+=====')
|
||||
logging.info(
|
||||
' | Blanket Po Line | Product | Ordered Qty | Remaining '
|
||||
'to transfer | Received Qty |')
|
||||
for line in self.po1.order_line:
|
||||
logging.info(
|
||||
' %s | %s | %d | %d '
|
||||
' | %d ' % (
|
||||
line.blanket_po_line, line.product_id.name,
|
||||
line.product_qty,
|
||||
line.remaining_to_po_transfer, line.qty_received))
|
||||
logging.info(
|
||||
'========================================================='
|
||||
'========================')
|
||||
|
||||
with self.assertRaises(Warning):
|
||||
self.po1.button_cancel()
|
||||
|
||||
self.picking = self.po1.picking_ids[0]
|
||||
self.assertEqual(self.picking.move_lines[-1].quantity_done, 0.0)
|
||||
self.assertEqual(self.picking.move_lines[-2].quantity_done, 0.0)
|
||||
self.picking.move_lines[-1].quantity_done = 2
|
||||
|
||||
self.po1._compute_picking()
|
||||
self.po1._compute_is_shipped()
|
||||
|
||||
logging.info(
|
||||
'*****************************************************')
|
||||
logging.info('Delivery Order - %s' % (self.picking.name))
|
||||
logging.info(
|
||||
'========================================================='
|
||||
'======================+=====')
|
||||
logging.info(
|
||||
' id | Product | Initial Demand | Reserved | Done |')
|
||||
for move in self.picking.move_lines:
|
||||
logging.info(
|
||||
'%d | %s | %d |%d | %d' % (
|
||||
move.id, move.product_id.name, move.product_uom_qty,
|
||||
move.reserved_availability, move.quantity_done))
|
||||
logging.info(
|
||||
'========================================================='
|
||||
'========================')
|
||||
|
||||
self.picking.force_assign()
|
||||
res_dict = self.picking.button_validate()
|
||||
backorder_wizard = self.env[(res_dict.get('res_model'))].browse(
|
||||
res_dict.get('res_id'))
|
||||
backorder_wizard.process()
|
||||
self.picking.action_done()
|
||||
|
||||
logging.info(
|
||||
'*****************************************************')
|
||||
logging.info('Purchase Order after validate Delivery order- %s' % (
|
||||
self.po1.name))
|
||||
logging.info(
|
||||
'============================================================'
|
||||
'===================+=====')
|
||||
logging.info(
|
||||
' | Blanket Po Line | Product | Ordered Qty | Remaining '
|
||||
'to transfer | Received Qty |')
|
||||
for line in self.po1.order_line:
|
||||
logging.info(
|
||||
' %s | %s | %d | %d '
|
||||
' | %d ' % (
|
||||
line.blanket_po_line, line.product_id.name,
|
||||
line.product_qty,
|
||||
line.remaining_to_po_transfer, line.qty_received))
|
||||
logging.info(
|
||||
'========================================================='
|
||||
'========================')
|
180
addons/blanket_so_po/tests/test_sale_to_invoice_and_delivery.py
Normal file
180
addons/blanket_so_po/tests/test_sale_to_invoice_and_delivery.py
Normal file
@ -0,0 +1,180 @@
|
||||
# Part of Flectra. See LICENSE file for full copyright and licensing
|
||||
# details.
|
||||
|
||||
import logging
|
||||
|
||||
from flectra.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestSaleOrder(TransactionCase):
|
||||
def setUp(self):
|
||||
super(TestSaleOrder, self).setUp()
|
||||
self.so_model = self.env['sale.order']
|
||||
self.so_line_model = self.env['sale.order.line']
|
||||
self.stock_picking_model = self.env['stock.picking']
|
||||
self.stock_move_model = self.env['stock.move']
|
||||
self.stock_location_model = self.env['stock.location']
|
||||
self.sale_wizard = self.env['sale.transfer.products']
|
||||
self.invoice_wizard = self.env['sale.advance.payment.inv']
|
||||
self.sale1 = self.env.ref('blanket_so_po.sale_order_blanket1')
|
||||
self.so_line_with_blanket = self.env.ref(
|
||||
'blanket_so_po.sale_order_line_blanket_1')
|
||||
self.so_line_without_blanket = self.env.ref(
|
||||
'blanket_so_po.sale_order_line_blanket_2')
|
||||
self.so_line_without_blanket2 = self.env.ref(
|
||||
'blanket_so_po.sale_order_line_blanket_3')
|
||||
self.inv_obj = self.env['account.invoice']
|
||||
|
||||
def test_1_sale_with_blanket(self):
|
||||
self.sale1.force_quotation_send()
|
||||
self.sale1.action_confirm()
|
||||
self.assertTrue(self.sale1.state, 'sale')
|
||||
self.assertTrue(self.sale1.invoice_status, 'to invoice')
|
||||
logging.info('Test Cases for Blanket Sale order')
|
||||
logging.info('Sale Order - %s' % (self.sale1.name))
|
||||
logging.info(
|
||||
'============================================================'
|
||||
'===================+=====')
|
||||
logging.info(
|
||||
' | Blanket So Line | Product | Ordered Qty | Remaining '
|
||||
'to transfer | Delivered |')
|
||||
for line in self.sale1.order_line:
|
||||
logging.info(
|
||||
' %s | %s | %d | %d '
|
||||
' | %d ' % (
|
||||
line.blanket_so_line, line.product_id.name,
|
||||
line.product_uom_qty, line.remaining_to_so_transfer,
|
||||
line.qty_delivered))
|
||||
logging.info(
|
||||
'========================================================'
|
||||
'=========================')
|
||||
uom_qty = self.so_line_with_blanket.product_uom_qty
|
||||
transfer_qty = 2
|
||||
remaining_qty = 0.0
|
||||
|
||||
logging.info(
|
||||
'*****************************************************')
|
||||
logging.info(
|
||||
'========================================================='
|
||||
'======================+=====')
|
||||
logging.info(
|
||||
' | Product | Initial Demand | Reserved | Done |')
|
||||
for picking in self.sale1.picking_ids:
|
||||
for line in picking.move_lines:
|
||||
logging.info('| %s | %d |%d | %d' % (
|
||||
line.product_id.name, line.product_uom_qty,
|
||||
line.reserved_availability, line.quantity_done))
|
||||
logging.info(
|
||||
'======================================================='
|
||||
'==========================')
|
||||
|
||||
remaining_qty = uom_qty - transfer_qty
|
||||
transfer_wizard = self.sale_wizard.create(
|
||||
{'ref_id': self.so_line_with_blanket.id,
|
||||
'transfer_qty': transfer_qty})
|
||||
transfer_wizard.split_qty_wt_newline()
|
||||
self.assertEqual(remaining_qty, uom_qty - transfer_qty,
|
||||
'Remaining to transfer qty is different')
|
||||
|
||||
transfer_qty += 5
|
||||
remaining_qty = uom_qty - transfer_qty
|
||||
transfer_wizard = self.sale_wizard.create(
|
||||
{'ref_id': self.so_line_with_blanket.id, 'transfer_qty': 5})
|
||||
transfer_wizard.split_qty_wt_newline()
|
||||
self.assertEqual(remaining_qty, uom_qty - transfer_qty,
|
||||
'Remaining to transfer qty is different')
|
||||
|
||||
logging.info(
|
||||
'*****************************************************')
|
||||
logging.info(
|
||||
'Sale Order after Blanket Split lines- %s' % (self.sale1.name))
|
||||
logging.info(
|
||||
'========================================================='
|
||||
'======================+=====')
|
||||
logging.info(
|
||||
' | Blanket So Line | Product | Ordered Qty | Remaining '
|
||||
'to transfer | Delivered |')
|
||||
for line in self.sale1.order_line:
|
||||
logging.info(
|
||||
' %s | %s | %d | %d '
|
||||
' | %d ' % (
|
||||
line.blanket_so_line, line.product_id.name,
|
||||
line.product_uom_qty, line.remaining_to_so_transfer,
|
||||
line.qty_delivered))
|
||||
logging.info(
|
||||
'======================================================='
|
||||
'==========================')
|
||||
|
||||
self.assertEqual(self.sale1.picking_ids.move_lines[-1].quantity_done,
|
||||
0.0)
|
||||
self.assertEqual(self.sale1.picking_ids.move_lines[-2].quantity_done,
|
||||
0.0)
|
||||
self.sale1.picking_ids.move_lines[-1].quantity_done = 2
|
||||
|
||||
logging.info(
|
||||
'*****************************************************')
|
||||
logging.info('Delivery Order - %s' % (self.sale1.picking_ids.name))
|
||||
logging.info(
|
||||
'==========================================================='
|
||||
'====================+=====')
|
||||
logging.info(
|
||||
' | Product | Initial Demand | Reserved | Done |')
|
||||
for move in self.sale1.picking_ids.move_lines:
|
||||
logging.info('| %s | %d |%d | %d' % (
|
||||
move.product_id.name, move.product_uom_qty,
|
||||
move.reserved_availability, move.quantity_done))
|
||||
logging.info(
|
||||
'======================================================='
|
||||
'==========================')
|
||||
|
||||
self.sale1.picking_ids.action_confirm()
|
||||
self.sale1.picking_ids.action_assign()
|
||||
res_dict = self.sale1.picking_ids.button_validate()
|
||||
backorder_wizard = self.env[(res_dict.get('res_model'))].browse(
|
||||
res_dict.get('res_id'))
|
||||
backorder_wizard.process()
|
||||
|
||||
logging.info(
|
||||
'*****************************************************')
|
||||
logging.info(
|
||||
'Sale Order after validate Delivery order- %s' % (
|
||||
self.sale1.name))
|
||||
logging.info(
|
||||
'=========================================================='
|
||||
'=====================+=====')
|
||||
logging.info(
|
||||
' | Blanket So Line | Product | Ordered Qty | Remaining '
|
||||
'to transfer | Delivered |invoice status')
|
||||
for line in self.sale1.order_line:
|
||||
logging.info(
|
||||
' %s | %s | %d | %d '
|
||||
' | %d |%s' % (
|
||||
line.blanket_so_line, line.product_id.name,
|
||||
line.product_uom_qty, line.remaining_to_so_transfer,
|
||||
line.qty_delivered, line.invoice_status))
|
||||
logging.info(
|
||||
'======================================================='
|
||||
'==========================')
|
||||
|
||||
self.assertEqual(len(self.sale1.picking_ids), 2,
|
||||
'There is no 2 pickings are available')
|
||||
|
||||
context = {"active_model": 'sale.order',
|
||||
"active_ids": [self.sale1.id],
|
||||
"active_id": self.sale1.id}
|
||||
|
||||
for invoice in self.sale1.invoice_ids:
|
||||
logging.info('Invoice of Delivered Quantity.')
|
||||
for line in invoice.invoice_line_ids:
|
||||
logging.info(
|
||||
'==================================================')
|
||||
logging.info(
|
||||
'| Product | Quantity | Unit Price | Subtotal |')
|
||||
logging.info(
|
||||
'=================================================|')
|
||||
logging.info(
|
||||
'|%s |%d |%d |%d | ' % (
|
||||
line.product_id.name, line.quantity,
|
||||
line.price_unit,
|
||||
line.price_subtotal))
|
||||
invoice.with_context(context).invoice_validate()
|
51
addons/blanket_so_po/views/purchase_view.xml
Executable file
51
addons/blanket_so_po/views/purchase_view.xml
Executable file
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<flectra>
|
||||
|
||||
<record id="inherit_blanket_purchase_order_form_view" model="ir.ui.view">
|
||||
<field name="name">inherit.purchase.form.view</field>
|
||||
<field name="model">purchase.order</field>
|
||||
<field name="inherit_id" ref="purchase.purchase_order_form"/>
|
||||
<field name="priority">1</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="/form/sheet/notebook/page/field[@name='order_line']/form/sheet/group"
|
||||
position="before">
|
||||
<header>
|
||||
<button name="%(action_purchase_transfer_products)d"
|
||||
type="action" string="Transfer"
|
||||
class="oe_highlight"
|
||||
attrs="{'invisible':['|', '|', ('blanket_po_line', '=', False), ('remaining_to_po_transfer','<=',0),('state','!=','purchase')]}"/>
|
||||
</header>
|
||||
</xpath>
|
||||
<xpath expr="/form/sheet/notebook/page/field[@name='order_line']/form/sheet/group/group/div/field[@name='product_uom']"
|
||||
position="before">
|
||||
<field name="state" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='order_line']/tree"
|
||||
position="attributes">
|
||||
<attribute name="editable"/>
|
||||
</xpath>
|
||||
<xpath expr="/form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='name']"
|
||||
position="after">
|
||||
<field name='blanket_po_line'/>
|
||||
</xpath>
|
||||
<xpath expr="/form/sheet/notebook/page/field[@name='order_line']/form/sheet/group/group/field[@name='taxes_id']"
|
||||
position="before">
|
||||
<field name='blanket_po_line'
|
||||
attrs="{'readonly':[('state','=','sale')]}"/>
|
||||
</xpath>
|
||||
<xpath expr="/form/sheet/notebook/page/field[@name='order_line']/form/sheet/group/group/field[@name='product_id']"
|
||||
position="after">
|
||||
<field name="remaining_to_po_transfer"
|
||||
attrs="{'invisible':[('state','!=','purchase')]}"
|
||||
string="Remaining to Transfer"/>
|
||||
</xpath>
|
||||
<xpath expr="/form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='qty_received']"
|
||||
position="after">
|
||||
<field name="remaining_to_po_transfer"
|
||||
string="Remaining to Transfer"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</flectra>
|
47
addons/blanket_so_po/views/sale_view.xml
Executable file
47
addons/blanket_so_po/views/sale_view.xml
Executable file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<flectra>
|
||||
|
||||
<record id="inherit_blanket_sale_order_form_view" model="ir.ui.view">
|
||||
<field name="name">inherit.sale.order.form.view</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="inherit_id" ref="sale.view_order_form"/>
|
||||
<field name="priority">1</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="/form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='name']"
|
||||
position="after">
|
||||
<field name='blanket_so_line'/>
|
||||
</xpath>
|
||||
<xpath expr="/form/sheet/notebook/page/field[@name='order_line']/form/group/group/field[@name='tax_id']"
|
||||
position="before">
|
||||
<field name="blanket_so_line"
|
||||
attrs="{'readonly':[('state','=','sale')]}"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='order_line']/form/group"
|
||||
position="before">
|
||||
<header>
|
||||
<button name="%(action_sale_transfer_products)d"
|
||||
type="action" string="Transfer"
|
||||
class="oe_highlight"
|
||||
attrs="{'invisible':['|', '|', ('blanket_so_line', '=', False), ('remaining_to_so_transfer','<=',0),('state','!=','sale')]}"/>
|
||||
</header>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='order_line']/tree"
|
||||
position="attributes">
|
||||
<attribute name="editable"/>
|
||||
</xpath>
|
||||
<xpath expr="/form/sheet/notebook/page/field[@name='order_line']/form/group/group/label[@for='product_uom_qty']"
|
||||
position="before">
|
||||
<field name="remaining_to_so_transfer"
|
||||
attrs="{'invisible':[('state','!=','sale')]}"
|
||||
string="Remaining to Transfer"/>
|
||||
</xpath>
|
||||
<xpath expr="/form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='product_uom_qty']"
|
||||
position="after">
|
||||
<field name="remaining_to_so_transfer"
|
||||
string="Remaining to Transfer"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</flectra>
|
6
addons/blanket_so_po/wizard/__init__.py
Executable file
6
addons/blanket_so_po/wizard/__init__.py
Executable file
@ -0,0 +1,6 @@
|
||||
# Part of Flectra. See LICENSE file for full copyright and licensing
|
||||
# details.
|
||||
|
||||
|
||||
from . import transfer_po_products
|
||||
from . import transfer_so_products
|
56
addons/blanket_so_po/wizard/transfer_po_products.py
Normal file
56
addons/blanket_so_po/wizard/transfer_po_products.py
Normal file
@ -0,0 +1,56 @@
|
||||
# Part of Flectra. See LICENSE file for full copyright and licensing
|
||||
# details.
|
||||
|
||||
from flectra import api, fields, models, _
|
||||
from flectra.exceptions import ValidationError
|
||||
|
||||
|
||||
class PurchaseTransferProducts(models.TransientModel):
|
||||
_name = 'purchase.transfer.products'
|
||||
_description = 'Transfer products from Purchase Lines'
|
||||
|
||||
transfer_qty = fields.Float("Transfer Qty")
|
||||
ref_id = fields.Many2one('purchase.order.line',
|
||||
string="Product Reference",
|
||||
readonly=True)
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
context = dict(self._context)
|
||||
result = super(PurchaseTransferProducts, self).default_get(
|
||||
fields)
|
||||
purchase_line = self.env['purchase.order.line'].browse(
|
||||
context.get('active_id'))
|
||||
result.update({
|
||||
'ref_id': purchase_line.id or False,
|
||||
'transfer_qty': purchase_line.remaining_to_po_transfer})
|
||||
return result
|
||||
|
||||
@api.multi
|
||||
def split_qty_wt_newline_po(self):
|
||||
self.ensure_one()
|
||||
if self.ref_id.remaining_to_po_transfer < self.transfer_qty:
|
||||
raise ValidationError(_(
|
||||
'Sorry, You can not transfer more than requested or '
|
||||
'remains!'))
|
||||
elif self.transfer_qty <= 0:
|
||||
raise ValidationError(_(
|
||||
'Sorry, You can not transfer zero or negative '
|
||||
'quantity!'))
|
||||
self.ref_id._create_or_update_picking()
|
||||
pickings = self.ref_id.order_id.picking_ids.filtered(
|
||||
lambda p: p.state not in ('done', 'cancel'))
|
||||
self.ref_id.remaining_to_po_transfer -= self.transfer_qty
|
||||
for picking in pickings:
|
||||
for line in picking.move_lines.filtered(
|
||||
lambda l: l.product_id.id == self.ref_id.product_id.id and
|
||||
l.purchase_line_id.id == self.ref_id.id):
|
||||
total_product_qty = \
|
||||
self.ref_id.remaining_to_po_transfer + \
|
||||
self.ref_id.qty_received
|
||||
line.product_uom_qty = \
|
||||
self.ref_id.product_qty - total_product_qty
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'reload',
|
||||
}
|
39
addons/blanket_so_po/wizard/transfer_po_products_view.xml
Executable file
39
addons/blanket_so_po/wizard/transfer_po_products_view.xml
Executable file
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<flectra>
|
||||
|
||||
<record id="purchase_transfer_products_form_view" model="ir.ui.view">
|
||||
<field name="name">purchase.transfer.products.form.view</field>
|
||||
<field name="model">purchase.transfer.products</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<group>
|
||||
<field name="ref_id"/>
|
||||
<field name="transfer_qty" default_focus="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="split_qty_wt_newline_po" string="Transfer"
|
||||
type="object" class="oe_highlight"
|
||||
context="{'blanket':True, 'transfer_qty':transfer_qty}"/>
|
||||
or
|
||||
<button special="cancel" string="Cancel"
|
||||
class="oe_highlight"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_purchase_transfer_products"
|
||||
model="ir.actions.act_window">
|
||||
<field name="name">Transfer Products</field>
|
||||
<field name="res_model">purchase.transfer.products</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="purchase_transfer_products_form_view"/>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</flectra>
|
||||
|
52
addons/blanket_so_po/wizard/transfer_so_products.py
Normal file
52
addons/blanket_so_po/wizard/transfer_so_products.py
Normal file
@ -0,0 +1,52 @@
|
||||
# Part of Flectra. See LICENSE file for full copyright and licensing
|
||||
# details.
|
||||
|
||||
from flectra import api, fields, models, _
|
||||
from flectra.exceptions import ValidationError
|
||||
|
||||
|
||||
class SaleTransferProducts(models.TransientModel):
|
||||
_name = 'sale.transfer.products'
|
||||
_description = 'Transfer products from Sale Lines'
|
||||
|
||||
transfer_qty = fields.Float("Transfer Qty")
|
||||
ref_id = fields.Many2one('sale.order.line', string="Product Reference",
|
||||
readonly=True)
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
context = dict(self._context)
|
||||
result = super(SaleTransferProducts, self).default_get(fields)
|
||||
sale_line = self.env['sale.order.line'].browse(
|
||||
context.get('active_id'))
|
||||
result.update({
|
||||
'ref_id': sale_line.id or False,
|
||||
'transfer_qty': sale_line.remaining_to_so_transfer})
|
||||
return result
|
||||
|
||||
@api.multi
|
||||
def split_qty_wt_newline(self):
|
||||
if self.ref_id.remaining_to_so_transfer < self.transfer_qty:
|
||||
raise ValidationError(_(
|
||||
'Sorry, You can not transfer more than requested or '
|
||||
'remains!'))
|
||||
elif self.transfer_qty <= 0:
|
||||
raise ValidationError(_(
|
||||
'Sorry, You can not transfer zero or negative '
|
||||
'quantity!'))
|
||||
self.ref_id._action_launch_procurement_rule()
|
||||
pickings = self.ref_id.order_id.picking_ids.filtered(
|
||||
lambda p: p.state not in ('done', 'cancel'))
|
||||
self.ref_id.remaining_to_so_transfer -= self.transfer_qty
|
||||
for picking in pickings:
|
||||
for line in picking.move_lines.filtered(
|
||||
lambda l: l.product_id.id == self.ref_id.product_id.id and
|
||||
l.sale_line_id.id == self.ref_id.id):
|
||||
total_product_qty = self.ref_id.remaining_to_so_transfer \
|
||||
+ self.ref_id.qty_delivered
|
||||
line.product_uom_qty = self.ref_id.product_uom_qty - \
|
||||
total_product_qty
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'reload',
|
||||
}
|
38
addons/blanket_so_po/wizard/transfer_so_products_view.xml
Executable file
38
addons/blanket_so_po/wizard/transfer_so_products_view.xml
Executable file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<flectra>
|
||||
|
||||
<record id="sale_transfer_products_form_view" model="ir.ui.view">
|
||||
<field name="name">sale.transfer.products.form.view</field>
|
||||
<field name="model">sale.transfer.products</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<group>
|
||||
<field name="ref_id"/>
|
||||
<field name="transfer_qty" default_focus="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="split_qty_wt_newline" string="Transfer"
|
||||
type="object" class="oe_highlight"
|
||||
context="{'blanket':True, 'transfer_qty':transfer_qty}"/>
|
||||
or
|
||||
<button special="cancel" string="Cancel"
|
||||
class="oe_highlight"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_sale_transfer_products"
|
||||
model="ir.actions.act_window">
|
||||
<field name="name">Transfer Products</field>
|
||||
<field name="res_model">sale.transfer.products</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="sale_transfer_products_form_view"/>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</flectra>
|
@ -195,7 +195,7 @@ class TestStockValuation(TransactionCase):
|
||||
wizard.process()
|
||||
|
||||
# the unit price of the stock move has been updated to the latest value
|
||||
self.assertEquals(move1.price_unit, price_unit_usd_new_rate)
|
||||
self.assertEquals(round(move1.price_unit), round(price_unit_usd_new_rate))
|
||||
|
||||
self.assertAlmostEqual(self.product1.stock_value, price_unit_usd_new_rate * 10, delta=0.1)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user