合并企业版代码(未测试,先提交到测试分支)

This commit is contained in:
qihao.gong@jikimo.com
2023-04-14 17:42:23 +08:00
parent 7a7b3d7126
commit d28525526a
1300 changed files with 513579 additions and 5426 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View 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;

View 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>

View 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;
}
}

View 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;

View 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};
}
}
}
}
}

View 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>

View 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;

View 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;
}
}

View 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 &lt;= 0">
<span>Best Time! Congratulations!</span>
</t>
<t t-elif="data.position &lt;= 4">
<span>Wow, you made the the Top 5!</span>
</t>
<t t-elif="data.position &lt;= 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>

View 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;

View 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;
}
}

View 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>

View 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;

View 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>

View 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
];
}
}

View 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

View 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');
}
}

View 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;
}
}
}

View 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,
});

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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,
});
}
}

View File

@@ -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);

View 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>

View File

@@ -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,
};
});

View 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'},
]);