odoo-addon-shopfloor-delivery-shipment-mobile 18.0.1.0.0.3__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.
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Copyright 2021 Camptocamp SA (http://www.camptocamp.com)
3
+ * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
4
+ */
5
+
6
+ import {demotools} from "/shopfloor_mobile_base/static/src/demo/demo.core.esm.js";
7
+
8
+ const pick = demotools.makePicking();
9
+ const pack = demotools.makePack();
10
+ const shipment = {
11
+ id: 1,
12
+ name: "SA/OUT/0000001",
13
+ state: "confirmed",
14
+ dock: {
15
+ id: 1,
16
+ name: "Dock 01",
17
+ },
18
+ };
19
+
20
+ const lading = [];
21
+ for (let i = 0; i < 5; i++) {
22
+ lading.push(
23
+ demotools.makePicking({
24
+ load_state: _.sample(["all", "partial", "none"]),
25
+ loaded_pickings_count: _.random(0, 5),
26
+ loaded_packages_count: _.random(0, 5),
27
+ total_packages_count: _.random(5, 10),
28
+ loaded_bulk_lines_count: _.random(0, 5),
29
+ total_bulk_lines_count: _.random(5, 10),
30
+ loaded_weight: _.random(100, 500),
31
+ })
32
+ );
33
+ }
34
+
35
+ const on_dock = [];
36
+ for (let i = 0; i < 4; i++) {
37
+ on_dock.push(
38
+ demotools.makePicking({
39
+ load_state: _.sample(["all", "partial", "none"]),
40
+ loaded_pickings_count: _.random(0, 5),
41
+ loaded_packages_count: _.random(0, 5),
42
+ total_packages_count: _.random(5, 10),
43
+ loaded_bulk_lines_count: _.random(0, 5),
44
+ total_bulk_lines_count: _.random(5, 10),
45
+ loaded_weight: _.random(100, 500),
46
+ })
47
+ );
48
+ }
49
+
50
+ const DELIVERY_SHIPMENT_CASE = {
51
+ scan_dock: {
52
+ next_state: "scan_document",
53
+ data: {
54
+ scan_document: {
55
+ shipment_advice: _.cloneDeep(shipment),
56
+ },
57
+ },
58
+ },
59
+ scan_document: {
60
+ OP1: {
61
+ next_state: "scan_document",
62
+ message: {
63
+ message_type: "info",
64
+ body: "Operation found",
65
+ },
66
+ data: {
67
+ scan_document: {
68
+ picking: _.cloneDeep(pick),
69
+ shipment_advice: _.cloneDeep(shipment),
70
+ },
71
+ },
72
+ },
73
+ PACK1: {
74
+ next_state: "scan_document",
75
+ message: {
76
+ message_type: "info",
77
+ body: "Pack found",
78
+ },
79
+ data: {
80
+ scan_document: {
81
+ shipment_advice: _.cloneDeep(shipment),
82
+ packaging: _.cloneDeep(pack),
83
+ },
84
+ },
85
+ },
86
+ next_state: "loading_list",
87
+ data: {
88
+ loading_list: {
89
+ shipment_advice: _.cloneDeep(shipment),
90
+ lading: lading,
91
+ on_dock: on_dock,
92
+ },
93
+ },
94
+ },
95
+ loading_list: {
96
+ next_state: "validate",
97
+ data: {
98
+ validate: {
99
+ shipment_advice: _.cloneDeep(shipment),
100
+ lading: lading[0],
101
+ loaded_weight: 100,
102
+ on_dock: on_dock,
103
+ },
104
+ },
105
+ },
106
+ // TODO: this should be improved to use real data from submitted request.
107
+ validate: {
108
+ next_state: "scan_dock",
109
+ message: {
110
+ message_type: "info",
111
+ body: "Shipment ABC has been validated",
112
+ },
113
+ data: {
114
+ scan_dock: {},
115
+ },
116
+ },
117
+ };
118
+
119
+ const menuitem_id = demotools.addAppMenu(
120
+ {
121
+ name: "Delivery shipment 1",
122
+ scenario: "delivery_shipment",
123
+ picking_types: [{id: 27, name: "Random type"}],
124
+ },
125
+ "delivery_shipment_1"
126
+ );
127
+
128
+ demotools.add_case("delivery_shipment", menuitem_id, DELIVERY_SHIPMENT_CASE);
@@ -0,0 +1,536 @@
1
+ /**
2
+ * Copyright 2021 Camptocamp SA (http://www.camptocamp.com)
3
+ * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
4
+ */
5
+
6
+ import {ScenarioBaseMixin} from "/shopfloor_mobile_base/static/src/scenario/mixins.esm.js";
7
+ import {process_registry} from "/shopfloor_mobile_base/static/src/services/process_registry.esm.js";
8
+
9
+ const DeliveryShipment = {
10
+ mixins: [ScenarioBaseMixin],
11
+ template: `
12
+ <Screen :screen_info="screen_info">
13
+ <template v-slot:header>
14
+ <state-display-info :info="state.display_info" v-if="state.display_info"/>
15
+ </template>
16
+ <searchbar
17
+ v-if="state.on_scan"
18
+ v-on:found="on_scan"
19
+ :input_placeholder="search_input_placeholder"
20
+ />
21
+
22
+ <v-row align="center" v-if="state_is('loading_list')">
23
+ <v-col class="text-center" cols="12">
24
+ <v-btn-toggle mandatory v-model="filter_state">
25
+ <v-btn active-class="success" value="lading" >
26
+ Lading
27
+ </v-btn>
28
+ <v-btn active-class="warning" value="dock">
29
+ On Dock
30
+ </v-btn>
31
+ </v-btn-toggle>
32
+ </v-col>
33
+ </v-row>
34
+
35
+ <detail-picking
36
+ v-if="state_is('loading_list')"
37
+ v-for="picking in filter_pickings(pickings())"
38
+ :record="picking"
39
+ :options="picking_options(picking)"
40
+ :key="make_component_key(['shipment-picking', picking.id])"
41
+ :card_color="utils.colors.color_for(operation_color(picking, shipment()))"
42
+ />
43
+
44
+ <item-detail-card
45
+ v-if="state_is('scan_document')"
46
+ :key="make_state_component_key(['delivery-shipment-dock', state.data.shipment_advice.dock.id])"
47
+ :record="state.data.shipment_advice.dock"
48
+ :options="{main: true, key_title: 'name'}"
49
+ :card_color="utils.colors.color_for('screen_step_done')"
50
+ />
51
+
52
+ <item-detail-card
53
+ v-if="state_is('scan_document') && !_.isEmpty(filter_location())"
54
+ :key="make_state_component_key(['delivery-shipment-location', filter_location().id])"
55
+ :record="state.data.location"
56
+ :options="{title_icon: 'mdi-filter-outline', main: true, key_title: 'name'}"
57
+ :card_color="utils.colors.color_for('screen_step_done')"
58
+ />
59
+
60
+ <item-detail-card
61
+ v-if="!_.isEmpty(picking())"
62
+ :key="make_state_component_key(['delivery-shipment-pick', picking().id])"
63
+ :record="picking()"
64
+ :options="{main: true, title_icon: 'mdi-filter-outline', key_title: 'name', title_action_field: {path: 'name', action_val_path: 'name'}}"
65
+ :card_color="utils.colors.color_for('screen_step_done')"
66
+ />
67
+
68
+
69
+ <div v-if="state_is('scan_document')" v-for="(value, name, index) in this.state.data.content">
70
+ <v-card color="blue lighten-1" class="detail v-card mt-5 main mb-2">
71
+ <v-card-title>{{ name }}</v-card-title>
72
+ <v-card-subtitle> {{ package_level_process(value.package_levels) }}</v-card-subtitle>
73
+ </v-card>
74
+ <item-detail-card
75
+ v-for="packlevel in value.package_levels"
76
+ :key="make_state_component_key(['shipment-pack', packlevel.id])"
77
+ :record="packlevel.package_src"
78
+ :options="pack_options(packlevel)"
79
+ :card_color="pack_color(packlevel)"
80
+ />
81
+ <item-detail-card
82
+ v-for="line in value.move_lines"
83
+ :key="make_state_component_key(['shipment-product', line.product.id])"
84
+ :record="line"
85
+ :options="line_options(line)"
86
+ :card_color="line_color(line)"
87
+ />
88
+ </div>
89
+
90
+ <v-card color="warning" class="detail v-card main mb-2" v-if="state_is('validate')">
91
+ <v-card-title>Are you sure you want to close the shipment ?</v-card-title>
92
+ </v-card>
93
+ <item-detail-card v-if="state_is('validate')"
94
+ :key="make_state_component_key(['lading-detail'])"
95
+ :record="state.data.lading"
96
+ :card_color="utils.colors.color_for(shipment_summary_color(state.data.lading))"
97
+ :options="lading_summary_options(state.data.lading)"
98
+ >
99
+ <template v-slot:title>
100
+ <span>Lading</span>
101
+ </template>
102
+ </item-detail-card>
103
+ <item-detail-card v-if="state_is('validate')"
104
+ :key="make_state_component_key(['dock-detail'])"
105
+ :record="state.data.on_dock"
106
+ :card_color="utils.colors.color_for(shipment_summary_color(state.data.on_dock))"
107
+ :options="ondock_summary(state.data.on_dock)"
108
+ >
109
+ <template v-slot:title>
110
+ <span>On Dock</span>
111
+ </template>
112
+ </item-detail-card>
113
+
114
+
115
+ <div class="button-list button-vertical-list full" v-if="!state_is('scan_dock')">
116
+ <v-row align="center" v-if="state_is('scan_document')">
117
+ <v-col class="text-center" cols="12">
118
+ <btn-action color="default" @click="state.on_go2loading_list">Shipment Advice</btn-action>
119
+ </v-col>
120
+ </v-row>
121
+ <v-row align="center" v-if="state_is('loading_list')">
122
+ <v-col class="text-center" cols="12">
123
+ <btn-action color="default" @click="state.on_close_shipment">Close Shipment</btn-action>
124
+ </v-col>
125
+ </v-row>
126
+ <v-row align="center" v-if="state_is('validate')">
127
+ <v-col class="text-center" cols="12">
128
+ <btn-action color="default" @click="state.on_confirm">Confirm</btn-action>
129
+ </v-col>
130
+ </v-row>
131
+ <v-row align="center">
132
+ <v-col class="text-center" cols="12">
133
+ <btn-action color="default" @click="state.on_back">{{ btn_back_label() }}</btn-action>
134
+ </v-col>
135
+ </v-row>
136
+ </div>
137
+
138
+ </Screen>
139
+ `,
140
+ methods: {
141
+ screen_title: function () {
142
+ if (_.isEmpty(this.state.data.shipment_advice)) {
143
+ return this.menu_item().name;
144
+ }
145
+ return this.state.data.shipment_advice.name;
146
+ },
147
+ btn_back_label: function () {
148
+ return this.state.button_back_label || "Back";
149
+ },
150
+ // The current picking
151
+ picking: function (from_state = "") {
152
+ var data = {};
153
+ if (from_state) {
154
+ data = this.state_get_data(from_state);
155
+ } else {
156
+ data = this.state.data;
157
+ }
158
+ if (_.isEmpty(data.picking)) {
159
+ return {};
160
+ }
161
+ return data.picking;
162
+ },
163
+ filter_location: function (from_state = "") {
164
+ var data = {};
165
+ if (from_state) {
166
+ data = this.state_get_data(from_state);
167
+ } else {
168
+ data = this.state.data;
169
+ }
170
+ if (_.isEmpty(data.location)) {
171
+ return {};
172
+ }
173
+ return data.location;
174
+ },
175
+ pickings: function () {
176
+ if (this.filter_state === "dock" && !_.isEmpty(this.state.data.on_dock)) {
177
+ return this.state.data.on_dock;
178
+ } else if (
179
+ this.filter_state === "lading" &&
180
+ !_.isEmpty(this.state.data.lading)
181
+ ) {
182
+ return this.state.data.lading;
183
+ }
184
+ return {};
185
+ },
186
+ filter_pickings: function (pickings) {
187
+ let res = pickings;
188
+ const nameFilter = this.state.data.filter_name;
189
+ if (nameFilter) {
190
+ res = _.filter(pickings, (pick) => {
191
+ return pick.name.indexOf(nameFilter) >= 0;
192
+ });
193
+ }
194
+ return res;
195
+ },
196
+ shipment: function () {
197
+ if (_.isEmpty(this.state.data.shipment_advice)) {
198
+ return {};
199
+ }
200
+ return this.state.data.shipment_advice;
201
+ },
202
+ picking_options: function (picking) {
203
+ return {
204
+ main: true,
205
+ key_title: "name",
206
+ title_action_icon: "mdi-help-circle",
207
+ on_title_action: this.state.on_back2picking,
208
+ theme_dark: this.operation_color(picking, this.shipment()) === "error",
209
+ fields: [
210
+ {path: "carrier.name", label: "Carrier"},
211
+ {
212
+ path: this._picking_options_package_path(picking),
213
+ label: "Packages",
214
+ },
215
+ {
216
+ path: this._picking_options_move_line_path(picking),
217
+ label: "Lines",
218
+ },
219
+ ],
220
+ };
221
+ },
222
+ _picking_options_package_path: function (picking) {
223
+ return this.filter_state === "lading" &&
224
+ picking.loaded_packages_progress_f < 1
225
+ ? "loaded_packages_progress"
226
+ : "package_level_count";
227
+ },
228
+ _picking_options_move_line_path: function (picking) {
229
+ return this.filter_state === "lading" &&
230
+ picking.loaded_move_lines_progress_f < 1
231
+ ? "loaded_move_lines_progress"
232
+ : "move_line_count";
233
+ },
234
+ pack_options: function (pack) {
235
+ const action = pack.is_done
236
+ ? () => {
237
+ this.unload_pack(pack);
238
+ }
239
+ : null;
240
+ return {
241
+ key_title: "name",
242
+ on_title_action: action,
243
+ title_action_icon: "mdi-upload",
244
+ // This helps having the transporter package fit in one line
245
+ title_class: "v-card__title-smaller",
246
+ };
247
+ },
248
+ pack_color: function (pack) {
249
+ const color = pack.is_done ? "screen_step_done" : "screen_step_todo";
250
+ return this.utils.colors.color_for(color);
251
+ },
252
+ package_level_process(package_levels) {
253
+ const package_level_count = package_levels.length;
254
+ const done_package_level_count = package_levels.reduce((acc, next) => {
255
+ return next.is_done ? acc + 1 : acc;
256
+ }, 0);
257
+ return `Progress: ${done_package_level_count} / ${package_level_count}`;
258
+ },
259
+ line_options: function (line) {
260
+ const action =
261
+ line.qty_done === line.quantity
262
+ ? () => {
263
+ this.unload_line(line);
264
+ }
265
+ : null;
266
+ return {
267
+ key_title: "product.display_name",
268
+ on_title_action: action,
269
+ title_action_icon: "mdi-upload",
270
+ fields: [
271
+ {path: "lot.name", label: "Lot"},
272
+ {path: "quantity", label: "Qty"},
273
+ ],
274
+ };
275
+ },
276
+ line_color: function (line) {
277
+ const color =
278
+ line.qty_done >= line.quantity
279
+ ? "screen_step_done"
280
+ : "screen_step_todo";
281
+ return this.utils.colors.color_for(color);
282
+ },
283
+ unload_line: function (line) {
284
+ this.wait_call(
285
+ this.odoo.call("unload_move_line", {
286
+ shipment_advice_id: this.shipment().id,
287
+ move_line_id: line.id,
288
+ location_id: this.filter_location().id,
289
+ })
290
+ );
291
+ },
292
+ unload_pack: function (pack) {
293
+ this.wait_call(
294
+ this.odoo.call("unload_package_level", {
295
+ shipment_advice_id: this.shipment().id,
296
+ package_level_id: pack.id,
297
+ location_id: this.filter_location().id,
298
+ })
299
+ );
300
+ },
301
+ operation_color: function (pick, shipment) {
302
+ var color = "";
303
+ if (pick.is_fully_loaded) {
304
+ color = "success";
305
+ } else if (pick.is_partially_loaded) {
306
+ color = "warning";
307
+ } else if (shipment.is_planned) {
308
+ color = "error";
309
+ } else {
310
+ color = "success";
311
+ }
312
+ return color;
313
+ },
314
+ loaded_total: function (data, key) {
315
+ // Return two value formatted has a fraction
316
+ return (
317
+ data["loaded_" + key].toString() + "/" + data["total_" + key].toString()
318
+ );
319
+ },
320
+ is_fully_loaded: function (data) {
321
+ // Check if the summary from last screen is fully loaded
322
+ // May need to know if planned or not
323
+ if (data.total_packages_count !== data.loaded_packages_count) {
324
+ return false;
325
+ } else if (data.total_bulk_lines_count !== data.loaded_bulk_lines_count) {
326
+ return false;
327
+ }
328
+ return true;
329
+ },
330
+ lading_summary_options: function (data) {
331
+ return {
332
+ theme_dark: this.shipment_summary_color(data) === "error",
333
+ fields: [
334
+ {
335
+ path: "dummy",
336
+ renderer: () => {
337
+ return this.state.data.shipment_advice.dock.name;
338
+ },
339
+ label: "Loading dock",
340
+ display_no_value: true,
341
+ },
342
+ {
343
+ path: "loaded_pickings_count",
344
+ label: "Deliveries",
345
+ display_no_value: true,
346
+ },
347
+ {
348
+ path: "dummy",
349
+ renderer: () => {
350
+ return this.loaded_total(data, "packages_count");
351
+ },
352
+ label: "Packages",
353
+ display_no_value: true,
354
+ },
355
+ {
356
+ path: "dummy",
357
+ renderer: () => {
358
+ return this.loaded_total(data, "bulk_lines_count");
359
+ },
360
+ label: "Bulk moves",
361
+ display_no_value: true,
362
+ },
363
+ {
364
+ path: "loaded_weight",
365
+ label: "Total load (Kg)",
366
+ display_no_value: true,
367
+ },
368
+ ],
369
+ };
370
+ },
371
+ ondock_summary: function (data) {
372
+ return {
373
+ theme_dark: this.shipment_summary_color(data) === "error",
374
+ fields: [
375
+ {
376
+ path: "total_pickings_count",
377
+ label: "Deliveries",
378
+ display_no_value: true,
379
+ },
380
+ {
381
+ path: "total_packages_count",
382
+ label: "Package",
383
+ display_no_value: true,
384
+ },
385
+ {
386
+ path: "total_bulk_lines_count",
387
+ label: "Bulk moves",
388
+ display_no_value: true,
389
+ },
390
+ ],
391
+ };
392
+ },
393
+ shipment_summary_color: function (data) {
394
+ const isLading = "loaded_weight" in data;
395
+ var color = "";
396
+ if (isLading) {
397
+ if (this.is_fully_loaded(data)) {
398
+ color = "screen_step_done";
399
+ } else {
400
+ color = "warning";
401
+ }
402
+ } else if (data.total_pickings_count > 0) {
403
+ color = "error";
404
+ } else {
405
+ color = "success";
406
+ }
407
+ return color;
408
+ },
409
+ },
410
+ data: function () {
411
+ return {
412
+ usage: "delivery_shipment",
413
+ initial_state_key: "scan_dock",
414
+ filter_state: "dock",
415
+ states: {
416
+ scan_dock: {
417
+ display_info: {
418
+ title: "Start by scanning a dock",
419
+ scan_placeholder: "Scan dock",
420
+ },
421
+ on_scan: (scanned) => {
422
+ this.wait_call(
423
+ this.odoo.call("scan_dock", {
424
+ barcode: scanned.text,
425
+ confirmation:
426
+ this.state.data.confirmation_required || "",
427
+ })
428
+ );
429
+ },
430
+ },
431
+ scan_document: {
432
+ display_info: {
433
+ title: "Scan some shipment's content or a location",
434
+ scan_placeholder: () => {
435
+ if (_.isEmpty(this.picking())) {
436
+ return "Scan a lot, a pack, an operation or a location";
437
+ }
438
+ return "Scan a lot, a product, a pack, an operation or a location";
439
+ },
440
+ },
441
+ on_scan: (scanned) => {
442
+ const data = {
443
+ barcode: scanned.text,
444
+ shipment_advice_id: this.shipment().id,
445
+ picking_id: this.picking().id,
446
+ location_id: this.filter_location().id,
447
+ };
448
+ this.wait_call(this.odoo.call("scan_document", data));
449
+ },
450
+ on_back: () => {
451
+ if (
452
+ _.isEmpty(this.filter_location()) &&
453
+ _.isEmpty(this.picking())
454
+ ) {
455
+ // No filtering back to scan_dock
456
+ this.wait_call(this.odoo.call("scan_dock", {barcode: ""}));
457
+ } else {
458
+ // Stay on scan_document but without filtering
459
+ this.wait_call(
460
+ this.odoo.call("scan_document", {
461
+ barcode: "",
462
+ shipment_advice_id: this.shipment().id,
463
+ })
464
+ );
465
+ }
466
+ },
467
+ on_go2loading_list: () => {
468
+ this.wait_call(
469
+ this.odoo.call("loading_list", {
470
+ shipment_advice_id: this.shipment().id,
471
+ })
472
+ );
473
+ },
474
+ },
475
+ loading_list: {
476
+ display_info: {
477
+ title: "Filter documents",
478
+ scan_placeholder: "Scan a document number",
479
+ },
480
+ on_scan: (scanned) => {
481
+ this.state_set_data({filter_name: scanned.text});
482
+ },
483
+ on_back: () => {
484
+ const data = {
485
+ barcode: "",
486
+ shipment_advice_id: this.shipment().id,
487
+ picking_id: this.picking("scan_document").id,
488
+ location_id: this.filter_location("scan_document").id,
489
+ };
490
+ this.wait_call(this.odoo.call("scan_document", data));
491
+ },
492
+ on_back2picking: (picking) => {
493
+ this.wait_call(
494
+ this.odoo.call("scan_document", {
495
+ barcode: picking.name,
496
+ shipment_advice_id: this.shipment().id,
497
+ })
498
+ );
499
+ },
500
+ on_close_shipment: () => {
501
+ this.wait_call(
502
+ this.odoo.call("validate", {
503
+ shipment_advice_id: this.shipment().id,
504
+ })
505
+ );
506
+ },
507
+ },
508
+ validate: {
509
+ display_info: {
510
+ title: "Shipment closure confirmation",
511
+ },
512
+ button_back_label: "Cancel",
513
+ on_back: () => {
514
+ this.wait_call(
515
+ this.odoo.call("loading_list", {
516
+ shipment_advice_id: this.shipment().id,
517
+ })
518
+ );
519
+ },
520
+ on_confirm: () => {
521
+ this.wait_call(
522
+ this.odoo.call("validate", {
523
+ shipment_advice_id: this.shipment().id,
524
+ confirmation: true,
525
+ })
526
+ );
527
+ },
528
+ },
529
+ },
530
+ };
531
+ },
532
+ };
533
+
534
+ process_registry.add("delivery_shipment", DeliveryShipment);
535
+
536
+ export default DeliveryShipment;