diff --git a/addons/claim_from_delivery/claim_delivery_view.xml b/addons/claim_from_delivery/claim_delivery_view.xml index e1a1a43062e5e5121dd8a415f50a7b955c3c2b24..8ce2e7bb7ce32b0e8e85160190963e1e1e10bb44 100644 --- a/addons/claim_from_delivery/claim_delivery_view.xml +++ b/addons/claim_from_delivery/claim_delivery_view.xml @@ -14,8 +14,8 @@ <field name="model">stock.picking</field> <field name="inherit_id" ref="stock.view_picking_form"/> <field name="arch" type="xml"> - <xpath expr="//button[@name='do_partial_open_barcode']" position="before"> - <button class="oe_inline oe_stat_button" type="action" attrs="{'invisible': [('picking_type_code', '!=', 'outgoing')]}" + <xpath expr="//div[@name='button_box']" position="inside"> + <button class="oe_stat_button" type="action" attrs="{'invisible': [('picking_type_code', '!=', 'outgoing')]}" name="%(action_claim_from_delivery)d" icon="fa-comments" > <field string="Claims" name="claim_count_out" widget="statinfo"/> </button> diff --git a/addons/mrp/mrp_view.xml b/addons/mrp/mrp_view.xml index f2d2bf1253b6cbbfcace803e94669309a5db540f..1913fe3e3cea0503b5491d9113cf76090e9c88e0 100644 --- a/addons/mrp/mrp_view.xml +++ b/addons/mrp/mrp_view.xml @@ -717,7 +717,7 @@ <header> <button name="button_confirm" states="draft" string="Confirm Production" class="oe_highlight"/> <button name="%(act_mrp_product_produce)d" states="ready,in_production" string="Produce" type="action" class="oe_highlight"/> - <button name="action_assign" states="confirmed,picking_except" string="Check Availability" type="object" class="oe_highlight"/> + <button name="action_assign" states="confirmed,picking_except" string="Reserve" type="object" class="oe_highlight"/> <button name="force_production" states="confirmed" string="Force Reservation" type="object"/> <button name="button_produce" states="ready" string="Mark as Started"/> <button name="button_cancel" states="draft,ready,in_production" string="Cancel Production"/> diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 1751a41611b027614bf2a039a4cc58dc60c695cf..967b2bd2fa8bcd88fb8b27f9709b8411bbef4e3f 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -796,6 +796,15 @@ class pos_order(osv.osv): addr = order.partner_id and partner_obj.address_get(cr, uid, [order.partner_id.id], ['delivery']) or {} picking_type = order.picking_type_id picking_id = False + location_id = order.location_id.id + if order.partner_id: + destination_id = order.partner_id.property_stock_customer.id + elif picking_type: + if not picking_type.default_location_dest_id: + raise UserError(_('Missing source or destination location for picking type %s. Please configure those fields and try again.' % (picking_type.name,))) + destination_id = picking_type.default_location_dest_id.id + else: + destination_id = partner_obj.default_get(cr, uid, ['property_stock_customer'], context=context)['property_stock_customer'] if picking_type: picking_id = picking_obj.create(cr, uid, { 'origin': order.name, @@ -806,17 +815,10 @@ class pos_order(osv.osv): 'move_type': 'direct', 'note': order.note or "", 'invoice_state': 'none', + 'location_id': location_id, + 'location_dest_id': destination_id, }, context=context) self.write(cr, uid, [order.id], {'picking_id': picking_id}, context=context) - location_id = order.location_id.id - if order.partner_id: - destination_id = order.partner_id.property_stock_customer.id - elif picking_type: - if not picking_type.default_location_dest_id: - raise UserError(_('Missing source or destination location for picking type %s. Please configure those fields and try again.' % (picking_type.name,))) - destination_id = picking_type.default_location_dest_id.id - else: - destination_id = partner_obj.default_get(cr, uid, ['property_stock_customer'], context=context)['property_stock_customer'] move_list = [] for line in order.lines: diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 2722f73b624da14e26c1460beba7f27f302f31ec..e09fb1fad2cfe80f8b8c8ff364cddf05cf278d88 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -834,7 +834,9 @@ class purchase_order(osv.osv): 'picking_type_id': order.picking_type_id.id, 'partner_id': order.partner_id.id, 'date': order.date_order, - 'origin': order.name + 'origin': order.name, + 'location_id': order.partner_id.property_stock_supplier.id, + 'location_dest_id': order.location_id.id, } picking_id = self.pool.get('stock.picking').create(cr, uid, picking_vals, context=context) self._create_stock_moves(cr, uid, order, order.order_line, picking_id, context=context) diff --git a/addons/purchase/test/fifo_returns.yml b/addons/purchase/test/fifo_returns.yml index 9f206b45e91d578fa6d5625759daa15fc4cc820b..6be83f0f8bf859f99d539cd67b7f9ad02ffaa2fe 100644 --- a/addons/purchase/test/fifo_returns.yml +++ b/addons/purchase/test/fifo_returns.yml @@ -72,7 +72,11 @@ pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_fiforet2")).picking_ids return_id = self.create(cr, uid, {}, context={'active_model':'stock.picking', 'active_id': pick_ids[0].id}) return_picking_id, dummy = self._create_returns(cr, uid, [return_id], context={'active_id': pick_ids[0].id}) - self.pool.get('stock.picking').do_transfer(cr, uid, [return_picking_id]) + # Important to pass through confirmation and assignation + pick_obj = self.pool.get('stock.picking') + pick_obj.action_confirm(cr, uid, [return_picking_id]) + pick_obj.action_assign(cr, uid, [return_picking_id]) + pick_obj.do_transfer(cr, uid, [return_picking_id]) - Check the standard price of the product changed to 80.0 as we returned the quants of purchase order 2 - diff --git a/addons/stock/__openerp__.py b/addons/stock/__openerp__.py index b47a0c3d84fffc036ef9e0adf914595315049ed7..e6ae98bf4ca10257a4433df7a87bf8c080d5431f 100644 --- a/addons/stock/__openerp__.py +++ b/addons/stock/__openerp__.py @@ -57,7 +57,7 @@ Dashboard / Reports for Warehouse Management will include: 'wizard/stock_return_picking_view.xml', 'wizard/make_procurement_view.xml', 'wizard/orderpoint_procurement_view.xml', - 'wizard/stock_transfer_details.xml', + 'wizard/stock_pack_details.xml', 'stock_incoterms.xml', 'stock_report.xml', 'stock_view.xml', @@ -72,16 +72,16 @@ Dashboard / Reports for Warehouse Management will include: 'views/report_stockpicking.xml', 'views/report_stockpicking_operations.xml', 'views/report_stockinventory.xml', - 'views/stock.xml', 'report/report_stock_forecast.xml', 'stock_dashboard.xml', + 'wizard/stock_immediate_transfer.xml', + 'wizard/stock_backorder_confirmation.xml' ], 'test': [ 'test/inventory.yml', 'test/move.yml', 'test/procrule.yml', 'test/stock_users.yml', - 'stock_demo.yml', 'test/shipment.yml', 'test/packing.yml', 'test/packingneg.yml', @@ -90,5 +90,4 @@ Dashboard / Reports for Warehouse Management will include: 'installable': True, 'application': True, 'auto_install': False, - 'qweb': ['static/src/xml/picking.xml'], } diff --git a/addons/stock/procurement.py b/addons/stock/procurement.py index 2ade80f003b03bae18e95452239214e38a035274..0c816bb05012c597e20fe9296d4d4d5979adb472 100644 --- a/addons/stock/procurement.py +++ b/addons/stock/procurement.py @@ -149,16 +149,25 @@ class procurement_order(osv.osv): old_proc = False key = tuple() key_routes = {} + proc = False for proc, route in product_routes: - key += (route,) - if old_proc != proc: + if not old_proc: + old_proc = proc + if old_proc == proc: + key += (route,) + else: if key: if key_routes.get(key): - key_routes[key] += [proc] + key_routes[key] += [old_proc] else: - key_routes[key] = [proc] + key_routes[key] = [old_proc] old_proc = proc - key = tuple() + key = (route,) + if proc: #do not forget last one as we passed through it + if key_routes.get(key): + key_routes[key] += [proc] + else: + key_routes[key] = [proc] return key_routes @@ -193,7 +202,6 @@ class procurement_order(osv.osv): if res and res[0]: results_dict[procurement.id] = res[0] - procurements_to_check = [x for x in procurements if x.id not in results_dict.keys()] #group by warehouse_id: wh_dict = self._get_wh_loc_dict(cr, uid, procurements_to_check, context=context) diff --git a/addons/stock/security/ir.model.access.csv b/addons/stock/security/ir.model.access.csv index 21e110f48513c38da62e40f215459c044c4f8399..f410e13a5b30dd77549dcda5a748dd825d0b0633 100644 --- a/addons/stock/security/ir.model.access.csv +++ b/addons/stock/security/ir.model.access.csv @@ -65,7 +65,7 @@ access_procurement_rule,procurement.rule.flow,model_procurement_rule,base.group_ access_procurement_rule_internal,procurement.rule.flow internal,model_procurement_rule,base.group_user,1,0,0,0 access_stock_pack_operation_manager,stock.pack.operation manager,model_stock_pack_operation,stock.group_stock_manager,1,1,1,1 access_stock_pack_operation_user,stock.pack.operation user,model_stock_pack_operation,stock.group_stock_user,1,1,1,1 -access_stock_pack_operation_all,stock.pack.operation all users,model_stock_pack_operation,base.group_user,1,0,0,0 +access_stock_pack_operation_all,stock.pack.operation all users,model_stock_pack_operation,base.group_user,1,1,1,1 access_product_putaway_all,product.putaway all users,model_product_putaway,base.group_user,1,0,0,0 access_product_putaway_manager,product.putaway all managers,model_product_putaway,stock.group_stock_manager,1,1,1,1 access_stock_removal_all,product.removal all users,model_product_removal,base.group_user,1,0,0,0 @@ -73,7 +73,7 @@ access_stock_fixed_putaway_strat,stock_fixed_putaway_strat managers,model_stock_ access_stock_fixed_putaway_user,stock_fixed_putaway_strat user,model_stock_fixed_putaway_strat,stock.group_stock_user,1,0,0,0 access_stock_move_operation_link_manager,stock.move.operation.link manager,model_stock_move_operation_link,stock.group_stock_manager,1,1,1,1 access_stock_move_operation_link_user,stock.move.operation.link user,model_stock_move_operation_link,stock.group_stock_user,1,1,1,1 -access_stock_move_operation_link_all,stock.move.operation.link all users,model_stock_move_operation_link,base.group_user,1,0,0,0 +access_stock_move_operation_link_all,stock.move.operation.link all users,model_stock_move_operation_link,base.group_user,1,1,1,1 access_product_price_history_stock_user,prices.history stock user,product.model_product_price_history,stock.group_stock_user,1,0,1,0 access_product_price_history_stock_manager,prices.history stock manager,product.model_product_price_history,stock.group_stock_manager,1,1,1,1 access_barcode_nomenclature_stock_user,barcode.nomenclature.stock.user,barcodes.model_barcode_nomenclature,stock.group_stock_user,1,0,0,0 diff --git a/addons/stock/static/src/css/barcode.css b/addons/stock/static/src/css/barcode.css deleted file mode 100644 index 1b47d6626904a3bc8082bd85115f904504b3b3d2..0000000000000000000000000000000000000000 --- a/addons/stock/static/src/css/barcode.css +++ /dev/null @@ -1,134 +0,0 @@ -.in_container_hidden { - display: none; -} -.in_container { -} -.oe_pick_app_header{ - margin-top: 0; -} -.oe_picking { - cursor: pointer; -} -.oe_kanban.oe_picking { - min-height: 80px; - margin-bottom: 10px; - border: 1px solid rgba(0, 0, 0, 0.16); - border-bottom-color: rgba(0, 0, 0, 0.3); - -webkit-transition: -webkit-transform, -webkit-box-shadow, border 200ms linear; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; - padding: 5px; -} - -.oe_kanban_color_0 { - background-color: white; - color: #5a5a5a; -} -.oe_kanban_color_1 { - background-color: #cccccc; - color: #424242; -} -.oe_kanban_color_2 { - background-color: #ffc7c7; - color: #7a3737; -} -.oe_kanban_color_3 { - background-color: #fff1c7; - color: #756832; -} -.oe_kanban_color_4 { - background-color: #e3ffc7; - color: #5d6937; -} -.oe_kanban_color_5 { - background-color: #c7ffd5; - color: #1a7759; -} -.oe_kanban_color_6 { - background-color: #c7ffff; - color: #1a5d83; -} -.oe_kanban_color_7 { - background-color: #c7d5ff; - color: #3b3e75; -} -.oe_kanban_color_8 { - background-color: #e3c7ff; - color: #4c3668; -} -.oe_kanban_color_9 { - background-color: #ffc7f1; - color: #6d2c70; -} - -/*Blinking text*/ -.blink_me { - -webkit-animation-name: blinker; - -webkit-animation-duration: 1s; - -webkit-animation-timing-function: linear; - -webkit-animation-iteration-count: 2; - - -moz-animation-name: blinker; - -moz-animation-duration: 1s; - -moz-animation-timing-function: linear; - -moz-animation-iteration-count: 2; - - animation-name: blinker; - animation-duration: 1s; - animation-timing-function: linear; - animation-iteration-count: 2; -} - -@-moz-keyframes blinker { - 0% { opacity: 1.0; } - 50% { opacity: 0.0; } - 100% { opacity: 1.0; } -} - -@-webkit-keyframes blinker { - 0% { opacity: 1.0; } - 50% { opacity: 0.0; } - 100% { opacity: 1.0; } -} - -@keyframes blinker { - 0% { opacity: 1.0; } - 50% { opacity: 0.0; } - 100% { opacity: 1.0; } -} - -/*hide OpenERP leftbar, table should use all width by default and display vertical scrollbar if needed*/ -.oe_leftbar { - display: none; -} - -table.oe_webclient.oe_content_full_screen{ - width: 100%; -} - -body{ - overflow-y: visible !important; -} - -/* --- Styling of OpenERP Elements --- - Needed for loading and error box */ - -/* Increase z-index value to insure that loading goes above navbar of bootstrap*/ -.openerp .oe_loading { - display: none; - z-index: 1000; - position: fixed; - top: 0; - right: 50%; - padding: 4px 12px; - background: #a61300; - color: white; - text-align: center; - border: 1px solid #990000; - border-top: none; - -moz-border-radius-bottomright: 8px; - -moz-border-radius-bottomleft: 8px; - border-bottom-right-radius: 8px; - border-bottom-left-radius: 8px; -} \ No newline at end of file diff --git a/addons/stock/static/src/css/stock.css b/addons/stock/static/src/css/stock.css deleted file mode 100644 index 532eb4f18d1fb144f62ba6d67fb28ef7f900bc83..0000000000000000000000000000000000000000 --- a/addons/stock/static/src/css/stock.css +++ /dev/null @@ -1,21 +0,0 @@ -.openerp .oe_kanban_view .oe_kanban_stock_picking_type { - width: 345px; - cursor: default; - min-height: 245px !important; -} -.openerp .oe_stock_scan_image { - opacity: 0.2; - margin: 0 5px 0; -} -.openerp .oe_stock_scan_image:hover { - opacity: 1 -} - -.oe_stock_scan_image_btn { - /*height : 42px;*/ -} -.oe_stock_scan_button { - border: none !important; - background: none !important; - box-shadow: none !important; -} diff --git a/addons/stock/static/src/img/scan.png b/addons/stock/static/src/img/scan.png deleted file mode 100644 index fe679687ce713ef777e4c1833c7b96e49c3023bb..0000000000000000000000000000000000000000 Binary files a/addons/stock/static/src/img/scan.png and /dev/null differ diff --git a/addons/stock/static/src/js/widgets.js b/addons/stock/static/src/js/widgets.js deleted file mode 100644 index d0ad9dcc7ee8ab9faefa8bf8938d1af0f45c2647..0000000000000000000000000000000000000000 --- a/addons/stock/static/src/js/widgets.js +++ /dev/null @@ -1,1070 +0,0 @@ -odoo.define('stock.widgets', function (require) { -"use strict"; - -var core = require('web.core'); -var data = require('web.data'); -var Dialog = require('web.Dialog'); -var Model = require('web.Model'); -var session = require('web.session'); -var web_client = require('web.web_client'); -var Widget = require('web.Widget'); -var kanban_common = require('web_kanban.common'); - -var _t = core._t; -var QWeb = core.qweb; - -// This widget makes sure that the scaling is disabled on mobile devices. -// Widgets that want to display fullscreen on mobile phone need to extend this -// widget. -var MobileWidget = Widget.extend({ - start: function(){ - if(!$('#oe-mobilewidget-viewport').length){ - $('head').append('<meta id="oe-mobilewidget-viewport" name="viewport" content="initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">'); - } - return this._super(); - }, - destroy: function(){ - $('#oe-mobilewidget-viewport').remove(); - return this._super(); - }, -}); - -var PickingEditorWidget = Widget.extend({ - template: 'PickingEditorWidget', - init: function(parent,options){ - this._super(parent,options); - this.rows = []; - this.search_filter = ""; - }, - get_header: function(){ - return this.getParent().get_header(); - }, - get_location: function(){ - var model = this.getParent(); - var locations = []; - _.each(model.locations, function(loc){ - locations.push({name: loc.complete_name, id: loc.id,}); - }); - return locations; - }, - get_logisticunit: function(){ - var model = this.getParent(); - var ul = []; - _.each(model.uls, function(ulog){ - ul.push({name: ulog.name, id: ulog.id,}); - }); - return ul; - }, - get_rows: function(){ - var model = this.getParent(); - this.rows = []; - var self = this; - var pack_created = []; - _.each( model.packoplines, function(packopline){ - var pack = undefined; - var color = ""; - if (packopline.product_id[1] !== undefined){ pack = packopline.package_id[1];} - if (packopline.product_qty == packopline.qty_done){ color = "success "; } - if (packopline.product_qty < packopline.qty_done){ color = "danger "; } - //also check that we don't have a line already existing for that package - if (packopline.result_package_id[1] !== undefined && $.inArray(packopline.result_package_id[0], pack_created) === -1){ - var myPackage = $.grep(model.packages, function(e){ return e.id == packopline.result_package_id[0]; })[0]; - self.rows.push({ - cols: { product: packopline.result_package_id[1], - qty: '', - rem: '', - uom: undefined, - lot: undefined, - pack: undefined, - container: packopline.result_package_id[1], - container_id: undefined, - loc: packopline.location_id[1], - dest: packopline.location_dest_id[1], - id: packopline.result_package_id[0], - product_id: undefined, - can_scan: false, - head_container: true, - processed: packopline.processed, - package_id: myPackage.id, - ul_id: myPackage.ul_id[0], - }, - classes: ('success container_head ') + (packopline.processed === "true" ? 'processed hidden ':''), - }); - pack_created.push(packopline.result_package_id[0]); - } - self.rows.push({ - cols: { product: packopline.product_id[1] || packopline.package_id[1], - qty: packopline.product_qty, - rem: packopline.qty_done, - uom: packopline.product_uom_id[1], - lot: packopline.lot_id[1], - pack: pack, - container: packopline.result_package_id[1], - container_id: packopline.result_package_id[0], - loc: packopline.location_id[1], - dest: packopline.location_dest_id[1], - id: packopline.id, - product_id: packopline.product_id[0], - can_scan: packopline.result_package_id[1] === undefined ? true : false, - head_container: false, - processed: packopline.processed, - package_id: undefined, - ul_id: -1, - }, - classes: color + (packopline.result_package_id[1] !== undefined ? 'in_container_hidden ' : '') + (packopline.processed === "true" ? 'processed hidden ':''), - }); - }); - //sort element by things to do, then things done, then grouped by packages - var group_by_container = _.groupBy(self.rows, function(row){ - return row.cols.container; - }); - var sorted_row = []; - if (group_by_container.undefined !== undefined){ - group_by_container.undefined.sort(function(a,b){return (b.classes === '') - (a.classes === '');}); - $.each(group_by_container.undefined, function(key, value){ - sorted_row.push(value); - }); - } - - $.each(group_by_container, function(key, value){ - if (key !== 'undefined'){ - $.each(value, function(k,v){ - sorted_row.push(v); - }); - } - }); - - return sorted_row; - }, - renderElement: function(){ - var self = this; - this._super(); - this.check_content_screen(); - this.$('.js_pick_done').click(function(){ self.getParent().done(); }); - this.$('.js_pick_print').click(function(){ self.getParent().print_picking(); }); - this.$('.oe_pick_app_header').text(self.get_header()); - this.$('.oe_searchbox').keyup(function(){ - self.on_searchbox($(this).val()); - }); - this.$('.js_putinpack').click(function(){ self.getParent().pack(); }); - this.$('.js_drop_down').click(function(){ self.getParent().drop_down();}); - this.$('.js_clear_search').click(function(){ - self.on_searchbox(''); - self.$('.oe_searchbox').val(''); - }); - this.$('.oe_searchbox').focus(function(){ - self.getParent().barcode_scanner.disconnect(); - }); - this.$('.oe_searchbox').blur(function(){ - self.getParent().barcode_scanner.connect(function(ean){ - self.get_Parent().scan(ean); - }); - }); - this.$('#js_select').change(function(){ - var selection = self.$('#js_select option:selected').attr('value'); - if (selection === "ToDo"){ - self.getParent().$('.js_pick_pack').removeClass('hidden'); - self.getParent().$('.js_drop_down').removeClass('hidden'); - self.$('.js_pack_op_line.processed').addClass('hidden'); - self.$('.js_pack_op_line:not(.processed)').removeClass('hidden'); - } - else{ - self.getParent().$('.js_pick_pack').addClass('hidden'); - self.getParent().$('.js_drop_down').addClass('hidden'); - self.$('.js_pack_op_line.processed').removeClass('hidden'); - self.$('.js_pack_op_line:not(.processed)').addClass('hidden'); - } - self.on_searchbox(self.search_filter); - }); - this.$('.js_plus').click(function(){ - var id = $(this).data('product-id'); - var op_id = $(this).parents("[data-id]:first").data('id'); - self.getParent().scan_product_id(id,1,op_id); - }); - this.$('.js_minus').click(function(){ - var id = $(this).data('product-id'); - var op_id = $(this).parents("[data-id]:first").data('id'); - self.getParent().scan_product_id(id,-1,op_id); - }); - this.$('.js_unfold').click(function(){ - var op_id = $(this).parent().data('id'); - var line = $(this).parent(); - //select all js_pack_op_line with class in_container_hidden and correct container-id - var select = self.$('.js_pack_op_line.in_container_hidden[data-container-id='+op_id+']'); - if (select.length > 0){ - //we unfold - line.addClass('warning'); - select.removeClass('in_container_hidden'); - select.addClass('in_container'); - } - else{ - //we fold - line.removeClass('warning'); - select = self.$('.js_pack_op_line.in_container[data-container-id='+op_id+']'); - select.removeClass('in_container'); - select.addClass('in_container_hidden'); - } - }); - this.$('.js_create_lot').click(function(){ - var op_id = $(this).parents("[data-id]:first").data('id'); - var lot_name = false; - self.$('.js_lot_scan').val(''); - var $lot_modal = self.$el.siblings('#js_LotChooseModal'); - //disconnect scanner to prevent scanning a product in the back while dialog is open - self.getParent().barcode_scanner.disconnect(); - $lot_modal.modal(); - //focus input - $lot_modal.on('shown.bs.modal', function(){ - self.$('.js_lot_scan').focus(); - }); - //reactivate scanner when dialog close - $lot_modal.on('hidden.bs.modal', function(){ - self.getParent().barcode_scanner.connect(function(ean){ - self.getParent().scan(ean); - }); - }); - self.$('.js_lot_scan').focus(); - //button action - self.$('.js_validate_lot').click(function(){ - //get content of input - var name = self.$('.js_lot_scan').val(); - if (name.length !== 0){ - lot_name = name; - } - $lot_modal.modal('hide'); - //we need this here since it is not sure the hide event - //will be catch because we refresh the view after the create_lot call - self.getParent().barcode_scanner.connect(function(ean){ - self.getParent().scan(ean); - }); - self.getParent().create_lot(op_id, lot_name); - }); - }); - this.$('.js_delete_pack').click(function(){ - var pack_id = $(this).parents("[data-id]:first").data('id'); - self.getParent().delete_package_op(pack_id); - }); - this.$('.js_print_pack').click(function(){ - var pack_id = $(this).parents("[data-id]:first").data('id'); - // $(this).parents("[data-id]:first").data('id') - self.getParent().print_package(pack_id); - }); - this.$('.js_submit_value').submit(function(){ - var op_id = $(this).parents("[data-id]:first").data('id'); - var value = parseFloat($("input", this).val()); - if (value>=0){ - self.getParent().set_operation_quantity(value, op_id); - } - $("input", this).val(""); - return false; - }); - this.$('.js_qty').focus(function(){ - self.getParent().barcode_scanner.disconnect(); - }); - this.$('.js_qty').blur(function(){ - var op_id = $(this).parents("[data-id]:first").data('id'); - var value = parseFloat($(this).val()); - if (value>=0){ - self.getParent().set_operation_quantity(value, op_id); - } - - self.getParent().barcode_scanner.connect(function(ean){ - self.getParent().scan(ean); - }); - }); - this.$('.js_change_src').click(function(){ - var op_id = $(this).parents("[data-id]:first").data('id');//data('op_id'); - self.$('#js_loc_select').addClass('source'); - self.$('#js_loc_select').data('op-id',op_id); - self.$el.siblings('#js_LocationChooseModal').modal(); - }); - this.$('.js_change_dst').click(function(){ - var op_id = $(this).parents("[data-id]:first").data('id'); - self.$('#js_loc_select').data('op-id',op_id); - self.$el.siblings('#js_LocationChooseModal').modal(); - }); - this.$('.js_pack_change_dst').click(function(){ - var op_id = $(this).parents("[data-id]:first").data('id'); - self.$('#js_loc_select').addClass('pack'); - self.$('#js_loc_select').data('op-id',op_id); - self.$el.siblings('#js_LocationChooseModal').modal(); - }); - this.$('.js_validate_location').click(function(){ - //get current selection - var select_dom_element = self.$('#js_loc_select'); - var loc_id = self.$('#js_loc_select option:selected').data('loc-id'); - var src_dst = false; - var op_id = select_dom_element.data('op-id'); - if (select_dom_element.hasClass('pack')){ - select_dom_element.removeClass('source'); - var op_ids = []; - self.$('.js_pack_op_line[data-container-id='+op_id+']').each(function(){ - op_ids.push($(this).data('id')); - }); - op_id = op_ids; - } - else if (select_dom_element.hasClass('source')){ - src_dst = true; - select_dom_element.removeClass('source'); - } - if (loc_id === false){ - //close window - self.$el.siblings('#js_LocationChooseModal').modal('hide'); - } - else{ - self.$el.siblings('#js_LocationChooseModal').modal('hide'); - self.getParent().change_location(op_id, parseInt(loc_id), src_dst); - - } - }); - this.$('.js_pack_configure').click(function(){ - var pack_id = $(this).parents(".js_pack_op_line:first").data('package-id'); - var ul_id = $(this).parents(".js_pack_op_line:first").data('ulid'); - self.$('#js_packconf_select').val(ul_id); - self.$('#js_packconf_select').data('pack-id',pack_id); - self.$el.siblings('#js_PackConfModal').modal(); - }); - this.$('.js_validate_pack').click(function(){ - //get current selection - var select_dom_element = self.$('#js_packconf_select'); - var ul_id = self.$('#js_packconf_select option:selected').data('ul-id'); - var pack_id = select_dom_element.data('pack-id'); - self.$el.siblings('#js_PackConfModal').modal('hide'); - if (pack_id){ - self.getParent().set_package_pack(pack_id, ul_id); - $('.container_head[data-package-id="'+pack_id+'"]').data('ulid', ul_id); - } - }); - - //remove navigation bar from default openerp GUI - $('td.navbar').html('<div></div>'); - }, - on_searchbox: function(query){ - //hide line that has no location matching the query and highlight location that match the query - this.search_filter = query; - var processed = ".processed"; - if (this.$('#js_select option:selected').attr('value') == "ToDo"){ - processed = ":not(.processed)"; - } - if (query !== '') { - this.$('.js_loc:not(.js_loc:Contains('+query+'))').removeClass('info'); - this.$('.js_loc:Contains('+query+')').addClass('info'); - this.$('.js_pack_op_line'+processed+':not(.js_pack_op_line:has(.js_loc:Contains('+query+')))').addClass('hidden'); - this.$('.js_pack_op_line'+processed+':has(.js_loc:Contains('+query+'))').removeClass('hidden'); - } - //if no query specified, then show everything - if (query === '') { - this.$('.js_loc').removeClass('info'); - this.$('.js_pack_op_line'+processed+'.hidden').removeClass('hidden'); - } - this.check_content_screen(); - }, - check_content_screen: function(){ - //get all visible element and if none has positive qty, disable put in pack and process button - var self = this; - var processed = this.$('.js_pack_op_line.processed'); - var qties = this.$('.js_pack_op_line:not(.processed):not(.hidden) .js_qty').map(function(){return $(this).val();}); - var container = this.$('.js_pack_op_line.container_head:not(.processed):not(.hidden)'); - var disabled = true; - $.each(qties,function(index, value){ - if(value>0) { - disabled = false; - } - }); - - if (disabled){ - if (container.length===0){ - self.$('.js_drop_down').addClass('disabled'); - } - else { - self.$('.js_drop_down').removeClass('disabled'); - } - self.$('.js_pick_pack').addClass('disabled'); - if (processed.length === 0){ - self.$('.js_pick_done').addClass('disabled'); - } - else { - self.$('.js_pick_done').removeClass('disabled'); - } - } - else{ - self.$('.js_drop_down').removeClass('disabled'); - self.$('.js_pick_pack').removeClass('disabled'); - self.$('.js_pick_done').removeClass('disabled'); - } - }, - get_current_op_selection: function(ignore_container){ - //get ids of visible on the screen - var pack_op_ids = []; - this.$('.js_pack_op_line:not(.processed):not(.js_pack_op_line.hidden):not(.container_head)').each(function(){ - var cur_id = $(this).data('id'); - pack_op_ids.push(parseInt(cur_id)); - }); - //get list of element in this.rows where rem > 0 and container is empty is specified - var list = []; - _.each(this.rows, function(row){ - if (row.cols.rem > 0 && (ignore_container || row.cols.container === undefined)){ - list.push(row.cols.id); - } - }); - //return only those visible with rem qty > 0 and container empty - return _.intersection(pack_op_ids, list); - }, - remove_blink: function(){ - this.$('.js_pack_op_line.blink_me').removeClass('blink_me'); - }, - blink: function(op_id){ - this.$('.js_pack_op_line[data-id="'+op_id+'"]').addClass('blink_me'); - }, - check_done: function(){ - var model = this.getParent(); - var done = true; - _.each( model.packoplines, function(packopline){ - if (packopline.processed === "false"){ - done = false; - return done; - } - }); - return done; - }, - get_visible_ids: function(){ - var visible_op_ids = []; - var op_ids = this.$('.js_pack_op_line:not(.processed):not(.hidden):not(.container_head):not(.in_container):not(.in_container_hidden)').map(function(){ - return $(this).data('id'); - }); - $.each(op_ids, function(key, op_id){ - visible_op_ids.push(parseInt(op_id)); - }); - return visible_op_ids; - }, -}); - -var PickingMenuWidget = MobileWidget.extend({ - template: 'PickingMenuWidget', - init: function(parent, params){ - this._super(parent,params); - var self = this; - $(window).bind('hashchange', function(){ - var states = $.bbq.getState(); - if (states.action === "stock.ui"){ - self.do_action({ - type: 'ir.actions.client', - tag: 'stock.ui', - target: 'current', - },{ - clear_breadcrumbs: true, - }); - } - }); - this.picking_types = []; - this.loaded = this.load(); - this.scanning_type = 0; - this.barcode_scanner = new BarcodeScanner(); - this.pickings_by_type = {}; - this.pickings_by_id = {}; - this.picking_search_string = ""; - }, - load: function(){ - var self = this; - return new Model('stock.picking.type').get_func('search_read')([],[]) - .then(function(types){ - self.picking_types = types; - var type_ids = []; - for(var i = 0; i < types.length; i++){ - self.pickings_by_type[types[i].id] = []; - type_ids.push(types[i].id); - } - self.pickings_by_type[0] = []; - - return new Model('stock.picking').call('search_read',[ [['state','in', ['assigned', 'partially_available']], ['picking_type_id', 'in', type_ids]], [] ], {context: new data.CompoundContext()}); - - }).then(function(pickings){ - self.pickings = pickings; - for(var i = 0; i < pickings.length; i++){ - var picking = pickings[i]; - self.pickings_by_type[picking.picking_type_id[0]].push(picking); - self.pickings_by_id[picking.id] = picking; - self.picking_search_string += '' + picking.id + ':' + (picking.name ? picking.name.toUpperCase(): '') + '\n'; - } - - }); - }, - renderElement: function(){ - this._super(); - var self = this; - this.$('.js_pick_quit').click(function(){ self.quit(); }); - this.$('.js_pick_scan').click(function(){ self.scan_picking($(this).data('id')); }); - this.$('.js_pick_last').click(function(){ self.goto_last_picking_of_type($(this).data('id')); }); - this.$('.oe_searchbox').keyup(function(){ - self.on_searchbox($(this).val()); - }); - //remove navigation bar from default openerp GUI - $('td.navbar').html('<div></div>'); - }, - start: function(){ - this._super(); - var self = this; - web_client.set_content_full_screen(true); - this.barcode_scanner.connect(function(barcode){ - self.on_scan(barcode); - }); - this.loaded.then(function(){ - self.renderElement(); - }); - }, - goto_picking: function(picking_id){ - $.bbq.pushState('#action=stock.ui&picking_id='+picking_id); - $(window).trigger('hashchange'); - }, - goto_last_picking_of_type: function(type_id){ - $.bbq.pushState('#action=stock.ui&picking_type_id='+type_id); - $(window).trigger('hashchange'); - }, - search_picking: function(barcode){ - try { - var re = RegExp("([0-9]+):.*?"+barcode.toUpperCase(),"gi"); - } - catch(e) { - //avoid crash if a not supported char is given (like '\' or ')') - return []; - } - - var results = []; - var r; - for(var i = 0; i < 100; i++){ - r = re.exec(this.picking_search_string); - if(r){ - var picking = this.pickings_by_id[Number(r[1])]; - if(picking){ - results.push(picking); - } - }else{ - break; - } - } - return results; - }, - on_scan: function(barcode){ - var self = this; - for(var i = 0, len = this.pickings.length; i < len; i++){ - var picking = this.pickings[i]; - if(picking.name.toUpperCase() === $.trim(barcode.toUpperCase())){ - this.goto_picking(picking.id); - break; - } - } - this.$('.js_picking_not_found').removeClass('hidden'); - - clearTimeout(this.picking_not_found_timeout); - this.picking_not_found_timeout = setTimeout(function(){ - self.$('.js_picking_not_found').addClass('hidden'); - },2000); - - }, - on_searchbox: function(query){ - var self = this; - - clearTimeout(this.searchbox_timeout); - this.searchbox_timout = setTimeout(function(){ - if(query){ - self.$('.js_picking_not_found').addClass('hidden'); - self.$('.js_picking_categories').addClass('hidden'); - self.$('.js_picking_search_results').html( - QWeb.render('PickingSearchResults',{results:self.search_picking(query)}) - ); - self.$('.js_picking_search_results .oe_picking').click(function(){ - self.goto_picking($(this).data('id')); - }); - self.$('.js_picking_search_results').removeClass('hidden'); - }else{ - self.$('.js_title_label').removeClass('hidden'); - self.$('.js_picking_categories').removeClass('hidden'); - self.$('.js_picking_search_results').addClass('hidden'); - } - },100); - }, - quit: function(){ - return new Model("ir.model.data").get_func("search_read")([['name', '=', 'action_picking_type_form']], ['res_id']).pipe(function(res) { - window.location = '/web#action=' + res[0]['res_id']; - }); - }, - destroy: function(){ - this._super(); - this.barcode_scanner.disconnect(); - web_client.set_content_full_screen(false); - }, -}); -core.action_registry.add('stock.menu', PickingMenuWidget); - -var PickingMainWidget = MobileWidget.extend({ - template: 'PickingMainWidget', - init: function(parent,params){ - this._super(parent,params); - var self = this; - $(window).bind('hashchange', function(){ - var states = $.bbq.getState(); - if (states.action === "stock.menu"){ - self.do_action({ - type: 'ir.actions.client', - tag: 'stock.menu', - target: 'current', - },{ - clear_breadcrumbs: true, - }); - } - }); - var init_hash = $.bbq.getState(); - this.picking_type_id = init_hash.picking_type_id ? init_hash.picking_type_id:0; - this.picking_id = init_hash.picking_id ? init_hash.picking_id:undefined; - this.picking = null; - this.pickings = []; - this.packoplines = null; - this.selected_operation = { id: null, picking_id: null}; - this.packages = null; - this.barcode_scanner = new BarcodeScanner(); - this.locations = []; - this.uls = []; - if(this.picking_id){ - this.loaded = this.load(this.picking_id); - }else{ - this.loaded = this.load(); - } - - }, - - // load the picking data from the server. If picking_id is undefined, it will take the first picking - // belonging to the category - load: function(picking_id){ - var self = this; - var loaded_picking; - - function load_picking_list(type_id){ - var pickings = new $.Deferred(); - new Model('stock.picking') - .call('get_next_picking_for_ui',[{'default_picking_type_id':parseInt(type_id)}]) - .then(function(picking_ids){ - if(!picking_ids || picking_ids.length === 0){ - (new Dialog(self,{ - title: _t('No Picking Available'), - buttons: [{ - text:_t('Ok'), - click: function(){ - self.menu(); - } - }] - }, _t('<p>We could not find a picking to display.</p>'))).open(); - - pickings.reject(); - }else{ - self.pickings = picking_ids; - pickings.resolve(picking_ids); - } - }); - - return pickings; - } - - // if we have a specified picking id, we load that one, and we load the picking of the same type as the active list - if( picking_id ){ - loaded_picking = new Model('stock.picking') - .call('read',[[parseInt(picking_id)], [], new data.CompoundContext()]) - .then(function(picking){ - self.picking = picking[0]; - self.picking_type_id = picking[0].picking_type_id[0]; - return load_picking_list(self.picking.picking_type_id[0]); - }); - }else{ - // if we don't have a specified picking id, we load the pickings belong to the specified type, and then we take - // the first one of that list as the active picking - loaded_picking = new $.Deferred(); - load_picking_list(self.picking_type_id) - .then(function(){ - return new Model('stock.picking').call('read',[self.pickings[0],[], new data.CompoundContext()]); - }) - .then(function(picking){ - self.picking = picking; - self.picking_type_id = picking.picking_type_id[0]; - loaded_picking.resolve(); - }); - } - - return loaded_picking.then(function(){ - return new Model('stock.location').call('search',[[['usage','=','internal']]]).then(function(locations_ids){ - return new Model('stock.location').call('read',[locations_ids, []]).then(function(locations){ - self.locations = locations; - }); - }); - }).then(function(){ - return new Model('stock.picking').call('check_group_pack').then(function(result){ - self.show_pack = result; - return result; - }); - }).then(function(){ - return new Model('stock.picking').call('check_group_lot').then(function(result){ - self.show_lot = result; - return result; - }); - }).then(function(){ - if (self.picking.pack_operation_exist === false){ - self.picking.recompute_pack_op = false; - return new Model('stock.picking').call('do_prepare_partial',[[self.picking.id]]); - } - }).then(function(){ - return new Model('stock.pack.operation').call('search',[[['picking_id','=',self.picking.id]]]); - }).then(function(pack_op_ids){ - return new Model('stock.pack.operation').call('read',[pack_op_ids, [], new data.CompoundContext()]); - }).then(function(operations){ - self.packoplines = operations; - var package_ids = []; - - for(var i = 0; i < operations.length; i++){ - if(!_.contains(package_ids,operations[i].result_package_id[0])){ - if (operations[i].result_package_id[0]){ - package_ids.push(operations[i].result_package_id[0]); - } - } - } - return new Model('stock.quant.package').call('read',[package_ids, [], new data.CompoundContext()]); - }).then(function(packages){ - self.packages = packages; - }).then(function(){ - return new Model('product.ul').call('search',[[]]); - }).then(function(uls_ids){ - return new Model('product.ul').call('read',[uls_ids, []]); - }).then(function(uls){ - self.uls = uls; - }); - }, - start: function(){ - // this._super(); - var self = this; - web_client.set_content_full_screen(true); - this.barcode_scanner.connect(function(ean){ - self.scan(ean); - }); - - this.$('.js_pick_quit').click(function(){ self.quit(); }); - this.$('.js_pick_prev').click(function(){ self.picking_prev(); }); - this.$('.js_pick_next').click(function(){ self.picking_next(); }); - this.$('.js_pick_menu').click(function(){ self.menu(); }); - this.$('.js_reload_op').click(function(){ self.reload_pack_operation();}); - - return $.when(this._super(), this.loaded).done(function(){ - self.picking_editor = new PickingEditorWidget(self); - self.picking_editor.replace(self.$('.oe_placeholder_picking_editor')); - - if( self.picking.id === self.pickings[0]){ - self.$('.js_pick_prev').addClass('disabled'); - }else{ - self.$('.js_pick_prev').removeClass('disabled'); - } - - if( self.picking.id === self.pickings[self.pickings.length-1] ){ - self.$('.js_pick_next').addClass('disabled'); - }else{ - self.$('.js_pick_next').removeClass('disabled'); - } - if (self.picking.recompute_pack_op){ - self.$('.oe_reload_op').removeClass('hidden'); - } - else { - self.$('.oe_reload_op').addClass('hidden'); - } - if (!self.show_pack){ - self.$('.js_pick_pack').addClass('hidden'); - } - if (!self.show_lot){ - self.$('.js_create_lot').addClass('hidden'); - } - - }).fail(function(error) {console.log(error);}); - - }, - on_searchbox: function(query){ - var self = this; - self.picking_editor.on_searchbox(query.toUpperCase()); - }, - // reloads the data from the provided picking and refresh the ui. - // (if no picking_id is provided, gets the first picking in the db) - refresh_ui: function(picking_id){ - var self = this; - var remove_search_filter = ""; - if (self.picking.id === picking_id){ - remove_search_filter = self.$('.oe_searchbox').val(); - } - return this.load(picking_id) - .then(function(){ - self.picking_editor.remove_blink(); - self.picking_editor.renderElement(); - if (!self.show_pack){ - self.$('.js_pick_pack').addClass('hidden'); - } - if (!self.show_lot){ - self.$('.js_create_lot').addClass('hidden'); - } - if (self.picking.recompute_pack_op){ - self.$('.oe_reload_op').removeClass('hidden'); - } - else { - self.$('.oe_reload_op').addClass('hidden'); - } - - if( self.picking.id === self.pickings[0]){ - self.$('.js_pick_prev').addClass('disabled'); - }else{ - self.$('.js_pick_prev').removeClass('disabled'); - } - - if( self.picking.id === self.pickings[self.pickings.length-1] ){ - self.$('.js_pick_next').addClass('disabled'); - }else{ - self.$('.js_pick_next').removeClass('disabled'); - } - if (remove_search_filter === ""){ - self.$('.oe_searchbox').val(''); - self.on_searchbox(''); - } - else{ - self.$('.oe_searchbox').val(remove_search_filter); - self.on_searchbox(remove_search_filter); - } - }); - }, - get_header: function(){ - if(this.picking){ - return this.picking.name; - }else{ - return ''; - } - }, - menu: function(){ - $.bbq.pushState('#action=stock.menu'); - $(window).trigger('hashchange'); - }, - scan: function(ean){ //scans a barcode, sends it to the server, then reload the ui - var self = this; - var product_visible_ids = this.picking_editor.get_visible_ids(); - return new Model('stock.picking') - .call('process_barcode_from_ui', [self.picking.id, ean, product_visible_ids]) - .then(function(result){ - if (result.filter_loc !== false){ - //check if we have receive a location as answer - if (result.filter_loc !== undefined){ - var modal_loc_hidden = self.$('#js_LocationChooseModal').attr('aria-hidden'); - if (modal_loc_hidden === "false"){ - self.$('#js_LocationChooseModal .js_loc_option[data-loc-id='+result.filter_loc_id+']').attr('selected','selected'); - } - else{ - self.$('.oe_searchbox').val(result.filter_loc); - self.on_searchbox(result.filter_loc); - } - } - } - if (result.operation_id !== false){ - self.refresh_ui(self.picking.id).then(function(){ - return self.picking_editor.blink(result.operation_id); - }); - } - }); - }, - scan_product_id: function(product_id,increment,op_id){ //performs the same operation as a scan, but with product id instead, increment is the value to increment (-1 or 1) - var self = this; - return new Model('stock.picking') - .call('process_product_id_from_ui', [self.picking.id, product_id, op_id, increment]) - .then(function(){ - return self.refresh_ui(self.picking.id); - }); - }, - pack: function(){ - var self = this; - var pack_op_ids = self.picking_editor.get_current_op_selection(false); - if (pack_op_ids.length !== 0){ - return new Model('stock.picking') - .call('action_pack',[[[self.picking.id]], pack_op_ids]) - .then(function(){ - //TODO: the functionality using current_package_id in context is not needed anymore - session.user_context.current_package_id = false; - return self.refresh_ui(self.picking.id); - }); - } - }, - drop_down: function(){ - var self = this; - var pack_op_ids = self.picking_editor.get_current_op_selection(true); - if (pack_op_ids.length !== 0){ - return new Model('stock.pack.operation') - .call('action_drop_down', [pack_op_ids]) - .then(function(){ - return self.refresh_ui(self.picking.id).then(function(){ - if (self.picking_editor.check_done()){ - return self.done(); - } - }); - }); - } - }, - done: function(){ - var self = this; - return new Model('stock.picking') - .call('action_done_from_ui',[self.picking.id, {'default_picking_type_id': self.picking_type_id}]) - .then(function(new_picking_ids){ - if (new_picking_ids){ - return self.refresh_ui(new_picking_ids[0]); - } - else { - return 0; - } - }); - }, - create_lot: function(op_id, lot_name){ - var self = this; - return new Model('stock.pack.operation') - .call('create_and_assign_lot',[parseInt(op_id), lot_name]) - .then(function(){ - return self.refresh_ui(self.picking.id); - }); - }, - change_location: function(op_id, loc_id, is_src_dst){ - var self = this; - var vals = {'location_dest_id': loc_id}; - if (is_src_dst){ - vals = {'location_id': loc_id}; - } - return new Model('stock.pack.operation') - .call('write',[op_id, vals]) - .then(function(){ - return self.refresh_ui(self.picking.id); - }); - }, - print_package: function(package_id){ - var self = this; - return new Model('stock.quant.package') - .call('action_print',[[package_id]]) - .then(function(action){ - return self.do_action(action); - }); - }, - print_picking: function(){ - var self = this; - return new Model('stock.picking').call('do_print_picking',[[self.picking.id]]) - .then(function(action){ - return self.do_action(action); - }); - }, - picking_next: function(){ - for(var i = 0; i < this.pickings.length; i++){ - if(this.pickings[i] === this.picking.id){ - if(i < this.pickings.length -1){ - $.bbq.pushState('picking_id='+this.pickings[i+1]); - this.refresh_ui(this.pickings[i+1]); - return; - } - } - } - }, - picking_prev: function(){ - for(var i = 0; i < this.pickings.length; i++){ - if(this.pickings[i] === this.picking.id){ - if(i > 0){ - $.bbq.pushState('picking_id='+this.pickings[i-1]); - this.refresh_ui(this.pickings[i-1]); - return; - } - } - } - }, - delete_package_op: function(pack_id){ - var self = this; - return new Model('stock.pack.operation').call('search', [[['result_package_id', '=', pack_id]]]) - .then(function(op_ids) { - return new Model('stock.pack.operation').call('write', [op_ids, {'result_package_id':false}]) - .then(function() { - return self.refresh_ui(self.picking.id); - }); - }); - }, - set_operation_quantity: function(quantity, op_id){ - var self = this; - if(quantity >= 0){ - return new Model('stock.pack.operation') - .call('write',[[op_id],{'qty_done': quantity }]) - .then(function(){ - self.refresh_ui(self.picking.id); - }); - } - - }, - set_package_pack: function(package_id, pack){ - return new Model('stock.quant.package') - .call('write',[[package_id],{'ul_id': pack }]); - }, - reload_pack_operation: function(){ - var self = this; - return new Model('stock.picking') - .call('do_prepare_partial',[[self.picking.id]]) - .then(function(){ - self.refresh_ui(self.picking.id); - }); - }, - quit: function(){ - this.destroy(); - return new Model("ir.model.data").get_func("search_read")([['name', '=', 'action_picking_type_form']], ['res_id']).pipe(function(res) { - window.location = '/web#action=' + res[0]['res_id']; - }); - }, - destroy: function(){ - this._super(); - // this.disconnect_numpad(); - this.barcode_scanner.disconnect(); - web_client.set_content_full_screen(false); - }, -}); -core.action_registry.add('stock.ui', PickingMainWidget); - -var BarcodeScanner = core.Class.extend({ - connect: function(callback){ - var code = ""; - var timeStamp = 0; - var timeout = null; - - this.handler = function(e){ - if(e.which === 13){ //ignore returns - return; - } - - if(timeStamp + 50 < new Date().getTime()){ - code = ""; - } - - timeStamp = new Date().getTime(); - clearTimeout(timeout); - - code += String.fromCharCode(e.which); - - timeout = setTimeout(function(){ - if(code.length >= 3){ - callback(code); - } - code = ""; - },100); - }; - - $('body').on('keypress', this.handler); - - }, - disconnect: function(){ - $('body').off('keypress', this.handler); - }, -}); - -kanban_common.KanbanRecord.include({ - on_card_clicked: function() { - if (this.view.dataset.model === 'stock.picking.type') { - this.$('.oe_kanban_stock_picking_type_list').first().click(); - } else { - this._super.apply(this, arguments); - } - }, -}); - -}); diff --git a/addons/stock/static/src/less/stock_dashboard.less b/addons/stock/static/src/less/stock_dashboard.less deleted file mode 100644 index cf272240922923a3e5988b07c2dbe106517fa503..0000000000000000000000000000000000000000 --- a/addons/stock/static/src/less/stock_dashboard.less +++ /dev/null @@ -1,6 +0,0 @@ -.oe_kanban_view.o_kanban_dashboard.o_stock_kanban { - .oe_kanban_record { - max-width: 500px; - min-width: 350px; - } -} \ No newline at end of file diff --git a/addons/stock/static/src/xml/picking.xml b/addons/stock/static/src/xml/picking.xml deleted file mode 100644 index 0c92f59157589426e1d6fca966f3a751b691714c..0000000000000000000000000000000000000000 --- a/addons/stock/static/src/xml/picking.xml +++ /dev/null @@ -1,274 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<templates id="template" xml:space="preserve"> - - <t t-name='PickingEditorWidget'> - - <div class="modal fade" id="js_LocationChooseModal" tabindex="-1" role="dialog" aria-labelledby="LocationChooseModal" aria-hidden="true"> - <div class="modal-dialog"> - <div class="modal-content"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> - <h4 class="modal-title" id="myModalLabel">Choose a location</h4> - </div> - <div class="modal-body"> - <p>Scan a location or select it in the list below</p> - <select id="js_loc_select" class="form-control"> - <option class="js_loc_option" data-loc-id="false"></option> - <t t-foreach="widget.get_location()" t-as="loc"> - <option class="js_loc_option" t-att-data-loc-id="loc.id"><t t-esc="loc.name"/></option> - </t> - </select> - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> - <button type="button" class="btn btn-primary js_validate_location">Change Location</button> - </div> - </div> - </div> - </div> - - <div class="modal fade" id="js_LotChooseModal" tabindex="-1" role="dialog" aria-labelledby="LotChooseModal" aria-hidden="true"> - <div class="modal-dialog"> - <div class="modal-content"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> - <h4 class="modal-title" id="myModalLabel">Create Lot</h4> - </div> - <div class="modal-body"> - <p>Scan a lot or type it below (leave empty to generate one automatically)</p> - <input class='col-xs-6 js_lot_scan' type='text'/> - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> - <button type="button" class="btn btn-primary js_validate_lot">Create Lot</button> - </div> - </div> - </div> - </div> - - <div class="modal fade" id="js_PackConfModal" tabindex="-1" role="dialog" aria-labelledby="PackConfModal" aria-hidden="true"> - <div class="modal-dialog"> - <div class="modal-content"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> - <h4 class="modal-title" id="myModalLabel">Configure package</h4> - </div> - <div class="modal-body"> - <p>Package type</p> - <select id="js_packconf_select" class="form-control"> - <option class="js_packing_option" data-ul-id="false"></option> - <t t-foreach="widget.get_logisticunit()" t-as="ul"> - <option class="js_packing_option" t-att-data-ul-id="ul.id" t-att-value="ul.id"><t t-esc="ul.name"/></option> - </t> - </select> - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> - <button type="button" class="btn btn-primary js_validate_pack">Validate package</button> - </div> - </div> - </div> - </div> - - <div class="row"> - <div> - <div class="col-sm-4 col-xs-6"> - <h2 class="oe_pick_app_header" /> - </div> - <div class="col-sm-8 col-xsâ»6 text-right"> - - <button type="button" class='btn btn-default js_pick_done'> Create backorder </button> - <button type="button" class='btn btn-default js_pick_print'> Print </button> - - </div> - </div> - </div> - - <div class="row"> - <div> - <div class="col-md-3 col-sm-4 col-xs-6"> - <h3><strong> - <select id="js_select" class="form-control"> - <option value="ToDo" id="js_select_todo">Operations ToDo</option> - <option value="Processed" id="js_select_processed">Operations Processed</option> - </select> - </strong> - </h3> - </div> - <div class="col-md-3 col-sm-4 col-xs-6"> - <h2> - <div class="input-group"> - <input type="text" class="form-control oe_searchbox" placeholder="Filter by location..."/> - <span class="input-group-btn"> - <button class="btn btn-danger js_clear_search" type="button">x</button> - </span> - </div> - </h2> - </div> - <div class="col-md-6 col-sm-4 col-xs-12 text-right"> - <h3> - <button type="button" class='btn btn-default js_pick_pack js_putinpack'> Put in Pack </button> - <button type="button" class='btn btn-danger js_drop_down fa fa-arrow-circle-o-down'> Put in Cart </button> - </h3> - </div> - </div> - </div> - - <div> - <table class='table table-condensed js_op_table_todo'> - <thead> - <tr> - <th class="text-left">Product</th> - <th class='text-center' width="150">Scanned</th> - <th class='text-center'>Todo</th> - <th>From</th> - <th>To</th> - </tr> - </thead> - - <tbody> - <t t-foreach="widget.get_rows()" t-as="row"> - <tr t-att-class="row.classes + 'js_pack_op_line'" t-att-data-id="row.cols.id" t-att-data-container-id="row.cols.container_id" t-att-data-package-id="row.cols.package_id" t-att-data-ulid="row.cols.ul_id"> - <td t-att-class="'brctbl-col1 text-left' + row.cols.head_container ? ' js_unfold' : ''"> - <t t-if="!row.cols.head_container && row.cols.container"><span class="fa fa-level-up fa-rotate-90" style="margin-left:10px;margin-right:10px;"></span></t> - <t t-esc="row.cols.product" /> - </td> - <td class='brctbl-col2 text-center js_row_qty'> - <t t-if="row.cols.processed == 'false' && !row.cols.container"> - <div class="input-group"> - <span class="input-group-addon js_minus input-sm" t-att-data-product-id='row.cols.product_id'><a href="#"><i class="fa fa-minus"></i></a></span> - <form class="js_submit_value"> - <input type="text" class="form-control text-center js_qty" t-att-value="row.cols.rem"></input> - <!-- <input type="submit" class="hidden"></input> --> - </form> - <span class="input-group-addon js_plus input-sm" t-att-data-product-id='row.cols.product_id'><a href="#"><i class="fa fa-plus"></i></a></span> - </div> - </t> - <t t-if="(row.cols.processed == 'true' || row.cols.container)"> - <t t-esc="row.cols.rem" /> - </t> - </td> - <td class="brctbl-col3 text-center"> - <t t-esc="row.cols.qty"/> <t t-esc="row.cols.uom" /> - </td> - <td class="brctbl-col4 js_loc"> - <t t-esc="row.cols.loc" /> - <t t-if="row.cols.pack" ><span> : <t t-esc="row.cols.pack" /></span></t> - <t t-if="row.cols.lot" ><span> : <t t-esc="row.cols.lot" /></span></t> - </td> - <td class="brctbl-col5 js_loc"> <t t-esc="row.cols.dest" /> - <div class="pull-right btn-group"> - <button type="button" class="btn btn-default dropdown-toggle fa fa-cog" data-toggle="dropdown"> - <span class="caret"></span> - </button> - <ul class="dropdown-menu" role="menu"> - <t t-if="row.cols.product_id"> - <li><a class="js_create_lot" href="#">Create / Change Lot</a></li> - </t> - <t t-if="!row.cols.head_container && !row.cols.container"> - <li><a class="js_change_src" href="#">Change source location</a></li> - <li><a class="js_change_dst" href="#">Change destination location</a></li> - </t> - <t t-if="row.cols.head_container"> - <li><a class="js_pack_change_dst" href="#">Change destination location</a></li> - <li class="divider"></li> - <li><a class="js_pack_configure" href="#">Configure package</a></li> - <li><a class="js_delete_pack" href="#">Remove from package</a></li> - <li><a class="js_print_pack" href="#">Print package label</a></li> - </t> - </ul> - </div> - </td> - </tr> - </t> - </tbody> - </table> - </div> - </t> - - <t t-name="PickingSearchResults"> - <div class="panel-heading"> - <h3 class="panel-title">Search Results</h3> - </div> - <div class="panel-body"> - <t t-if="results.length === 0"> - <strong>No picking found.</strong> - </t> - <t t-if="results.length > 0"> - <t t-foreach="results" t-as="picking"> - <div class="col-lg-3 col-md-4"> - <div class="panel panel-default oe_picking" t-att-data-id="picking.id"> - <div class="panel-body"> - <strong class='oe_picking_name'><t t-esc="picking.name" /></strong> - </div> - </div> - </div> - </t> - </t> - </div> - </t> - - <t t-name="PickingMenuWidget"> - <div class="navbar navbar-inverse navbar-static-top" role="navigation"> - <div class="container"> - <div class="navbar-header navbar-form navbar-left"> - <input type='text' class="oe_searchbox form-control pull-left" placeholder='Search'/> - </div> - <div class="navbar-header navbar-form navbar-right"> - <button type="button" class="btn btn-danger js_pick_quit pull-right">Quit</button> - </div> - - </div> - </div> - <div class="container"> - - <h1 class="js_title_label">Select your operation</h1> - <div class='js_picking_not_found alert alert-warning hidden'> - Scanned picking could not be found - </div> - - <div class='js_picking_search_results panel panel-info hidden'> - </div> - - <div class="row js_picking_categories"> - <t t-foreach="widget.picking_types" t-as="type"> - <div class="col-lg-3 col-md-4"> - <div t-att-class="'oe_kanban oe_picking oe_kanban_color_' + type.color + ' ' + (widget.pickings_by_type[type.id].length === 0 ? 'oe_empty':'js_pick_last') " - t-att-data-id="type.id"> - <t t-if="type.code == 'incoming'" ><span class="fa fa-sign-in fa-2x"></span></t> - <t t-if="type.code == 'outgoing'" ><span class="fa fa-truck fa-2x fa-flip-horizontal"></span></t> - <t t-if="type.code == 'internal'" ><span class="fa fa-retweet fa-2x"></span></t> - <strong><span><t t-esc="type.complete_name"/></span></strong> - <div><t t-if="widget.pickings_by_type[type.id].length > 0"> - <span class='badge'><t t-esc="widget.pickings_by_type[type.id].length" /> picking(s) </span> - </t></div> - </div> - </div> - </t> - </div> - </div> - </t> - - - <t t-name="PickingMainWidget"> - <div class="navbar navbar-inverse navbar-static-top" role="navigation"> - <div class="container"> - <div class="navbar-left"> - <button type="button" class="btn btn-primary navbar-btn js_pick_menu">Menu</button> - </div> - <div class="navbar-right"> - <button type="button" class="btn btn-default navbar-btn js_pick_prev">< Previous</button> - <button type="button" class="btn btn-default navbar-btn js_pick_next">Next ></button> - </div> - </div> - </div> - - <div class="container"> - <div class='oe_placeholder_picking_editor'/> - <div class="text-right small oe_reload_op"> - The reserved stock changed. You might want to <button class="btn btn-default js_reload_op"> Recompute </button> the operations. - </div> - </div> - </t> - -</templates> diff --git a/addons/stock/stock.py b/addons/stock/stock.py index beb09b8528dc924d26b19d4a399f6160dafadd85..4f96d0db47ca290c8f532d9514b6bc4bd51c349e 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -12,7 +12,7 @@ from openerp.osv import fields, osv from openerp.tools.float_utils import float_compare, float_round from openerp.tools.translate import _ from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FORMAT -from openerp import SUPERUSER_ID, api, models, fields as new_fields +from openerp import SUPERUSER_ID, api, models import openerp.addons.decimal_precision as dp from openerp.addons.procurement import procurement import logging @@ -365,9 +365,6 @@ class stock_quant(osv.osv): #reserve quants if toreserve: self.write(cr, SUPERUSER_ID, toreserve, {'reservation_id': move.id}, context=context) - #if move has a picking_id, write on that picking that pack_operation might have changed and need to be recomputed - if move.picking_id: - self.pool.get('stock.picking').write(cr, uid, [move.picking_id.id], {'recompute_pack_op': True}, context=context) #check if move'state needs to be set as 'assigned' rounding = move.product_id.uom_id.rounding if float_compare(reserved_availability, move.product_qty, precision_rounding=rounding) == 0 and move.state in ('confirmed', 'waiting') : @@ -597,8 +594,6 @@ class stock_quant(osv.osv): related_quants = [x.id for x in move.reserved_quant_ids] if related_quants: #if move has a picking_id, write on that picking that pack_operation might have changed and need to be recomputed - if move.picking_id: - self.pool.get('stock.picking').write(cr, uid, [move.picking_id.id], {'recompute_pack_op': True}, context=context) if move.partially_available: self.pool.get("stock.move").write(cr, uid, [move.id], {'partially_available': False}, context=context) self.write(cr, SUPERUSER_ID, related_quants, {'reservation_id': False}, context=context) @@ -643,7 +638,7 @@ class stock_quant(osv.osv): # Stock Picking #---------------------------------------------------------- -class stock_picking(osv.osv): +class stock_picking(models.Model): _name = "stock.picking" _inherit = ['mail.thread'] _description = "Transfer" @@ -693,8 +688,31 @@ class stock_picking(osv.osv): ptype_id = vals.get('picking_type_id', context.get('default_picking_type_id', False)) sequence_id = self.pool.get('stock.picking.type').browse(cr, user, ptype_id, context=context).sequence_id.id vals['name'] = self.pool.get('ir.sequence').next_by_id(cr, user, sequence_id, context=context) + # As the on_change in one2many list is WIP, we will overwrite the locations on the stock moves here + # As it is a create the format will be a list of (0, 0, dict) + if vals.get('move_lines') and vals.get('location_id') and vals.get('location_dest_id'): + for move in vals['move_lines']: + if len(move) == 3: + move[2]['location_id'] = vals['location_id'] + move[2]['location_dest_id'] = vals['location_dest_id'] return super(stock_picking, self).create(cr, user, vals, context) + def write(self, cr, uid, ids, vals, context=None): + res = super(stock_picking, self).write(cr, uid, ids, vals, context=context) + after_vals = {} + if vals.get('location_id'): + after_vals['location_id'] = vals['location_id'] + if vals.get('location_dest_id'): + after_vals['location_dest_id'] = vals['location_dest_id'] + # Change locations of moves if those of the picking change + if after_vals: + moves = [] + for pick in self.browse(cr, uid, ids, context=context): + moves += [x.id for x in pick.move_lines if not x.scrapped] + if moves: + self.pool['stock.move'].write(cr, uid, moves, after_vals, context=context) + return res + def _state_get(self, cr, uid, ids, field_name, arg, context=None): '''The state of a picking depends on the state of its related stock.move draft: the picking has no line or any one of the lines is draft @@ -703,7 +721,10 @@ class stock_picking(osv.osv): ''' res = {} for pick in self.browse(cr, uid, ids, context=context): - if (not pick.move_lines) or any([x.state == 'draft' for x in pick.move_lines]): + if not pick.move_lines: + res[pick.id] = pick.launch_pack_operations and 'assigned' or 'draft' + continue + if any([x.state == 'draft' for x in pick.move_lines]): res[pick.id] = 'draft' continue if all([x.state == 'cancel' for x in pick.move_lines]): @@ -759,19 +780,33 @@ class stock_picking(osv.osv): continue return res - def check_group_lot(self, cr, uid, context=None): - """ This function will return true if we have the setting to use lots activated. """ - return self.pool.get('res.users').has_group(cr, uid, 'stock.group_production_lot') - - def check_group_pack(self, cr, uid, context=None): - """ This function will return true if we have the setting to use package activated. """ - return self.pool.get('res.users').has_group(cr, uid, 'stock.group_tracking_lot') - def action_assign_owner(self, cr, uid, ids, context=None): for picking in self.browse(cr, uid, ids, context=context): packop_ids = [op.id for op in picking.pack_operation_ids] self.pool.get('stock.pack.operation').write(cr, uid, packop_ids, {'owner_id': picking.owner_id.id}, context=context) + def onchange_picking_type(self, cr, uid, ids, picking_type_id): + res = {} + if picking_type_id: + picking_type = self.pool['stock.picking.type'].browse(cr, uid, picking_type_id) + res['value'] = {'location_id': picking_type.default_location_src_id.id, + 'location_dest_id': picking_type.default_location_dest_id.id} + return res + + def _default_location_destination(self): + context = self._context or {} + if context.get('default_picking_type_id', False): + pick_type = self.env['stock.picking.type'].browse(context['default_picking_type_id']) + return pick_type.default_location_dest_id and pick_type.default_location_dest_id.id or False + return False + + def _default_location_source(self): + context = self._context or {} + if context.get('default_picking_type_id', False): + pick_type = self.env['stock.picking.type'].browse(context['default_picking_type_id']) + return pick_type.default_location_src_id and pick_type.default_location_src_id.id or False + return False + _columns = { 'name': fields.char('Reference', select=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, copy=False), 'origin': fields.char('Source Document', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, help="Reference of the document", select=True), @@ -780,7 +815,7 @@ class stock_picking(osv.osv): 'move_type': fields.selection([('direct', 'Partial'), ('one', 'All at once')], 'Delivery Method', required=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, help="It specifies goods to be deliver partially or all at once"), 'state': fields.function(_state_get, type="selection", copy=False, store={ - 'stock.picking': (lambda self, cr, uid, ids, ctx: ids, ['move_type'], 20), + 'stock.picking': (lambda self, cr, uid, ids, ctx: ids, ['move_type', 'launch_pack_operations'], 20), 'stock.move': (_get_pickings, ['state', 'picking_id', 'partially_available'], 20)}, selection=[ ('draft', 'Draft'), @@ -800,6 +835,15 @@ class stock_picking(osv.osv): * Transferred: has been processed, can't be modified or cancelled anymore\n * Cancelled: has been cancelled, can't be confirmed anymore""" ), + 'location_id': fields.many2one('stock.location', required=True, string="Source Location Zone", + default=_default_location_source, readonly=True, states={'draft': [('readonly', False)]}), + 'location_dest_id': fields.many2one('stock.location', required=True,string="Destination Location Zone", + default=_default_location_destination, readonly=True, states={'draft': [('readonly', False)]}), + 'move_lines': fields.one2many('stock.move', 'picking_id', string="Stock Moves", copy=True), + 'move_lines_related': fields.related('move_lines', type='one2many', relation='stock.move', string="Move Lines"), + 'picking_type_id': fields.many2one('stock.picking.type', 'Picking Type', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, required=True), + 'picking_type_code': fields.related('picking_type_id', 'code', type='selection', selection=[('incoming', 'Suppliers'), ('outgoing', 'Customers'), ('internal', 'Internal')]), + 'picking_type_entire_packs': fields.related('picking_type_id', 'show_entire_packs', type='boolean'), 'priority': fields.function(get_min_max_date, multi="min_max_date", fnct_inv=_set_priority, type='selection', selection=procurement.PROCUREMENT_PRIORITIES, string='Priority', store={'stock.move': (_get_pickings, ['priority', 'picking_id'], 20)}, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, select=1, help="Priority for this picking. Setting manually a value here would set it as priority for all the moves", track_visibility='onchange', required=True), @@ -809,30 +853,23 @@ class stock_picking(osv.osv): store={'stock.move': (_get_pickings, ['date_expected', 'picking_id'], 20)}, type='datetime', string='Max. Expected Date', select=2, help="Scheduled time for the last part of the shipment to be processed"), 'date': fields.datetime('Creation Date', help="Creation Date, usually the time of the order", select=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, track_visibility='onchange'), 'date_done': fields.datetime('Date of Transfer', help="Completion Date of Transfer", readonly=True, copy=False), - 'move_lines': fields.one2many('stock.move', 'picking_id', 'Internal Moves', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, copy=True), 'quant_reserved_exist': fields.function(_get_quant_reserved_exist, type='boolean', string='Quant already reserved ?', help='technical field used to know if there is already at least one quant reserved on moves of a given picking'), 'partner_id': fields.many2one('res.partner', 'Partner', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}), 'company_id': fields.many2one('res.company', 'Company', required=True, select=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}), 'pack_operation_ids': fields.one2many('stock.pack.operation', 'picking_id', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, string='Related Packing Operations'), + 'pack_operation_product_ids': fields.one2many('stock.pack.operation', 'picking_id', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, domain=[('product_id', '!=', False)], string='Non pack'), + 'pack_operation_pack_ids': fields.one2many('stock.pack.operation', 'picking_id', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, domain=[('product_id', '=', False)], string='Pack'), 'pack_operation_exist': fields.function(_get_pack_operation_exist, type='boolean', string='Pack Operation Exists?', help='technical field for attrs in view'), - 'picking_type_id': fields.many2one('stock.picking.type', 'Picking Type', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, required=True), - 'picking_type_code': fields.related('picking_type_id', 'code', type='char', string='Picking Type Code', help="Technical field used to display the correct label on print button in the picking view"), - 'owner_id': fields.many2one('res.partner', 'Owner', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, help="Default Owner"), # Used to search on pickings 'product_id': fields.related('move_lines', 'product_id', type='many2one', relation='product.product', string='Product'), 'recompute_pack_op': fields.boolean('Recompute pack operation?', help='True if reserved quants changed, which mean we might need to recompute the package operations', copy=False), - 'location_id': fields.related('move_lines', 'location_id', type='many2one', relation='stock.location', string='Location', - readonly=True, store={'stock.picking': (lambda self, cr, uid, ids, ctx: ids, ['move_lines'], 10), - 'stock.move': (_get_pickings, ['location_id', 'picking_id'], 10),}), - 'location_dest_id': fields.related('move_lines', 'location_dest_id', type='many2one', relation='stock.location', string='Destination Location', - readonly=True, store={'stock.picking': (lambda self, cr, uid, ids, ctx: ids, ['move_lines'], 10), - 'stock.move': (_get_pickings, ['location_dest_id', 'picking_id'], 10),}), 'group_id': fields.related('move_lines', 'group_id', type='many2one', relation='procurement.group', string='Procurement Group', readonly=True, store={ 'stock.picking': (lambda self, cr, uid, ids, ctx: ids, ['move_lines'], 10), 'stock.move': (_get_pickings, ['group_id', 'picking_id'], 10), }), + 'launch_pack_operations': fields.boolean("Launch Pack Operations", copy=False), } _defaults = { @@ -842,7 +879,8 @@ class stock_picking(osv.osv): 'priority': '1', # normal 'date': fields.datetime.now, 'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.picking', context=c), - 'recompute_pack_op': True, + 'recompute_pack_op': False, + 'launch_pack_operations': False, } _sql_constraints = [ ('name_uniq', 'unique(name, company_id)', 'Reference must be unique per company!'), @@ -858,10 +896,15 @@ class stock_picking(osv.osv): context = dict(context or {}, active_ids=ids) return self.pool.get("report").get_action(cr, uid, ids, 'stock.report_picking_operations', context=context) + def launch_packops(self, cr, uid, ids, context=None): + self.write(cr, uid, ids, {'launch_pack_operations': True}, context=context) + def action_confirm(self, cr, uid, ids, context=None): todo = [] todo_force_assign = [] for picking in self.browse(cr, uid, ids, context=context): + if not picking.move_lines: + self.launch_packops(cr, uid, [picking.id], context=context) if picking.location_id.usage in ('supplier', 'inventory', 'production'): todo_force_assign.append(picking.id) for r in picking.move_lines: @@ -894,11 +937,10 @@ class stock_picking(osv.osv): """ Changes state of picking to available if moves are confirmed or waiting. @return: True """ - for pick in self.browse(cr, uid, ids, context=context): + pickings = self.browse(cr, uid, ids, context=context) + for pick in pickings: move_ids = [x.id for x in pick.move_lines if x.state in ['confirmed', 'waiting']] self.pool.get('stock.move').force_assign(cr, uid, move_ids, context=context) - #pack_operation might have changed and need to be recomputed - self.write(cr, uid, ids, {'recompute_pack_op': True}, context=context) return True def action_cancel(self, cr, uid, ids, context=None): @@ -934,19 +976,6 @@ class stock_picking(osv.osv): move_obj.unlink(cr, uid, move_ids, context=context) return super(stock_picking, self).unlink(cr, uid, ids, context=context) - def write(self, cr, uid, ids, vals, context=None): - if vals.get('move_lines') and not vals.get('pack_operation_ids'): - # pack operations are directly dependant of move lines, it needs to be recomputed - pack_operation_obj = self.pool['stock.pack.operation'] - existing_package_ids = pack_operation_obj.search(cr, uid, [('picking_id', 'in', ids)], context=context) - if existing_package_ids: - pack_operation_obj.unlink(cr, uid, existing_package_ids, context) - res = super(stock_picking, self).write(cr, uid, ids, vals, context=context) - #if we changed the move lines or the pack operations, we need to recompute the remaining quantities of both - if 'move_lines' in vals or 'pack_operation_ids' in vals: - self.do_recompute_remaining_quantities(cr, uid, ids, context=context) - return res - def _create_backorder(self, cr, uid, picking, backorder_moves=[], context=None): """ Move all non-done lines into a new backorder picking. If the key 'do_only_split' is given in the context, then move all lines not in context.get('split', []) instead of all non-done lines. """ @@ -970,6 +999,7 @@ class stock_picking(osv.osv): self.write(cr, uid, [picking.id], {'date_done': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context) self.action_confirm(cr, uid, [backorder_id], context=context) + self.action_assign(cr, uid, [backorder_id], context=context) return backorder_id return False @@ -1130,16 +1160,6 @@ class stock_picking(osv.osv): processed_products.add(move.product_id.id) return vals - @api.cr_uid_ids_context - def open_barcode_interface(self, cr, uid, picking_ids, context=None): - final_url="/stock/barcode/#action=stock.ui&picking_id="+str(picking_ids[0]) - return {'type': 'ir.actions.act_url', 'url':final_url, 'target': 'self',} - - @api.cr_uid_ids_context - def do_partial_open_barcode(self, cr, uid, picking_ids, context=None): - self.do_prepare_partial(cr, uid, picking_ids, context=context) - return self.open_barcode_interface(cr, uid, picking_ids, context=context) - @api.cr_uid_ids_context def do_prepare_partial(self, cr, uid, picking_ids, context=None): context = context or {} @@ -1169,6 +1189,7 @@ class stock_picking(osv.osv): else: forced_qties[move.product_id] = forced_qty for vals in self._prepare_pack_ops(cr, uid, picking, picking_quants, forced_qties, context=context): + vals['fresh_record'] = False pack_operation_obj.create(cr, uid, vals, context=ctx) #recompute the remaining quantities all at once self.do_recompute_remaining_quantities(cr, uid, picking_ids, context=context) @@ -1189,7 +1210,7 @@ class stock_picking(osv.osv): self.pool.get('stock.pack.operation').unlink(cr, uid, pack_line_to_unreserve, context=context) self.pool.get('stock.move').do_unreserve(cr, uid, moves_to_unreserve, context=context) - def recompute_remaining_qty(self, cr, uid, picking, context=None): + def recompute_remaining_qty(self, cr, uid, picking, done_qtys=False, context=None): def _create_link_for_index(operation_id, index, product_id, qty_to_assign, quant_id=False): move_dict = prod2move_ids[product_id][index] qty_on_link = min(move_dict['remaining_qty'], qty_to_assign) @@ -1253,7 +1274,7 @@ class stock_picking(osv.osv): for ops in operations: #for each operation, create the links with the stock move by seeking on the matching reserved quants, #and deffer the operation if there is some ambiguity on the move to select - if ops.package_id and not ops.product_id: + if ops.package_id and not ops.product_id and (not done_qtys or ops.product_qty): #entire package quant_ids = package_obj.get_content(cr, uid, [ops.package_id.id], context=context) for quant in quant_obj.browse(cr, uid, quant_ids, context=context): @@ -1268,7 +1289,8 @@ class stock_picking(osv.osv): need_rereserve = True elif ops.product_id.id: #Check moves with same product - qty_to_assign = uom_obj._compute_qty_obj(cr, uid, ops.product_uom_id, ops.product_qty, ops.product_id.uom_id, context=context) + product_qty = done_qtys and ops.qty_done or ops.product_qty + qty_to_assign = uom_obj._compute_qty_obj(cr, uid, ops.product_uom_id, product_qty, ops.product_id.uom_id, context=context) for move_dict in prod2move_ids.get(ops.product_id.id, []): move = move_dict['move'] for quant in move.reserved_quant_ids: @@ -1301,18 +1323,18 @@ class stock_picking(osv.osv): all_op_processed = _create_link_for_product(ops.id, product_id, remaining_qty) and all_op_processed return (need_rereserve, all_op_processed) - def picking_recompute_remaining_quantities(self, cr, uid, picking, context=None): + def picking_recompute_remaining_quantities(self, cr, uid, picking, done_qtys=False, context=None): need_rereserve = False all_op_processed = True if picking.pack_operation_ids: - need_rereserve, all_op_processed = self.recompute_remaining_qty(cr, uid, picking, context=context) + need_rereserve, all_op_processed = self.recompute_remaining_qty(cr, uid, picking, done_qtys=done_qtys, context=context) return need_rereserve, all_op_processed @api.cr_uid_ids_context - def do_recompute_remaining_quantities(self, cr, uid, picking_ids, context=None): + def do_recompute_remaining_quantities(self, cr, uid, picking_ids, done_qtys=False, context=None): for picking in self.browse(cr, uid, picking_ids, context=context): if picking.pack_operation_ids: - self.recompute_remaining_qty(cr, uid, picking, context=context) + self.recompute_remaining_qty(cr, uid, picking, done_qtys=done_qtys, context=context) def _prepare_values_extra_move(self, cr, uid, op, product, remaining_qty, context=None): """ @@ -1371,33 +1393,75 @@ class stock_picking(osv.osv): self.action_assign(cr, uid, [picking.id], context=context) else: stock_move_obj.do_unreserve(cr, uid, move_ids, context=context) - stock_move_obj.action_assign(cr, uid, move_ids, context=context) - - @api.cr_uid_ids_context - def do_enter_transfer_details(self, cr, uid, picking, context=None): - if not context: - context = {} - - context.update({ - 'active_model': self._name, - 'active_ids': picking, - 'active_id': len(picking) and picking[0] or False - }) - - created_id = self.pool['stock.transfer_details'].create(cr, uid, {'picking_id': len(picking) and picking[0] or False}, context) - return self.pool['stock.transfer_details'].wizard_view(cr, uid, created_id, context) + stock_move_obj.action_assign(cr, uid, move_ids, no_prepare=True, context=context) + def do_new_transfer(self, cr, uid, ids, context=None): + pack_op_obj = self.pool['stock.pack.operation'] + data_obj = self.pool['ir.model.data'] + for pick in self.browse(cr, uid, ids, context=context): + to_delete = [] + # In draft or with no pack operations edited yet, ask if we can just do everything + if pick.state == 'draft' or all([x.qty_done == 0.0 for x in pick.pack_operation_ids]): + view = data_obj.xmlid_to_res_id(cr, uid, 'stock.view_immediate_transfer') + wiz_id = self.pool['stock.immediate.transfer'].create(cr, uid, {'pick_id': pick.id}, context=context) + return { + 'name': _('Immediate Transfer?'), + 'type': 'ir.actions.act_window', + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'stock.immediate.transfer', + 'views': [(view, 'form')], + 'view_id': view, + 'target': 'new', + 'res_id': wiz_id, + 'context': context, + } + + # Check backorder should check for other barcodes + if self.check_backorder(cr, uid, pick, context=context): + view = data_obj.xmlid_to_res_id(cr, uid, 'stock.view_backorder_confirmation') + wiz_id = self.pool['stock.backorder.confirmation'].create(cr, uid, {'pick_id': pick.id}, context=context) + return { + 'name': _('Create Backorder?'), + 'type': 'ir.actions.act_window', + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'stock.backorder.confirmation', + 'views': [(view, 'form')], + 'view_id': view, + 'target': 'new', + 'res_id': wiz_id, + 'context': context, + } + for operation in pick.pack_operation_ids: + if operation.qty_done < 0: + raise UserError(_('No negative quantities allowed')) + if operation.qty_done > 0: + pack_op_obj.write(cr, uid, operation.id, {'product_qty': operation.qty_done}, context=context) + else: + to_delete.append(operation.id) + if to_delete: + pack_op_obj.unlink(cr, uid, to_delete, context=context) + self.do_transfer(cr, uid, ids, context=context) + return + + def check_backorder(self, cr, uid, picking, context=None): + need_rereserve, all_op_processed = self.picking_recompute_remaining_quantities(cr, uid, picking, done_qtys=True, context=context) + for move in picking.move_lines: + if float_compare(move.remaining_qty, 0, precision_rounding = move.product_id.uom_id.rounding) != 0: + return True + return False - @api.cr_uid_ids_context - def do_transfer(self, cr, uid, picking_ids, context=None): + def do_transfer(self, cr, uid, ids, context=None): """ If no pack operation, we do simple action_done of the picking Otherwise, do the pack operations """ if not context: context = {} + stock_move_obj = self.pool.get('stock.move') - for picking in self.browse(cr, uid, picking_ids, context=context): + for picking in self.browse(cr, uid, ids, context=context): if not picking.pack_operation_ids: self.action_done(cr, uid, [picking.id], context=context) continue @@ -1435,8 +1499,6 @@ class stock_picking(osv.osv): elif context.get('do_only_split'): context = dict(context, split=todo_move_ids) self._create_backorder(cr, uid, picking, context=context) - if toassign_move_ids: - stock_move_obj.action_assign(cr, uid, toassign_move_ids, context=context) return True @api.cr_uid_ids_context @@ -1448,115 +1510,26 @@ class stock_picking(osv.osv): ctx['do_only_split'] = True return self.do_transfer(cr, uid, picking_ids, context=ctx) - def get_next_picking_for_ui(self, cr, uid, context=None): - """ returns the next pickings to process. Used in the barcode scanner UI""" - if context is None: - context = {} - domain = [('state', 'in', ('assigned', 'partially_available'))] - if context.get('default_picking_type_id'): - domain.append(('picking_type_id', '=', context['default_picking_type_id'])) - return self.search(cr, uid, domain, context=context) - - def action_done_from_ui(self, cr, uid, picking_id, context=None): - """ called when button 'done' is pushed in the barcode scanner UI """ - #write qty_done into field product_qty for every package_operation before doing the transfer - pack_op_obj = self.pool.get('stock.pack.operation') - for operation in self.browse(cr, uid, picking_id, context=context).pack_operation_ids: - pack_op_obj.write(cr, uid, operation.id, {'product_qty': operation.qty_done}, context=context) - self.do_transfer(cr, uid, [picking_id], context=context) - #return id of next picking to work on - return self.get_next_picking_for_ui(cr, uid, context=context) - - @api.cr_uid_ids_context - def action_pack(self, cr, uid, picking_ids, operation_filter_ids=None, context=None): - """ Create a package with the current pack_operation_ids of the picking that aren't yet in a pack. - Used in the barcode scanner UI and the normal interface as well. - operation_filter_ids is used by barcode scanner interface to specify a subset of operation to pack""" - if operation_filter_ids == None: - operation_filter_ids = [] - stock_operation_obj = self.pool.get('stock.pack.operation') - package_obj = self.pool.get('stock.quant.package') - stock_move_obj = self.pool.get('stock.move') - package_id = False - for picking_id in picking_ids: - operation_search_domain = [('picking_id', '=', picking_id), ('result_package_id', '=', False)] - if operation_filter_ids != []: - operation_search_domain.append(('id', 'in', operation_filter_ids)) - operation_ids = stock_operation_obj.search(cr, uid, operation_search_domain, context=context) + def put_in_pack(self, cr, uid, ids, context=None): + stock_move_obj = self.pool["stock.move"] + stock_operation_obj = self.pool["stock.pack.operation"] + package_obj = self.pool["stock.quant.package"] + for pick in self.browse(cr, uid, ids, context=context): + operations = [x for x in pick.pack_operation_ids if x.qty_done > 0 and (not x.result_package_id)] pack_operation_ids = [] - if operation_ids: - for operation in stock_operation_obj.browse(cr, uid, operation_ids, context=context): - #If we haven't done all qty in operation, we have to split into 2 operation - op = operation - if (operation.qty_done < operation.product_qty): - new_operation = stock_operation_obj.copy(cr, uid, operation.id, {'product_qty': operation.qty_done,'qty_done': operation.qty_done}, context=context) - stock_operation_obj.write(cr, uid, operation.id, {'product_qty': operation.product_qty - operation.qty_done,'qty_done': 0, 'lot_id': False}, context=context) - op = stock_operation_obj.browse(cr, uid, new_operation, context=context) - pack_operation_ids.append(op.id) - if op.product_id and op.location_id and op.location_dest_id: - stock_move_obj.check_tracking_product(cr, uid, op.product_id, op.lot_id.id, op.location_id, op.location_dest_id, context=context) + for operation in operations: + #If we haven't done all qty in operation, we have to split into 2 operation + op = operation + if operation.qty_done < operation.product_qty: + new_operation = stock_operation_obj.copy(cr, uid, operation.id, {'product_qty': operation.qty_done,'qty_done': operation.qty_done}, context=context) + stock_operation_obj.write(cr, uid, operation.id, {'product_qty': operation.product_qty - operation.qty_done,'qty_done': 0, 'lot_id': False}, context=context) + op = stock_operation_obj.browse(cr, uid, new_operation, context=context) + pack_operation_ids.append(op.id) + if op.product_id and op.location_id and op.location_dest_id: + stock_move_obj.check_tracking_product(cr, uid, op.product_id, op.lot_id.id, op.location_id, op.location_dest_id, context=context) + if operations: package_id = package_obj.create(cr, uid, {}, context=context) stock_operation_obj.write(cr, uid, pack_operation_ids, {'result_package_id': package_id}, context=context) - return package_id - - def process_product_id_from_ui(self, cr, uid, picking_id, product_id, op_id, increment=1, context=None): - return self.pool.get('stock.pack.operation')._search_and_increment(cr, uid, picking_id, [('product_id', '=', product_id),('id', '=', op_id)], increment=increment, context=context) - - def process_barcode_from_ui(self, cr, uid, picking_id, barcode_str, visible_op_ids, context=None): - '''This function is called each time there barcode scanner reads an input''' - stock_operation_obj = self.pool.get('stock.pack.operation') - answer = {'filter_loc': False, 'operation_id': False} - - # Barcode Nomenclatures - picking_type_id = self.browse(cr, uid, [picking_id], context=context).picking_type_id.id - barcode_nom = self.pool.get('stock.picking.type').browse(cr, uid, [picking_type_id], context=context).barcode_nomenclature_id - parsed_result = barcode_nom.parse_barcode(barcode_str) - - #check if the barcode is a weighted barcode or simply a product - if parsed_result['type'] in ['weight', 'product', 'package']: - weight=1 - if parsed_result['type'] == 'weight': - domain = ['|', ('barcode', '=', parsed_result['base_code']), ('default_code', '=', parsed_result['base_code'])] - weight=parsed_result['value'] - obj = self.pool.get('product.product') - id_in_operation = 'product_id' - elif parsed_result['type'] == 'product': - domain = ['|', ('barcode', '=', parsed_result['code']), ('default_code', '=', parsed_result['code'])] - obj = self.pool.get('product.product') - id_in_operation = 'product_id' - else: - domain = [('name', '=', parsed_result['code'])] - obj = self.pool.get('stock.quant.package') - id_in_operation = 'package_id' - - matching_product_ids = obj.search(cr, uid, domain, context=context) - if matching_product_ids: - op_id = stock_operation_obj._search_and_increment(cr, uid, picking_id, [(id_in_operation, '=', matching_product_ids[0])], filter_visible=True, visible_op_ids=visible_op_ids, increment=weight, context=context) - answer['operation_id'] = op_id - return answer - - #check if the barcode correspond to a lot - elif parsed_result['type'] == 'lot': - lot_obj = self.pool.get('stock.production.lot') - matching_lot_ids = lot_obj.search(cr, uid, [('name', '=', parsed_result['code'])], context=context) - if matching_lot_ids: - lot = lot_obj.browse(cr, uid, matching_lot_ids[0], context=context) - op_id = stock_operation_obj._search_and_increment(cr, uid, picking_id, [('product_id', '=', lot.product_id.id), ('lot_id', '=', lot.id)], filter_visible=True, visible_op_ids=visible_op_ids, increment=1, context=context) - answer['operation_id'] = op_id - return answer - - #check if the barcode correspond to a location - elif parsed_result['type'] == 'location': - stock_location_obj = self.pool.get('stock.location') - matching_location_ids = stock_location_obj.search(cr, uid, [('barcode', '=', parsed_result['code'])], context=context) - if matching_location_ids: - #if we have a location, return immediatly with the location name - location = stock_location_obj.browse(cr, uid, matching_location_ids[0], context=None) - answer['filter_loc'] = stock_location_obj._name_get(cr, uid, location, context=None) - answer['filter_loc_id'] = matching_location_ids[0] - return answer - - return answer class stock_production_lot(osv.osv): @@ -1821,26 +1794,10 @@ class stock_move(osv.osv): 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', help="Technical field depicting the warehouse to consider for the route selection on the next procurement (if any)."), } - def _default_location_destination(self, cr, uid, context=None): - context = context or {} - if context.get('default_picking_type_id', False): - pick_type = self.pool.get('stock.picking.type').browse(cr, uid, context['default_picking_type_id'], context=context) - return pick_type.default_location_dest_id and pick_type.default_location_dest_id.id or False - return False - - def _default_location_source(self, cr, uid, context=None): - context = context or {} - if context.get('default_picking_type_id', False): - pick_type = self.pool.get('stock.picking.type').browse(cr, uid, context['default_picking_type_id'], context=context) - return pick_type.default_location_src_id and pick_type.default_location_src_id.id or False - return False - def _default_destination_address(self, cr, uid, context=None): return False _defaults = { - 'location_id': _default_location_source, - 'location_dest_id': _default_location_destination, 'partner_id': _default_destination_address, 'state': 'draft', 'priority': '1', @@ -2108,6 +2065,8 @@ class stock_move(osv.osv): 'move_type': move.group_id and move.group_id.move_type or 'direct', 'partner_id': move.partner_id.id or False, 'picking_type_id': move.picking_type_id and move.picking_type_id.id or False, + 'location_id': move.location_id.id, + 'location_dest_id': move.location_dest_id.id, } pick = pick_obj.create(cr, uid, values, context=context) return self.write(cr, uid, move_ids, {'picking_id': pick}, context=context) @@ -2183,7 +2142,9 @@ class stock_move(osv.osv): """ Changes the state to assigned. @return: True """ - return self.write(cr, uid, ids, {'state': 'assigned'}, context=context) + res = self.write(cr, uid, ids, {'state': 'assigned'}, context=context) + self.check_recompute_pack_op(cr, uid, ids, context=context) + return res def check_tracking_product(self, cr, uid, product, lot_id, location, location_dest, context=None): check = False @@ -2201,9 +2162,24 @@ class stock_move(osv.osv): """ Checks if serial number is assigned to stock move or not and raise an error if it had to. """ self.check_tracking_product(cr, uid, move.product_id, lot_id, move.location_id, move.location_dest_id, context=context) - - def action_assign(self, cr, uid, ids, context=None): + def check_recompute_pack_op(self, cr, uid, ids, context=None): + pickings = list(set([x.picking_id for x in self.browse(cr, uid, ids, context=context) if x.picking_id])) + pickings_partial = [] + pickings_write = [] + pick_obj = self.pool['stock.picking'] + for pick in pickings: + # Check if someone was treating the picking already + if not any([x.qty_done > 0 for x in pick.pack_operation_ids]): + pickings_partial.append(pick.id) + else: + pickings_write.append(pick.id) + if pickings_partial: + pick_obj.do_prepare_partial(cr, uid, pickings_partial, context=context) + if pickings_write: + pick_obj.write(cr, uid, pickings_write, {'recompute_pack_op': True}, context=context) + + def action_assign(self, cr, uid, ids, no_prepare=False, context=None): """ Checks the product type and accordingly writes the state. """ context = context or {} @@ -2266,8 +2242,11 @@ class stock_move(osv.osv): quant_obj.quants_reserve(cr, uid, quants, move, context=context) #force assignation of consumable products and incoming from supplier/inventory/production + # Do not take force_assign as it would create pack operations if to_assign_moves: - self.force_assign(cr, uid, to_assign_moves, context=context) + self.write(cr, uid, to_assign_moves, {'state': 'assigned'}, context=context) + if not no_prepare: + self.check_recompute_pack_op(cr, uid, ids, context=context) def action_cancel(self, cr, uid, ids, context=None): """ Cancels the moves and if all moves are cancelled it cancels the picking. @@ -3924,6 +3903,8 @@ class stock_pack_operation(osv.osv): _name = "stock.pack.operation" _description = "Packing Operation" + _order = "result_package_id desc, id" + def _get_remaining_prod_quantities(self, cr, uid, operation, context=None): '''Get the remaining quantities per product on an operation with a package. This function returns a dictionary''' #if the operation doesn't concern a package, it's not relevant to call this function @@ -3958,9 +3939,11 @@ class stock_pack_operation(osv.osv): def product_id_change(self, cr, uid, ids, product_id, product_uom_id, product_qty, context=None): res = self.on_change_tests(cr, uid, ids, product_id, product_uom_id, product_qty, context=context) - if product_id and not product_uom_id: - product = self.pool.get('product.product').browse(cr, uid, product_id, context=context) + uom_obj = self.pool['product.uom'] + product = self.pool.get('product.product').browse(cr, uid, product_id, context=context) + if product_id and not product_uom_id or uom_obj.browse(cr, uid, product_uom_id, context=context).category_id.id != product.uom_id.category_id.id: res['value']['product_uom_id'] = product.uom_id.id + res['domain'] = {'product_uom': [('category_id','=',product.uom_id.category_id.id)]} return res def on_change_tests(self, cr, uid, ids, product_id, product_uom_id, product_qty, context=None): @@ -3984,12 +3967,38 @@ class stock_pack_operation(osv.osv): } return res + def _compute_location_description(self, cr, uid, ids, field_name, arg, context=None): + res = {} + for op in self.browse(cr, uid, ids, context=context): + from_name = op.location_id.name + to_name = op.location_dest_id.name + if op.package_id and op.product_id: + from_name += " : " + op.package_id.name + if op.result_package_id: + to_name += " : " + op.result_package_id.name + res[op.id] = {'from_loc': from_name, + 'to_loc': to_name} + return res + + def _get_bool(self, cr, uid, ids, field_name, arg, context=None): + res = {} + for pack in self.browse(cr, uid, ids, context=context): + res[pack.id] = (pack.qty_done > 0.0) + return res + + def _set_processed_qty(self, cr, uid, id, field_name, field_value, arg, context=None): + op = self.browse(cr, uid, id, context=context) + if field_value and op.qty_done == 0: + self.write(cr, uid, [id], {'qty_done': 1.0}, context=context) + return True + _columns = { 'picking_id': fields.many2one('stock.picking', 'Stock Picking', help='The stock operation where the packing has been made', required=True), 'product_id': fields.many2one('product.product', 'Product', ondelete="CASCADE"), # 1 - 'product_uom_id': fields.many2one('product.uom', 'Product Unit of Measure'), - 'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True), - 'qty_done': fields.float('Quantity Processed', digits_compute=dp.get_precision('Product Unit of Measure')), + 'product_uom_id': fields.many2one('product.uom', 'Unit of Measure'), + 'product_qty': fields.float('To Do', digits_compute=dp.get_precision('Product Unit of Measure'), required=True), + 'qty_done': fields.float('Processed', digits_compute=dp.get_precision('Product Unit of Measure')), + 'processed_boolean': fields.function(_get_bool, fnct_inv=_set_processed_qty, type='boolean', string='Processed'), 'package_id': fields.many2one('stock.quant.package', 'Source Package'), # 2 'lot_id': fields.many2one('stock.production.lot', 'Lot/Serial Number'), 'result_package_id': fields.many2one('stock.quant.package', 'Destination Package', help="If set, the operations are packed into this package", required=False, ondelete='cascade'), @@ -4002,16 +4011,30 @@ class stock_pack_operation(osv.osv): 'remaining_qty': fields.function(_get_remaining_qty, type='float', digits = 0, string="Remaining Qty", help="Remaining quantity in default UoM according to moves matched with this operation. "), 'location_id': fields.many2one('stock.location', 'Source Location', required=True), 'location_dest_id': fields.many2one('stock.location', 'Destination Location', required=True), - 'processed': fields.selection([('true','Yes'), ('false','No')],'Has been processed?', required=True), + 'from_loc': fields.function(_compute_location_description, type='char', string='From', multi='loc', readonly=True), + 'to_loc': fields.function(_compute_location_description, type='char', string='To', multi='loc', readonly=True), + 'fresh_record': fields.boolean('Newly created pack operation'), + 'state': fields.related('picking_id', 'state', type='selection', selection=[ + ('draft', 'Draft'), + ('cancel', 'Cancelled'), + ('waiting', 'Waiting Another Operation'), + ('confirmed', 'Waiting Availability'), + ('partially_available', 'Partially Available'), + ('assigned', 'Ready to Transfer'), + ('done', 'Transferred'), + ]), } _defaults = { 'date': fields.date.context_today, - 'qty_done': 0, - 'processed': lambda *a: 'false', + 'qty_done': 0.0, + 'product_qty': 0.0, + 'processed_boolean': lambda *a: False, + 'fresh_record': True, } def write(self, cr, uid, ids, vals, context=None): + vals['fresh_record'] = False context = context or {} res = super(stock_pack_operation, self).write(cr, uid, ids, vals, context=context) if isinstance(ids, (int, long)): @@ -4028,6 +4051,11 @@ class stock_pack_operation(osv.osv): self.pool.get("stock.picking").do_recompute_remaining_quantities(cr, uid, [vals['picking_id']], context=context) return res_id + def unlink(self, cr, uid, ids, context=None): + if any([x.state in ('done', 'cancel') for x in self.browse(cr, uid, ids, context=context)]): + raise UserError(_('You can not delete pack operations of a done picking')) + return super(stock_pack_operation, self).unlink(cr, uid, ids, context=context) + def action_drop_down(self, cr, uid, ids, context=None): ''' Used by barcode interface to say that pack_operation has been moved from src location to destination location, if qty_done is less than product_qty than we have to split the @@ -4406,6 +4434,7 @@ class stock_picking_type(osv.osv): 'default_location_dest_id': fields.many2one('stock.location', 'Default Destination Location'), 'code': fields.selection([('incoming', 'Suppliers'), ('outgoing', 'Customers'), ('internal', 'Internal')], 'Type of Operation', required=True), 'return_picking_type_id': fields.many2one('stock.picking.type', 'Picking Type for Returns'), + 'show_entire_packs': fields.boolean('Show entire packs to move'), 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', ondelete='cascade'), 'active': fields.boolean('Active'), diff --git a/addons/stock/stock_demo.yml b/addons/stock/stock_demo.yml index 671dc84c09c23953de83b9775e78ee1f44b3a30d..a2dc7e537f479ee029dacf9bc4fe088902550f1e 100644 --- a/addons/stock/stock_demo.yml +++ b/addons/stock/stock_demo.yml @@ -66,6 +66,8 @@ picking_type_id: stock.picking_type_out origin: 'outgoing shipment main_warehouse' partner_id: base.res_partner_6 + location_id: stock.stock_location_stock + location_dest_id: stock.stock_location_customers move_lines: - product_id: product.product_product_27 product_uom: product.product_uom_unit @@ -79,6 +81,8 @@ origin: 'outgoing shipment' partner_id: base.res_partner_6 date: !eval "'%s-%s-15' %((datetime.now()-timedelta(days=60)).year, (datetime.now()-timedelta(days=60)).month)" + location_id: stock.stock_location_stock + location_dest_id: stock.stock_location_customers move_lines: - product_id: product.product_product_6 product_uom: product.product_uom_unit @@ -98,6 +102,8 @@ origin: 'outgoing shipment your_company warehouse' partner_id: base.res_partner_6 date: !eval "'%s-%s-15' % ((datetime.now()-timedelta(days=30)).month,(datetime.now()-timedelta(days=30)).month)" + location_id: stock.stock_location_stock + location_dest_id: stock.stock_location_customers move_lines: - product_id: product.product_product_31 product_uom: product.product_uom_unit @@ -110,6 +116,8 @@ picking_type_id: stock.picking_type_out origin: 'your company warehouse' partner_id: base.res_partner_6 + location_id: stock.stock_location_stock + location_dest_id: stock.stock_location_customers move_lines: - product_id: product.product_product_5 product_uom: product.product_uom_unit @@ -123,9 +131,10 @@ !record {model: stock.picking, id: incomming_shipment}: picking_type_id: stock.picking_type_in origin: 'incoming_shipment for test' + location_id: stock_location_suppliers + location_dest_id: stock_location_14 - !record {model: stock.move, id: incomming_shipment_icecream}: - picking_id: stock.incomming_shipment product_id: product_icecream product_uom: product.product_uom_kgm product_uom_qty: 50.0 @@ -137,6 +146,8 @@ picking_type_id: stock.picking_type_in origin: 'incoming_shipment' partner_id: base.res_partner_6 + location_id: stock.stock_location_suppliers + location_dest_id: stock.stock_location_stock move_lines: - product_id: product.product_product_48 product_uom: product.product_uom_unit @@ -149,6 +160,8 @@ picking_type_id: stock.picking_type_in origin: 'incoming_shipment main_warehouse' partner_id: base.res_partner_6 + location_id: stock.stock_location_suppliers + location_dest_id: stock.stock_location_stock move_lines: - product_id: product.product_product_22 product_uom: product.product_uom_unit @@ -161,6 +174,8 @@ picking_type_id: stock.picking_type_in origin: 'incoming_shipment your_company warehouse' partner_id: base.res_partner_6 + location_id: stock.stock_location_suppliers + location_dest_id: stock.stock_location_stock move_lines: - product_id: product.product_product_10 product_uom: product.product_uom_unit @@ -170,16 +185,18 @@ location_dest_id: stock.stock_location_stock - !record {model: stock.picking, id: incomming_shipment4}: - picking_type_id: stock.picking_type_in - origin: 'incoming_shipment_main_warehouse' - partner_id: base.res_partner_6 - move_lines: - - product_id: product.product_product_31 - product_uom: product.product_uom_unit - product_uom_qty: 50.0 - picking_type_id: stock.picking_type_in - location_id: stock.stock_location_suppliers - location_dest_id: stock.stock_location_stock + picking_type_id: stock.picking_type_in + origin: 'incoming_shipment_main_warehouse' + partner_id: base.res_partner_6 + location_id: stock.stock_location_suppliers + location_dest_id: stock.stock_location_stock + move_lines: + - product_id: product.product_product_31 + product_uom: product.product_uom_unit + product_uom_qty: 50.0 + picking_type_id: stock.picking_type_in + location_id: stock.stock_location_suppliers + location_dest_id: stock.stock_location_stock - Create STOCK_PICKING_IN for Chicago Warehouse - @@ -187,6 +204,8 @@ picking_type_id: chi_picking_type_in origin: 'incoming_chicago_warehouse' partner_id: base.res_partner_6 + location_id: stock.stock_location_suppliers + location_dest_id: stock.stock_location_shop0 move_lines: - product_id: product.product_product_9 product_uom: product.product_uom_unit @@ -194,11 +213,14 @@ picking_type_id: chi_picking_type_in location_id: stock.stock_location_suppliers location_dest_id: stock.stock_location_shop0 + - !record {model: stock.picking, id: incomming_chicago_warehouse1}: picking_type_id: chi_picking_type_in origin: 'incoming_shipment_chicago_warehouse' partner_id: base.res_partner_6 + location_id: stock.stock_location_suppliers + location_dest_id: stock.stock_location_shop0 move_lines: - product_id: product.product_product_48 product_uom: product.product_uom_unit @@ -218,6 +240,8 @@ picking_type_id: chi_picking_type_in origin: 'incoming_shipment chicago_warehouse' partner_id: base.res_partner_6 + location_id: stock.stock_location_suppliers + location_dest_id: stock.stock_location_shop0 move_lines: - product_id: product.product_product_32 product_uom: product.product_uom_unit @@ -231,6 +255,8 @@ origin: 'chicago_warehouse' partner_id: base.res_partner_6 date: !eval "'%s-%s-2' % ((datetime.now()-timedelta(days=30)).month,(datetime.now()-timedelta(days=30)).month)" + location_id: stock.stock_location_suppliers + location_dest_id: stock.stock_location_shop0 move_lines: - product_id: product.product_product_22 product_uom: product.product_uom_unit @@ -245,6 +271,8 @@ picking_type_id: chi_picking_type_out origin: 'outgoing_chicago_warehouse' partner_id: base.res_partner_6 + location_id: stock.stock_location_shop0 + location_dest_id: stock.stock_location_customers move_lines: - product_id: product.product_product_27 product_uom: product.product_uom_unit @@ -257,6 +285,8 @@ picking_type_id: chi_picking_type_out origin: 'outgoing_shipment_chicago_warehouse' partner_id: base.res_partner_6 + location_id: stock.stock_location_shop0 + location_dest_id: stock.stock_location_customers move_lines: - product_id: product.product_product_6 product_uom: product.product_uom_unit @@ -275,6 +305,8 @@ picking_type_id: chi_picking_type_out origin: 'chicago_warehouse' partner_id: base.res_partner_6 + location_id: stock.stock_location_shop0 + location_dest_id: stock.stock_location_customers move_lines: - product_id: product.product_product_31 product_uom: product.product_uom_unit @@ -286,6 +318,8 @@ !record {model: stock.picking, id: outgoing_chicago_warehouse3}: picking_type_id: chi_picking_type_out origin: 'outgoing chicago warehouse' + location_id: stock.stock_location_shop0 + location_dest_id: stock.stock_location_customers partner_id: base.res_partner_6 move_lines: - product_id: product.product_product_5 @@ -302,4 +336,4 @@ record_chi_out = self.browse(cr, uid, ref('outgoing_chicago_warehouse1'), context=context) if record.state != 'done' and record_chi_in.state != 'done' and record_chi_out.state != 'done': self.force_assign(cr, uid, [ref('outgoing_shipment_main_warehouse'),ref('outgoing_chicago_warehouse3'),ref('outgoing_shipment_main_warehouse2')], context=context) - self.do_transfer(cr, uid, [ref('outgoing_shipment_main_warehouse1'),ref('incomming_chicago_warehouse1'),ref('outgoing_chicago_warehouse1')], context=context) + self.do_transfer(cr, uid, [ref('outgoing_shipment_main_warehouse1'),ref('incomming_chicago_warehouse1'),ref('outgoing_chicago_warehouse1')], context=context) \ No newline at end of file diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index 1319a61d90c984c5d8abadbd27973d4a70991e57..e0659d35b8297a56d48c75aeaf02aa791aa5fe73 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -689,20 +689,21 @@ <form string="Transfer"> <header> <button name="action_confirm" states="draft" string="Mark as Todo" type="object" class="oe_highlight" groups="base.group_user"/> - <button name="action_assign" states="confirmed" string="Check Availability" type="object" class="oe_highlight" groups="base.group_user"/> - <button name="rereserve_pick" states="partially_available" string="Recheck Availability" type="object" class="oe_highlight" groups="base.group_user"/> + <button name="do_new_transfer" states="draft,partially_available,assigned" string="Validate" groups="stock.group_stock_user" type="object" class="oe_highlight"/> + <button name="action_assign" states="confirmed,waiting" string="Reserve" type="object" class="oe_highlight" groups="base.group_user"/> + <button name="rereserve_pick" states="partially_available" string="Recheck Reservation" type="object" class="oe_highlight" groups="base.group_user"/> <button name="force_assign" states="confirmed,waiting,partially_available" string="Force Availability" type="object" groups="base.group_user"/> - <button name="do_enter_transfer_details" states="assigned,partially_available" string="Transfer" groups="stock.group_stock_user" type="object" class="oe_highlight"/> - <button name="do_print_picking" string="Print Picking List" groups="stock.group_stock_user" type="object" attrs="{'invisible': ['|', ('picking_type_code', '=', 'outgoing'), ('state', '!=', 'assigned')]}"/> + <button name="do_print_picking_operations" string="Print Picking List" groups="stock.group_stock_user" type="object" attrs="{'invisible': ['|', ('picking_type_code', '=', 'outgoing'), ('state', 'not in', ('assigned', 'partially_available'))]}"/> <button name="%(act_stock_return_picking)d" string="Reverse Transfer" states="done" type="action" groups="base.group_user"/> <button name="action_cancel" states="assigned,confirmed,partially_available,draft,waiting" string="Cancel Transfer" groups="base.group_user" type="object"/> <button name="do_unreserve" string="Unreserve" groups="base.group_user" type="object" attrs="{'invisible': [('quant_reserved_exist', '=', False)]}"/> + <button name="do_prepare_partial" attrs="{'invisible': ['|', ('launch_pack_operations', '=', True), '|', ('state', 'not in', ('assigned', 'partially_available')), ('pack_operation_ids', '!=', [])]}" + string="Proceed..." type="object"/> + <field name="launch_pack_operations" invisible="1"/> <field name="state" widget="statusbar" statusbar_visible="draft,confirmed,partially_available,assigned,done" statusbar_colors='{"shipping_except":"red","invoice_except":"red","waiting_date":"blue"}'/> </header> <sheet> - <div class="oe_right oe_button_box"> - <button name="do_partial_open_barcode" groups="stock.group_stock_user" type="object" class="oe_stock_scan_button" attrs="{'invisible': ['|',('pack_operation_exist', '=', True),('state','not in',('assigned', 'partially_available'))]}"><img src="/stock/static/src/img/scan.png" class="oe_stock_scan_image oe_stock_scan_image_btn"/></button> - <button name="open_barcode_interface" groups="stock.group_stock_user" type="object" class="oe_stock_scan_button" attrs="{'invisible': ['|',('pack_operation_exist', '=', False),('state','not in',('assigned', 'partially_available'))]}"><img src="/stock/static/src/img/scan.png" class="oe_stock_scan_image oe_stock_scan_image_btn"/></button> + <div class="oe_right oe_button_box" name="button_box"> </div> <h1> <field name="name" class="oe_inline" attrs="{'invisible': [('name','=','/')]}" readonly="1"/> @@ -710,12 +711,12 @@ <group> <group> <field name="partner_id"/> + <field name="location_id" groups="stock.group_locations" attrs="{'invisible': [('picking_type_code', '=', 'incoming')]}"/> + <field name="location_dest_id" groups="stock.group_locations" attrs="{'invisible': [('picking_type_code', '=', 'outgoing')]}"/> <field name="backorder_id" readonly="1" attrs="{'invisible': [('backorder_id','=',False)]}"/> </group> <group> - <field name="date"/> <field name="min_date"/> - <field name="date_done"/> <field name="origin" placeholder="e.g. PO0032" class="oe_inline"/> <label for="owner_id" groups="stock.group_tracking_owner"/> <div groups="stock.group_tracking_owner"> @@ -726,10 +727,53 @@ </group> </group> <notebook> - <page string="Products"> - <separator string="Stock Moves" attrs="{'invisible': [('pack_operation_exist', '=', False)]}"/> - <field name="move_lines" context="{'address_in_id': partner_id, 'form_view_ref':'stock.view_move_picking_form', 'tree_view_ref':'stock.view_move_picking_tree', 'default_picking_type_id': picking_type_id}"> - <kanban class="o_kanban_mobile o_kanban_one2many"> + <page string="Operations" attrs="{'invisible': [('state', 'in', ('draft', 'confirmed', 'waiting'))]}"> + <button name="do_prepare_partial" type="object" string="Recompute" attrs="{'invisible': [('recompute_pack_op','=', False)]}"/> + <field name="recompute_pack_op" invisible="1"/> + <field name="pack_operation_ids" invisible="1"/> + <field name="pack_operation_product_ids" context="{'default_picking_id': id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id}"> + <tree editable="bottom" colors="grey:result_package_id;red:qty_done>product_qty;green:qty_done==product_qty and state!='done'"> + <field name="package_id" groups="stock.group_tracking_lot" invisible="1"/> + <field name="product_id" on_change="product_id_change(product_id, product_uom_id, product_qty)" required="1" attrs="{'readonly': [('fresh_record', '=', False)]}"/> + <field name="fresh_record" invisible="1"/> + <field name="product_uom_id" attrs="{'readonly': [('fresh_record', '=', False)]}" groups="product.group_uom"/> + <field name="lot_id" domain="[('product_id','=?', product_id)]" context="{'product_id': product_id}" groups="stock.group_production_lot" attrs="{'readonly': [('fresh_record', '=', False)]}"/> + <field name="picking_id" invisible="1"/> + <field name="owner_id" groups="stock.group_tracking_owner"/> + <field name="location_id" domain="[('id', 'child_of', parent.location_id)]" invisible="1"/> + <field name="location_dest_id" domain="[('id', 'child_of', parent.location_dest_id)]" invisible="1"/> + <field name="from_loc" groups="stock.group_locations,stock.group_tracking_lot"/> + <field name="to_loc" groups="stock.group_locations,stock.group_tracking_lot"/> + <field name="result_package_id" groups="stock.group_tracking_lot" context="{'location_id': location_dest_id}" invisible="1"/> + <field name="state" invisible="1"/> + <field name="qty_done"/> + <field name="product_qty" readonly="1" attrs="{'required': [('product_id', '!=', False)]}"/> + <button name="%(stock.pack_details)d" string="Modify" type="action" icon="STOCK_EXECUTE" + states="confirmed,assigned,waiting,partially_available"/> + </tree> + </field> + <button class="oe_link oe_right" name="put_in_pack" type="object" string="↳Put in Pack" attrs="{'invisible': [('state', 'in', ('done', 'cancel'))]}" groups="stock.group_tracking_lot"/> + <field name="picking_type_entire_packs" invisible="1"/> + <field name="pack_operation_pack_ids" attrs="{'invisible': [('pack_operation_pack_ids', '=', []), ('picking_type_entire_packs', '=', False)]}" context="{'default_picking_id': id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_picking_id': id}" groups="stock.group_tracking_lot"> + <tree editable="bottom" colors="grey:qty_done>0;"> + <field name="package_id" required="1" string="Package To Move"/> + <field name="picking_id" invisible="1"/> + <field name="location_id" domain="[('id', 'child_of', parent.location_id)]" invisible="1"/> + <field name="from_loc" groups="stock.group_locations,stock.group_tracking_lot"/> + <field name="to_loc" groups="stock.group_locations,stock.group_tracking_lot"/> + <field name="location_dest_id" domain="[('id', 'child_of', parent.location_dest_id)]" invisible="1"/> + <field name="result_package_id" groups="stock.group_tracking_lot" context="{'location_id': location_dest_id}" invisible="1"/> + <field name="state" invisible="1"/> + <field name="qty_done" invisible="1"/> + <field name="processed_boolean"/> + <button name="%(stock.pack_details)d" string="Modify" type="action" icon="STOCK_EXECUTE" + states="confirmed,assigned,waiting,partially_available"/> + </tree> + </field> + </page> + <page string="Initial Demand" attrs="{'invisible': [('state', 'not in', ('draft', 'confirmed', 'waiting'))]}"> + <field name="move_lines" context="{'address_in_id': partner_id, 'form_view_ref':'stock.view_move_picking_form', 'tree_view_ref':'stock.view_move_picking_tree', 'default_picking_type_id': picking_type_id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id}"> + <kanban class="o_kanban_mobile o_kanban_one2many"> <field name="product_id"/> <field name="availability"/> <field name="product_uom_qty"/> @@ -759,34 +803,16 @@ <field name="pack_operation_exist" invisible="1"/> <field name="note" placeholder="Add an internal note..." class="oe_inline"/> </page> - <page string="Operations" attrs="{'invisible': ['|', ('state','!=','done'), ('pack_operation_ids','=',[])]}"> - <field name="pack_operation_ids" context="{'default_location_id': location_id, 'default_location_dest_id': location_dest_id}"> - <tree editable="top"> - <field name="package_id" groups="stock.group_tracking_lot"/> - <field name="product_id" on_change="product_id_change(product_id, product_uom_id, product_qty)"/> - <field name="product_uom_id" groups="product.group_uom"/> - <field name="lot_id" domain="[('product_id','=?', product_id)]" context="{'product_id': product_id}" groups="stock.group_production_lot"/> - <field name="picking_id" invisible="1"/> - <field name="owner_id" groups="stock.group_tracking_owner"/> - <field name="product_qty" attrs="{'required': [('product_id', '!=', False)]}"/> - <field name="location_id" domain="[('id', 'child_of', parent.location_id)]"/> - <field name="location_dest_id" domain="[('id', 'child_of', parent.location_dest_id)]"/> - <field name="result_package_id" groups="stock.group_tracking_lot" context="{'location_id': location_dest_id}"/> - </tree> - </field> - <p class="oe_grey" groups="stock.group_tracking_lot"> - If there is no product but a source package, this means the source package was moved entirely. If there is a product and a source package, the product was taken from the source package. - </p> + <page string="Initial Demand" attrs="{'invisible': [('state', 'in', ('draft', 'confirmed', 'waiting', 'done'))]}"> + <field name="move_lines_related" readonly="1"/> </page> - <page string="Additional Info"> - <group string="General Informations"> + <page string="Additional Info" name="extra"> + <group> <group> <field name="move_type"/> - <field name="picking_type_id"/> + <field name="picking_type_id" on_change="onchange_picking_type(picking_type_id)"/> <field name="picking_type_code" invisible="1"/> - <field name="quant_reserved_exist" invisible="1"/> - <field name="location_id" invisible="1"/> - <field name="location_dest_id" invisible="1"/> + <field name="quant_reserved_exist" invisible="1"/> </group> <group> <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/> @@ -1049,8 +1075,6 @@ <field name="date" groups="base.group_no_one"/> <field name="date_expected"/> <field name="state"/> - <button name="action_done" states="confirmed,assigned" string="Process" type="object" - groups="stock.group_stock_user" icon="gtk-go-forward" help="Done"/> </tree> </field> </record> @@ -1060,14 +1084,15 @@ <field name="model">stock.move</field> <field eval="4" name="priority"/> <field name="arch" type="xml"> - <tree decoration-muted="scrapped == True" string="Stock Moves"> - <field name="product_id"/> + <tree decoration-muted="scrapped == True" colors="grey:scrapped == True" string="Stock Moves" editable="bottom"> + <field name="product_id" on_change="onchange_product_id(product_id,location_id,location_dest_id, parent.partner_id)"/> + <field name="name" invisible="1"/> <field name="product_uom_qty" on_change="onchange_quantity(product_id, product_uom_qty, product_uom, product_uos)"/> <field name="product_uom" string="Unit of Measure" groups="product.group_uom"/> - <field name="product_uos_qty" groups="product.group_uos"/> - <field name="product_uos" groups="product.group_uos"/> - <field name="location_id" groups="stock.group_locations" invisible="1"/> - <field name="picking_id" invisible="1" /> + <field name="product_uos_qty" groups="product.group_uos" invisible="1"/> + <field name="product_uos" groups="product.group_uos" invisible="1"/> + <field name="location_id" invisible="1"/> + <field name="picking_id" invisible="1"/> <field name="create_date" invisible="1" /> <field name="date_expected" invisible="1" /> <button name="%(stock.move_scrap)d" @@ -1078,9 +1103,8 @@ <field name="scrapped" invisible="1"/> <field name="availability" invisible="1"/> <field name="reserved_availability" invisible="1"/> - <field name="location_dest_id" groups="stock.group_locations"/> + <field name="location_dest_id" invisible="1"/> <field name="remaining_qty" invisible="1"/> - <field name="string_availability_info"/> <field name="state"/> </tree> </field> @@ -1093,10 +1117,6 @@ <field name="arch" type="xml"> <form string="Stock Moves"> <header> - <button name="action_confirm" states="draft" string="Process Later" type="object" class="oe_highlight"/> - <button name="action_done" states="draft,assigned,confirmed" string="Process Entirely" type="object" class="oe_highlight"/> - <button name="force_assign" states="confirmed" string="Set Available" type="object" class="oe_highlight"/> - <button name="action_cancel" states="draft,assigned,confirmed" string="Cancel Move" type="object"/> <field name="state" widget="statusbar" statusbar_visible="draft,confirmed,assigned,done" statusbar_colors='{"waiting":"blue","confirmed":"blue"}'/> </header> <sheet> @@ -1146,7 +1166,7 @@ <field name="move_dest_id" groups="base.group_no_one" readonly="1"/> </group> <group name="quants_grp" string="Reserved Quants" colspan="4" groups="base.group_no_one"> - <field name="reserved_quant_ids" nolabel="1"/> + <field name="string_availability_info"/> </group> </group> </sheet> @@ -1161,15 +1181,11 @@ <field name="arch" type="xml"> <form string="Stock Moves"> <header> - <button name="force_assign" states="confirmed" string="Force Availability" type="object" groups="base.group_user"/> - <button name="action_confirm" states="draft" string="Confirm" type="object" groups="base.group_user"/> - <button name="do_unreserve" states="assigned" string="Cancel Availability" type="object" groups="base.group_user"/> - <field name="state" widget="statusbar" statusbar_visible="draft,assigned,done"/> + <field name="state" widget="statusbar"/> </header> <group> - <group> + <group string="#Products"> <field name="product_id" on_change="onchange_product_id(product_id,location_id,location_dest_id, parent.partner_id)"/> - <field name="procure_method" attrs="{'readonly': [('state', '!=', 'draft')]}" groups="stock.group_adv_location"/> <field name="picking_type_id" invisible="1"/> <label for="product_uom_qty"/> <div> @@ -1181,27 +1197,28 @@ states="draft,waiting,confirmed,assigned" groups="base.group_user"/> </div> - <label for="product_uos_qty" groups="product.group_uos"/> - <div groups="product.group_uos"> + <label for="product_uos_qty" groups="product.group_uos" invisible="1"/> + <div groups="product.group_uos" invisible="1"> <field name="product_uos_qty" on_change="onchange_uos_quantity(product_id, product_uos_qty, product_uos, product_uom)" class="oe_inline"/> <field name="product_uos" on_change="onchange_quantity(product_id, product_uom_qty, product_uom, product_uos)" class="oe_inline"/> </div> - <field name="name"/> <field name="product_tmpl_id" invisible="1"/> - <field name="product_packaging" groups="product.group_stock_packaging" domain="[('product_tmpl_id','=',product_tmpl_id)]"/> </group> - <group> + <group string="Locations" groups="base.group_no_one"> + <field name="name"/> + <field name="location_id"/> + <field name="location_dest_id"/> + </group> + </group> + <group> + <group string="Conditions"> + <field name="procure_method" attrs="{'readonly': [('state', '!=', 'draft')]}" groups="stock.group_adv_location"/> <field name="create_date" invisible="1"/> - <field name="date"/> - <field name="date_expected" on_change="onchange_date(date,date_expected)"/> + <field name="date_expected"/> <field name="move_dest_id" groups="base.group_no_one" readonly="1"/> </group> - <group string="Locations" groups="stock.group_locations"> - <field name="location_id" domain="[('usage','<>','view')]"/> - <field name="location_dest_id" domain="[('usage','<>','view')]"/> - </group> - <group name="quants_grp" string="Reserved Quants" colspan="4" groups="base.group_no_one"> - <field name="reserved_quant_ids"/> + <group name="quants_grp" string="Reserved"> + <field name="string_availability_info"/> </group> </group> </form> @@ -1436,6 +1453,7 @@ <field name="code" on_change="onchange_picking_code(code)"/> <field name="return_picking_type_id"/> <field name="barcode_nomenclature_id"/> + <field name="show_entire_packs" groups="stock.group_tracking_lot"/> </group> </group> <separator string="Locations"/> @@ -1767,7 +1785,7 @@ <field name="model">stock.quant</field> <field eval="10" name="priority"/> <field name="arch" type="xml"> - <tree string="Quants" create="0"> + <tree string="Quants" create="0" delete="0"> <field name="product_id"/> <field name="qty"/> <field name="product_uom_id" groups="product.group_uom"/> diff --git a/addons/stock/test/packingneg.yml b/addons/stock/test/packingneg.yml index 1bd642d3d441c0cccd2d2293f3c41fcf4607257e..ffae79ba640d3d36d9fc067c9f64ebe1bf0d3a8b 100644 --- a/addons/stock/test/packingneg.yml +++ b/addons/stock/test/packingneg.yml @@ -31,7 +31,6 @@ - !python {model: stock.picking}: | self.action_confirm(cr, uid, [ref('pick_neg')], context=context) - self.do_prepare_partial(cr, uid, [ref('pick_neg')], context=context) - Put 120 pieces on Palneg 1 (package), 120 pieces on Palneg 2 with lot A and 60 pieces on Palneg 3 - @@ -79,7 +78,6 @@ - !python {model: stock.picking}: | stock_pack = self.pool.get('stock.pack.operation') - self.do_prepare_partial(cr, uid, [ref('delivery_order_neg')], context=context) delivery_id = self.browse(cr, uid, ref('delivery_order_neg'), context=context) for rec in delivery_id.pack_operation_ids: if rec.package_id.name == 'Palneg 2': @@ -130,8 +128,6 @@ - !python {model: stock.picking}: | self.action_confirm(cr, uid, [ref("delivery_reconcile")]) - self.action_confirm(cr, uid, [ref("delivery_reconcile")]) - self.do_prepare_partial(cr, uid, [ref("delivery_reconcile")]) pick = self.browse(cr, uid, ref("delivery_reconcile")) ops_obj = self.pool.get("stock.pack.operation") pack_obj = self.pool.get("stock.quant.package") @@ -148,4 +144,3 @@ pick = self.pool.get('stock.picking').browse(cr, uid, ref('delivery_reconcile')) customer_quant = self.search(cr, uid, [('product_id', '=', ref('product_neg')), ('location_id', '=', ref('stock_location_customers')), ('lot_id.name','=', 'Lot neg'), ('qty','=', 20)]) assert pick.move_lines[0].id in [x.id for x in self.browse(cr, uid, customer_quant[0]).history_ids] - diff --git a/addons/stock/test/procrule.yml b/addons/stock/test/procrule.yml index 891299e4ba5989624d597966823e4fee1e1ad595..c1985bda679196d1c569164b4cd99b04cd8ffe93 100644 --- a/addons/stock/test/procrule.yml +++ b/addons/stock/test/procrule.yml @@ -14,11 +14,11 @@ name: Delivery order for procurement partner_id: base.res_partner_2 picking_type_id: stock.picking_type_out + location_id: stock.stock_location_output + location_dest_id: stock.stock_location_customers move_lines: - product_id: product.product_product_3 product_uom_qty: 10.00 - location_id: stock.stock_location_output - location_dest_id: stock.stock_location_customers procure_method: make_to_order - Confirm delivery order. diff --git a/addons/stock/test/wiseoperator.yml b/addons/stock/test/wiseoperator.yml index 78721d5f58bccf4044cf22c712811600ef7f5dc4..0b663e61a8e8a7e86cbc5b64288114814f1ad931 100644 --- a/addons/stock/test/wiseoperator.yml +++ b/addons/stock/test/wiseoperator.yml @@ -10,7 +10,7 @@ - Create an incoming picking for this product of 10 PCE from suppliers to stock - - !record {model: stock.picking, id: pick1}: + !record {model: stock.picking, id: pick1_wise}: name: Incoming picking partner_id: base.res_partner_2 picking_type_id: picking_type_in @@ -23,23 +23,23 @@ Confirm and assign picking and prepare partial - !python {model: stock.picking}: | - self.action_confirm(cr, uid, [ref('pick1')], context=context) - self.do_prepare_partial(cr, uid, [ref('pick1')], context=context) + self.action_confirm(cr, uid, [ref('pick1_wise')], context=context) + self.do_prepare_partial(cr, uid, [ref('pick1_wise')], context=context) - Put 6 pieces in shelf1 and 4 pieces in shelf2 - !python {model: stock.picking}: | - record = self.browse(cr, uid, ref('pick1'), context=context) + record = self.browse(cr, uid, ref('pick1_wise'), context=context) stock_pack = self.pool.get('stock.pack.operation') stock_quant_pack = self.pool.get('stock.quant.package') package1 = stock_quant_pack.create(cr, uid, {'name': 'Pack 1'}, context=context) stock_pack.write(cr, uid, record.pack_operation_ids[0].id, {'result_package_id': package1, 'product_qty': 4, 'location_dest_id': ref('stock_location_components')}) - new_pack1 = stock_pack.create(cr, uid, {'product_id': ref('product_wise'), 'product_uom_id': ref('product.product_uom_unit'), 'picking_id': ref('pick1'), 'product_qty': 6.0, 'location_id': ref('stock_location_suppliers'), 'location_dest_id': ref('stock_location_14')}, context=context) + new_pack1 = stock_pack.create(cr, uid, {'product_id': ref('product_wise'), 'product_uom_id': ref('product.product_uom_unit'), 'picking_id': ref('pick1_wise'), 'product_qty': 6.0, 'location_id': ref('stock_location_suppliers'), 'location_dest_id': ref('stock_location_14')}, context=context) - Transfer the receipt - !python {model: stock.picking}: | - self.do_transfer(cr, uid, [ref('pick1')], context=context) + self.do_transfer(cr, uid, [ref('pick1_wise')], context=context) - Check the system created 2 quants - diff --git a/addons/stock/tests/test_owner_available.py b/addons/stock/tests/test_owner_available.py index ec74f3e65d95de2e94b70b630ac03c2e7e31155c..7aa1e30d2e42a7f640d277b5e4d2dc6d876100f2 100644 --- a/addons/stock/tests/test_owner_available.py +++ b/addons/stock/tests/test_owner_available.py @@ -26,7 +26,9 @@ class TestVirtualAvailable(TestStockCommon): }) self.picking_out = self.env['stock.picking'].create({ - 'picking_type_id': self.ref('stock.picking_type_out')}) + 'picking_type_id': self.ref('stock.picking_type_out'), + 'location_id': self.stock_location, + 'location_dest_id': self.customer_location}) self.env['stock.move'].create({ 'name': 'a move', 'product_id': self.productA.id, @@ -34,10 +36,13 @@ class TestVirtualAvailable(TestStockCommon): 'product_uom': self.productA.uom_id.id, 'picking_id': self.picking_out.id, 'location_id': self.stock_location, - 'location_dest_id': self.customer_location}) + 'location_dest_id': self.customer_location + }) self.picking_out_2 = self.env['stock.picking'].create({ - 'picking_type_id': self.ref('stock.picking_type_out')}) + 'picking_type_id': self.ref('stock.picking_type_out'), + 'location_id': self.stock_location, + 'location_dest_id': self.customer_location}) self.env['stock.move'].create({ 'restrict_partner_id': self.ref('base.res_partner_4'), 'name': 'another move', @@ -46,7 +51,8 @@ class TestVirtualAvailable(TestStockCommon): 'product_uom': self.productA.uom_id.id, 'picking_id': self.picking_out_2.id, 'location_id': self.stock_location, - 'location_dest_id': self.customer_location}) + 'location_dest_id': self.customer_location + }) def test_without_owner(self): diff --git a/addons/stock/tests/test_stock_flow.py b/addons/stock/tests/test_stock_flow.py index bd3302b5be98eb35753a240391ba4660e63b0601..64b52e453afa70a31f686adcdb6e9f8a9391d9db 100644 --- a/addons/stock/tests/test_stock_flow.py +++ b/addons/stock/tests/test_stock_flow.py @@ -20,7 +20,9 @@ class TestStockFlow(TestStockCommon): picking_in = self.PickingObj.create({ 'partner_id': self.partner_delta_id, - 'picking_type_id': self.picking_type_in}) + 'picking_type_id': self.picking_type_in, + 'location_id': self.supplier_location, + 'location_dest_id': self.stock_location}) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, @@ -161,7 +163,9 @@ class TestStockFlow(TestStockCommon): picking_out = self.PickingObj.create({ 'partner_id': self.partner_agrolite_id, - 'picking_type_id': self.picking_type_out}) + 'picking_type_id': self.picking_type_out, + 'location_id': self.stock_location, + 'location_dest_id': self.customer_location}) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, @@ -432,7 +436,9 @@ class TestStockFlow(TestStockCommon): picking_in_A = self.PickingObj.create({ 'partner_id': self.partner_delta_id, - 'picking_type_id': self.picking_type_in}) + 'picking_type_id': self.picking_type_in, + 'location_id': self.supplier_location, + 'location_dest_id': self.stock_location}) self.MoveObj.create({ 'name': self.DozA.name, 'product_id': self.DozA.id, @@ -526,7 +532,9 @@ class TestStockFlow(TestStockCommon): picking_in_B = self.PickingObj.create({ 'partner_id': self.partner_delta_id, - 'picking_type_id': self.picking_type_in}) + 'picking_type_id': self.picking_type_in, + 'location_id': self.supplier_location, + 'location_dest_id': self.stock_location}) self.MoveObj.create({ 'name': self.DozA.name, 'product_id': self.DozA.id, @@ -711,7 +719,9 @@ class TestStockFlow(TestStockCommon): before_out_quantity = self.kgB.qty_available picking_out = self.PickingObj.create({ 'partner_id': self.partner_agrolite_id, - 'picking_type_id': self.picking_type_out}) + 'picking_type_id': self.picking_type_out, + 'location_id': self.stock_location, + 'location_dest_id': self.customer_location}) self.MoveObj.create({ 'name': self.kgB.name, 'product_id': self.kgB.id, @@ -749,7 +759,9 @@ class TestStockFlow(TestStockCommon): picking_out = self.PickingObj.create({ 'partner_id': self.partner_agrolite_id, - 'picking_type_id': self.picking_type_out}) + 'picking_type_id': self.picking_type_out, + 'location_id': self.stock_location, + 'location_dest_id': self.customer_location}) self.MoveObj.create({ 'name': self.DozA.name, 'product_id': self.DozA.id, @@ -867,7 +879,9 @@ class TestStockFlow(TestStockCommon): productKG = self.ProductObj.create({'name': 'Product KG', 'uom_id': self.uom_kg.id, 'uom_po_id': self.uom_kg.id}) picking_in = self.PickingObj.create({ 'partner_id': self.partner_delta_id, - 'picking_type_id': self.picking_type_in}) + 'picking_type_id': self.picking_type_in, + 'location_id': self.supplier_location, + 'location_dest_id': self.stock_location}) self.MoveObj.create({ 'name': productKG.name, 'product_id': productKG.id, @@ -912,7 +926,9 @@ class TestStockFlow(TestStockCommon): self.assertEqual(productKG.qty_available, 1000, 'Wrong quantity available of product (%s found instead of 1000)' % (productKG.qty_available)) picking_out = self.PickingObj.create({ 'partner_id': self.partner_agrolite_id, - 'picking_type_id': self.picking_type_out}) + 'picking_type_id': self.picking_type_out, + 'location_id': self.stock_location, + 'location_dest_id': self.customer_location}) self.MoveObj.create({ 'name': productKG.name, 'product_id': productKG.id, diff --git a/addons/stock/views/stock.xml b/addons/stock/views/stock.xml deleted file mode 100644 index 9f351cce2c147f5c68879596b9c05f2205f520fa..0000000000000000000000000000000000000000 --- a/addons/stock/views/stock.xml +++ /dev/null @@ -1,68 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<openerp> - <data> - -<template id="assets_backend" name="stock assets" inherit_id="web.assets_backend"> - <xpath expr="." position="inside"> - <link rel="stylesheet" href="/stock/static/src/css/stock.css"/> - <link rel="stylesheet" href="/stock/static/src/less/stock_dashboard.less"/> - <script type="text/javascript" src="/stock/static/src/js/widgets.js"></script> - </xpath> -</template> - -<template id="barcode_index" name="Barcode Scanner"><!DOCTYPE html> -<html> - <head> - <title>Barcode Scanner</title> - - <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/> - <meta http-equiv="content-type" content="text/html, charset=utf-8" /> - - <meta name="viewport" content=" width=1024, user-scalable=no"/> - <meta name="apple-mobile-web-app-capable" content="yes"/> - <meta name="mobile-web-app-capable" content="yes"/> - - <link rel="shortcut icon" sizes="80x51" href="/stock/static/src/img/scan.png"/> - <link rel="shortcut icon" href="/web/static/src/img/favicon.ico" type="image/x-icon"/> - - <link rel="stylesheet" href="/stock/static/src/css/barcode.css" /> - <link rel="stylesheet" href="/web/static/lib/bootstrap/css/bootstrap.css" /> - <link rel="stylesheet" href="/web/static/lib/jquery.ui/jquery-ui.css" /> - <link rel="stylesheet" href="/web/static/lib/fontawesome/css/font-awesome.css" /> - - <t t-call-assets="web.assets_common" t-css="false" /> - <t t-call-assets="web.assets_backend" t-css="false" /> - - <script type="text/javascript" id="loading-script" t-raw="init"> - odoo.define('web.web_client', function (require) { - var WebClient = require('web.WebClient'); - var web_client = new WebClient(); - web_client.do_push_state = function (){}; - $(function () { - web_client.setElement($(document.body)); - web_client.show_application = function () { - web_client.action_manager.do_action("stock.ui", {}); - }; - web_client.start(); - }); - return web_client; - }); - odoo.init(); - - </script> - - </head> - <body> - <div class='openerp openerp_webclient_container'> - <table class='oe_webclient'> - <tr> - <td class='oe_application' /> - </tr> - </table> - </div> - </body> -</html> -</template> - - </data> -</openerp> diff --git a/addons/stock/wizard/__init__.py b/addons/stock/wizard/__init__.py index e7ab46b8d25953071f609e125f6ee1c29f5a05a5..7a4969f423506a31d971649d1a7b4176feb3621a 100644 --- a/addons/stock/wizard/__init__.py +++ b/addons/stock/wizard/__init__.py @@ -6,4 +6,6 @@ import stock_return_picking import stock_change_product_qty import make_procurement_product import orderpoint_procurement -import stock_transfer_details +import stock_pack_details +import stock_immediate_transfer +import stock_backorder_confirmation diff --git a/addons/stock/wizard/stock_backorder_confirmation.py b/addons/stock/wizard/stock_backorder_confirmation.py new file mode 100644 index 0000000000000000000000000000000000000000..a271bf8053fcbbce8a6ed189c20f6651eb934f38 --- /dev/null +++ b/addons/stock/wizard/stock_backorder_confirmation.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from openerp import models, fields, api + +class stock_backorder_confirmation(models.TransientModel): + _name = 'stock.backorder.confirmation' + _description = 'Backorder Confirmation' + + pick_id = fields.Many2one('stock.picking') + + @api.model + def default_get(self, fields): + res = {} + active_id = self._context.get('active_id') + if active_id: + res = {'pick_id': active_id} + return res + + @api.multi + def _process(self, cancel_backorder=False): + self.ensure_one() + for pack in self.pick_id.pack_operation_ids: + if pack.qty_done > 0: + pack.product_qty = pack.qty_done + else: + pack.unlink() + self.pick_id.do_transfer() + if cancel_backorder: + backorder_pick = self.env['stock.picking'].search([('backorder_id', '=', self.pick_id.id)]) + backorder_pick.action_cancel() + + @api.multi + def process(self): + self.ensure_one() + self._process() + + @api.multi + def process_cancel_backorder(self): + self.ensure_one() + self._process(cancel_backorder=True) diff --git a/addons/stock/wizard/stock_backorder_confirmation.xml b/addons/stock/wizard/stock_backorder_confirmation.xml new file mode 100644 index 0000000000000000000000000000000000000000..1a58c6d369fb8517832524f1a12d8761dd9ca32b --- /dev/null +++ b/addons/stock/wizard/stock_backorder_confirmation.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<odoo> + <record id="view_backorder_confirmation" model="ir.ui.view"> + <field name="name">stock_backorder_confirmation</field> + <field name="model">stock.backorder.confirmation</field> + <field name="arch" type="xml"> + <form string="Backorder creation"> + <group> + Do we need to create a backorder? + </group> + <footer> + <button name="process" string="Create Backorder" type="object" class="oe_highlight"/> + <button name="process_cancel_backorder" string="No Backorder" type="object" class="oe_highlight"/> + or + <button string="_Cancel" class="oe_link" special="cancel" /> + </footer> + </form> + </field> + </record> +</odoo> \ No newline at end of file diff --git a/addons/stock/wizard/stock_immediate_transfer.py b/addons/stock/wizard/stock_immediate_transfer.py new file mode 100644 index 0000000000000000000000000000000000000000..04c681cfa31765fa5e07b5d6052f3da77552e5c6 --- /dev/null +++ b/addons/stock/wizard/stock_immediate_transfer.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from openerp import models, fields, api + +class stock_immediate_transfer(models.TransientModel): + _name = 'stock.immediate.transfer' + _description = 'Immediate Transfer' + + pick_id = fields.Many2one('stock.picking') + + @api.model + def default_get(self, fields): + res = {} + active_id = self._context.get('active_id') + if active_id: + res = {'pick_id': active_id} + return res + + @api.multi + def process(self): + self.ensure_one() + # If still in draft => confirm and assign + if self.pick_id.state == 'draft': + self.pick_id.action_confirm() + if self.pick_id.state != 'assigned': + self.pick_id.action_assign() + for pack in self.pick_id.pack_operation_ids: + if pack.product_qty > 0: + pack.write({'qty_done': pack.product_qty}) + else: + pack.unlink() + self.pick_id.do_transfer() \ No newline at end of file diff --git a/addons/stock/wizard/stock_immediate_transfer.xml b/addons/stock/wizard/stock_immediate_transfer.xml new file mode 100644 index 0000000000000000000000000000000000000000..67084df776d9ac9a401e9a465013e46e33ed6fb4 --- /dev/null +++ b/addons/stock/wizard/stock_immediate_transfer.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<odoo> + <record id="view_immediate_transfer" model="ir.ui.view"> + <field name="name">view_immediate_transfer</field> + <field name="model">stock.immediate.transfer</field> + <field name="arch" type="xml"> + <form string="Everything at once?"> + <group> + <p> + You did not enter any quantities. Click apply if you want to process everything at once. + </p> + </group> + <footer> + <button name="process" string="_Apply" type="object" class="oe_highlight"/> + or + <button string="_Cancel" class="oe_link" special="cancel" /> + </footer> + </form> + </field> + </record> +</odoo> \ No newline at end of file diff --git a/addons/stock/wizard/stock_pack_details.py b/addons/stock/wizard/stock_pack_details.py new file mode 100644 index 0000000000000000000000000000000000000000..afab6d65b42d0e32b73367dd8cf608fbbdec9a62 --- /dev/null +++ b/addons/stock/wizard/stock_pack_details.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from openerp import models, fields, api +import openerp.addons.decimal_precision as dp + +class stock_pack_details(models.TransientModel): + _name = 'stock.pack.details' + _description = 'Pack details' + + pack_id = fields.Many2one('stock.pack.operation', 'Pack operation') + product_id = fields.Many2one('product.product', 'Product') + product_uom_id = fields.Many2one('product.uom', 'Product Unit of Measure') + qty_done = fields.Float('Processed Qty', digits=dp.get_precision('Product Unit of Measure')) + quantity = fields.Float('Quantity', digits=dp.get_precision('Product Unit of Measure'), readonly=True) + package_id = fields.Many2one('stock.quant.package', 'Source package', domain="['|', ('location_id', 'child_of', location_id), ('location_id','=',False)]") + lot_id = fields.Many2one('stock.production.lot', 'Lot/Serial Number') + location_id = fields.Many2one('stock.location', 'Source Location', required=True) + location_dest_id = fields.Many2one('stock.location', 'Destination Location', required=True) + result_package_id = fields.Many2one('stock.quant.package', 'Destination package', domain="['|', ('location_id', 'child_of', location_dest_id), ('location_id','=',False)]") + picking_source_location_id = fields.Many2one('stock.location', related='pack_id.picking_id.location_id', readonly=True) + picking_destination_location_id = fields.Many2one('stock.location', related='pack_id.picking_id.location_dest_id', readonly=True) + + @api.model + def default_get(self, fields): + res = {} + active_id = self._context.get('active_id') + if active_id: + pack_op = self.env['stock.pack.operation'].browse(active_id) + res = { + 'pack_id': pack_op.id, + 'product_id': pack_op.product_id.id, + 'product_uom_id': pack_op.product_uom_id.id, + 'quantity': pack_op.product_qty, + 'qty_done': pack_op.qty_done, + 'package_id': pack_op.package_id.id, + 'lot_id': pack_op.lot_id.id, + 'location_id': pack_op.location_id.id, + 'location_dest_id': pack_op.location_dest_id.id, + 'result_package_id': pack_op.result_package_id.id, + } + return res + + @api.multi + def split_quantities(self): + for wiz in self: + if wiz.quantity>1 and wiz.qty_done < wiz.quantity: + pack2 = self.pack_id.copy({'qty_done': 0.0, 'product_qty': wiz.quantity - wiz.qty_done}) + wiz.quantity = wiz.qty_done + self.pack_id.write({'qty_done': wiz.qty_done, 'product_qty': wiz.quantity}) + return { + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'stock.picking', + 'type': 'ir.actions.act_window', + 'res_id': self.pack_id.picking_id.id, + } + + @api.one + def process(self): + pack = self.pack_id + pack.write({ + 'product_id': self.product_id.id, + 'product_uom_id': self.product_uom_id.id, + 'qty_done': self.qty_done, + 'package_id': self.package_id.id, + 'lot_id': self.lot_id.id, + 'location_id': self.location_id.id, + 'location_dest_id': self.location_dest_id.id, + 'result_package_id': self.result_package_id.id, + }) + return {} \ No newline at end of file diff --git a/addons/stock/wizard/stock_pack_details.xml b/addons/stock/wizard/stock_pack_details.xml new file mode 100644 index 0000000000000000000000000000000000000000..23b1ddbb55196d113e997d644187fcafe380baa2 --- /dev/null +++ b/addons/stock/wizard/stock_pack_details.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8"?> +<odoo> + <record id="view_pack_details" model="ir.ui.view"> + <field name="name">Enter pack details</field> + <field name="model">stock.pack.details</field> + <field name="arch" type="xml"> + <form string="Enter Pack Details"> + <group> + <group string="#Products"> + <field name="pack_id" invisible="1"/> + <field name="picking_source_location_id" invisible="True"/> + <field name="picking_destination_location_id" invisible="True"/> + + <field name="product_id" attrs="{'required': [('package_id', '=', False)]}"/> + <field name="lot_id" groups="stock.group_production_lot" domain="[('product_id','=?', product_id)]" context="{'product_id': product_id}"/> + </group> + <group string="From"> + <field name="location_id" domain="[('id', 'child_of', picking_source_location_id)]"/> + <field name="package_id" attrs="{'required': [('product_id', '=', False)]}" groups="stock.group_tracking_lot" context="{'location_id': location_id}"/> + </group> + </group> + <group> + <group string="Actions"> + <label for="qty_done" string="Done"/> + <div class="oe_inline"> + <field name="qty_done"/> + OF + <field name="quantity"/> + <field name="product_uom_id" options="{"no_open": True}" groups="product.group_uom"/> + <button name="split_quantities" string="Split" type="object" icon="STOCK_PREFERENCES" attrs="{'invisible': ['|', ('quantity', '=', 1), ('qty_done', '=', 0.0)]}"/> + </div> + </group> + <group string="To"> + <field name="location_dest_id" domain="[('id', 'child_of', picking_destination_location_id)]"/> + <field name="result_package_id" groups="stock.group_tracking_lot" context="{'location_id': location_dest_id}"/> + </group> + </group> + <footer> + <button name="process" string="_Apply" type="object" class="oe_highlight"/> + or + <button string="_Cancel" class="oe_link" special="cancel" /> + </footer> + </form> + </field> + </record> + + <record id="pack_details" model="ir.actions.act_window"> + <field name="name">Pack Details</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">stock.pack.details</field> + <field name="view_id" ref="view_pack_details"/> + <field name="view_type">form</field> + <field name="view_mode">form</field> + <field name="target">new</field> + <field name="context">{}</field> + </record> +</odoo> \ No newline at end of file diff --git a/addons/stock/wizard/stock_return_picking.py b/addons/stock/wizard/stock_return_picking.py index 5d87562b6708f697611bd19db999dfd76e4a75ba..4488358de79e572b5e4fae8e4134c21cf7093f35 100644 --- a/addons/stock/wizard/stock_return_picking.py +++ b/addons/stock/wizard/stock_return_picking.py @@ -107,6 +107,8 @@ class stock_return_picking(osv.osv_memory): 'picking_type_id': pick_type_id, 'state': 'draft', 'origin': pick.name, + 'location_id': pick.location_dest_id.id, + 'location_dest_id': pick.location_id.id, }, context=context) for data_get in data_obj.browse(cr, uid, data['product_return_moves'], context=context): @@ -139,8 +141,6 @@ class stock_return_picking(osv.osv_memory): if not returned_lines: raise UserError(_("Please specify at least one non-zero quantity.")) - pick_obj.action_confirm(cr, uid, [new_picking], context=context) - pick_obj.action_assign(cr, uid, [new_picking], context) return new_picking, pick_type_id def create_returns(self, cr, uid, ids, context=None): diff --git a/addons/stock/wizard/stock_transfer_details.py b/addons/stock/wizard/stock_transfer_details.py deleted file mode 100644 index 0b179914450b8b37a26fe9ec5062b3ba7dfcf87f..0000000000000000000000000000000000000000 --- a/addons/stock/wizard/stock_transfer_details.py +++ /dev/null @@ -1,164 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from openerp import models, fields, api -from openerp.tools.translate import _ -import openerp.addons.decimal_precision as dp -from datetime import datetime - -class stock_transfer_details(models.TransientModel): - _name = 'stock.transfer_details' - _description = 'Picking wizard' - - picking_id = fields.Many2one('stock.picking', 'Picking') - item_ids = fields.One2many('stock.transfer_details_items', 'transfer_id', 'Items', domain=[('product_id', '!=', False)]) - packop_ids = fields.One2many('stock.transfer_details_items', 'transfer_id', 'Packs', domain=[('product_id', '=', False)]) - picking_source_location_id = fields.Many2one('stock.location', string="Head source location", related='picking_id.location_id', store=False, readonly=True) - picking_destination_location_id = fields.Many2one('stock.location', string="Head destination location", related='picking_id.location_dest_id', store=False, readonly=True) - - def default_get(self, cr, uid, fields, context=None): - if context is None: context = {} - res = super(stock_transfer_details, self).default_get(cr, uid, fields, context=context) - picking_ids = context.get('active_ids', []) - active_model = context.get('active_model') - - if not picking_ids or len(picking_ids) != 1: - # Partial Picking Processing may only be done for one picking at a time - return res - assert active_model in ('stock.picking'), 'Bad context propagation' - picking_id, = picking_ids - picking = self.pool.get('stock.picking').browse(cr, uid, picking_id, context=context) - items = [] - packs = [] - if not picking.pack_operation_ids: - picking.do_prepare_partial() - for op in picking.pack_operation_ids: - item = { - 'packop_id': op.id, - 'product_id': op.product_id.id, - 'product_uom_id': op.product_uom_id.id, - 'quantity': op.product_qty, - 'package_id': op.package_id.id, - 'lot_id': op.lot_id.id, - 'sourceloc_id': op.location_id.id, - 'destinationloc_id': op.location_dest_id.id, - 'result_package_id': op.result_package_id.id, - 'date': op.date, - 'owner_id': op.owner_id.id, - } - if op.product_id: - items.append(item) - elif op.package_id: - packs.append(item) - res.update(item_ids=items) - res.update(packop_ids=packs) - return res - - @api.one - def do_detailed_transfer(self): - processed_ids = [] - # Create new and update existing pack operations - for lstits in [self.item_ids, self.packop_ids]: - for prod in lstits: - pack_datas = { - 'product_id': prod.product_id.id, - 'product_uom_id': prod.product_uom_id.id, - 'product_qty': prod.quantity, - 'package_id': prod.package_id.id, - 'lot_id': prod.lot_id.id, - 'location_id': prod.sourceloc_id.id, - 'location_dest_id': prod.destinationloc_id.id, - 'result_package_id': prod.result_package_id.id, - 'date': prod.date if prod.date else datetime.now(), - 'owner_id': prod.owner_id.id, - } - if prod.packop_id: - prod.packop_id.with_context(no_recompute=True).write(pack_datas) - processed_ids.append(prod.packop_id.id) - else: - pack_datas['picking_id'] = self.picking_id.id - packop_id = self.env['stock.pack.operation'].create(pack_datas) - processed_ids.append(packop_id.id) - # Delete the others - packops = self.env['stock.pack.operation'].search(['&', ('picking_id', '=', self.picking_id.id), '!', ('id', 'in', processed_ids)]) - packops.unlink() - - # Execute the transfer of the picking - self.picking_id.do_transfer() - - return True - - @api.multi - def wizard_view(self): - view = self.env.ref('stock.view_stock_enter_transfer_details') - - return { - 'name': _('Enter transfer details'), - 'type': 'ir.actions.act_window', - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'stock.transfer_details', - 'views': [(view.id, 'form')], - 'view_id': view.id, - 'target': 'new', - 'res_id': self.ids[0], - 'context': self.env.context, - } - - -class stock_transfer_details_items(models.TransientModel): - _name = 'stock.transfer_details_items' - _description = 'Picking wizard items' - - transfer_id = fields.Many2one('stock.transfer_details', 'Transfer') - packop_id = fields.Many2one('stock.pack.operation', 'Operation') - product_id = fields.Many2one('product.product', 'Product') - product_uom_id = fields.Many2one('product.uom', 'Product Unit of Measure') - quantity = fields.Float('Quantity', digits=dp.get_precision('Product Unit of Measure'), default = 1.0) - package_id = fields.Many2one('stock.quant.package', 'Source package', domain="['|', ('location_id', 'child_of', sourceloc_id), ('location_id','=',False)]") - lot_id = fields.Many2one('stock.production.lot', 'Lot/Serial Number') - sourceloc_id = fields.Many2one('stock.location', 'Source Location', required=True) - destinationloc_id = fields.Many2one('stock.location', 'Destination Location', required=True) - result_package_id = fields.Many2one('stock.quant.package', 'Destination package', domain="['|', ('location_id', 'child_of', destinationloc_id), ('location_id','=',False)]") - date = fields.Datetime('Date') - owner_id = fields.Many2one('res.partner', 'Owner', help="Owner of the quants") - - - - @api.multi - def split_quantities(self): - for det in self: - if det.quantity>1: - det.quantity = (det.quantity-1) - new_id = det.copy(context=self.env.context) - new_id.quantity = 1 - new_id.packop_id = False - if self and self[0]: - return self[0].transfer_id.wizard_view() - - @api.multi - def put_in_pack(self): - newpack = None - for packop in self: - if not packop.result_package_id: - if not newpack: - newpack = self.pool['stock.quant.package'].create(self._cr, self._uid, {'location_id': packop.destinationloc_id.id if packop.destinationloc_id else False}, self._context) - packop.result_package_id = newpack - if self and self[0]: - return self[0].transfer_id.wizard_view() - - @api.multi - def product_id_change(self, product, uom=False): - result = {} - if product: - prod = self.env['product.product'].browse(product) - result['product_uom_id'] = prod.uom_id and prod.uom_id.id - return {'value': result, 'domain': {}, 'warning':{} } - - @api.multi - def source_package_change(self, sourcepackage): - result = {} - if sourcepackage: - pack = self.env['stock.quant.package'].browse(sourcepackage) - result['sourceloc_id'] = pack.location_id and pack.location_id.id - return {'value': result, 'domain': {}, 'warning':{} } diff --git a/addons/stock/wizard/stock_transfer_details.xml b/addons/stock/wizard/stock_transfer_details.xml deleted file mode 100644 index d00c52e00b032de2abd3749d842dae1bc27039e7..0000000000000000000000000000000000000000 --- a/addons/stock/wizard/stock_transfer_details.xml +++ /dev/null @@ -1,61 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<openerp> - <data> - <record id="view_stock_enter_transfer_details" model="ir.ui.view"> - <field name="name">Enter transfer details</field> - <field name="model">stock.transfer_details</field> - <field name="arch" type="xml"> - <form string="Transfer details" version="7"> - <field name="picking_source_location_id" invisible="True"/> - <field name="picking_destination_location_id" invisible="True"/> - <div class="oe_grey" groups="stock.group_tracking_lot"> - Setting a product and a source package means that the product will be taken - out of the package. - </div> - <group> - <field name="item_ids" - context="{'default_sourceloc_id':picking_source_location_id, - 'default_destinationloc_id':picking_destination_location_id}" nolabel="1"> - <tree string="Inventory Details" editable="bottom" > - <field name="package_id" groups="stock.group_tracking_lot"/> - <field name="product_id" required="True" context="{'uom':product_uom_id}" on_change="product_id_change(product_id,product_uom_id,context)"/> - <field name="quantity"/> - <button name="split_quantities" string="Split" type="object" icon="STOCK_PREFERENCES" attrs="{'invisible': [('quantity', '=', 1)]}"/> - <field name="product_uom_id" options="{"no_open": True}" groups="product.group_uom"/> - <field name="sourceloc_id" domain="[('id', 'child_of', parent.picking_source_location_id)]"/> - <field name="destinationloc_id" domain="[('id', 'child_of', parent.picking_destination_location_id)]"/> - <field name="result_package_id" groups="stock.group_tracking_lot" context="{'location_id': destinationloc_id}"/> - <button name="put_in_pack" string="Pack" type="object" icon="terp-product" attrs="{'invisible': [('result_package_id', '!=', False)]}" groups="stock.group_tracking_lot"/> - <field name="lot_id" groups="stock.group_production_lot" domain="[('product_id','=?', product_id)]" context="{'product_id': product_id}"/> - </tree> - </field> - </group> - <group string="Packages To Move" groups="stock.group_tracking_lot"> - <div class="oe_grey"> - The source package will be moved entirely. If you specify a destination package, the source package will be put in the destination package. - </div> - </group> - <group groups="stock.group_tracking_lot"> - <field name="packop_ids" - context="{'default_sourceloc_id':picking_source_location_id, - 'default_destinationloc_id':picking_destination_location_id}" nolabel="1"> - <tree editable="bottom"> - <field name="package_id" required="True" on_change="source_package_change(package_id)"/> - <field name="sourceloc_id" domain="[('id', 'child_of', parent.picking_source_location_id)]"/> - <field name="destinationloc_id" domain="[('id', 'child_of', parent.picking_destination_location_id)]"/> - <field name="result_package_id"/> - <button name="put_in_pack" string="Pack" type="object" icon="terp-product" attrs="{'invisible': [('result_package_id', '!=', False)]}"/> - </tree> - </field> - </group> - <footer> - <button name="do_detailed_transfer" string="_Apply" type="object" class="oe_highlight"/> - or - <button string="_Cancel" class="oe_link" special="cancel" /> - </footer> - </form> - </field> - </record> - - </data> -</openerp> diff --git a/addons/stock_account/stock_account_view.xml b/addons/stock_account/stock_account_view.xml index ffcf8546d4fe15adb91e02a7b087e80982f61cc1..2924dffb6e08566f8318fecd8e70901383c9cc2c 100644 --- a/addons/stock_account/stock_account_view.xml +++ b/addons/stock_account/stock_account_view.xml @@ -58,7 +58,7 @@ <button name="%(action_stock_invoice_onshipping)d" string="Create Invoice" attrs="{'invisible': ['|',('state','<>','done'),('invoice_state','<>','2binvoiced')]}" type="action" class="oe_highlight" groups="base.group_user"/> </xpath> <field name="move_lines" position="attributes"> - <attribute name="context">{'default_invoice_state': invoice_state, 'address_in_id': partner_id, 'form_view_ref':'stock.view_move_picking_form', 'tree_view_ref':'stock.view_move_picking_tree', 'default_picking_type_id': picking_type_id}</attribute> + <attribute name="context">{'default_invoice_state': invoice_state, 'address_in_id': partner_id, 'form_view_ref':'stock.view_move_picking_form', 'tree_view_ref':'stock.view_move_picking_tree', 'default_picking_type_id': picking_type_id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id}</attribute> </field> <xpath expr="//field[@name='move_type']" position="after"> <field name="invoice_state" groups="account.group_account_invoice"/> diff --git a/addons/stock_invoice_directly/__init__.py b/addons/stock_invoice_directly/__init__.py index b051a2ad957c2a9db3d6a7694c0d2583c1f80895..815313c1bc559f3465e261cff8e24461c086d3a6 100644 --- a/addons/stock_invoice_directly/__init__.py +++ b/addons/stock_invoice_directly/__init__.py @@ -2,3 +2,4 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import stock_invoice_directly +import wizards \ No newline at end of file diff --git a/addons/stock_invoice_directly/stock_invoice_directly.py b/addons/stock_invoice_directly/stock_invoice_directly.py index 541ca80164b8590f7e287dbde480d29474e96439..a07cba9e1af567b853d8938879ffd1036f4b39ed 100644 --- a/addons/stock_invoice_directly/stock_invoice_directly.py +++ b/addons/stock_invoice_directly/stock_invoice_directly.py @@ -9,15 +9,16 @@ from openerp.tools.translate import _ class stock_picking(osv.osv): _inherit = 'stock.picking' - @api.cr_uid_ids_context - def do_transfer(self, cr, uid, picking_ids, context=None): + def do_new_transfer(self, cr, uid, ids, context=None): """Launch Create invoice wizard if invoice state is To be Invoiced, after processing the picking. """ if context is None: context = {} - res = super(stock_picking, self).do_transfer(cr, uid, picking_ids, context=context) - pick_ids = [p.id for p in self.browse(cr, uid, picking_ids, context) if p.invoice_state == '2binvoiced'] + res = super(stock_picking, self).do_new_transfer(cr, uid, ids, context=context) + if res: #If it is already returning a wizard + return res + pick_ids = [p.id for p in self.browse(cr, uid, ids, context) if p.invoice_state == '2binvoiced'] if pick_ids: context = dict(context, active_model='stock.picking', active_ids=pick_ids) return { @@ -29,4 +30,4 @@ class stock_picking(osv.osv): 'target': 'new', 'context': context } - return res + return res \ No newline at end of file diff --git a/addons/stock_invoice_directly/wizards.py b/addons/stock_invoice_directly/wizards.py new file mode 100644 index 0000000000000000000000000000000000000000..f36edd30530bb5bd53e588f74f1a80c2ab8ec945 --- /dev/null +++ b/addons/stock_invoice_directly/wizards.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from openerp import models, fields, api +from openerp.tools.translate import _ + +class stock_immediate_transfer(models.TransientModel): + _inherit = 'stock.immediate.transfer' + + @api.multi + def process(self): + super(stock_immediate_transfer, self).process() + pick = self.pick_id.id + context = dict(self.env.context, active_model='stock.picking', active_id=pick, active_ids=[pick]) + print context + return { + 'name': _('Create Invoice'), + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'stock.invoice.onshipping', + 'type': 'ir.actions.act_window', + 'target': 'new', + 'context': context + } + + +class stock_backorder_confirmation(models.TransientModel): + _inherit = 'stock.backorder.confirmation' + + @api.multi + def process(self): + res = super(stock_backorder_confirmation, self).process() + context = dict(self.env.context, active_model='stock.picking', active_id=self.pick_id.id, active_ids=[self.pick_id.id]) + return { + 'name': _('Create Invoice'), + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'stock.invoice.onshipping', + 'type': 'ir.actions.act_window', + 'target': 'new', + 'context': context + } + + @api.multi + def process_cancel_backorder(self): + res = super(stock_backorder_confirmation, self).process_cancel_backorder() + context = dict(self.env.context, active_model='stock.picking', active_id=self.pick_id.id, active_ids=[self.pick_id.id]) + return { + 'name': _('Create Invoice'), + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'stock.invoice.onshipping', + 'type': 'ir.actions.act_window', + 'target': 'new', + 'context': context + } \ No newline at end of file diff --git a/addons/stock_picking_wave/stock_picking_wave_demo.xml b/addons/stock_picking_wave/stock_picking_wave_demo.xml index 72766f368b9bcb4b7e69f03650b67a724e048e78..e752c6f741206e5f16f774b6f6fde88387848060 100644 --- a/addons/stock_picking_wave/stock_picking_wave_demo.xml +++ b/addons/stock_picking_wave/stock_picking_wave_demo.xml @@ -24,14 +24,18 @@ <field name="priority">2</field> <field name="picking_type_id" ref="stock.picking_type_internal"/> <field name="wave_id" ref="stock_picking_wave_freeze_1"/> + <field name="location_id" ref="stock.stock_location_stock"/> + <field name="location_dest_id" ref="stock.stock_location_output"/> </record> <record id="Picking_B" model="stock.picking"> <field name="name">Picking #2</field> <field name="move_type">one</field> <field name="state">assigned</field> <field name="priority">1</field> - <field name="picking_type_id" ref="stock.picking_type_internal"/> - <field name="wave_id" ref="stock_picking_wave_dry_1"/> + <field name="picking_type_id" ref="stock.picking_type_internal"/> + <field name="wave_id" ref="stock_picking_wave_dry_1"/> + <field name="location_id" ref="stock.stock_location_stock"/> + <field name="location_dest_id" ref="stock.stock_location_output"/> </record> <record id="Picking_C" model="stock.picking"> <field name="name">Picking #3</field> @@ -39,7 +43,9 @@ <field name="state">assigned</field> <field name="priority">1</field> <field name="picking_type_id" ref="stock.picking_type_internal"/> - <field name="wave_id" ref="stock_picking_wave_freeze_1"/> + <field name="wave_id" ref="stock_picking_wave_freeze_1"/> + <field name="location_id" ref="stock.stock_location_stock"/> + <field name="location_dest_id" ref="stock.stock_location_output"/> </record> <!-- add Product -->