odoo-addon-shopfloor-mobile 16.0.1.0.0.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. odoo/addons/shopfloor_mobile/README.rst +215 -0
  2. odoo/addons/shopfloor_mobile/__init__.py +0 -0
  3. odoo/addons/shopfloor_mobile/__manifest__.py +17 -0
  4. odoo/addons/shopfloor_mobile/i18n/es_AR.po +39 -0
  5. odoo/addons/shopfloor_mobile/i18n/pt_BR.po +14 -0
  6. odoo/addons/shopfloor_mobile/i18n/shopfloor_mobile.pot +13 -0
  7. odoo/addons/shopfloor_mobile/readme/CONTRIBUTORS.rst +12 -0
  8. odoo/addons/shopfloor_mobile/readme/CREDITS.rst +5 -0
  9. odoo/addons/shopfloor_mobile/readme/DESCRIPTION.rst +31 -0
  10. odoo/addons/shopfloor_mobile/readme/HISTORY.rst +4 -0
  11. odoo/addons/shopfloor_mobile/readme/ROADMAP.rst +29 -0
  12. odoo/addons/shopfloor_mobile/readme/USAGE.rst +34 -0
  13. odoo/addons/shopfloor_mobile/static/description/icon.png +0 -0
  14. odoo/addons/shopfloor_mobile/static/description/index.html +555 -0
  15. odoo/addons/shopfloor_mobile/static/wms/.gitignore +21 -0
  16. odoo/addons/shopfloor_mobile/static/wms/src/components/batch_picking_detail.js +69 -0
  17. odoo/addons/shopfloor_mobile/static/wms/src/components/batch_picking_line_detail.js +141 -0
  18. odoo/addons/shopfloor_mobile/static/wms/src/components/detail/detail_location.js +66 -0
  19. odoo/addons/shopfloor_mobile/static/wms/src/components/detail/detail_lot.js +91 -0
  20. odoo/addons/shopfloor_mobile/static/wms/src/components/detail/detail_operation.js +50 -0
  21. odoo/addons/shopfloor_mobile/static/wms/src/components/detail/detail_package.js +73 -0
  22. odoo/addons/shopfloor_mobile/static/wms/src/components/detail/detail_picking.js +40 -0
  23. odoo/addons/shopfloor_mobile/static/wms/src/components/detail/detail_product.js +70 -0
  24. odoo/addons/shopfloor_mobile/static/wms/src/components/detail/detail_transfer.js +128 -0
  25. odoo/addons/shopfloor_mobile/static/wms/src/components/forms/form_edit_stock_picking.js +39 -0
  26. odoo/addons/shopfloor_mobile/static/wms/src/components/manual_select_color.js +24 -0
  27. odoo/addons/shopfloor_mobile/static/wms/src/components/misc.js +201 -0
  28. odoo/addons/shopfloor_mobile/static/wms/src/components/packaging-qty-picker.js +329 -0
  29. odoo/addons/shopfloor_mobile/static/wms/src/components/scenario_picking_detail/mixins.js +130 -0
  30. odoo/addons/shopfloor_mobile/static/wms/src/components/scenario_picking_detail/picking_select.js +135 -0
  31. odoo/addons/shopfloor_mobile/static/wms/src/components/scenario_picking_detail/picking_summary.js +212 -0
  32. odoo/addons/shopfloor_mobile/static/wms/src/css/main.css +73 -0
  33. odoo/addons/shopfloor_mobile/static/wms/src/css/normalize.css +351 -0
  34. odoo/addons/shopfloor_mobile/static/wms/src/demo/demo.checkout.js +257 -0
  35. odoo/addons/shopfloor_mobile/static/wms/src/demo/demo.cluster_picking.js +188 -0
  36. odoo/addons/shopfloor_mobile/static/wms/src/demo/demo.delivery.js +79 -0
  37. odoo/addons/shopfloor_mobile/static/wms/src/demo/demo.location_content_transfer.js +179 -0
  38. odoo/addons/shopfloor_mobile/static/wms/src/demo/demo.scan_anything.js +124 -0
  39. odoo/addons/shopfloor_mobile/static/wms/src/demo/demo.single_pack_transfer.js +83 -0
  40. odoo/addons/shopfloor_mobile/static/wms/src/demo/demo.zone_picking.js +277 -0
  41. odoo/addons/shopfloor_mobile/static/wms/src/i18n/add_translations_to_registry.js +4 -0
  42. odoo/addons/shopfloor_mobile/static/wms/src/i18n/en.json +31 -0
  43. odoo/addons/shopfloor_mobile/static/wms/src/i18n/fr.json +27 -0
  44. odoo/addons/shopfloor_mobile/static/wms/src/scenario/checkout.js +390 -0
  45. odoo/addons/shopfloor_mobile/static/wms/src/scenario/checkout_states.js +380 -0
  46. odoo/addons/shopfloor_mobile/static/wms/src/scenario/cluster_picking.js +481 -0
  47. odoo/addons/shopfloor_mobile/static/wms/src/scenario/delivery.js +353 -0
  48. odoo/addons/shopfloor_mobile/static/wms/src/scenario/location_content_transfer.js +388 -0
  49. odoo/addons/shopfloor_mobile/static/wms/src/scenario/single_pack_transfer.js +132 -0
  50. odoo/addons/shopfloor_mobile/static/wms/src/scenario/zone_picking.js +838 -0
  51. odoo/addons/shopfloor_mobile/static/wms/src/screen.js +36 -0
  52. odoo/addons/shopfloor_mobile/static/wms/src/wms_utils.js +318 -0
  53. odoo/addons/shopfloor_mobile/templates/assets.xml +180 -0
  54. odoo_addon_shopfloor_mobile-16.0.1.0.0.6.dist-info/METADATA +235 -0
  55. odoo_addon_shopfloor_mobile-16.0.1.0.0.6.dist-info/RECORD +57 -0
  56. odoo_addon_shopfloor_mobile-16.0.1.0.0.6.dist-info/WHEEL +5 -0
  57. odoo_addon_shopfloor_mobile-16.0.1.0.0.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
