diff --git a/shopfloor_mobile/static/wms/src/components/batch_picking_detail.js b/shopfloor_mobile/static/wms/src/components/batch_picking_detail.js index e988cd5491..7b27274702 100644 --- a/shopfloor_mobile/static/wms/src/components/batch_picking_detail.js +++ b/shopfloor_mobile/static/wms/src/components/batch_picking_detail.js @@ -11,10 +11,22 @@ Vue.component("batch-picking-detail", { }, picking_detail_fields() { return [ + {path: "name", klass: "loud", action_val_path: "name"}, {path: "move_line_count", label: "Lines"}, {path: "weight", label: "Weight"}, ]; }, + picking_list_options() { + return { + group_title_default: "Pickings list", + group_color: this.utils.colors.color_for("screen_step_todo"), + list_item_options: { + key_title: "name", + bold_title: true, + fields: this.picking_detail_fields(), + }, + }; + }, }, template: `
@@ -40,13 +52,10 @@ Vue.component("batch-picking-detail", {
- Pickings list - +
diff --git a/shopfloor_mobile/static/wms/src/components/list.js b/shopfloor_mobile/static/wms/src/components/list.js index a74121c7ba..ddfeea55de 100644 --- a/shopfloor_mobile/static/wms/src/components/list.js +++ b/shopfloor_mobile/static/wms/src/components/list.js @@ -36,7 +36,14 @@ Vue.component("list", { listable() { if (!this.grouped_records.length) { // Simulate grouping (allows to keep template the same) - return [{key: "no-group", title: "", records: this.records}]; + return [ + { + key: "no-group", + title: this.opts.group_title_default, + + records: this.records, + }, + ]; } return this.grouped_records; }, @@ -52,6 +59,7 @@ Vue.component("list", { list_item_actions: [], list_item_on_click: null, list_item_options: {}, + group_title_default: "", }); return opts; }, @@ -81,15 +89,14 @@ Vue.component("list", { }, template: `
- + + {{ group.title }} -
- {{ group.title }} -
- +
+ + - - - - -
+ + + + +
-
-

No item to list.

-
+ + + +

No item to list.

