合并企业版代码(未测试,先提交到测试分支)
BIN
mrp_workorder/static/img/qp_stool_1.jpeg
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
mrp_workorder/static/img/qp_stool_2.jpeg
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
mrp_workorder/static/img/qp_stool_3.jpeg
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
mrp_workorder/static/img/qp_stool_5.jpeg
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
mrp_workorder/static/img/steps.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
mrp_workorder/static/img/stool.jpeg
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
mrp_workorder/static/img/stool_foot.jpeg
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
mrp_workorder/static/img/stool_top.jpeg
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
mrp_workorder/static/img/workorder.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
60
mrp_workorder/static/src/components/menuPopup.js
Normal file
@@ -0,0 +1,60 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
const { Component } = owl;
|
||||
|
||||
class MenuPopup extends Component {
|
||||
|
||||
setup() {
|
||||
this.rpc = useService('rpc');
|
||||
this.orm = useService('orm');
|
||||
this.action = useService('action');
|
||||
}
|
||||
|
||||
get step() {
|
||||
return this.props.popupData.selectedStepId;
|
||||
}
|
||||
|
||||
get title() {
|
||||
return this.props.popupData.title;
|
||||
}
|
||||
|
||||
block() {
|
||||
const options = {
|
||||
additionalContext: { default_workcenter_id: this.props.popupData.workcenterId },
|
||||
onClose: this.props.onClose,
|
||||
};
|
||||
this.props.onClosePopup('menu');
|
||||
this.action.doAction('mrp.act_mrp_block_workcenter_wo', options);
|
||||
}
|
||||
|
||||
async callAction(method) {
|
||||
const action = await this.orm.call(
|
||||
'mrp.workorder',
|
||||
method,
|
||||
[[this.props.popupData.workorderId]]
|
||||
);
|
||||
this.props.onClosePopup('menu');
|
||||
this.action.doAction(action, { onClose: this.props.onClose });
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.props.onClosePopup('menu');
|
||||
}
|
||||
|
||||
async proposeChange(changeType, title, message) {
|
||||
const action = await this.orm.call(
|
||||
'mrp.workorder',
|
||||
'action_propose_change',
|
||||
[[this.props.popupData.workorderId], changeType, title],
|
||||
);
|
||||
this.props.onClosePopup('menu');
|
||||
this.action.doAction(action, { onClose: () => {
|
||||
this.props.onClose(message);
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
MenuPopup.template = 'mrp_workorder.MenuPopup';
|
||||
|
||||
export default MenuPopup;
|
||||
29
mrp_workorder/static/src/components/menuPopup.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="mrp_workorder.MenuPopup" owl="1">
|
||||
<div role="dialog" class="popup">
|
||||
<div class="popup-selection">
|
||||
<header class="title h3" t-esc="title"/>
|
||||
<div class="button_list">
|
||||
<button class="btn btn-danger text-uppercase" t-on-click="block">Block</button>
|
||||
<button class="btn btn-primary" t-on-click="() => this.callAction('button_scrap')">Scrap</button>
|
||||
<button class="btn btn-primary" t-on-click="() => this.callAction('action_add_component')">Add Component</button>
|
||||
<button class="btn btn-primary" t-on-click="() => this.callAction('action_add_byproduct')" name="addByProduct">Add By-product</button>
|
||||
|
||||
<span><h2>Suggest a Worksheet improvement</h2></span>
|
||||
<t t-if="step">
|
||||
<button class="btn btn-primary" t-on-click="() => this.proposeChange('update_step', 'Update Instructions', 'Your New Instructions feedback was created')">Update Instructions</button>
|
||||
<button class="btn btn-primary" t-on-click="() => this.proposeChange('remove_step', 'Remove Step', 'Your feedback to delete this step was created')">Delete this Step</button>
|
||||
</t>
|
||||
<button class="btn btn-primary" t-on-click="() => this.callAction('action_add_step')">Add a Step</button>
|
||||
<t t-if="step">
|
||||
<button class="btn btn-primary" t-on-click="() => this.proposeChange('set_picture', 'Change Picture', '')">Set a New picture</button>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="footer">
|
||||
<div class="btn cancel text-uppercase" t-on-click="cancel">Cancel</div>
|
||||
</footer>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
163
mrp_workorder/static/src/components/popup.scss
Normal file
@@ -0,0 +1,163 @@
|
||||
$wo-border-separation: $border-width solid $border-color;
|
||||
|
||||
.o_tablet_popups {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
background-color: rgba(60, 60, 60, 0.4);
|
||||
|
||||
&.o_unblock {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.popup {
|
||||
z-index: 1200;
|
||||
top: 5%;
|
||||
left: 25%;
|
||||
width: 50%;
|
||||
position: fixed;
|
||||
background: $o-gray-200;
|
||||
|
||||
button{
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
button:hover{
|
||||
background: default;
|
||||
}
|
||||
|
||||
.body.traceback {
|
||||
height: 238px;
|
||||
overflow: auto;
|
||||
font-size: 14px;
|
||||
white-space: pre-wrap;
|
||||
text-align: left;
|
||||
font-family: 'Inconsolata';
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.centered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: auto;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.title {
|
||||
padding: 20px;
|
||||
border-bottom: $wo-border-separation;
|
||||
font-size: large;
|
||||
color: $o-black;
|
||||
}
|
||||
|
||||
.footer {
|
||||
border-top: $wo-border-separation;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.btn {
|
||||
min-width: 40%;
|
||||
line-height: 80px;
|
||||
margin: 5px;
|
||||
font-size: 1.3em;
|
||||
flex: 1 1;
|
||||
|
||||
cursor: pointer;
|
||||
&:not(.btn-link) {
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn_employee {
|
||||
background-color: $o-gray-100;
|
||||
}
|
||||
|
||||
.button_list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
padding: 5px;
|
||||
|
||||
span {
|
||||
margin-top: 1.5em;
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
text-align: center;
|
||||
align-content: center;
|
||||
h2 {
|
||||
color: $o-black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.popup-selection .selection {
|
||||
overflow-y: auto;
|
||||
max-height: 273px;
|
||||
font-size: 16px;
|
||||
width: auto;
|
||||
line-height: 50px;
|
||||
|
||||
&-item {
|
||||
background: rgb(230,230,230);
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
padding: 0px 16px;
|
||||
color: $o-black;
|
||||
|
||||
&:nth-child(odd) {
|
||||
background: $o-tooltip-title-background-color;
|
||||
}
|
||||
&.selected {
|
||||
background: #6EC89B;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popup-input {
|
||||
width: 70%;
|
||||
margin: 20px auto;
|
||||
padding: 10px;
|
||||
box-shadow: 0px 0px 0px 3px #6ec89b;
|
||||
min-height: 50px;
|
||||
background: white;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
font-family: "Lato";
|
||||
}
|
||||
|
||||
|
||||
.popup-numpad {
|
||||
width: 70%;
|
||||
margin: 12px auto;
|
||||
|
||||
button {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
border-radius: 25px;
|
||||
margin: 4px;
|
||||
border: 1px solid #cacaca;
|
||||
background: none;
|
||||
font-size: large;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.footer > div {
|
||||
color: $o-black;
|
||||
}
|
||||
}
|
||||
29
mrp_workorder/static/src/components/step.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
class StepComponent extends Component {
|
||||
get stepClass() {
|
||||
if (this.props.step.id === this.props.selectedStepId) {
|
||||
return "o_selected o_highlight";
|
||||
} else if (this.props.step.is_deleted) {
|
||||
return "o_deleted";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
selectStep() {
|
||||
this.props.onSelectStep(this.props.step.id);
|
||||
}
|
||||
|
||||
get title() {
|
||||
return this.props.step.title || this.props.step.test_type;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StepComponent.template = 'mrp_workorder.StepComponent';
|
||||
StepComponent.props = ["step", "onSelectStep", "selectedStepId"];
|
||||
|
||||
export default StepComponent;
|
||||
96
mrp_workorder/static/src/components/step.scss
Normal file
@@ -0,0 +1,96 @@
|
||||
.o_tablet_client_action .o_tablet_timeline {
|
||||
border-right: 1px solid #c9ccd2;
|
||||
.o_tablet_step {
|
||||
flex: 0 0 auto;
|
||||
border-bottom: 1px solid #c9ccd2;
|
||||
background: none;
|
||||
padding: 2rem;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&_details {
|
||||
.fa:first-child {
|
||||
opacity: 0.5;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&.o_faulty {
|
||||
background-color: rgba(map-get($theme-colors, 'danger'), 0.25);
|
||||
|
||||
&.o_selected {
|
||||
box-shadow: inset 0px 0px 0px 3px map-get($theme-colors, 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
&.o_line_completed {
|
||||
background: #F5F5F5;
|
||||
}
|
||||
|
||||
&.o_selected {
|
||||
background-color: #f4a46057;
|
||||
}
|
||||
&.o_deleted {
|
||||
background-color: #a3363657;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.o_barcode_product_ref {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.o_line_button {
|
||||
min-width: 60px;
|
||||
height: 60px;
|
||||
padding: 0 8px;
|
||||
border-radius: 8px;
|
||||
line-height: 16px;
|
||||
font-size: 16px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-transform: none;
|
||||
|
||||
&.o_shortcut_displayed {
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
&.btn-secondary {
|
||||
@include o-hover-opacity();
|
||||
}
|
||||
|
||||
&.o_set {
|
||||
border: 4px solid $o-brand-primary;
|
||||
color: $o-brand-primary;
|
||||
|
||||
&.o_difference {
|
||||
color: orange;
|
||||
border-color: orange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_next_expected {
|
||||
color: #00A09D;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.o_step_title {
|
||||
color: white;
|
||||
}
|
||||
.o_tablet_quality_state {
|
||||
text-align: center;
|
||||
$color_by_state: ("to_do": "lightgray", "ok": "lightgreen", "fail": "lightcoral");
|
||||
|
||||
@each $state, $color in $color_by_state {
|
||||
.o_tablet_step_#{$state} {
|
||||
color: #{$color};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
mrp_workorder/static/src/components/step.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mrp_workorder.StepComponent" owl="1">
|
||||
<div class="list-group-item o_tablet_step" t-att-class="stepClass" t-on-click="selectStep">
|
||||
<div class="row">
|
||||
<div class="col-9 o_step_title">
|
||||
<t t-esc="title"/>
|
||||
</div>
|
||||
<div class="col-3 o_tablet_quality_state">
|
||||
<div t-if="props.step.quality_state == 'pass'" class="o_tablet_step_ok">
|
||||
<i class="fa fa-check"/>
|
||||
</div>
|
||||
<div t-elif="props.step.quality_state == 'fail'" class="o_tablet_step_fail" >
|
||||
<i class="fa fa-times"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
32
mrp_workorder/static/src/components/summary_step.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
const { onMounted, onWillStart, Component } = owl;
|
||||
|
||||
class SummaryStep extends Component {
|
||||
setup() {
|
||||
this.effect = useService('effect');
|
||||
this.orm = useService('orm');
|
||||
onMounted(() => this.makeRainbow());
|
||||
onWillStart(() => this.getData());
|
||||
}
|
||||
|
||||
async getData() {
|
||||
this.data = await this.orm.call(
|
||||
'mrp.workorder',
|
||||
'get_summary_data',
|
||||
[this.props.workorder]
|
||||
);
|
||||
}
|
||||
|
||||
makeRainbow() {
|
||||
if (this.data.show_rainbow) {
|
||||
this.effect.add({type: 'rainbow_man', fadeout: "fast"});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SummaryStep.template = 'mrp_workorder.SummaryStep';
|
||||
SummaryStep.props = ["workorder"];
|
||||
|
||||
export default SummaryStep;
|
||||
35
mrp_workorder/static/src/components/summary_step.scss
Normal file
@@ -0,0 +1,35 @@
|
||||
.o_tablet_client_action .o_tablet_summary {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 4em;
|
||||
|
||||
.o_tablet_summary_quality {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 1.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 50%;
|
||||
th,td {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
margin: 0 0.5em;
|
||||
font-size: 1.5em;
|
||||
color: yellow;
|
||||
}
|
||||
}
|
||||
28
mrp_workorder/static/src/components/summary_step.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mrp_workorder.SummaryStep" owl="1">
|
||||
<div class="o_tablet_summary">
|
||||
<h1>Good Job!</h1>
|
||||
<span> Completion Time: <t t-esc="data.duration" t-options='{"widget": "float_time"}'/> minutes</span>
|
||||
<t t-if="data.position <= 0">
|
||||
<span>Best Time! Congratulations!</span>
|
||||
</t>
|
||||
<t t-elif="data.position <= 4">
|
||||
<span>Wow, you made the the Top 5!</span>
|
||||
</t>
|
||||
<t t-elif="data.position <= 9">
|
||||
<span>Well done, you're in the Top 10!</span>
|
||||
</t>
|
||||
<div class="mt-5 o_tablet_summary_quality">
|
||||
<span>Quality</span>
|
||||
<t t-foreach="[...Array(data.quality_score).keys()]" t-key="i" t-as="i">
|
||||
<i class="fa fa-star"/>
|
||||
</t>
|
||||
<t t-foreach="[...Array(3 - data.quality_score).keys()]" t-key="i" t-as="i">
|
||||
<i class="fa fa-star-o"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
275
mrp_workorder/static/src/components/tablet.js
Normal file
@@ -0,0 +1,275 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useBus, useService } from "@web/core/utils/hooks";
|
||||
import { View } from "@web/views/view";
|
||||
import DocumentViewer from '@mrp_workorder/components/viewer';
|
||||
import StepComponent from '@mrp_workorder/components/step';
|
||||
import ViewsWidgetAdapter from '@mrp_workorder/components/views_widget_adapter';
|
||||
import MenuPopup from '@mrp_workorder/components/menuPopup';
|
||||
import SummaryStep from '@mrp_workorder/components/summary_step';
|
||||
|
||||
const { EventBus, useState, useEffect, onWillStart, Component, markup} = owl;
|
||||
|
||||
/**
|
||||
* Main Component
|
||||
* Gather the workorder and its quality check information.
|
||||
*/
|
||||
|
||||
class Tablet extends Component {
|
||||
//--------------------------------------------------------------------------
|
||||
// Lifecycle
|
||||
//--------------------------------------------------------------------------
|
||||
setup() {
|
||||
this.rpc = useService('rpc');
|
||||
this.orm = useService('orm');
|
||||
this.notification = useService('notification');
|
||||
this.state = useState({
|
||||
selectedStepId: 0,
|
||||
workingState: "",
|
||||
});
|
||||
|
||||
this.popup = useState({
|
||||
menu: {
|
||||
isShown: false,
|
||||
data: {},
|
||||
}
|
||||
});
|
||||
this.workorderId = this.props.action.context.active_id;
|
||||
this.additionalContext = this.props.action.context;
|
||||
this.workorderBus = new EventBus();
|
||||
useBus(this.workorderBus, "refresh", async () => {
|
||||
await this.getState();
|
||||
this.render();
|
||||
});
|
||||
useBus(this.workorderBus, "workorder_event", (ev) => {
|
||||
this[ev.detail]();
|
||||
});
|
||||
this.barcode = useService('barcode');
|
||||
useBus(this.barcode.bus, 'barcode_scanned', (event) => this._onBarcodeScanned(event.detail.barcode));
|
||||
onWillStart(async () => {
|
||||
await this._onWillStart();
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
this._scrollToHighlighted();
|
||||
});
|
||||
}
|
||||
|
||||
_scrollToHighlighted() {
|
||||
let selectedLine = document.querySelector('.o_tablet_timeline .o_tablet_step.o_selected');
|
||||
if (selectedLine) {
|
||||
// If a line is selected, checks if this line is entirely visible
|
||||
// and if it's not, scrolls until the line is.
|
||||
const headerHeight = document.querySelector('.o_form_view').offsetHeight.height;
|
||||
const lineRect = selectedLine.getBoundingClientRect();
|
||||
const page = document.querySelector('.o_tablet_timeline');
|
||||
// Computes the real header's height (the navbar is present if the page was refreshed).
|
||||
let scrollCoordY = false;
|
||||
if (lineRect.top < headerHeight) {
|
||||
scrollCoordY = lineRect.top - headerHeight + page.scrollTop;
|
||||
} else if (lineRect.bottom > window.innerHeight) {
|
||||
const pageRect = page.getBoundingClientRect();
|
||||
scrollCoordY = page.scrollTop - (pageRect.bottom - lineRect.bottom);
|
||||
}
|
||||
if (scrollCoordY !== false) { // Scrolls to the line only if it's not entirely visible.
|
||||
page.scroll({ left: 0, top: scrollCoordY, behavior: this._scrollBehavior });
|
||||
this._scrollBehavior = 'smooth';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getState() {
|
||||
this.data = await this.orm.call(
|
||||
'mrp.workorder',
|
||||
'get_workorder_data',
|
||||
[this.workorderId],
|
||||
);
|
||||
this.viewsId = this.data['views'];
|
||||
this.steps = this.data['quality.check'];
|
||||
this.state.workingState = this.data.working_state;
|
||||
if (this.steps.length && this.steps.every(step => step.quality_state !== 'none')) {
|
||||
this.createSummaryStep();
|
||||
} else {
|
||||
this.state.selectedStepId = this.data['mrp.workorder'].current_quality_check_id;
|
||||
}
|
||||
}
|
||||
|
||||
createSummaryStep() {
|
||||
this.steps.push({
|
||||
id: 0,
|
||||
title: 'Summary',
|
||||
test_type: '',
|
||||
});
|
||||
this.state.selectedStepId = 0;
|
||||
}
|
||||
|
||||
async selectStep(id) {
|
||||
await this.saveCurrentStep(id);
|
||||
}
|
||||
|
||||
async saveCurrentStep(newId) {
|
||||
await new Promise((resolve) =>
|
||||
this.workorderBus.trigger("force_save_workorder", { resolve })
|
||||
);
|
||||
if (this.state.selectedStepId) {
|
||||
await new Promise((resolve) =>
|
||||
this.workorderBus.trigger("force_save_check", { resolve })
|
||||
);
|
||||
}
|
||||
await this.orm.write("mrp.workorder", [this.workorderId], {
|
||||
current_quality_check_id: newId,
|
||||
});
|
||||
this.state.selectedStepId = newId;
|
||||
}
|
||||
|
||||
get worksheetData() {
|
||||
if (this.selectedStep) {
|
||||
if (this.selectedStep.worksheet_document) {
|
||||
return {
|
||||
resModel: 'quality.check',
|
||||
resId: this.state.selectedStepId,
|
||||
resField: 'worksheet_document',
|
||||
value: this.selectedStep.worksheet_document,
|
||||
page: 1,
|
||||
};
|
||||
} else if (this.selectedStep.worksheet_url) {
|
||||
return {
|
||||
resModel: "quality.point",
|
||||
resId: this.selectedStep.point_id,
|
||||
resField: "worksheet_url",
|
||||
value: this.selectedStep.worksheet_url,
|
||||
page: 1,
|
||||
};
|
||||
} else if (this.data.operation !== undefined && this.selectedStep.worksheet_page) {
|
||||
if (this.data.operation.worksheet) {
|
||||
return {
|
||||
resModel: "mrp.routing.workcenter",
|
||||
resId: this.data.operation.id,
|
||||
resField: "worksheet",
|
||||
value: this.data.operation.worksheet,
|
||||
page: this.selectedStep.worksheet_page,
|
||||
};
|
||||
} else if (this.data.operation.worksheet_url) {
|
||||
return {
|
||||
resModel: "mrp.routing.workcenter",
|
||||
resId: this.data.operation.id,
|
||||
resField: "worksheet_url",
|
||||
value: this.data.operation.worksheet_url,
|
||||
page: this.selectedStep.worksheet_page,
|
||||
};
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (this.data.operation.worksheet) {
|
||||
return {
|
||||
resModel: 'mrp.routing.workcenter',
|
||||
resId: this.data.operation.id,
|
||||
resField: 'worksheet',
|
||||
value: this.data.operation.worksheet,
|
||||
page: 1,
|
||||
};
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
get selectedStep() {
|
||||
return this.state.selectedStepId && this.steps.find(
|
||||
l => l.id === this.state.selectedStepId
|
||||
);
|
||||
}
|
||||
|
||||
get views() {
|
||||
const data = {
|
||||
workorder: {
|
||||
type: 'workorder_form',
|
||||
mode: 'edit',
|
||||
resModel: 'mrp.workorder',
|
||||
viewId: this.viewsId.workorder,
|
||||
resId: this.workorderId,
|
||||
display: { controlPanel: false },
|
||||
workorderBus: this.workorderBus,
|
||||
},
|
||||
check: {
|
||||
type: 'workorder_form',
|
||||
mode: 'edit',
|
||||
resModel: 'quality.check',
|
||||
viewId: this.viewsId.check,
|
||||
resId: this.state.selectedStepId,
|
||||
display: { controlPanel: false },
|
||||
workorderBus: this.workorderBus,
|
||||
},
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
get checkInstruction() {
|
||||
let note = this.data['mrp.workorder'].operation_note;
|
||||
if (note && note !== '<p><br></p>') {
|
||||
return markup(note);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
get isBlocked() {
|
||||
return this.state.workingState === 'blocked';
|
||||
}
|
||||
|
||||
showPopup(props, popupId) {
|
||||
this.popup[popupId].isShown = true;
|
||||
this.popup[popupId].data = props;
|
||||
}
|
||||
|
||||
closePopup(popupId) {
|
||||
this.getState();
|
||||
this.popup[popupId].isShown = false;
|
||||
}
|
||||
|
||||
async onCloseRerender(message) {
|
||||
if (message) {
|
||||
this.notification.add(this.env._t(message), {type: 'success'});
|
||||
}
|
||||
await this.getState();
|
||||
this.render();
|
||||
}
|
||||
|
||||
openMenuPopup() {
|
||||
this.showPopup({
|
||||
title: 'Menu',
|
||||
workcenterId: this.data['mrp.workorder'].workcenter_id,
|
||||
selectedStepId: this.state.selectedStepId,
|
||||
workorderId: this.workorderId,
|
||||
}, 'menu');
|
||||
}
|
||||
|
||||
async _onWillStart() {
|
||||
await this.getState();
|
||||
}
|
||||
|
||||
_onBarcodeScanned(barcode) {
|
||||
if (barcode.startsWith('O-BTN.') || barcode.startsWith('O-CMD.')) {
|
||||
// Do nothing. It's already handled by the barcode service.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Tablet.props = ['action', '*'];
|
||||
Tablet.template = 'mrp_workorder.Tablet';
|
||||
Tablet.components = {
|
||||
StepComponent,
|
||||
DocumentViewer,
|
||||
ViewsWidgetAdapter,
|
||||
MenuPopup,
|
||||
SummaryStep,
|
||||
View,
|
||||
};
|
||||
|
||||
registry.category('actions').add('tablet_client_action', Tablet);
|
||||
|
||||
export default Tablet;
|
||||
227
mrp_workorder/static/src/components/tablet.scss
Normal file
@@ -0,0 +1,227 @@
|
||||
|
||||
$o-wo-tablet-padding: $o-horizontal-padding;
|
||||
$o-wo-tablet-btn-margin: 2px;
|
||||
$o-wo-tablet-bg: #404040; // emulate the pdf reader
|
||||
$o-wo-tablet-btn-bg: #505050; // emulate the pdf reader
|
||||
$o-wo-tablet-bg-dark: #333333;
|
||||
$o-wo-tablet-border-bg: #c9ccd2;
|
||||
$o-wo-tablet-text: color-contrast($o-wo-tablet-bg, $body-color, #FFF);
|
||||
|
||||
.o_tablet_client_action {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
padding: 0;
|
||||
background-color: $o-wo-tablet-bg;
|
||||
color: $o-wo-tablet-text;
|
||||
height: 100%;
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: $o-wo-tablet-border-bg;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: gray;
|
||||
}
|
||||
|
||||
// font-size: $o-navbar-entry-font-size;
|
||||
|
||||
// Components
|
||||
h1,h2,h3,h4,h5,h6 {
|
||||
color: $o-wo-tablet-text;
|
||||
}
|
||||
.o_workorder_blocking_screen {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.o_view_controller.o_form_view {
|
||||
color: $o-wo-tablet-text;
|
||||
background-color: $o-wo-tablet-bg;
|
||||
background-repeat: repeat;
|
||||
// background-image: url(/web/static/lib/pdfjs/web/images/texture.png);
|
||||
border: $o-wo-tablet-bg;
|
||||
text-transform: capitalize;
|
||||
min-height: auto;
|
||||
height: auto!important;
|
||||
position: unset!important;
|
||||
.row {
|
||||
width: 100%;
|
||||
}
|
||||
.o_form_nosheet {
|
||||
padding: 0;
|
||||
}
|
||||
.o_workorder_tablet_form {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
padding: 0 $o-wo-tablet-padding*0.5;
|
||||
margin-top: 0;
|
||||
font-size: 1.3em;
|
||||
&.form_top {
|
||||
min-height: 100px;
|
||||
border-bottom: 1px solid $o-wo-tablet-border-bg;
|
||||
background-color: $o-wo-tablet-bg-dark;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
span.o_tag {
|
||||
background-color: $o-wo-tablet-bg-dark;
|
||||
box-shadow: none;
|
||||
}
|
||||
.o_unblock {
|
||||
z-index: 100;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.o_workorder_bar_content{
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-items: center;
|
||||
|
||||
&.o_workorder_bar_left {
|
||||
flex: 1 0;
|
||||
}
|
||||
|
||||
&.o_workorder_bar_center {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
justify-content: flex-start;
|
||||
&.workorder_bar_left {
|
||||
flex-flow: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
&.o_workorder_bar_right {
|
||||
justify-content: flex-end;
|
||||
flex: 1 0;
|
||||
align-content: baseline;
|
||||
@include media-breakpoint-down(sm) {
|
||||
justify-content: flex-start;
|
||||
.o_actions {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.o_workorder_lot {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
// First Top Block
|
||||
.o_workorder_bar {
|
||||
display: flex;
|
||||
justify-content: space_around;
|
||||
flex-flow: row nowrap;
|
||||
padding: $o-wo-tablet-padding*0.5 0;
|
||||
@include media-breakpoint-down(sm) {
|
||||
flex-flow: column;
|
||||
}
|
||||
.o_workorder_field {
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
// Last top Block
|
||||
.o_workorder_actions {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
@include media-breakpoint-down(sm) {
|
||||
flex-flow: column;
|
||||
}
|
||||
padding: $o-wo-tablet-padding*0.5 $o-wo-tablet-padding - $o-wo-tablet-btn-margin $o-wo-tablet-padding;
|
||||
box-shadow: 0 1px 1px rgba(black, 0.3);
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.o_workorder_left {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-size: 1em;
|
||||
padding: 0.4em 1em;
|
||||
margin: $o-wo-tablet-btn-margin;
|
||||
|
||||
&.btn-secondary {
|
||||
color: $o-wo-tablet-text;
|
||||
&:not(.btn-link) {
|
||||
background-color: $o-wo-tablet-btn-bg;
|
||||
border: $o-wo-tablet-bg;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_field_widget {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_tablet_bottom_content {
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
.o_tablet_timeline {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
flex-basis: 20%;
|
||||
}
|
||||
|
||||
.o_tablet_instructions_content {
|
||||
flex-basis: 100%;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
padding-left: 0;
|
||||
&:first-child {
|
||||
margin-bottom: -24px;
|
||||
}
|
||||
.o_tablet_instruction_note {
|
||||
padding: $o-wo-tablet-padding*0.5;
|
||||
overflow: auto;
|
||||
}
|
||||
.o_tablet_document {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
iframe {
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
img {
|
||||
max-width: 75%;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.o_input {
|
||||
background-color: $o-wo-tablet-bg;
|
||||
color: $o-wo-tablet-text;
|
||||
padding: 0.4em 1em;
|
||||
}
|
||||
|
||||
.o_form_label {
|
||||
color: $o-wo-tablet-text;
|
||||
}
|
||||
}
|
||||
34
mrp_workorder/static/src/components/tablet.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<div t-name="mrp_workorder.Tablet" class="o_content o_tablet_client_action" owl="1">
|
||||
<div class="o_workorder_blocking_screen" t-if="isBlocked"/>
|
||||
<View t-props="views['workorder']"/>
|
||||
<div class="o_tablet_bottom_content">
|
||||
<div t-if="steps.length" class="o_tablet_timeline" t-on-select-step="selectStep">
|
||||
<t t-foreach="steps" t-as="step" t-key="step.id">
|
||||
<StepComponent step="step"
|
||||
selectedStepId="state.selectedStepId"
|
||||
onSelectStep.bind="selectStep"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="o_tablet_instructions_content">
|
||||
<t t-if="state.selectedStepId">
|
||||
<View t-props="views['check']" t-key="state.selectedStepId"/>
|
||||
<div t-if="checkInstruction" class="o_tablet_instruction_note">
|
||||
<t t-out="checkInstruction"/>
|
||||
</div>
|
||||
<t t-if="worksheetData">
|
||||
<DocumentViewer t-props="worksheetData"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-elif="steps.length !== 0">
|
||||
<SummaryStep workorder="workorderId"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="popup['menu'].isShown" class="o_tablet_popups" >
|
||||
<MenuPopup popupData="popup['menu'].data" onClosePopup.bind="closePopup" onClose.bind="onCloseRerender"/>
|
||||
</div>
|
||||
</div>
|
||||
</templates>
|
||||
81
mrp_workorder/static/src/components/viewer.js
Normal file
@@ -0,0 +1,81 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { PdfViewerField } from '@web/views/fields/pdf_viewer/pdf_viewer_field';
|
||||
import { ImageField } from '@web/views/fields/image/image_field';
|
||||
import { SlidesViewer } from "@mrp/views/fields/google_slides_viewer";
|
||||
|
||||
const { Component, useEffect, useRef } = owl;
|
||||
|
||||
class DocumentViewer extends Component {
|
||||
|
||||
setup() {
|
||||
this.magicNumbers = {
|
||||
'JVBER': 'pdf',
|
||||
'/': 'jpg',
|
||||
'R': 'gif',
|
||||
'i': 'png',
|
||||
'P': 'svg+xml',
|
||||
};
|
||||
this.pdfIFrame = useRef('pdf_viewer');
|
||||
useEffect(() => {
|
||||
this.updatePdf();
|
||||
});
|
||||
}
|
||||
|
||||
updatePdf() {
|
||||
if (this.pdfIFrame.el) {
|
||||
const iframe = this.pdfIFrame.el.firstElementChild;
|
||||
iframe.removeAttribute('style');
|
||||
// Once the PDF viewer is loaded, hides everything except the page.
|
||||
iframe.addEventListener('load', () => {
|
||||
iframe.contentDocument.querySelector('.toolbar').style.display = 'none';
|
||||
iframe.contentDocument.querySelector('body').style.background = 'none';
|
||||
iframe.contentDocument.querySelector('#viewerContainer').style.boxShadow = 'none';
|
||||
iframe.contentDocument.querySelector('#mainContainer').style.margin = '-2.5em';
|
||||
});
|
||||
}
|
||||
}
|
||||
get type() {
|
||||
if (!this.props || !this.props.value) {
|
||||
return false;
|
||||
}
|
||||
if (this.props.resField === "worksheet_url") {
|
||||
return "google_slide";
|
||||
}
|
||||
for (const [magicNumber, type] of Object.entries(this.magicNumbers)) {
|
||||
if (this.props.value.startsWith(magicNumber)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
get viewerProps() {
|
||||
let viewerProps = {
|
||||
record: {
|
||||
resModel: this.props.resModel,
|
||||
resId: this.props.resId,
|
||||
data: {},
|
||||
},
|
||||
name: this.props.resField,
|
||||
value: this.props.value,
|
||||
readonly: true,
|
||||
};
|
||||
viewerProps['record']['data'][this.props.resField] = this.props.resField;
|
||||
viewerProps['record']['data'][`$(this.props.resField)_page`] = this.props.page || 1;
|
||||
if (this.type === 'pdf') {
|
||||
viewerProps['fileNameField'] = this.props.resField;
|
||||
}
|
||||
return viewerProps;
|
||||
}
|
||||
}
|
||||
|
||||
DocumentViewer.template = 'mrp_workorder.DocumentViewer';
|
||||
DocumentViewer.props = ["resField", "resModel", "resId", "value", "page"];
|
||||
DocumentViewer.components = {
|
||||
PdfViewerField,
|
||||
ImageField,
|
||||
SlidesViewer,
|
||||
};
|
||||
|
||||
export default DocumentViewer;
|
||||
21
mrp_workorder/static/src/components/viewer.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mrp_workorder.DocumentViewer" owl="1">
|
||||
<t t-if="type === 'pdf'">
|
||||
<div class="o_tablet_document" t-ref="pdf_viewer">
|
||||
<PdfViewerField t-props="viewerProps"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-elif="type === 'google_slide'">
|
||||
<div class="o_tablet_document">
|
||||
<SlidesViewer t-props="viewerProps"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-elif="type !== false">
|
||||
<div class="o_tablet_document">
|
||||
<ImageField t-props="viewerProps"/>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</templates>
|
||||
28
mrp_workorder/static/src/components/views_widget_adapter.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { ComponentAdapter } from 'web.OwlCompatibility';
|
||||
|
||||
export default class ViewsWidgetAdapter extends ComponentAdapter {
|
||||
setup() {
|
||||
super.setup(...arguments);
|
||||
// Overwrite the OWL/legacy env with the WOWL's one.
|
||||
this.env = owl.Component.env;
|
||||
}
|
||||
|
||||
renderWidget() {
|
||||
this.widget._render(this.props.data.currentId);
|
||||
}
|
||||
|
||||
get widgetArgs() {
|
||||
const {model, view, additionalContext, params, mode, view_type, bus} = this.props.data;
|
||||
return [
|
||||
model,
|
||||
view,
|
||||
additionalContext,
|
||||
params,
|
||||
mode,
|
||||
view_type,
|
||||
bus
|
||||
];
|
||||
}
|
||||
}
|
||||
BIN
mrp_workorder/static/src/pdf/barcodes_actions_Manufacturing.pdf
Normal file
33
mrp_workorder/static/src/pdf/make_barcodes_manufacturing.sh
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/bin/sh
|
||||
|
||||
barcode -t 2x7+40+40 -m 40x20 -p "210x297mm" -e code128b -n > barcodes_TMP_FILE.ps << BARCODES
|
||||
O-BTN.pause
|
||||
O-BTN.next
|
||||
O-BTN.prev
|
||||
O-BTN.skip
|
||||
O-BTN.cloWO
|
||||
O-BTN.cloMO
|
||||
O-BTN.pass
|
||||
O-BTN.fail
|
||||
O-BTN.finish
|
||||
O-BTN.record
|
||||
O-BTN.continue
|
||||
BARCODES
|
||||
|
||||
cat > barcodesHeaders_TMP_FILE.ps << HEADER
|
||||
/showTitle { /Helvetica findfont 12 scalefont setfont moveto show } def
|
||||
(CONTINUE/PAUSE) 79 780 showTitle
|
||||
(VALIDATE/NEXT) 336 780 showTitle
|
||||
(PREVIOUS) 79 672 showTitle
|
||||
(SKIP) 336 672 showTitle
|
||||
(MARK AS DONE) 79 565 showTitle
|
||||
(MARK AS DONE AND CLOSE MO) 336 565 showTitle
|
||||
(PASS) 79 456 showTitle
|
||||
(FAIL) 336 456 showTitle
|
||||
(FINISH STEPS) 79 347 showTitle
|
||||
(RECORD PRODUCTION) 336 347 showTitle
|
||||
(CONTINUE CONSUMPTION) 79 238 showTitle
|
||||
HEADER
|
||||
|
||||
cat barcodesHeaders_TMP_FILE.ps barcodes_TMP_FILE.ps | ps2pdf - - > barcodes_actions_Manufacturing.pdf
|
||||
rm barcodesHeaders_TMP_FILE.ps barcodes_TMP_FILE.ps
|
||||
18
mrp_workorder/static/src/scss/empty_screen.scss
Normal file
@@ -0,0 +1,18 @@
|
||||
@mixin no-content-image ($img) {
|
||||
@extend %o-nocontent-init-image;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
max-width: 680px;
|
||||
margin-top: -80px;
|
||||
background-size: contain;
|
||||
background: transparent url("/mrp_workorder/static/img/" + $img) no-repeat center;
|
||||
}
|
||||
|
||||
.o_view_nocontent {
|
||||
&_workorder:before {
|
||||
@include no-content-image('workorder.png');
|
||||
}
|
||||
&_steps:before {
|
||||
@include no-content-image('steps.png');
|
||||
}
|
||||
}
|
||||
226
mrp_workorder/static/src/scss/tablet_view.scss
Normal file
@@ -0,0 +1,226 @@
|
||||
.o_web_client {
|
||||
$o-wo-tablet-padding: $o-horizontal-padding;
|
||||
$o-wo-tablet-btn-margin: 2px;
|
||||
$o-wo-tablet-bg: #404040; // emulate the pdf reader
|
||||
$o-wo-tablet-text: color-contrast($o-wo-tablet-bg, $body-color, #FFF);
|
||||
|
||||
.o_legacy_form_view.o_workorder_tablet {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
padding: 0;
|
||||
background-color: $o-wo-tablet-bg;
|
||||
background-image: url(/web/static/lib/pdfjs/web/images/texture.png);
|
||||
background-repeat: repeat;
|
||||
color: $o-wo-tablet-text;
|
||||
height: auto;
|
||||
overflow: auto;
|
||||
|
||||
font-size: 16px;
|
||||
|
||||
@include media-breakpoint-up(xl) {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
// Components
|
||||
h1,h2,h3,h4,h5,h6 {
|
||||
color: $o-wo-tablet-text;
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-size: 1em;
|
||||
padding: 0.4em 1em;
|
||||
margin: 0 $o-wo-tablet-btn-margin;
|
||||
|
||||
&.btn-secondary {
|
||||
color: $o-wo-tablet-text;
|
||||
background-color: $o-wo-tablet-bg;
|
||||
border: $o-wo-tablet-bg;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
&.fa-plus-square-o {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
&.o_workorder_icon_btn {
|
||||
padding: 0.4em;
|
||||
|
||||
&.o_workorder_icon_back {
|
||||
padding: 0.4em 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
.o_workorder_btn_icon_small {
|
||||
font-size: 0.8em;
|
||||
margin-top: -1em;
|
||||
|
||||
&.float-end {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_input {
|
||||
background-color: $o-wo-tablet-bg;
|
||||
color: $o-wo-tablet-text;
|
||||
padding: 0.4em 1em;
|
||||
}
|
||||
|
||||
.o_field_widget {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.o_workorder_bar_content {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
flex: 1 0 auto;
|
||||
align-items: center;
|
||||
|
||||
&.workorder_bar_left {
|
||||
word-break: break-all;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
justify-content: flex-start;
|
||||
&.workorder_bar_left {
|
||||
flex-flow: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
&.o_workorder_bar_content_right {
|
||||
justify-content: flex-end;
|
||||
@include media-breakpoint-down(md) {
|
||||
justify-content: flex-start;
|
||||
.o_actions {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Both Top Blocks
|
||||
.workorder_bar, .workorder_actions {
|
||||
background-color: darken($o-wo-tablet-bg, 5%);
|
||||
background-image: url(/web/static/lib/pdfjs/web/images/texture.png);
|
||||
}
|
||||
|
||||
// First Top Block
|
||||
.workorder_bar {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
padding: $o-wo-tablet-padding*0.5 $o-wo-tablet-padding;
|
||||
padding-left: $o-wo-tablet-padding - $o-wo-tablet-btn-margin;
|
||||
@include media-breakpoint-down(md) {
|
||||
flex-flow: column;
|
||||
}
|
||||
}
|
||||
|
||||
// Last top Block
|
||||
.workorder_actions {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
@include media-breakpoint-down(md) {
|
||||
flex-flow: column;
|
||||
}
|
||||
padding: $o-wo-tablet-padding*0.5 $o-wo-tablet-padding - $o-wo-tablet-btn-margin $o-wo-tablet-padding;
|
||||
box-shadow: 0 1px 1px rgba(black, 0.3);
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.o_form_label {
|
||||
color: $o-wo-tablet-text;
|
||||
}
|
||||
|
||||
// Step's info block
|
||||
.o_workorder_data {
|
||||
background-color: $o-wo-tablet-bg;
|
||||
padding: $o-wo-tablet-padding*0.5 $o-wo-tablet-padding 0;
|
||||
font-size: 0.9em;
|
||||
|
||||
// Final resume table
|
||||
table.o_list_table {
|
||||
color: $body-color;
|
||||
}
|
||||
|
||||
// Horizontal form
|
||||
.o_workorder_form {
|
||||
padding-bottom: $o-wo-tablet-padding*0.5;
|
||||
justify-content: space-between;
|
||||
@include media-breakpoint-down(md) {
|
||||
flex-flow: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
> div {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.o_workorder_field {
|
||||
> div.o_workorder_lot {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
> .o_lot_number {
|
||||
width: 50%;
|
||||
&:not(.o_with_button) button.o_external_button {
|
||||
display: block !important;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
> div {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.o_form_label {
|
||||
opacity: 0.7;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
a, a::first-line {
|
||||
font-weight: bold;
|
||||
color: $o-wo-tablet-text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PDF Viewer block
|
||||
.workorder_pdf {
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
|
||||
div.o_field_pdfviewer, div.o_field_pdf_viewer, span.o_embed_url_viewer {
|
||||
margin: 0;
|
||||
@include media-breakpoint-up(md) {
|
||||
height: auto;
|
||||
}
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
|
||||
iframe {
|
||||
display: block;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@include o-position-absolute(0,0,0,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Work order Modal
|
||||
.workorder_menu {
|
||||
.btn {
|
||||
margin-bottom: $o-wo-tablet-padding;
|
||||
padding: 1em;
|
||||
font-size: 2em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
62
mrp_workorder/static/src/views/form_view.js
Normal file
@@ -0,0 +1,62 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useViewButtons } from "@web/views/view_button/view_button_hook";
|
||||
import { useBus } from "@web/core/utils/hooks";
|
||||
|
||||
import { formView } from "@web/views/form/form_view";
|
||||
import { FormController } from "@web/views/form/form_controller";
|
||||
|
||||
const { useRef } = owl;
|
||||
|
||||
export class WorkorderFormController extends FormController {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.workorderBus = this.props.workorderBus;
|
||||
useBus(this.workorderBus, "force_save_workorder", async (ev) => {
|
||||
if (this.model.root.resModel === "mrp.workorder") {
|
||||
await this.model.root.save({ stayInEdition: true });
|
||||
ev.detail.resolve();
|
||||
}
|
||||
});
|
||||
useBus(this.workorderBus, "force_save_check", async (ev) => {
|
||||
if (this.model.root.resModel === "quality.check") {
|
||||
await this.model.root.save({ stayInEdition: true });
|
||||
ev.detail.resolve();
|
||||
}
|
||||
});
|
||||
const rootRef = useRef("root");
|
||||
// before executing button action
|
||||
const beforeExecuteAction = async (params) => {
|
||||
await this.model.root.save({ stayInEdition: true });
|
||||
if (params.type && params.type === "workorder_event") {
|
||||
this.workorderBus.trigger("workorder_event", params.name);
|
||||
return false;
|
||||
}
|
||||
if (this.model.root.resModel === "mrp.workorder") {
|
||||
if (this.model.root.data.current_quality_check_id) {
|
||||
await new Promise((resolve) =>
|
||||
this.workorderBus.trigger("force_save_check", { resolve })
|
||||
);
|
||||
}
|
||||
}
|
||||
if (this.model.root.resModel === "quality.check") {
|
||||
await new Promise((resolve) =>
|
||||
this.workorderBus.trigger("force_save_workorder", { resolve })
|
||||
);
|
||||
}
|
||||
};
|
||||
// after executing button action
|
||||
const reload = () => this.workorderBus.trigger("refresh");
|
||||
useViewButtons(this.model, rootRef, { beforeExecuteAction, reload });
|
||||
}
|
||||
}
|
||||
|
||||
WorkorderFormController.props = {
|
||||
...FormController.props,
|
||||
workorderBus: Object,
|
||||
};
|
||||
registry.category("views").add("workorder_form", {
|
||||
...formView,
|
||||
Controller: WorkorderFormController,
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { KanbanController } from '@web/views/kanban/kanban_controller';
|
||||
|
||||
export class MrpWorkorderKanbanController extends KanbanController {
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.context = {};
|
||||
this.orm = useService('orm');
|
||||
}
|
||||
|
||||
actionBack() {
|
||||
this.actionService.doAction('mrp.mrp_workcenter_kanban_action', {
|
||||
clearBreadcrumbs: true,
|
||||
});
|
||||
}
|
||||
|
||||
async openRecord(record, mode) {
|
||||
const action = await this.orm.call(
|
||||
'mrp.workorder',
|
||||
'open_tablet_view',
|
||||
[record.resId],
|
||||
);
|
||||
Object.assign(action.context, this.context);
|
||||
this.actionService.doAction(action);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { kanbanView } from "@web/views/kanban/kanban_view";
|
||||
import { MrpWorkorderKanbanController } from "./mrp_workorder_kanban_controller";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
export const MrpWorkorderKanbanView = {
|
||||
...kanbanView,
|
||||
Controller: MrpWorkorderKanbanController,
|
||||
buttonTemplate: 'mrp_workorder.overviewButtonsKanban',
|
||||
};
|
||||
|
||||
registry.category("views").add('tablet_kanban_view', MrpWorkorderKanbanView);
|
||||
@@ -0,0 +1,11 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { ListController } from "@web/views/list/list_controller";
|
||||
|
||||
export class MrpWorkorderListController extends ListController {
|
||||
actionBack() {
|
||||
this.actionService.doAction("mrp.mrp_workcenter_kanban_action", {
|
||||
clearBreadcrumbs: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { listView } from "@web/views/list/list_view";
|
||||
import { MrpWorkorderListController } from "./mrp_workorder_list_controller";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
export const MrpWorkorderListView = {
|
||||
...listView,
|
||||
Controller: MrpWorkorderListController,
|
||||
buttonTemplate: "mrp_workorder.overviewButtonsList",
|
||||
};
|
||||
|
||||
registry.category("views").add("tablet_list_view", MrpWorkorderListView);
|
||||
21
mrp_workorder/static/src/views/xml/mrp_workorder_buttons.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="mrp_workorder.overviewButtonsKanban" t-inherit="web.KanbanView.Buttons" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="(//div/*)[last()]" position="after">
|
||||
<div>
|
||||
<button t-on-click="actionBack" class="btn btn-secondary o_back_button">
|
||||
<i class="fa fa-arrow-left"/>
|
||||
</button>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
<t t-name="mrp_workorder.overviewButtonsList" t-inherit="web.ListView.Buttons" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="(//div/*)[last()]" position="after">
|
||||
<div>
|
||||
<button t-on-click="actionBack" class="btn btn-secondary o_back_button">
|
||||
<i class="fa fa-arrow-left"/>
|
||||
</button>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,93 @@
|
||||
odoo.define('mrp_workorder.tourHelper', function (require) {
|
||||
'use strict';
|
||||
|
||||
var tour = require('web_tour.tour');
|
||||
|
||||
function fail(errorMessage) {
|
||||
tour._consume_tour(tour.running_tour, errorMessage);
|
||||
}
|
||||
|
||||
function assertIn(item, itemList, info) {
|
||||
if (!itemList.includes(item)) {
|
||||
fail(info + ': "' + item + '" not in "' + itemList + '".');
|
||||
}
|
||||
}
|
||||
function assert(current, expected, info) {
|
||||
if (current !== expected) {
|
||||
fail(info + ': "' + current + '" instead of "' + expected + '".');
|
||||
}
|
||||
}
|
||||
|
||||
function assertRainbow(present = false) {
|
||||
const $summaryStep = $('.o_tablet_summary');
|
||||
const $rainbow = $('.o_reward_rainbow_man');
|
||||
assert(Boolean($summaryStep.length && present ? $rainbow.length : !$rainbow.length), true, 'Rainbow man check');
|
||||
}
|
||||
|
||||
function assertDoneButton(present = false) {
|
||||
const $doneButton = $('button.btn-primary[name=do_finish');
|
||||
assert(Boolean(present ? $doneButton.length : !$doneButton.length), true, 'mark as done check');
|
||||
}
|
||||
|
||||
function assertQtyToProduce(qty_producing, qty_remaining) {
|
||||
let $qty_producing = $('input[id="qty_producing"]');
|
||||
if ($qty_producing.length === 0) {
|
||||
$qty_producing = $('div[name="qty_producing"]');
|
||||
assert(Number($qty_producing[0].textContent), qty_producing, `wrong quantity done`);
|
||||
} else {
|
||||
assert(Number($qty_producing[0].value), qty_producing, `wrong quantity done`);
|
||||
}
|
||||
assert($qty_producing.length, 1, `no qty_producing`);
|
||||
|
||||
const $qty_remaining = $('div[name="qty_remaining"]');
|
||||
assert($qty_remaining.length, 1, `no qty_remaining`);
|
||||
assert(Number($qty_remaining[0].textContent), qty_remaining, `wrong quantity remaining`);
|
||||
}
|
||||
|
||||
function assertComponent(name, style, qty_done, qty_remaining) {
|
||||
assertIn(style, ['readonly', 'editable']);
|
||||
const $label = $('div[name="component_id"] > span');
|
||||
assert($label.length, 1, `no field`);
|
||||
assert($label[0].textContent, name, `wrong component name`);
|
||||
if (style === 'readonly') {
|
||||
const $qty_done = $('div[name="qty_done"]');
|
||||
assert($qty_done.length, 1, `no qty_done`);
|
||||
assert(Number($qty_done[0].textContent), qty_done, `wrong quantity done`);
|
||||
} else {
|
||||
const $qty_done = $('input[id="qty_done"]');
|
||||
assert($qty_done.length, 1, `no qty_done`);
|
||||
assert(Number($qty_done[0].value), qty_done, `wrong quantity done`);
|
||||
}
|
||||
const $qty_remaining = $('div[name="component_remaining_qty"]');
|
||||
assert($qty_remaining.length, 1, `no qty_remaining`);
|
||||
assert(Number($qty_remaining[0].textContent), qty_remaining, `wrong quantity remaining`);
|
||||
}
|
||||
|
||||
function assertCurrentCheck(text) {
|
||||
const $button = $('.o_selected');
|
||||
assert($button.length, 1, `no selected check`);
|
||||
assert($button[0].textContent, text, `wrong check title`);
|
||||
}
|
||||
|
||||
function assertCheckLength(length) {
|
||||
const button = $('.o_tablet_step');
|
||||
assert(button.length, length, `There should be "${length}" steps`);
|
||||
}
|
||||
function assertValidatedCheckLength(length) {
|
||||
const marks = $('.o_tablet_step_ok');
|
||||
assert(marks.length, length, `There should be "${length}" validated steps`);
|
||||
}
|
||||
|
||||
return {
|
||||
assert: assert,
|
||||
assertCurrentCheck: assertCurrentCheck,
|
||||
assertCheckLength: assertCheckLength,
|
||||
assertComponent: assertComponent,
|
||||
assertValidatedCheckLength: assertValidatedCheckLength,
|
||||
assertQtyToProduce: assertQtyToProduce,
|
||||
assertRainbow: assertRainbow,
|
||||
assertDoneButton: assertDoneButton,
|
||||
fail: fail,
|
||||
};
|
||||
|
||||
});
|
||||
292
mrp_workorder/static/tests/tours/tour_test_workorder.js
Normal file
@@ -0,0 +1,292 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import tour from 'web_tour.tour';
|
||||
import helper from 'mrp_workorder.tourHelper';
|
||||
|
||||
tour.register('test_add_component', {test: true}, [
|
||||
{
|
||||
trigger: '.o_tablet_client_action',
|
||||
run: function () {
|
||||
helper.assertCheckLength(2);
|
||||
helper.assertValidatedCheckLength(0);
|
||||
helper.assertQtyToProduce(1, 1);
|
||||
helper.assertCurrentCheck('Register Consumed Materials "Elon Musk"');
|
||||
helper.assertComponent('Elon Musk', 'readonly', 1, 1);
|
||||
}
|
||||
},
|
||||
{trigger: '.btn[name="button_start"]'},
|
||||
{
|
||||
trigger: '.o_workorder_icon_btn',
|
||||
extra_trigger: '.btn[name="button_pending"]',
|
||||
},
|
||||
{trigger: '.o_tablet_popups'},
|
||||
{trigger: '.btn:contains("Add Component")'},
|
||||
{trigger: '.modal-title:contains("Add Component")'},
|
||||
{
|
||||
trigger: "div.o_field_widget[name='product_id'] input ",
|
||||
position: 'bottom',
|
||||
run: 'text extra',
|
||||
}, {
|
||||
trigger: '.ui-menu-item > a:contains("extra")',
|
||||
in_modal: false,
|
||||
auto: true,
|
||||
}, {
|
||||
trigger: "div.o_field_widget[name='product_qty'] input",
|
||||
in_modal: true,
|
||||
position: 'bottom',
|
||||
run: 'text 3',
|
||||
},
|
||||
{trigger: '.btn-primary[name="add_product"]'},
|
||||
{
|
||||
trigger: '.o_tablet_client_action',
|
||||
run: function () {
|
||||
helper.assertCheckLength(3);
|
||||
helper.assertValidatedCheckLength(0);
|
||||
helper.assertQtyToProduce(1, 1);
|
||||
helper.assertCurrentCheck('Register Consumed Materials "extra"');
|
||||
helper.assertComponent('extra', 'editable', 3, 3);
|
||||
}
|
||||
}, {
|
||||
trigger: "div.o_field_widget[name='lot_id'] input ",
|
||||
position: 'bottom',
|
||||
run: 'text lot1',
|
||||
}, {
|
||||
trigger: '.ui-menu-item > a:contains("lot1")',
|
||||
in_modal: false,
|
||||
auto: true,
|
||||
}, {
|
||||
trigger: '.o_tablet_client_action',
|
||||
run: () => {
|
||||
helper.assertCheckLength(3);
|
||||
helper.assertValidatedCheckLength(0);
|
||||
helper.assertQtyToProduce(1, 1);
|
||||
helper.assertCurrentCheck('Register Consumed Materials "extra"');
|
||||
helper.assertComponent('extra', 'editable', 3, 3);
|
||||
helper.assert($('div.o_field_widget[name="lot_id"] input').val(), 'lot1');
|
||||
}
|
||||
},
|
||||
// go to Elon Musk step (second one since 'extra')
|
||||
{trigger: '.o_tablet_step:nth-child(2)'},
|
||||
{trigger: '.o_selected:contains("Elon")'},
|
||||
{
|
||||
trigger: '.o_tablet_client_action',
|
||||
run: function () {
|
||||
helper.assertCheckLength(3);
|
||||
helper.assertValidatedCheckLength(0);
|
||||
helper.assertQtyToProduce(1, 1);
|
||||
helper.assertCurrentCheck('Register Consumed Materials "Elon Musk"');
|
||||
helper.assertComponent('Elon Musk', 'readonly', 1, 1);
|
||||
}
|
||||
},
|
||||
// go to metal cylinder step
|
||||
{trigger: '.btn[name="action_next"]'},
|
||||
{trigger: 'div[name="component_id"]:contains("Metal")'},
|
||||
{
|
||||
trigger: '.o_tablet_client_action',
|
||||
run: function () {
|
||||
helper.assertComponent('Metal cylinder', 'editable', 2, 2);
|
||||
helper.assertCheckLength(3);
|
||||
helper.assertValidatedCheckLength(1);
|
||||
helper.assertQtyToProduce(1, 1);
|
||||
helper.assertCurrentCheck('Register Consumed Materials "Metal cylinder"');
|
||||
}
|
||||
}, {
|
||||
trigger: 'input[id="qty_done"]',
|
||||
position: 'bottom',
|
||||
run: 'text 1',
|
||||
}, {
|
||||
trigger: 'div.o_field_widget[name="lot_id"] input',
|
||||
position: 'bottom',
|
||||
run: 'text mc1',
|
||||
},
|
||||
{trigger: '.o_workorder_icon_btn'},
|
||||
{trigger: '.o_tablet_popups'},
|
||||
{trigger: '.btn:contains("Add By-product")'},
|
||||
{trigger: '.modal-title:contains("Add By-Product")'},
|
||||
{
|
||||
trigger: "div.o_field_widget[name='product_id'] input ",
|
||||
position: 'bottom',
|
||||
run: 'text extra-bp',
|
||||
}, {
|
||||
trigger: '.ui-menu-item > a:contains("extra-bp")',
|
||||
in_modal: false,
|
||||
auto: true,
|
||||
}, {
|
||||
trigger: "div.o_field_widget[name='product_qty'] input",
|
||||
in_modal: true,
|
||||
position: 'bottom',
|
||||
run: 'text 1',
|
||||
},
|
||||
{trigger: '.btn-primary[name="add_product"]'},
|
||||
{
|
||||
trigger: '.o_tablet_client_action',
|
||||
run: function () {
|
||||
helper.assertCheckLength(4);
|
||||
helper.assertValidatedCheckLength(1);
|
||||
helper.assertQtyToProduce(1, 1);
|
||||
helper.assertCurrentCheck('Register By-products "extra-bp"');
|
||||
helper.assertComponent('extra-bp', 'editable', 1, 1);
|
||||
}
|
||||
}, {
|
||||
trigger: "div.o_field_widget[name='lot_id'] input ",
|
||||
position: 'bottom',
|
||||
run: 'text lot2',
|
||||
}, {
|
||||
trigger: '.ui-menu-item > a:contains("lot2")',
|
||||
in_modal: false,
|
||||
auto: true,
|
||||
},
|
||||
{trigger: '.btn[name=action_next]'},
|
||||
{
|
||||
trigger: 'div[name="component_id"]:contains("Metal")',
|
||||
run: function () {
|
||||
helper.assertCheckLength(4);
|
||||
helper.assertValidatedCheckLength(2);
|
||||
helper.assertQtyToProduce(1, 1);
|
||||
helper.assertCurrentCheck('Register Consumed Materials "Metal cylinder"');
|
||||
helper.assertComponent('Metal cylinder', 'editable', 2, 2);
|
||||
}
|
||||
},
|
||||
{trigger: '.btn[name=action_next]'},
|
||||
// go back to the first not done check
|
||||
{
|
||||
trigger: 'div[name="component_id"]:contains("extra")',
|
||||
run: function () {
|
||||
helper.assertComponent('extra', 'editable', 3, 3);
|
||||
helper.assertCheckLength(4);
|
||||
helper.assertValidatedCheckLength(3);
|
||||
helper.assertQtyToProduce(1, 1);
|
||||
helper.assertCurrentCheck('Register Consumed Materials "extra"');
|
||||
}
|
||||
},
|
||||
{trigger: '.btn[name=action_next]'},
|
||||
// we have the rainbow man once
|
||||
{
|
||||
trigger: '.o_tablet_step:nth-child(5)',
|
||||
run: function () {
|
||||
helper.assertRainbow(true);
|
||||
}
|
||||
},
|
||||
{trigger: '.o_reward_rainbow_man'},
|
||||
{
|
||||
trigger: 'h1:contains("Good Job")',
|
||||
run: function () {
|
||||
helper.assertDoneButton(true);
|
||||
}
|
||||
},
|
||||
// we do not have it twice
|
||||
{trigger: '.o_tablet_step:nth-child(2)'},
|
||||
{
|
||||
trigger: 'div[name="component_id"]:contains("Elon")',
|
||||
run: function () {
|
||||
helper.assertCheckLength(5);
|
||||
helper.assertValidatedCheckLength(4);
|
||||
helper.assertQtyToProduce(1, 1);
|
||||
helper.assertCurrentCheck('Register Consumed Materials "Elon Musk"');
|
||||
helper.assertComponent('Elon Musk', 'readonly', 1, 0);
|
||||
}
|
||||
},
|
||||
{trigger: '.o_tablet_step:nth-child(5)'},
|
||||
{
|
||||
trigger: 'h1:contains("Good Job")',
|
||||
run: function () {
|
||||
helper.assertRainbow(false);
|
||||
helper.assertDoneButton(true);
|
||||
}
|
||||
},
|
||||
{
|
||||
trigger: "input[id='finished_lot_id']",
|
||||
position: 'bottom',
|
||||
run: 'text F0001',
|
||||
},
|
||||
{
|
||||
trigger: '.ui-menu-item > a:contains("F0001")',
|
||||
in_modal: false,
|
||||
auto: true,
|
||||
},
|
||||
{trigger: '.btn[name=do_finish]'},
|
||||
{trigger: '.o_searchview_input'},
|
||||
]);
|
||||
|
||||
tour.register('test_add_step', {test: true}, [
|
||||
{
|
||||
trigger: '.o_tablet_client_action',
|
||||
run: function () {
|
||||
helper.assertCheckLength(1);
|
||||
helper.assertValidatedCheckLength(0);
|
||||
helper.assertQtyToProduce(1, 1);
|
||||
helper.assertCurrentCheck('Register Consumed Materials "Metal cylinder"');
|
||||
helper.assertComponent('Metal cylinder', 'editable', 2, 2);
|
||||
}
|
||||
},
|
||||
{trigger: '.btn[name="button_start"]'},
|
||||
{
|
||||
trigger: '.o_workorder_icon_btn',
|
||||
extra_trigger: '.btn[name="button_pending"]',
|
||||
},
|
||||
{trigger: '.o_tablet_popups'},
|
||||
{trigger: '.btn:contains("Add a Step")'},
|
||||
{trigger: '.modal-title:contains("Add a Step")'},
|
||||
{
|
||||
trigger: "div[name=title] input",
|
||||
position: 'bottom',
|
||||
run: 'text my very new step',
|
||||
}, {
|
||||
trigger: "div[name=note] p",
|
||||
position: 'bottom',
|
||||
run: 'text why am I adding a step',
|
||||
},
|
||||
{trigger: '.btn-primary[name="add_check_in_chain"]'},
|
||||
{
|
||||
trigger: '.o_tablet_client_action',
|
||||
run: function () {
|
||||
helper.assertCheckLength(2);
|
||||
helper.assertValidatedCheckLength(0);
|
||||
helper.assertQtyToProduce(1, 1);
|
||||
helper.assertCurrentCheck('Register Consumed Materials "Metal cylinder"');
|
||||
helper.assertComponent('Metal cylinder', 'editable', 2, 2);
|
||||
}
|
||||
},
|
||||
// go to new step
|
||||
{trigger: '.o_tablet_step:nth-child(2)'},
|
||||
{trigger: 'div:contains("why am I")'},
|
||||
{
|
||||
trigger: '.o_tablet_client_action',
|
||||
run: function () {
|
||||
helper.assertCheckLength(2);
|
||||
helper.assertValidatedCheckLength(0);
|
||||
helper.assertQtyToProduce(1, 1);
|
||||
helper.assertCurrentCheck("my very new step");
|
||||
}
|
||||
},
|
||||
{trigger: 'div[name=note]:contains("why am I adding a step")'},
|
||||
{trigger: '.o_tablet_client_action'},
|
||||
{trigger: '.o_tablet_step:nth-child(1)'},
|
||||
{
|
||||
trigger: 'span:contains("Metal")',
|
||||
run: function () {
|
||||
helper.assertCheckLength(2);
|
||||
helper.assertValidatedCheckLength(0);
|
||||
helper.assertQtyToProduce(1, 1);
|
||||
helper.assertCurrentCheck('Register Consumed Materials "Metal cylinder"');
|
||||
helper.assertComponent('Metal cylinder', 'editable', 2, 2);
|
||||
}
|
||||
},
|
||||
{trigger: 'button[name=openMenuPopup]'},
|
||||
{trigger: '.o_tablet_popups'},
|
||||
{trigger: '.btn:contains("Update Instruction")'},
|
||||
{trigger: '.modal-title:contains("Update Instruction")'},
|
||||
{
|
||||
trigger: 'input#comment',
|
||||
run: 'text my reason',
|
||||
},
|
||||
|
||||
{trigger: '.btn-primary[name="process"]'},
|
||||
{trigger: '.o_tablet_client_action'},
|
||||
{trigger: '.btn[name=action_next]'},
|
||||
{trigger: 'div[name=note]:contains("why am I adding a step")'},
|
||||
{trigger: '.btn[name=action_next]'},
|
||||
{trigger: '.btn[name=action_generate_serial]'},
|
||||
{trigger: '.btn[name=do_finish]'},
|
||||
{trigger: '.o_searchview_input'},
|
||||
]);
|
||||