diff --git a/shopfloor/actions/data.py b/shopfloor/actions/data.py index 09ead326eb..b5f3e30622 100644 --- a/shopfloor/actions/data.py +++ b/shopfloor/actions/data.py @@ -159,6 +159,7 @@ def _package_level_parser(self): return [ "id", "is_done", + ("picking_id:picking", self._simple_record_parser()), ("package_id:package_src", self._package_parser), ("location_dest_id:location_dest", self._location_parser), ( diff --git a/shopfloor/services/schema.py b/shopfloor/services/schema.py index c7d78c39e3..ac610977ad 100644 --- a/shopfloor/services/schema.py +++ b/shopfloor/services/schema.py @@ -157,6 +157,7 @@ def package_level(self): return { "id": {"required": True, "type": "integer"}, "is_done": {"type": "boolean", "nullable": False, "required": True}, + "picking": self._schema_dict_of(self._simple_record()), "package_src": self._schema_dict_of(self.package()), "location_src": self._schema_dict_of(self.location()), "location_dest": self._schema_dict_of(self.location()), diff --git a/shopfloor/tests/test_actions_data.py b/shopfloor/tests/test_actions_data.py index 4be811ac45..fbd1942025 100644 --- a/shopfloor/tests/test_actions_data.py +++ b/shopfloor/tests/test_actions_data.py @@ -149,6 +149,7 @@ def test_data_package_level(self): expected = { "id": package_level.id, "is_done": False, + "picking": self.picking.jsonify(["id", "name"])[0], "package_src": self._expected_package(package_level.package_id), "location_dest": self._expected_location(package_level.location_dest_id), "location_src": self._expected_location( diff --git a/shopfloor_mobile/static/wms/index.html b/shopfloor_mobile/static/wms/index.html index 3b69e035ee..c480de64d4 100644 --- a/shopfloor_mobile/static/wms/index.html +++ b/shopfloor_mobile/static/wms/index.html @@ -36,7 +36,6 @@ - @@ -72,6 +71,9 @@ + + + diff --git a/shopfloor_mobile/static/wms/src/components/batch_picking_line_detail.js b/shopfloor_mobile/static/wms/src/components/batch_picking_line_detail.js index a4bb43ee82..11e32ded6e 100644 --- a/shopfloor_mobile/static/wms/src/components/batch_picking_line_detail.js +++ b/shopfloor_mobile/static/wms/src/components/batch_picking_line_detail.js @@ -1,3 +1,4 @@ +// TODO: rename as we used this not only for batch picking export var batch_picking_line = Vue.component("batch-picking-line-detail", { props: { line: Object, @@ -9,6 +10,10 @@ export var batch_picking_line = Vue.component("batch-picking-line-detail", { type: Boolean, default: false, }, + defaultDestinationKey: { + type: String, + default: "package_dest", + }, }, data() { return { @@ -16,19 +21,8 @@ export var batch_picking_line = Vue.component("batch-picking-line-detail", { }; }, computed: { - has_destination_pack() { - return _.result(this.line, "package_dest.id"); - }, - }, - methods: { - full_detail_fields() { - return [ - {path: "batch.name", label: "Batch"}, - {path: "picking.name", label: "Picking"}, - {path: "picking.origin", label: "Origin"}, - {path: "picking.partner.name", label: "Customer"}, - {path: "location_dest.name", label: "Destination"}, - ]; + destination() { + return _.result(this.line, this.$props.defaultDestinationKey); }, }, template: ` @@ -48,11 +42,11 @@ export var batch_picking_line = Vue.component("batch-picking-line-detail", { /> @@ -60,36 +54,22 @@ export var batch_picking_line = Vue.component("batch-picking-line-detail", { - - `, }); +// TODO: use `misc.line-actions-popup` instead export var batch_picking_line_actions = Vue.component("batch-picking-line-actions", { props: ["line"], data() { @@ -149,32 +129,3 @@ export var batch_picking_line_actions = Vue.component("batch-picking-line-action `, }); - -export var batch_picking_line_stock_out = Vue.component( - "batch-picking-line-stock-out", - { - props: ["line"], - methods: { - handle_action(action) { - this.$emit("action", action); - }, - }, - template: ` -
- -
- - - Confirm stock = 0 - - - - - back - - -
-
-`, - } -); diff --git a/shopfloor_mobile/static/wms/src/components/misc.js b/shopfloor_mobile/static/wms/src/components/misc.js index 0612275d9c..f9d43a33dc 100644 --- a/shopfloor_mobile/static/wms/src/components/misc.js +++ b/shopfloor_mobile/static/wms/src/components/misc.js @@ -88,10 +88,15 @@ Vue.component("state-display-info", { props: { info: Object, }, + computed: { + title: function() { + return _.isFunction(this.info.title) ? this.info.title() : this.info.title; + }, + }, template: ` -
+
-
{{ info.title }}
+
{{ title }}
`, @@ -266,3 +271,100 @@ Vue.component("btn-back", { `, }); + +Vue.component("btn-reset-config", { + props: { + redirect: { + type: Object, + default: function() { + return {name: "home"}; + }, + }, + }, + methods: { + reset_data: function() { + this.$root._clearConfig(); + this.$root._loadMenu(); + this.$root.$router.push(this.$props.redirect); + }, + }, + template: `Reload config and menu`, +}); + +Vue.component("line-actions-popup", { + props: { + line: { + type: Object, + }, + actions: { + type: Array, + default: function() { + return []; + }, + }, + }, + data() { + return { + dialog: false, + }; + }, + methods: { + handle_action(action) { + this.dialog = false; + this.$emit("action", action); + }, + }, + template: ` +
+ + + +
+ + + {{ action.name }} + + + + + Back + + +
+
+
+
+`, +}); + +Vue.component("line-stock-out", { + methods: { + handle_action(action) { + this.$emit(action); + }, + }, + template: ` +
+
+ + + Confirm stock = 0 + + + + + + + +
+
+`, +}); diff --git a/shopfloor_mobile/static/wms/src/components/screen.js b/shopfloor_mobile/static/wms/src/components/screen.js index f6e54eb0e3..092b7e678f 100644 --- a/shopfloor_mobile/static/wms/src/components/screen.js +++ b/shopfloor_mobile/static/wms/src/components/screen.js @@ -49,7 +49,6 @@ Vue.component("Screen", { }, show_profile_not_ready() { return ( - !this.$root.demo_mode && !this.$root.has_profile && this.$route.name != "profile" && this.$route.name != "settings" diff --git a/shopfloor_mobile/static/wms/src/components/userInformation/userInformation.js b/shopfloor_mobile/static/wms/src/components/userInformation/userInformation.js index 4630626390..b37e943f9d 100644 --- a/shopfloor_mobile/static/wms/src/components/userInformation/userInformation.js +++ b/shopfloor_mobile/static/wms/src/components/userInformation/userInformation.js @@ -3,12 +3,12 @@ Vue.component("user-information", { props: ["message"], template: ` - {{ message.body }} + {{ _.result(message, "body") }} `, computed: { alert_type: function() { - return this.message.message_type || "info"; + return _.result(this.message, "message_type", "info"); }, }, }); diff --git a/shopfloor_mobile/static/wms/src/demo/demo.core.js b/shopfloor_mobile/static/wms/src/demo/demo.core.js index 1eb385a60b..97566e41f3 100644 --- a/shopfloor_mobile/static/wms/src/demo/demo.core.js +++ b/shopfloor_mobile/static/wms/src/demo/demo.core.js @@ -3,7 +3,10 @@ import {process_registry} from "../services/process_registry.js"; export class DemoTools { constructor(demo_cases) { this.demo_cases = demo_cases.length ? demo_cases : {}; - // used to search w/ scan anything + // Used to collect fake menu entries + this.app_menus_by_id = {}; + this.app_menus = []; + // Used to search w/ scan anything this.indexed = {}; this.locations = [ this.makeLocation(), @@ -398,6 +401,19 @@ export class DemoTools { return _.head(this.makePickingLines({}, {picking_auto: true, lines_count: 1})); } + makePackageLevel(defaults = {}, options = {}) { + _.defaults(defaults, { + is_done: false, + picking: this.makePicking(), + package: this.makePack(), + product: this.makeProduct(), + location: this.makeLocation(), + // TODO: shall we add move lines to be able to show product details? + }); + const rec = this.makeSimpleRecord(defaults, options); + return rec; + } + partnerNames() { return [ "Edith Sanchez", @@ -438,19 +454,55 @@ export class DemoTools { ]; } - makeAppMenu() { - let menu; - _.forEach(process_registry.all(), function(component, key) { - menu.push({ - id: key, - process: { - id: key, - code: key, - }, - }); + makeAppConfig() { + return { + profiles: this.makeProfiles(), + }; + } + makeProfiles() { + const profiles = [ + {id: 1, name: "SCH Transport", warehouse: {id: 1, name: "Schlieren"}}, + {id: 2, name: "SCH Pick", warehouse: {id: 1, name: "Schlieren"}}, + ]; + return profiles; + } + getAppMenus() { + return this.app_menus; + } + /** + * + * @param {*} new_item + * + * id: 123, + * name: "Menu 1", + * scenario: "scenario_usage_key", + * picking_types: [{"id": 27, "name": "Random type"}] + */ + addAppMenu(new_item) { + // Generate a unique ID to be used in demo data + let menu_id = this.getRandomInt(); + while (menu_id in this.app_menus_by_id) { + menu_id = this.getRandomInt(); + } + new_item.id = menu_id; + this.app_menus_by_id[menu_id] = new_item; + + // Find insert index to keep items w/ same scenario grouped + let index = 0; + this.app_menus.every(function(item, i) { + if (item.scenario == new_item.scenario) { + index = i; + return false; + } }); - return menu; + if (_.isUndefined(new_item.picking_types)) { + new_item.picking_types = [{id: 99999999, name: "Fake type"}]; + } + this.app_menus.splice(index, 0, new_item); + // Return the unique id + return menu_id; } + /* Detect cyclic references between objects. diff --git a/shopfloor_mobile/static/wms/src/demo/demo.delivery.js b/shopfloor_mobile/static/wms/src/demo/demo.delivery.js index 12d0ae9a6d..77f1360479 100644 --- a/shopfloor_mobile/static/wms/src/demo/demo.delivery.js +++ b/shopfloor_mobile/static/wms/src/demo/demo.delivery.js @@ -1,9 +1,5 @@ import {demotools} from "./demo.core.js"; -const deliver_data = { - picking: demotools.makePicking({}, {lines_count: 5, line_random_pack: true}), -}; - let pickings = []; const count = 8; diff --git a/shopfloor_mobile/static/wms/src/demo/demo.location_content_transfer.js b/shopfloor_mobile/static/wms/src/demo/demo.location_content_transfer.js new file mode 100644 index 0000000000..606d7a91bd --- /dev/null +++ b/shopfloor_mobile/static/wms/src/demo/demo.location_content_transfer.js @@ -0,0 +1,170 @@ +import {demotools} from "./demo.core.js"; + +const DEMO_CASE = { + by_menu_id: {}, +}; + +// Case for recover existing work on single move line +const recover_single_move_line_menu_id = demotools.addAppMenu({ + name: "Loc.Cont.Transfer: recover single line", + scenario: "location_content_transfer", + picking_types: [{id: 27, name: "Random type"}], +}); +const single_line_case_move_line = demotools.makeSingleLineOperation(); +const RECOVER_SINGLE_MOVE_LINE_CASE = { + start_or_recover: { + next_state: "start_single", + message: { + message_type: "info", + body: "Recovered line from previous session.", + }, + data: { + start_single: { + recovered: true, + move_line: _.cloneDeep(single_line_case_move_line), + }, + }, + }, + scan_line: { + next_state: "scan_destination", + message: { + message_type: "info", + body: "Recovered line from previous session.", + }, + data: { + scan_destination: { + move_line: _.cloneDeep(single_line_case_move_line), + }, + }, + }, + set_destination_line: { + next_state: "start_single", + message: { + message_type: "info", + body: "Destination set on the line", + }, + data: { + start_single: { + move_line: _.cloneDeep(single_line_case_move_line), + }, + }, + }, +}; +DEMO_CASE.by_menu_id[recover_single_move_line_menu_id] = RECOVER_SINGLE_MOVE_LINE_CASE; + +// Case for recover existing work on single package level +const recover_single_package_level_menu_id = demotools.addAppMenu({ + name: "Loc.Cont.Transfer: recover single package level", + scenario: "location_content_transfer", + picking_types: [{id: 27, name: "Random type"}], +}); + +const single_line_package_level = demotools.makePackageLevel(); +const RECOVER_SINGLE_PACKAGE_LEVEL_CASE = { + start_or_recover: { + next_state: "start_single", + message: { + message_type: "info", + body: "Recovered package from previous session.", + }, + data: { + start_single: { + recovered: true, + package_level: _.cloneDeep(single_line_package_level), + }, + }, + }, + scan_line: { + next_state: "scan_destination", + message: { + message_type: "info", + body: "Recovered package from previous session.", + }, + data: { + scan_destination: { + package_level: _.cloneDeep(single_line_package_level), + }, + }, + }, + set_destination_line: { + next_state: "start_single", + message: { + message_type: "info", + body: "Destination set on the line", + }, + data: { + start_single: { + package_level: _.cloneDeep(single_line_package_level), + }, + }, + }, +}; +DEMO_CASE.by_menu_id[ + recover_single_package_level_menu_id +] = RECOVER_SINGLE_PACKAGE_LEVEL_CASE; + +// Case for recover existing work on several lines from the same location +const recover_move_lines_same_location_menu_id = demotools.addAppMenu({ + name: "Loc.Cont.Transfer: recover lines same loc.", + scenario: "location_content_transfer", + picking_types: [{id: 27, name: "Random type"}], +}); +const same_location = demotools.makeLocation(); +const scan_destination_all_move_lines = demotools.makePickingLines( + {}, + {lines_count: demotools.getRandomInt(5), line_random_pack: true} +); +for (let i = 0; i < scan_destination_all_move_lines.length; i++) { + // set same location (clone to avoid circular dep on storage) + scan_destination_all_move_lines[i].location_src = _.cloneDeep(same_location); +} +const RECOVER_MOVE_LINES_SAME_LOCATION_CASE = { + start_or_recover: { + next_state: "scan_destination_all", + message: { + message_type: "info", + body: "Recovered lines from previous session.", + }, + data: { + scan_destination_all: { + recovered: true, + move_lines: _.cloneDeep(scan_destination_all_move_lines), + }, + }, + }, + scan_line: { + next_state: "scan_destination", + message: { + message_type: "info", + body: "Recovered line from previous session.", + }, + data: { + scan_destination: { + move_lines: _.cloneDeep(scan_destination_all_move_lines), + }, + }, + }, + set_destination_all: { + next_state: "start", + message: { + message_type: "info", + body: "Destination set on all lines", + }, + data: { + start: {}, + }, + }, + go_to_single: { + next_state: "start_single", + data: { + start_single: { + move_line: _.cloneDeep(scan_destination_all_move_lines[0]), + }, + }, + }, +}; +DEMO_CASE.by_menu_id[ + recover_move_lines_same_location_menu_id +] = RECOVER_MOVE_LINES_SAME_LOCATION_CASE; + +demotools.add_case("location_content_transfer", DEMO_CASE); diff --git a/shopfloor_mobile/static/wms/src/homepage.js b/shopfloor_mobile/static/wms/src/homepage.js index f23dcc623e..d8e598ac7f 100644 --- a/shopfloor_mobile/static/wms/src/homepage.js +++ b/shopfloor_mobile/static/wms/src/homepage.js @@ -4,18 +4,32 @@ export var HomePage = Vue.component("home-page", { return this.$root.appmenu.menus; }, }, + methods: { + nuke_data_and_reload: function() { + // Force reload config (loads profiles) + this.$root.loadConfig(true); + // Enforce 1st profile + const selected_profile = _.head(this.$root.appconfig.profiles); + this.$root.trigger("profile:selected", selected_profile, true); + // Reload page + location.reload(); + }, + }, props: ["routes"], template: ` - + DEMO MODE ON +
+ + + Force reload data and refresh + + +
`, }); diff --git a/shopfloor_mobile/static/wms/src/i18n.translation.js b/shopfloor_mobile/static/wms/src/i18n.translation.js index 087e89e932..2d5a688844 100644 --- a/shopfloor_mobile/static/wms/src/i18n.translation.js +++ b/shopfloor_mobile/static/wms/src/i18n.translation.js @@ -2,6 +2,9 @@ export const messages = { // {{ $t('message.hello') }} "en-US": { screen: { + home: { + title: "Barcode scanner", + }, scan_anything: { title: "Scan {what}", }, @@ -41,6 +44,9 @@ export const messages = { }, "fr-FR": { screen: { + home: { + title: "Barcode scanner", + }, scan_anything: { name: "Scanner", title: "Scanner {what}", @@ -81,6 +87,9 @@ export const messages = { }, "de-DE": { screen: { + home: { + title: "Barcode scanner", + }, scan_anything: { name: "Gescannt", title: "Gescannt {what}", diff --git a/shopfloor_mobile/static/wms/src/main.js b/shopfloor_mobile/static/wms/src/main.js index 38516cf2b7..8f44cc640d 100644 --- a/shopfloor_mobile/static/wms/src/main.js +++ b/shopfloor_mobile/static/wms/src/main.js @@ -1,7 +1,6 @@ import {router} from "./router.js"; import {i18n} from "./i18n.js"; import {GlobalMixin} from "./mixin.js"; -import {Config} from "./services/config.js"; import {process_registry} from "./services/process_registry.js"; import {color_registry} from "./services/color_registry.js"; import {Odoo, OdooMocked} from "./services/odoo.js"; @@ -81,9 +80,6 @@ const app = new Vue({ this.$storage.set("profile", v); }, }, - // TODO: demo mode is half working now because - // if we don't load real menu/profiles 1st - // we don't have any menu nor profile to play with. profiles: function() { return this.appconfig ? this.appconfig.profiles || [] : []; }, @@ -126,8 +122,8 @@ const app = new Vue({ } return new OdooClass(params); }, - loadConfig: function() { - if (this.appconfig) { + loadConfig: function(force) { + if (this.appconfig && !force) { return this.appconfig; } // TODO: we can do this via watcher @@ -145,13 +141,15 @@ const app = new Vue({ const self = this; self.loading = true; const odoo = self.getOdoo({usage: "app"}); - const config = new Config(odoo); - return config.load().then(function() { - if (config.authenticated) { - self.appconfig = config.data; - self.authenticated = config.authenticated; + return odoo.call("user_config").then(function(result) { + if (!_.isUndefined(result.data)) { + self.appconfig = result.data; + self.authenticated = true; self.$storage.set("appconfig", self.appconfig); self.loading = false; + } else { + // TODO: any better thing to do here? + console.log(result); } }); }, @@ -178,6 +176,10 @@ const app = new Vue({ self.loading = false; }); }, + _clearConfig: function() { + this.$storage.remove("appconfig"); + return this._loadConfig(); + }, logout: function() { this.apikey = ""; this.authenticated = false; diff --git a/shopfloor_mobile/static/wms/src/scenario/cluster_picking.js b/shopfloor_mobile/static/wms/src/scenario/cluster_picking.js index 870cf3e79f..1ad0cfebf0 100644 --- a/shopfloor_mobile/static/wms/src/scenario/cluster_picking.js +++ b/shopfloor_mobile/static/wms/src/scenario/cluster_picking.js @@ -25,7 +25,7 @@ export var ClusterPicking = Vue.component("cluster-picking", { v-on:cancel="state.on_cancel" /> -
diff --git a/shopfloor_mobile/static/wms/src/scenario/location_content_transfer.js b/shopfloor_mobile/static/wms/src/scenario/location_content_transfer.js new file mode 100644 index 0000000000..905df4954a --- /dev/null +++ b/shopfloor_mobile/static/wms/src/scenario/location_content_transfer.js @@ -0,0 +1,323 @@ +import {ScenarioBaseMixin} from "./mixins.js"; +import {process_registry} from "../services/process_registry.js"; +import {demotools} from "../demo/demo.core.js"; // FIXME: dev only + +export var LocationContentTransfer = Vue.component("location-content-transfer", { + mixins: [ScenarioBaseMixin], + template: ` + + + +
+ + +
+ + + + + + + + + + + + + +
+
+ +
+ + + Split by line + + +
+
+ `, + // FIXME: just for dev + // mounted: function() { + // // TEST force state and data + // const state = "select_package"; + // const dcase = demotools.get_case(this.usage); + // const data = dcase["select_package"].data[state]; + // this.state_set_data(data, state); + // this._state_load(state); + // }, + methods: { + screen_title: function() { + if (!this.has_picking()) return this.menu_item().name; + return this.current_picking().name; + }, + // TODO: if we have this working we can remove the picking detail? + current_doc: function() { + const picking = this.current_picking(); + return { + record: picking, + identifier: picking.name, + }; + }, + // TODO: ask Joél if this is needed + current_picking: function() { + const data = this.state_get_data("start"); + if (_.isEmpty(data) || _.isEmpty(data.move_line.picking)) { + return {}; + } + return data.move_line.picking; + }, + has_picking: function() { + return !_.isEmpty(this.current_picking()); + }, + /** + * As we can get `move_line` or `package_level` for single items + * and `move_lines` or `package_levels` for multiple items, + * this function tries to make such data homogenous. + * We'll work alway with multiple records and loop on them. + */ + wrapped_context: function() { + const data = this.state.data; + let res = { + has_records: true, + records: data.move_lines, + _multi: true, + _type: "move_line", + }; + if (_.isEmpty(res.records) && data.move_line) { + res.records = [data.move_line]; + res._multi = false; + } + if (_.isEmpty(res.records) && data.package_levels) { + res.records = data.package_levels; + res._type = "pkg_level"; + } + if (_.isEmpty(res.records) && data.package_level) { + res.records = [data.package_level]; + res._type = "pkg_level"; + } + res.has_records = res.records.length > 0; + return res; + }, + move_line_detail_list_options: function(move_line) { + return this.utils.misc.move_line_product_detail_options(move_line, { + loud_labels: true, + fields_blacklist: ["product.qty_available"], + fields: [{path: "location_src.name", label: "From"}], + }); + }, + // Common actions + on_line_action: function(action) { + this["on_" + action.event_name].call(this); + }, + on_action_postpone: function() { + let endpoint, endpoint_data; + const data = this.state.data; + if (data.package_level) { + endpoint = "postpone_package"; + endpoint_data = { + package_level_id: data.package_level.id, + location_id: data.package_level.location_src.id, + }; + } else { + endpoint = "postpone_line"; + endpoint_data = { + move_line_id: data.move_line.id, + location_id: data.move_line.location_src.id, + }; + } + this.wait_call(this.odoo.call(endpoint, endpoint_data)); + }, + on_action_stock_out: function() { + this.state_set_data(this.state.data, "stock_issue"); + this.state_to("stock_issue"); + }, + }, + data: function() { + const self = this; + return { + usage: "location_content_transfer", + initial_state_key: "scan_location", + states: { + init: { + enter: () => { + this.state_reset_data(); + this.wait_call(this.odoo.call("start_or_recover")); + }, + }, + scan_location: { + display_info: { + title: "Start by scanning a location", + scan_placeholder: "Scan location", + }, + on_scan: scanned => { + this.wait_call( + this.odoo.call("scan_location", {barcode: scanned.text}) + ); + }, + }, + scan_destination_all: { + display_info: { + title: "Scan destination location for all items", + scan_placeholder: "Scan location", + }, + on_scan: scanned => { + const data = this.state.data; + this.wait_call( + this.odoo.call("set_destination_all", { + location_id: data.location.id, + barcode: scanned.text, + confirmation: data.confirmation_required, + }) + ); + }, + on_split_by_line: () => { + const location = this.state.data.location; + this.wait_call( + this.odoo.call("go_to_single", {location_id: location.id}) + ); + }, + }, + start_single: { + display_info: { + scan_placeholder: "Scan pack / product / lot", + }, + on_scan: scanned => { + let endpoint, endpoint_data; + const data = this.state.data; + if (data.package_level) { + endpoint = "scan_package"; + endpoint_data = { + package_level_id: data.package_level.id, + location_id: data.package_level.location_src.id, + barcode: scanned.text, + }; + } else { + endpoint = "scan_line"; + endpoint_data = { + move_line_id: data.move_line.id, + location_id: data.move_line.location_src.id, + barcode: scanned.text, + }; + } + this.wait_call(this.odoo.call(endpoint, endpoint_data)); + }, + }, + scan_destination: { + enter: () => { + this.reset_notification(); + }, + display_info: { + title: "Set a qty and scan destination location", + scan_placeholder: "Scan location", + }, + events: { + qty_edit: "on_qty_update", + }, + on_qty_update: qty => { + this.state.data.destination_qty = qty; + }, + on_scan: scanned => { + let endpoint, endpoint_data; + const data = this.state.data; + if (data.package_level) { + endpoint = "set_destination_package"; + endpoint_data = { + package_level_id: data.package_level.id, + location_id: data.package_level.location_src.id, + barcode: scanned.text, + confirmation: data.confirmation_required, + }; + } else { + endpoint = "set_destination_line"; + endpoint_data = { + move_line_id: data.move_line.id, + location_id: data.move_line.location_src.id, + barcode: scanned.text, + confirmation: data.confirmation_required, + quantity: + this.state.data.destination_qty || + data.move_line.quantity, + }; + } + this.wait_call(this.odoo.call(endpoint, endpoint_data)); + }, + on_split_by_line: () => { + const location = this.state.data.move_lines[0].location_src; + this.wait_call( + this.odoo.call("go_to_single", {location_id: location.id}) + ); + }, + }, + stock_issue: { + enter: () => { + this.reset_notification(); + }, + on_confirm_stock_issue: () => { + let endpoint, endpoint_data; + const data = this.state.data; + if (data.package_level) { + endpoint = "stock_out_package"; + endpoint_data = { + package_level_id: data.package_level.id, + location_id: data.package_level.location_src.id, + }; + } else { + endpoint = "stock_out_line"; + endpoint_data = { + move_line_id: data.move_line.id, + location_id: data.move_line.location_src.id, + }; + } + this.wait_call(this.odoo.call(endpoint, endpoint_data)); + }, + }, + }, + }; + }, +}); + +process_registry.add("location_content_transfer", LocationContentTransfer); diff --git a/shopfloor_mobile/static/wms/src/scenario/mixins.js b/shopfloor_mobile/static/wms/src/scenario/mixins.js index 7f3c29a468..90ab0a1f68 100644 --- a/shopfloor_mobile/static/wms/src/scenario/mixins.js +++ b/shopfloor_mobile/static/wms/src/scenario/mixins.js @@ -66,11 +66,8 @@ export var ScenarioBaseMixin = { return state; }, search_input_placeholder: function() { - if (this.state.scan_placeholder) { - // TMP backward compat - return this.state.scan_placeholder; - } - return this.state.display_info.scan_placeholder; + const placeholder = this.state.display_info.scan_placeholder; + return _.isFunction(placeholder) ? placeholder.call(this) : placeholder; }, show_cancel_button: function() { return this.state.display_info.show_cancel_button; @@ -182,10 +179,21 @@ export var ScenarioBaseMixin = { /* Alias "init" to the initial state and erase all existing data if any. - Used when we enter from a menu or to enforce data cleanup. + Used when we enter from a menu or to enforce data cleanup + or any other case where you want to erase all data on demand. */ this.state_reset_data_all(); - state_key = this.initial_state_key; + /** + * Special case: if `init` is defined as state + * you can use it do particular initialization. + * Eg: call the server to preload some data. + * In this case the state is responsible + * for handline the transition to another state + * or delegate it to server result via `next_state` key. + */ + if (!"init" in this.states) { + state_key = this.initial_state_key; + } } state_key = state_key || "start"; if (state_key == "start") { @@ -235,10 +243,6 @@ export var ScenarioBaseMixin = { ? this.initial_state_key : result.next_state; if (!_.isUndefined(result.data)) { - // TODO: we should find a way to reset the whole scenario data - // as soon as we start w/ the scenario by clicking the menu item - // and not just when you go to /start because you could get back - // to it from another state. this.state_reset_data(state_key); this.state_set_data(result.data[state_key], state_key); } diff --git a/shopfloor_mobile/static/wms/src/services/config.js b/shopfloor_mobile/static/wms/src/services/config.js deleted file mode 100644 index 4d7e88e8ff..0000000000 --- a/shopfloor_mobile/static/wms/src/services/config.js +++ /dev/null @@ -1,18 +0,0 @@ -export class Config { - constructor(odoo) { - this.odoo = odoo; - this.data = {}; - this.authenticated = false; - } - load() { - return this.odoo._call("app/user_config", "POST", {}).then(result => { - if (!_.isUndefined(result.data)) { - this.data = result.data; - this.authenticated = true; - } else { - // TODO: any better thing to do here? - console.log(result); - } - }); - } -} diff --git a/shopfloor_mobile/static/wms/src/services/odoo.js b/shopfloor_mobile/static/wms/src/services/odoo.js index 463aa7ee2c..ba6ca5b961 100644 --- a/shopfloor_mobile/static/wms/src/services/odoo.js +++ b/shopfloor_mobile/static/wms/src/services/odoo.js @@ -99,13 +99,18 @@ export class OdooMocked extends OdooMixin { const barcode = data ? data.barcode || data.identifier || data.location_barcode : null; - if (_.has(this.demo_data, barcode)) { + let demo_data = this.demo_data; + if (demo_data.by_menu_id && _.has(demo_data.by_menu_id, this.process_menu_id)) { + // Load subset of responses by menu id + demo_data = demo_data.by_menu_id[this.process_menu_id]; + } + if (_.has(demo_data, barcode)) { // Pick a specific case for this barcode - result = this.demo_data[barcode]; + result = demo_data[barcode]; } - if (_.has(this.demo_data, path)) { + if (_.has(demo_data, path)) { // Pick general case for this path - result = this.demo_data[path]; + result = demo_data[path]; } if (_.has(result, barcode)) { // Pick specific barcode case inside path case @@ -125,6 +130,12 @@ export class OdooMocked extends OdooMixin { console.dir("CALL RETURN data:", result); return Promise.resolve(result); } + user_config(params) { + return Promise.resolve({data: demotools.makeAppConfig()}); + } + menu(params) { + return Promise.resolve({data: {menus: demotools.getAppMenus()}}); + } scan(params) { let result = {}; const data = demotools.get_indexed(params.identifier); diff --git a/shopfloor_mobile/static/wms/src/settings/language.js b/shopfloor_mobile/static/wms/src/settings/language.js index 27fd45ec33..917108b578 100644 --- a/shopfloor_mobile/static/wms/src/settings/language.js +++ b/shopfloor_mobile/static/wms/src/settings/language.js @@ -1,9 +1,9 @@ export var Language = Vue.component("language", { template: ` - + diff --git a/shopfloor_mobile/static/wms/src/settings/profile.js b/shopfloor_mobile/static/wms/src/settings/profile.js index c1d3b15108..850771b0aa 100644 --- a/shopfloor_mobile/static/wms/src/settings/profile.js +++ b/shopfloor_mobile/static/wms/src/settings/profile.js @@ -10,7 +10,7 @@ export var Profile = Vue.component("profile", { }; }, template: ` - +