+
+
+
`, diff --git a/shopfloor_mobile/static/wms/src/components/manual_select.js b/shopfloor_mobile/static/wms/src/components/manual_select.js index 61d9bbb9f8..8237463718 100644 --- a/shopfloor_mobile/static/wms/src/components/manual_select.js +++ b/shopfloor_mobile/static/wms/src/components/manual_select.js @@ -164,6 +164,7 @@ Vue.component("manual-select", { list_item_extra_component: "", selected_event: "select", group_color: "", + group_title_default: "", }); return opts; }, @@ -180,7 +181,13 @@ Vue.component("manual-select", { selectable() { if (!this.grouped_records.length) { // Simulate grouping (allows to keep template the same) - return [{key: "no-group", title: "", records: this.records}]; + return [ + { + key: "no-group", + title: this.opts.group_title_default, + records: this.records, + }, + ]; } return this.grouped_records; }, @@ -224,7 +231,7 @@ Vue.component("manual-select", { :key="make_component_key([opts.list_item_component, index, rec.id])" /> - +
- + + +
@@ -262,8 +271,15 @@ Vue.component("manual-select", {
- -

No item to select.

+ + + + + +

No item to select.

+
+
+
diff --git a/shopfloor_mobile/static/wms/src/components/misc.js b/shopfloor_mobile/static/wms/src/components/misc.js index f301fd8447..df7273430f 100644 --- a/shopfloor_mobile/static/wms/src/components/misc.js +++ b/shopfloor_mobile/static/wms/src/components/misc.js @@ -51,12 +51,12 @@ Vue.component("last-operation", { Vue.component("get-work", { template: `
- + Get work - + Manual selection - +
`, }); @@ -117,12 +117,6 @@ Vue.component("edit-action", { `, }); -Vue.component("btn-back", { - template: ` - {{ $t("btn.back.title") }} - `, -}); - Vue.component("separator-title", { template: `

@@ -218,10 +212,39 @@ Vue.component("btn-action", { type: String, default: "", }, + color: { + type: String, + default: "", + }, + }, + computed: { + btn_color() { + let color = this.color; + if (!color) { + color = this.utils.colors.color_for( + this.action ? "btn_action_" + this.action : "btn_action" + ); + } + return color; + }, }, template: ` - + `, }); + +Vue.component("btn-back", { + methods: { + on_back: function() { + this.$root.trigger("go_back"); + this.$router.back(); + }, + }, + template: ` + + mdi-keyboard-backspace{{ $t("btn.back.title") }} + +`, +}); diff --git a/shopfloor_mobile/static/wms/src/components/packaging-qty-picker.js b/shopfloor_mobile/static/wms/src/components/packaging-qty-picker.js index 6195cb92e3..bf32854880 100644 --- a/shopfloor_mobile/static/wms/src/components/packaging-qty-picker.js +++ b/shopfloor_mobile/static/wms/src/components/packaging-qty-picker.js @@ -13,9 +13,9 @@ export var PackagingQtyPickerMixin = { methods: { on_change_pkg_qty: function(event) { const input = event.target; - let new_qty = parseInt(input.value, 10); + let new_qty = parseInt(input.value || 0, 10); const data = $(input).data(); - const origvalue = data.origvalue || 0; + const origvalue = parseInt(data.origvalue || 0, 10); // Check max qty reached const future_qty = this.value + data.pkg.qty * (new_qty - origvalue); if (new_qty && future_qty > this.original_value) { @@ -75,6 +75,10 @@ export var PackagingQtyPickerMixin = { // const min_unit = _.last(pkg_by_qty); pkg_by_qty.forEach(function(pkg) { let qty_per_pkg = 0; + if (!pkg.qty) { + console.error("Packaging with no quantity: skipping", pkg); + return {}; + } [qty_per_pkg, qty] = self._qty_by_pkg(pkg.qty, qty); if (qty_per_pkg) res[pkg.id] = qty_per_pkg; if (!qty) return; @@ -108,36 +112,33 @@ export var PackagingQtyPickerMixin = { compute_qty: function(newVal, oldVal) { this.value = this._compute_qty(); }, - }, - watch: { - value: { - handler: function(newVal, oldVal) { - this.$root.trigger("qty_edit", this.value); - }, + _init_editable() { + const self = this; + this.$watch( + "qty_by_pkg", + function() { + self.compute_qty(); + }, + {deep: true} + ); + this.qty_by_pkg = this.product_qty_by_packaging(); + this.orig_qty_by_pkg = this.qty_by_pkg; + // hooking via `v-on:change` we don't get the full event but only the qty :/ + // And forget about using v-text-field because it loses the full event object + $(".pkg-value", this.$el).change(this.on_change_pkg_qty); + $(".pkg-value", this.$el).on("focus click", function() { + $(this).select(); + }); + }, + _init_readonly() { + this.qty_by_pkg = this.product_qty_by_packaging(); + this.compute_qty(); }, }, created: function() { this.original_value = parseInt(this.opts.init_value, 10); this.value = parseInt(this.opts.init_value, 10); }, - mounted: function() { - const self = this; - this.$watch( - "qty_by_pkg", - function() { - self.compute_qty(); - }, - {deep: true} - ); - this.qty_by_pkg = this.product_qty_by_packaging(); - this.orig_qty_by_pkg = this.qty_by_pkg; - // hooking via `v-on:change` we don't get the full event but only the qty :/ - // And forget about using v-text-field because it loses the full event object - $(".pkg-value", this.$el).change(this.on_change_pkg_qty); - $(".pkg-value", this.$el).on("focus click", function() { - $(this).select(); - }); - }, computed: { opts() { const opts = _.defaults({}, this.$props.options, { @@ -180,6 +181,16 @@ export var PackagingQtyPickerMixin = { export var PackagingQtyPicker = Vue.component("packaging-qty-picker", { mixins: [PackagingQtyPickerMixin], + watch: { + value: { + handler: function(newVal, oldVal) { + this.$root.trigger("qty_edit", this.value); + }, + }, + }, + mounted: function() { + this._init_editable(); + }, template: `
@@ -211,12 +222,17 @@ export var PackagingQtyPicker = Vue.component("packaging-qty-picker", { export var PackagingQtyPickerDisplay = Vue.component("packaging-qty-picker-display", { mixins: [PackagingQtyPickerMixin], + mounted: function() { + this._init_readonly(); + }, template: `
, + + ({{ opts.init_value }} Units)
`, }); diff --git a/shopfloor_mobile/static/wms/src/components/scenario_picking_detail/picking_summary.js b/shopfloor_mobile/static/wms/src/components/scenario_picking_detail/picking_summary.js index 414683de83..cbb92a1595 100644 --- a/shopfloor_mobile/static/wms/src/components/scenario_picking_detail/picking_summary.js +++ b/shopfloor_mobile/static/wms/src/components/scenario_picking_detail/picking_summary.js @@ -12,6 +12,7 @@ Vue.component("picking-summary", { { showCounters: true, list_item_component: "picking-summary-content", + group_color: this.utils.colors.color_for("screen_step_done"), } ); return opts; @@ -22,11 +23,17 @@ Vue.component("picking-summary", { action_change_pkg: { comp_name: "edit-action", get_record: function(rec, action) { - if (rec.pack) { - // lines grouped, get real line - return rec.pack; - } - return rec.package_dest; + /** + * Here we always records grouped. + * If lines have a dest package or not + * we don't care for a simple reason: + * if the pack is there then all grouped lines + * have the same package. + * The edit action will check by itself + * if the pkg is there or not and pick the right record. + * Hence -> always return the move line. + */ + return rec.records[0]; }, options: { click_event: "pkg_change_type", @@ -78,7 +85,7 @@ Vue.component("picking-summary-content", { }, }, template: ` - +
@@ -101,7 +108,7 @@ Vue.component("picking-summary-content", {
- +
`, }); diff --git a/shopfloor_mobile/static/wms/src/components/userConfirmation/userConfirmation.js b/shopfloor_mobile/static/wms/src/components/userConfirmation/userConfirmation.js index 6f604e7db7..927797f202 100644 --- a/shopfloor_mobile/static/wms/src/components/userConfirmation/userConfirmation.js +++ b/shopfloor_mobile/static/wms/src/components/userConfirmation/userConfirmation.js @@ -1,24 +1,24 @@ -var userConfirmation = Vue.component("user-confirmation", { +Vue.component("user-confirmation", { props: ["title", "question"], methods: {}, template: ` -
+
- {{ question }} +

{{ question }}

- Yes - - No +
+ + + Yes + + + + + No + + +
diff --git a/shopfloor_mobile/static/wms/src/css/main.css b/shopfloor_mobile/static/wms/src/css/main.css index dac741d9b5..1b22a16a0b 100644 --- a/shopfloor_mobile/static/wms/src/css/main.css +++ b/shopfloor_mobile/static/wms/src/css/main.css @@ -193,7 +193,8 @@ ul.packaging span:first-child { height: 3.5rem; width: 100%; } -.manual-select[class*="with-group_color"] .v-card { +.manual-select[class*="with-group_color"] .v-card, +.list[class*="with-group_color"] .v-card { border: 5px solid; } .manual-select.with-groups .v-card__title { @@ -202,6 +203,7 @@ ul.packaging span:first-child { .manual-select.with-groups .v-card.select-group:not(:last-child) { margin-bottom: 1.2rem; } +.list .v-list, .manual-select .v-list { padding: 0; } @@ -232,9 +234,9 @@ ul.packaging span:first-child { .list .v-expansion-panel-content .v-expansion-panel-content__wrap { padding: 0 6px 10px; } -.list .list-item-wrapper:not(:last-child) .v-list-item__content { +/* .list .list-item-wrapper:not(:last-child) .v-list-item__content { border-bottom: 1px solid #ccc; -} +} */ .list .list-group:not(:last-child) { border-bottom: 2px solid #ccc; } @@ -276,7 +278,7 @@ ul.packaging span:first-child { margin-bottom: 0.4em; padding-bottom: 0.4em; } -.summary-content .summary-content-item:not(:last-child) { +.summary-content .v-expansion-panel-content .summary-content-item:not(:last-child) { border-bottom: 1px dotted #ccc; } .separator-title { @@ -333,10 +335,10 @@ ul.packaging span:first-child { .v-application--is-ltr .v-list-item__icon:last-of-type:not(:only-child) { margin-left: 0; } - +/* .list-item-wrapper:not(:last-child) { border-bottom: 1px solid #ccc; -} +} */ .list-item-wrapper .extra { padding: 0 16px; @@ -491,3 +493,16 @@ ul.packaging span:first-child { font-size: 1.1rem; line-height: 1.1rem; } + +/* +TODO: this is probably to be applied to all .manual-select. +I tested only w/ checkout/select_package for now + */ +.detail-picking-select .manual-select .v-list-item .v-list-item__action--stack { + flex-direction: column-reverse; +} + +/* TODO: likely to be applied to all */ +.list .v-list-item { + border-bottom: 1px solid #ccc; +} diff --git a/shopfloor_mobile/static/wms/src/router.js b/shopfloor_mobile/static/wms/src/router.js index 45d8b58d2c..291d7dccc4 100644 --- a/shopfloor_mobile/static/wms/src/router.js +++ b/shopfloor_mobile/static/wms/src/router.js @@ -38,6 +38,7 @@ const router = new VueRouter({ routes: routes, }); router.beforeEach(async (to, from, next) => { + // TODO: if we switch from one scenario to another we should flush the storage of the previous one await Vue.nextTick(); if (!router.app.authenticated && to.name != "login" && !router.app.demo_mode) { next("login"); diff --git a/shopfloor_mobile/static/wms/src/scenario/checkout.js b/shopfloor_mobile/static/wms/src/scenario/checkout.js index b3e3710e14..45358bb023 100644 --- a/shopfloor_mobile/static/wms/src/scenario/checkout.js +++ b/shopfloor_mobile/static/wms/src/scenario/checkout.js @@ -37,14 +37,13 @@ export var Checkout = Vue.component("checkout", {
- Back +
@@ -116,7 +115,6 @@ export var Checkout = Vue.component("checkout", {
- - Mark as done @@ -181,16 +179,15 @@ export var Checkout = Vue.component("checkout", {
-
- Confirm + Confirm - Back +
@@ -198,14 +195,6 @@ export var Checkout = Vue.component("checkout", { `, computed: { - // TODO: move these to methods - manual_select_picking_fields: function() { - return [ - {path: "partner.name"}, - {path: "origin"}, - {path: "move_line_count", label: "Lines"}, - ]; - }, existing_package_select_fields: function() { return [ {path: "weight"}, @@ -240,6 +229,20 @@ export var Checkout = Vue.component("checkout", { identifier: data.picking.name, }; }, + manual_selection_manual_select_options: function() { + return { + group_title_default: "Pickings to process", + group_color: this.utils.colors.color_for("screen_step_todo"), + list_item_options: { + bold_title: true, + fields: [ + {path: "partner.name"}, + {path: "origin"}, + {path: "move_line_count", label: "Lines"}, + ], + }, + }; + }, select_line_manual_select_opts: function() { return { group_color: this.utils.colors.color_for("screen_step_todo"), @@ -282,9 +285,10 @@ export var Checkout = Vue.component("checkout", { }, events: { select: "on_select", + go_back: "on_back", }, on_back: () => { - this.state_to("start"); + this.state_to("init"); this.reset_notification(); }, on_select: selected => { @@ -601,6 +605,9 @@ export var Checkout = Vue.component("checkout", { display_info: { title: "Confirm done", }, + events: { + go_back: "on_back", + }, on_confirm: () => { this.wait_call( this.odoo.call("done", { diff --git a/shopfloor_mobile/static/wms/src/scenario/mixins.js b/shopfloor_mobile/static/wms/src/scenario/mixins.js index ecd4689e25..1e4b938299 100644 --- a/shopfloor_mobile/static/wms/src/scenario/mixins.js +++ b/shopfloor_mobile/static/wms/src/scenario/mixins.js @@ -38,15 +38,15 @@ export var ScenarioBaseMixin = { */ this._state_load(this.$route.params.state); }, - mounted: function() { - const odoo_params = { - process_menu_id: this.menu_item.id, - profile_id: this.$root.profile.id, - usage: this.usage, - }; - this.odoo = this.$root.getOdoo(odoo_params); - }, computed: { + odoo: function() { + const odoo_params = { + process_menu_id: this.menu_item.id, + profile_id: this.$root.profile.id, + usage: this.usage, + }; + return this.$root.getOdoo(odoo_params); + }, menu_item: function() { const self = this; return _.head( diff --git a/shopfloor_mobile/static/wms/src/services/color_registry.js b/shopfloor_mobile/static/wms/src/services/color_registry.js index d7c2e1d184..e60624736d 100644 --- a/shopfloor_mobile/static/wms/src/services/color_registry.js +++ b/shopfloor_mobile/static/wms/src/services/color_registry.js @@ -13,7 +13,8 @@ color_registry.add_theme( error: "#c22a4a", info: "#5e60ab", success: "#8fbf44", - warning: "amber", + // warning: "#FFC107", + warning: "#e5ab00", /** * app specific */ @@ -28,8 +29,10 @@ color_registry.add_theme( */ btn_action: "primary lighten-2", btn_action_cancel: "error", - btn_action_warn: "amber", + btn_action_warn: "warning", btn_action_complete: "success", + btn_action_todo: "screen_step_todo", + btn_action_back: "info lighten-1", /** * selection */ diff --git a/shopfloor_mobile/static/wms/src/utils.js b/shopfloor_mobile/static/wms/src/utils.js index 617ef0ef80..f01b1da72d 100644 --- a/shopfloor_mobile/static/wms/src/utils.js +++ b/shopfloor_mobile/static/wms/src/utils.js @@ -79,13 +79,24 @@ export class Utils { }), "id" ); - const grouped = _.groupBy(lines, "package_dest.id"); + const grouped = _.groupBy(lines, function(l) { + const pack_id = _.result(l, "package_dest.id"); + if (pack_id) { + return "pack-" + pack_id; + } + return "raw-" + l.id + l.product.id; + }); let counter = 0; - _.forEach(grouped, function(products, pack_id) { + _.forEach(grouped, function(products, key) { counter++; - const pack = _.first(_.filter(packs, {id: parseInt(pack_id)})); + let pack = null; + if (key.startsWith("pack")) { + pack = _.first( + _.filter(packs, {id: parseInt(key.split("-").slice(-1)[0])}) + ); + } res.push({ - key: pack ? pack_id : products[0].id + "-" + counter, + key: key + "--" + counter, // No pack, just display the product name title: pack ? pack.name : products[0].display_name, pack: pack,