3
+ * @author Simone Orsi <simahawk@gmail.com>
4
+ * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
5
+ */
6
+
7
+ /* eslint-disable strict */
8
+ Vue.component("form-edit-stock-picking", {
9
+ props: ["record", "form"],
10
+ data: function () {
11
+ return {
12
+ form_values: {},
13
+ changed: false,
14
+ };
15
+ },
16
+ methods: {
17
+ on_select: function (selected, fname) {
18
+ this.$set(this.form_values, fname, selected.id);
19
+ this.changed = true;
20
+ this.$emit("change", {changed: this.changed, values: this.form_values});
21
+ },
22
+ },
23
+ template: `
24
+ <div :class="['form', $options._componentTag]">
25
+
26
+ <v-form ref="form">
27
+ <div class="fields-wrapper">
28
+ <separator-title>Change carrier</separator-title>
29
+ <manual-select
30
+ :records="form.carrier_id.select_options"
31
+ :options="{showActions: false, initValue: record.carrier.id}"
32
+ v-on:select="($event) => { on_select($event, 'carrier_id') }"
33
+ />
34
+ </div>
35
+ </v-form>
36
+
37
+ </div>
38
+ `,
39
+ });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Copyright 2022 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
3
+ * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
4
+ */
5
+
6
+ // Overriding manual-select from shopfloor_mobile_base
7
+ const Base = Vue.options.components["manual-select"];
8
+ const Custom = Base.extend({
9
+ methods: {
10
+ selected_color_klass(rec, modifier) {
11
+ let color;
12
+ if (rec && rec.qty_done && rec.quantity) {
13
+ if (rec.qty_done < rec.quantity)
14
+ color = this.utils.colors.color_for("item_selected_partial");
15
+ if (rec.qty_done > rec.quantity)
16
+ color = this.utils.colors.color_for("item_selected_excess");
17
+ if (color) return "active " + color + (modifier ? " " + modifier : "");
18
+ }
19
+ return Base.options.methods.selected_color_klass.call(this, rec, modifier);
20
+ },
21
+ },
22
+ });
23
+
24
+ Vue.component("manual-select", Custom);
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
3
+ * @author Simone Orsi <simahawk@gmail.com>
4
+ * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
5
+ */
6
+
7
+ /* eslint-disable strict */
8
+
9
+ import {ItemDetailMixin} from "/shopfloor_mobile_base/static/wms/src/components/detail/detail_mixin.js";
10
+
11
+ // TODO: could be merged w/ userConfirmation
12
+ Vue.component("last-operation", {
13
+ data: function () {
14
+ return {info: {}};
15
+ },
16
+ template: `
17
+ <div class="last-operation">
18
+ <v-dialog persistent fullscreen tile value=true>
19
+ <v-alert tile type="info" prominent transition="scale-transition">
20
+ <v-card outlined color="blue lighten-1" class="message mt-10">
21
+ <v-card-title>This was the last operation of the document.</v-card-title>
22
+ <v-card-text>The next operation is ready to be processed.</v-card-text>
23
+ </v-card>
24
+ <v-form class="mt-10">
25
+ <v-btn x-large color="success" @click="$emit('confirm')">{{ $t('btn.ok.title') }}</v-btn>
26
+ </v-form>
27
+ </v-alert>
28
+ </v-dialog>
29
+ </div>
30
+ `,
31
+ });
32
+
33
+ Vue.component("get-work", {
34
+ template: `
35
+ <div class="get-work fullscreen-buttons fullscreen-buttons-50">
36
+ <btn-action id="btn-get-work" @click="$emit('get_work')">
37
+ {{ $t('misc.btn_get_work') }}
38
+ </btn-action>
39
+ <btn-action id="btn-manual" color="default" @click="$emit('manual_selection')">
40
+ {{ $t('misc.btn_manual_selection') }}
41
+ </btn-action>
42
+ </div>
43
+ `,
44
+ });
45
+
46
+ Vue.component("stock-zero-check", {
47
+ template: `
48
+ <div class="stock-zero-check">
49
+ <v-dialog fullscreen tile value=true class="actions fullscreen">
50
+ <v-card>
51
+ <div class="button-list button-vertical-list">
52
+ <v-row align="center">
53
+ <v-col class="text-center" cols="12">
54
+ <v-btn x-large color="primary" @click="$emit('action', 'action_confirm_zero')">
55
+ {{ $t('misc.stock_zero_check.confirm_stock_zero') }}
56
+ </v-btn>
57
+ </v-col>
58
+ </v-row>
59
+ <v-row align="center">
60
+ <v-col class="text-center" cols="12">
61
+ <v-btn x-large color="warning" @click="$emit('action', 'action_confirm_not_zero')">
62
+ {{ $t('misc.stock_zero_check.confirm_stock_not_zero') }}
63
+ </v-btn>
64
+ </v-col>
65
+ </v-row>
66
+ </div>
67
+ </v-card>
68
+ </v-dialog>
69
+ </div>
70
+ `,
71
+ });
72
+ Vue.component("empty-location-icon", {
73
+ mixins: [ItemDetailMixin],
74
+ template: `
75
+ <v-icon color="orange" :class="$options._componentTag" v-if="record.location_will_be_empty">mdi-alert-rhombus-outline</v-icon>
76
+ `,
77
+ });
78
+
79
+ Vue.component("select-zone-item", {
80
+ mixins: [ItemDetailMixin],
81
+ template: `
82
+ <div :class="$options._componentTag">
83
+ <div class="detail-field mt-2 title">
84
+ <span class="counters">({{ $t("misc.lines_count", record) }})</span>
85
+ <span class="name font-weight-bold">{{ record.name }}</span>
86
+ </div>
87
+ <div v-for="op_type in record.operation_types" :key="make_component_key([op_type.id])">
88
+ <div class="detail-field mt-2">
89
+ <span class="counters">({{ $t("misc.lines_count", op_type) }})</span>
90
+ <span class="name">{{ op_type.name }}</span>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ `,
95
+ });
96
+
97
+ Vue.component("cancel-move-line-action", {
98
+ props: {
99
+ record: {
100
+ type: Object,
101
+ },
102
+ options: {
103
+ type: Object,
104
+ default: function () {
105
+ // Take control of which package key (source or destination) is used
106
+ // to cancel the line when cancel line action is available.
107
+ return {
108
+ package_cancel_key: "package_dest",
109
+ };
110
+ },
111
+ },
112
+ },
113
+ data() {
114
+ return {
115
+ dialog: false,
116
+ };
117
+ },
118
+ methods: {
119
+ on_user_confirm: function (answer) {
120
+ this.dialog = false;
121
+ if (answer === "yes") {
122
+ let data = {};
123
+ if (this.the_package) {
124
+ data = {package_id: this.the_package.id};
125
+ } else {
126
+ data = {line_id: this.record.id};
127
+ }
128
+ this.$root.trigger("cancel_picking_line", data);
129
+ }
130
+ },
131
+ },
132
+ computed: {
133
+ // `package` is a reserved identifier!
134
+ the_package: function () {
135
+ return _.result(this.record, this.$props.options.package_cancel_key);
136
+ },
137
+ message: function () {
138
+ const item = this.the_package
139
+ ? this.the_package.name
140
+ : this.record.product.name;
141
+ return "Please confirm cancellation for " + item;
142
+ },
143
+ },
144
+ template: `
145
+ <div class="action action-destroy">
146
+ <v-dialog v-model="dialog" fullscreen tile class="actions fullscreen text-center">
147
+ <template v-slot:activator="{ on }">
148
+ <v-btn icon class="destroy" x-large rounded color="error" v-on="on"><v-icon>mdi-close-circle</v-icon></v-btn>
149
+ </template>
150
+ <v-card>
151
+ <user-confirmation
152
+ v-on:user-confirmation="on_user_confirm"
153
+ v-bind:question="message"></user-confirmation>
154
+ </v-card>
155
+ </v-dialog>
156
+ </div>
157
+ `,
158
+ });
159
+
160
+ Vue.component("picking-list-item-progress-bar", {
161
+ mixins: [ItemDetailMixin],
162
+ computed: {
163
+ value() {
164
+ if (!_.isUndefined(this.record.progress)) {
165
+ return this.record.progress;
166
+ }
167
+ return this.utils.wms.picking_completeness(this.record);
168
+ },
169
+ },
170
+ template: `
171
+ <div :class="$options._componentTag">
172
+ <v-progress-linear :value="value" color="success" height="8"></v-progress-linear>
173
+ </div>
174
+ `,
175
+ });
176
+
177
+ Vue.component("line-stock-out", {
178
+ methods: {
179
+ handle_action(action) {
180
+ this.$emit(action);
181
+ },
182
+ },
183
+ template: `
184
+ <div :class="$options._componentTag">
185
+ <div class="button-list button-vertical-list full">
186
+ <v-row align="center">
187
+ <v-col class="text-center" cols="12">
188
+ <btn-action @click="handle_action('confirm_stock_issue')">
189
+ {{ $t('misc.stock_zero_check.confirm_stock_zero') }}
190
+ </btn-action>
191
+ </v-col>
192
+ </v-row>
193
+ <v-row align="center">
194
+ <v-col class="text-center" cols="12">
195
+ <btn-back />
196
+ </v-col>
197
+ </v-row>
198
+ </div>
199
+ </div>
200
+ `,
201
+ });
@@ -0,0 +1,329 @@
1
+ /**
2
+ * Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
3
+ * @author Simone Orsi <simahawk@gmail.com>
4
+ * Copyright 2021 Jacques-Etienne Baudoux (BCIM)
5
+ * @author Jacques-Etienne Baudoux <je@bcim.be>
6
+ * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
7
+ */
8
+
9
+ export var PackagingQtyPickerMixin = {
10
+ props: {
11
+ options: Object, // options are replaced by props
12
+ mode: String,
13
+ qtyInit: Number,
14
+ uom: {type: Object, required: true},
15
+ availablePackaging: Array,
16
+ pkgNameKey: String, // "code" or "name"
17
+ },
18
+ data: function () {
19
+ return {
20
+ qty: parseInt(this.qtyInit, 10),
21
+ qty_by_pkg: {},
22
+ qty_by_pkg_manual: false,
23
+ };
24
+ },
25
+ watch: {
26
+ qtyInit: function () {
27
+ this.qty = parseInt(this.qtyInit, 10);
28
+ },
29
+ qty: {
30
+ handler() {
31
+ if (!this.qty_by_pkg_manual) {
32
+ this.qty_by_pkg = this.product_qty_by_packaging();
33
+ }
34
+ this.qty_by_pkg_manual = false;
35
+ },
36
+ immediate: true,
37
+ },
38
+ },
39
+ methods: {
40
+ _handle_qty_error(event, input, new_qty) {
41
+ event.preventDefault();
42
+ // Make it red and shake it
43
+ $(input)
44
+ .closest(".inner-wrapper")
45
+ .addClass("error shake-it")
46
+ .delay(800)
47
+ .queue(function () {
48
+ // End animation
49
+ $(this)
50
+ .removeClass("error shake-it", 2000, "easeInOutQuad")
51
+ .dequeue();
52
+ // Restore value
53
+ $(input).val(new_qty);
54
+ });
55
+ },
56
+ packaging_by_id: function (id) {
57
+ // Special case for UOM ids as they can clash w/ pkg ids
58
+ // we prefix it w/ "uom-"
59
+ id = id.startsWith("uom-") ? id : parseInt(id, 10);
60
+ return _.find(this.sorted_packaging, ["id", id]);
61
+ },
62
+ /**
63
+ *
64
+ Calculate quantity by packaging.
65
+
66
+ Limitation: fractional quantities are lost.
67
+
68
+ :prod_qty:
69
+ :min_unit: minimal unit of measure as a tuple (qty, name).
70
+ Default: to UoM unit.
71
+ :returns: list of tuple in the form [(qty_per_package, package_name)]
72
+
73
+ * @param {*} prod_qty total qty to satisfy.
74
+ * @param {*} min_unit minimal unit of measure as a tuple (qty, name).
75
+ Default: to UoM unit.
76
+ */
77
+ product_qty_by_packaging: function () {
78
+ return this._product_qty_by_packaging(this.sorted_packaging, this.qty);
79
+ },
80
+ /**
81
+ * Produce a list of tuple of packaging qty and packaging name.
82
+ * TODO: refactor to handle fractional quantities (eg: 0.5 Kg)
83
+ *
84
+ * @param {*} pkg_by_qty packaging records sorted by major qty
85
+ * @param {*} qty total qty to satisfy
86
+ */
87
+ _product_qty_by_packaging: function (pkg_by_qty, qty) {
88
+ const self = this;
89
+ const res = {};
90
+ // Const min_unit = _.last(pkg_by_qty);
91
+ pkg_by_qty.forEach(function (pkg) {
92
+ let qty_per_pkg = 0;
93
+ [qty_per_pkg, qty] = self._qty_by_pkg(pkg.qty, qty);
94
+ res[pkg.id] = qty_per_pkg;
95
+ if (!qty) return;
96
+ });
97
+ return res;
98
+ },
99
+ /**
100
+ * Calculate qty needed for given package qty.
101
+ *
102
+ * @param {*} pkg_by_qty
103
+ * @param {*} qty
104
+ */
105
+ _qty_by_pkg: function (pkg_qty, qty) {
106
+ const precision = this.unit_uom.rounding || 3;
107
+ const remainder = _.round(qty % pkg_qty, precision);
108
+ const qty_for_pkg = (qty - remainder) / pkg_qty;
109
+ return [qty_for_pkg, remainder];
110
+ },
111
+ _compute_qty: function () {
112
+ const self = this;
113
+ let value = 0;
114
+ _.forEach(this.qty_by_pkg, function (qty, id) {
115
+ value += self.packaging_by_id(id).qty * qty;
116
+ });
117
+ return value;
118
+ },
119
+ compute_qty: function () {
120
+ this.qty = this._compute_qty();
121
+ },
122
+ },
123
+ computed: {
124
+ unit_uom: function () {
125
+ let unit = {};
126
+ if (!_.isEmpty(this.uom)) {
127
+ // Create an object like the packaging
128
+ // to be used seamlessly in the widget.
129
+ unit = {
130
+ id: "uom-" + this.uom.id,
131
+ name: this.uom.name,
132
+ qty: this.uom.factor,
133
+ rounding: this.uom.rounding,
134
+ };
135
+ }
136
+ return unit;
137
+ },
138
+ /**
139
+ * Sort packaging by qty and exclude the ones w/ qty = 0
140
+ * Include the uom
141
+ */
142
+ sorted_packaging: function () {
143
+ let packagings = _.reverse(
144
+ _.sortBy(
145
+ _.filter(this.availablePackaging, _.property("qty")),
146
+ _.property("qty")
147
+ )
148
+ );
149
+ let unit = [];
150
+ if (!_.isEmpty(this.unit_uom)) {
151
+ unit = [this.unit_uom];
152
+ }
153
+ return _.concat(packagings, unit);
154
+ },
155
+ /**
156
+ * Collect qty of contained packaging inside bigger packaging.
157
+ * Eg: "1 Pallet" contains "4 Big boxes".
158
+ */
159
+ contained_packaging: function () {
160
+ const self = this;
161
+ let res = {},
162
+ qty_per_pkg,
163
+ remaining,
164
+ elected_next_pkg;
165
+ const packaging = this.sorted_packaging;
166
+ _.forEach(packaging, function (pkg, i) {
167
+ const next_pkgs = packaging.slice(i + 1);
168
+ remaining = undefined;
169
+ _.every(next_pkgs, function (next_pkg) {
170
+ [qty_per_pkg, remaining] = self._qty_by_pkg(next_pkg.qty, pkg.qty);
171
+ elected_next_pkg = next_pkg;
172
+ return remaining;
173
+ });
174
+ if (remaining === 0) {
175
+ res[pkg.id] = {
176
+ pkg: elected_next_pkg,
177
+ qty: qty_per_pkg,
178
+ };
179
+ }
180
+ });
181
+ return res;
182
+ },
183
+ },
184
+ };
185
+
186
+ export var PackagingQtyPicker = Vue.component("packaging-qty-picker", {
187
+ mixins: [PackagingQtyPickerMixin],
188
+ props: {
189
+ readonly: Boolean,
190
+ qtyTodo: {type: Number, required: true},
191
+ pkgNameKey: {default: "name"},
192
+ },
193
+ data: function () {
194
+ return {
195
+ panel: 0, // expand panel by default
196
+ };
197
+ },
198
+ watch: {
199
+ qty_by_pkg: {
200
+ deep: true,
201
+ handler: function () {
202
+ // prevent watched qty to update again qty_by_pkg
203
+ this.qty_by_pkg_manual = true;
204
+ this.compute_qty();
205
+ this.qty_by_pkg_manual = false;
206
+ },
207
+ },
208
+ },
209
+ created: function () {
210
+ // Propagate the newly initialized quantity to the parent component
211
+ this.$root.trigger("qty_edit", this.qty);
212
+ },
213
+ updated: function () {
214
+ this.$root.trigger("qty_edit", this.qty);
215
+ },
216
+ computed: {
217
+ qty_color: function () {
218
+ if (this.qty == this.qtyTodo) {
219
+ if (this.readonly) return "";
220
+ return "background-color: rgb(143, 191, 68)";
221
+ }
222
+ if (this.qty > this.qtyTodo) {
223
+ return "background-color: orangered";
224
+ }
225
+ return "background-color: pink";
226
+ },
227
+ qty_todo_by_pkg: function () {
228
+ // Used to calculate the qty needed of each package type
229
+ // based on the qty todo.
230
+ let total_qty_todo = this.qtyTodo;
231
+ const res = {};
232
+ this.sorted_packaging.forEach((pkg) => {
233
+ let pkg_units = 0;
234
+ while (pkg.qty <= total_qty_todo) {
235
+ pkg_units++;
236
+ total_qty_todo -= pkg.qty;
237
+ }
238
+ res[pkg.id] = pkg_units;
239
+ });
240
+ return res;
241
+ },
242
+ },
243
+ template: `
244
+ <div :class="[$options._componentTag, mode ? 'mode-' + mode : '']">
245
+ <v-expansion-panels flat v-model="panel">
246
+ <v-expansion-panel>
247
+ <v-expansion-panel-header expand-icon="mdi-menu-down">
248
+ <v-row dense align="center">
249
+ <v-col cols="5" md="3">
250
+ <input type="number" v-model="qty" class="qty-done" :style="qty_color"
251
+ v-on:click.stop
252
+ :readonly="readonly"
253
+ />
254
+ </v-col>
255
+ <v-col cols="3" md="2" v-if="!readonly">
256
+ <span class="qty-todo">/ {{ qtyTodo }}</span>
257
+ </v-col>
258
+ <v-col>
259
+ {{ unit_uom.name }}
260
+ </v-col>
261
+ </v-row>
262
+ </v-expansion-panel-header>
263
+ <v-expansion-panel-content v-if="sorted_packaging.length > 1">
264
+ <v-row dense
265
+ align="center"
266
+ v-for="(pkg, index) in sorted_packaging"
267
+ :key="make_component_key([pkg.id])"
268
+ :class="(readonly && !qty_by_pkg[pkg.id]) ? 'd-none' : ''"
269
+ >
270
+ <v-col cols="4" md="2">
271
+ <input type="text" inputmode="decimal" class="qty-done"
272
+ v-model.lazy="qty_by_pkg[pkg.id]"
273
+ :data-origvalue="qty_by_pkg[pkg.id]"
274
+ :data-pkg="JSON.stringify(pkg)"
275
+ :readonly="readonly"
276
+ @focus="!readonly && ($event.target.value='')"
277
+ @blur="$event.target.value=qty_by_pkg[pkg.id]"
278
+ />
279
+ </v-col>
280
+ <v-col cols="2" md="2" v-if="!readonly">
281
+ <span class="qty-todo">/ {{ qty_todo_by_pkg[pkg.id] }}</span>
282
+ </v-col>
283
+ <v-col>
284
+ <div class="pkg-name"> {{ pkg[pkgNameKey] }}</div>
285
+ <div v-if="contained_packaging[pkg.id]" class="pkg-qty">(x{{ contained_packaging[pkg.id].qty }} {{ contained_packaging[pkg.id].pkg.name }})</div>
286
+ </v-col>
287
+ </v-row>
288
+ </v-expansion-panel-content>
289
+ </v-expansion-panel>
290
+ </v-expansion-panels>
291
+ </div>
292
+ `,
293
+ });
294
+
295
+ export var PackagingQtyPickerDisplay = Vue.component("packaging-qty-picker-display", {
296
+ mixins: [PackagingQtyPickerMixin],
297
+ props: {
298
+ nonZeroOnly: Boolean,
299
+ pkgNameKey: {default: "code"},
300
+ },
301
+ methods: {
302
+ display_pkg: function (pkg) {
303
+ return this.nonZeroOnly ? this.qty_by_pkg[pkg.id] > 0 : true;
304
+ },
305
+ },
306
+ computed: {
307
+ visible_packaging: function () {
308
+ let packagings = _.filter(this.sorted_packaging, this.display_pkg);
309
+ // Do not display if only uom packaging
310
+ if (
311
+ packagings.length == 1 &&
312
+ packagings[0].id.toString().startsWith("uom-")
313
+ )
314
+ return [];
315
+ return packagings;
316
+ },
317
+ },
318
+ template: `
319
+ <div :class="[$options._componentTag, mode ? 'mode-' + mode: '', 'd-inline']">
320
+ <span class="min-unit">{{ qty }} {{ unit_uom.name }}</span>
321
+ <span class="packaging" v-for="(pkg, index) in visible_packaging" :key="make_component_key([pkg.id])">
322
+ <span v-if="index == 0">(</span>
323
+ <span class="pkg-qty" v-text="qty_by_pkg[pkg.id]" />
324
+ <span class="pkg-name" v-text="pkg[pkgNameKey] || unit_uom.name" /><span class="sep" v-if="index != Object.keys(visible_packaging).length - 1"> + </span>
325
+ <span v-if="index == visible_packaging.length - 1">)</span>
326
+ </span>
327
+ </div>
328
+ `,
329
+ });