合并企业版代码(未测试,先提交到测试分支)
This commit is contained in:
60
mrp_workorder/static/src/components/menuPopup.js
Normal file
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user