diff --git a/addons/blanket_so_po/__init__.py b/addons/blanket_so_po/__init__.py new file mode 100755 index 00000000..c73120bb --- /dev/null +++ b/addons/blanket_so_po/__init__.py @@ -0,0 +1,4 @@ +# Part of Flectra. See LICENSE file for full copyright and licensing details. + +from . import wizard +from . import models diff --git a/addons/blanket_so_po/__manifest__.py b/addons/blanket_so_po/__manifest__.py new file mode 100644 index 00000000..5fae39ea --- /dev/null +++ b/addons/blanket_so_po/__manifest__.py @@ -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, +} diff --git a/addons/blanket_so_po/demo/blanket_purchase_demo.xml b/addons/blanket_so_po/demo/blanket_purchase_demo.xml new file mode 100644 index 00000000..7f605db9 --- /dev/null +++ b/addons/blanket_so_po/demo/blanket_purchase_demo.xml @@ -0,0 +1,41 @@ + + + + + + + + + + [RAM-SR5] RAM DDR SR5 + + + + 6000 + 10 + + + + + + [M-Wir] Mouse, Wireless] + + + + 200 + 5 + + + + + + [MBi9] Motherboard I9P57 + + + + 3000 + 8 + + + + diff --git a/addons/blanket_so_po/demo/blanket_sale_demo.xml b/addons/blanket_so_po/demo/blanket_sale_demo.xml new file mode 100644 index 00000000..7cb790fd --- /dev/null +++ b/addons/blanket_so_po/demo/blanket_sale_demo.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + Laptop E5023 + + 10 + + 2950.00 + + + + + + Headset USB + + 3 + + 2950.00 + + + + + + Webcamv + + 10 + + 45.00 + + + + diff --git a/addons/blanket_so_po/models/__init__.py b/addons/blanket_so_po/models/__init__.py new file mode 100644 index 00000000..76c0748f --- /dev/null +++ b/addons/blanket_so_po/models/__init__.py @@ -0,0 +1,5 @@ +# Part of Flectra. See LICENSE file for full copyright and licensing +# details. + +from . import sale +from . import purchase diff --git a/addons/blanket_so_po/models/purchase.py b/addons/blanket_so_po/models/purchase.py new file mode 100644 index 00000000..b2eacfc6 --- /dev/null +++ b/addons/blanket_so_po/models/purchase.py @@ -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 diff --git a/addons/blanket_so_po/models/sale.py b/addons/blanket_so_po/models/sale.py new file mode 100644 index 00000000..5973cab1 --- /dev/null +++ b/addons/blanket_so_po/models/sale.py @@ -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 diff --git a/addons/blanket_so_po/static/description/icon.png b/addons/blanket_so_po/static/description/icon.png new file mode 100644 index 00000000..3dc4657f Binary files /dev/null and b/addons/blanket_so_po/static/description/icon.png differ diff --git a/addons/blanket_so_po/tests/__init__.py b/addons/blanket_so_po/tests/__init__.py new file mode 100644 index 00000000..61859895 --- /dev/null +++ b/addons/blanket_so_po/tests/__init__.py @@ -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 diff --git a/addons/blanket_so_po/tests/test_purchase_to_invoice_and_delivery.py b/addons/blanket_so_po/tests/test_purchase_to_invoice_and_delivery.py new file mode 100644 index 00000000..f1d1edc7 --- /dev/null +++ b/addons/blanket_so_po/tests/test_purchase_to_invoice_and_delivery.py @@ -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( + '=========================================================' + '========================') diff --git a/addons/blanket_so_po/tests/test_sale_to_invoice_and_delivery.py b/addons/blanket_so_po/tests/test_sale_to_invoice_and_delivery.py new file mode 100644 index 00000000..1920d3ab --- /dev/null +++ b/addons/blanket_so_po/tests/test_sale_to_invoice_and_delivery.py @@ -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() diff --git a/addons/blanket_so_po/views/purchase_view.xml b/addons/blanket_so_po/views/purchase_view.xml new file mode 100755 index 00000000..be0c59db --- /dev/null +++ b/addons/blanket_so_po/views/purchase_view.xml @@ -0,0 +1,51 @@ + + + + + + inherit.purchase.form.view + purchase.order + + 1 + + +
+
+
+ + + + + + + + + + + + + + + + + + +
+
+ +
diff --git a/addons/blanket_so_po/views/sale_view.xml b/addons/blanket_so_po/views/sale_view.xml new file mode 100755 index 00000000..7f05658a --- /dev/null +++ b/addons/blanket_so_po/views/sale_view.xml @@ -0,0 +1,47 @@ + + + + + + inherit.sale.order.form.view + sale.order + + 1 + + + + + + + + +
+
+
+ + + + + + + + + +
+
+ +
diff --git a/addons/blanket_so_po/wizard/__init__.py b/addons/blanket_so_po/wizard/__init__.py new file mode 100755 index 00000000..c4e49532 --- /dev/null +++ b/addons/blanket_so_po/wizard/__init__.py @@ -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 diff --git a/addons/blanket_so_po/wizard/transfer_po_products.py b/addons/blanket_so_po/wizard/transfer_po_products.py new file mode 100644 index 00000000..7e5954c6 --- /dev/null +++ b/addons/blanket_so_po/wizard/transfer_po_products.py @@ -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', + } diff --git a/addons/blanket_so_po/wizard/transfer_po_products_view.xml b/addons/blanket_so_po/wizard/transfer_po_products_view.xml new file mode 100755 index 00000000..f4ade64f --- /dev/null +++ b/addons/blanket_so_po/wizard/transfer_po_products_view.xml @@ -0,0 +1,39 @@ + + + + + + purchase.transfer.products.form.view + purchase.transfer.products + +
+ + + + + + +
+
+
+
+
+ + + Transfer Products + purchase.transfer.products + form + form + + new + + +
+ diff --git a/addons/blanket_so_po/wizard/transfer_so_products.py b/addons/blanket_so_po/wizard/transfer_so_products.py new file mode 100644 index 00000000..f76f211d --- /dev/null +++ b/addons/blanket_so_po/wizard/transfer_so_products.py @@ -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', + } diff --git a/addons/blanket_so_po/wizard/transfer_so_products_view.xml b/addons/blanket_so_po/wizard/transfer_so_products_view.xml new file mode 100755 index 00000000..7fa2e212 --- /dev/null +++ b/addons/blanket_so_po/wizard/transfer_so_products_view.xml @@ -0,0 +1,38 @@ + + + + + + sale.transfer.products.form.view + sale.transfer.products + +
+ + + + + + +
+
+
+
+
+ + + Transfer Products + sale.transfer.products + form + form + + new + + +
diff --git a/addons/purchase/tests/test_stockvaluation.py b/addons/purchase/tests/test_stockvaluation.py index 2a39e208..93c8c83b 100644 --- a/addons/purchase/tests/test_stockvaluation.py +++ b/addons/purchase/tests/test_stockvaluation.py @@ -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)