质量模块和库存扫码
This commit is contained in:
21
stock_barcode/static/src/components/grouped_line.js
Normal file
21
stock_barcode/static/src/components/grouped_line.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import LineComponent from "@stock_barcode/components/line";
|
||||
|
||||
export default class GroupedLineComponent extends LineComponent {
|
||||
|
||||
get isSelected() {
|
||||
return this.line.virtual_ids.indexOf(this.env.model.selectedLineVirtualId) !== -1;
|
||||
}
|
||||
|
||||
get opened() {
|
||||
return this.env.model.groupKey(this.line) === this.env.model.unfoldLineKey;
|
||||
}
|
||||
|
||||
toggleSublines(ev) {
|
||||
ev.stopPropagation();
|
||||
this.env.model.toggleSublines(this.line);
|
||||
}
|
||||
}
|
||||
GroupedLineComponent.components = { LineComponent };
|
||||
GroupedLineComponent.template = 'stock_barcode.GroupedLineComponent';
|
||||
28
stock_barcode/static/src/components/grouped_line.scss
Normal file
28
stock_barcode/static/src/components/grouped_line.scss
Normal file
@@ -0,0 +1,28 @@
|
||||
.o_barcode_client_action .o_barcode_lines {
|
||||
.o_barcode_line_summary {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
flex-basis: 100%;
|
||||
|
||||
&.o_unfolded {
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.o_sublines .o_barcode_line {
|
||||
border-left-width: 12px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
box-shadow: none;
|
||||
border-bottom: 0;
|
||||
|
||||
&.o_selected {
|
||||
box-shadow: inset 0px 0px 0px 3px $o-enterprise-primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
stock_barcode/static/src/components/grouped_line.xml
Normal file
27
stock_barcode/static/src/components/grouped_line.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="stock_barcode.GroupedLineComponent" owl="1">
|
||||
<div t-on-click="select"
|
||||
class="o_barcode_line list-group-item d-flex flex-row flex-wrap pt-0"
|
||||
t-att-data-barcode="line.product_id.barcode" t-attf-class="{{componentClasses}}">
|
||||
<div class="o_barcode_line_summary d-flex flex-grow-1 py-2 mt-2" t-att-class="opened ? 'o_unfolded': ''">
|
||||
<div class="o_barcode_line_details flex-grow-1">
|
||||
<t t-call="stock_barcode.LineSourceLocation"/>
|
||||
<t t-call="stock_barcode.LineTitle"/>
|
||||
<t t-call="stock_barcode.LineQuantity"/>
|
||||
<t t-call="stock_barcode.LineDestinationLocation"/>
|
||||
</div>
|
||||
<button t-on-click="toggleSublines" class="o_line_button o_toggle_sublines btn btn-primary ms-2 ms-sm-4">
|
||||
<i t-att-class="'fa fa-2x fa-caret-' + (opened ? 'up' : 'down')"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="o_sublines mb-2 flex-grow-1" t-if="opened">
|
||||
<t t-foreach="line.lines" t-as="subline" t-key="subline.virtual_id">
|
||||
<LineComponent line="subline" displayUOM="props.displayUOM" subline="true"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
125
stock_barcode/static/src/components/line.js
Normal file
125
stock_barcode/static/src/components/line.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { bus } from 'web.core';
|
||||
const { Component } = owl;
|
||||
|
||||
export default class LineComponent extends Component {
|
||||
get destinationLocationPath () {
|
||||
return this._getLocationPath(this.env.model._defaultDestLocation(), this.line.location_dest_id);
|
||||
}
|
||||
|
||||
get displayDestinationLocation() {
|
||||
return !this.props.subline && this.env.model.displayDestinationLocation;
|
||||
}
|
||||
|
||||
get displayResultPackage() {
|
||||
return this.env.model.displayResultPackage;
|
||||
}
|
||||
|
||||
get displaySourceLocation() {
|
||||
return !this.props.subline && this.env.model.displaySourceLocation;
|
||||
}
|
||||
|
||||
get highlightLocation() {
|
||||
return this.env.model.lastScanned.sourceLocation &&
|
||||
this.env.model.lastScanned.sourceLocation.id == this.line.location_id.id;
|
||||
}
|
||||
|
||||
get isComplete() {
|
||||
if (!this.qtyDemand || this.qtyDemand != this.qtyDone) {
|
||||
return false;
|
||||
} else if (this.isTracked && !this.lotName) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
get isSelected() {
|
||||
return this.line.virtual_id === this.env.model.selectedLineVirtualId ||
|
||||
(this.line.package_id && this.line.package_id.id === this.env.model.lastScanned.packageId);
|
||||
}
|
||||
|
||||
get isTracked() {
|
||||
return this.line.product_id.tracking !== 'none';
|
||||
}
|
||||
|
||||
get lotName() {
|
||||
return (this.line.lot_id && this.line.lot_id.name) || this.line.lot_name || '';
|
||||
}
|
||||
|
||||
get nextExpected() {
|
||||
if (!this.isSelected) {
|
||||
return false;
|
||||
} else if (this.isTracked && !this.lotName) {
|
||||
return 'lot';
|
||||
} else if (this.qtyDemand && this.qtyDone < this.qtyDemand) {
|
||||
return 'quantity';
|
||||
}
|
||||
}
|
||||
|
||||
get qtyDemand() {
|
||||
return this.env.model.getQtyDemand(this.line);
|
||||
}
|
||||
|
||||
get qtyDone() {
|
||||
return this.env.model.getQtyDone(this.line);
|
||||
}
|
||||
|
||||
get quantityIsSet() {
|
||||
return this.line.inventory_quantity_set;
|
||||
}
|
||||
|
||||
get incrementQty() {
|
||||
return this.env.model.getIncrementQuantity(this.line);
|
||||
}
|
||||
|
||||
get line() {
|
||||
return this.props.line;
|
||||
}
|
||||
|
||||
get requireLotNumber() {
|
||||
return true;
|
||||
}
|
||||
|
||||
get sourceLocationPath() {
|
||||
return this._getLocationPath(this.env.model._defaultLocation(), this.line.location_id);
|
||||
}
|
||||
|
||||
get componentClasses() {
|
||||
return [
|
||||
this.isComplete ? 'o_line_completed' : 'o_line_not_completed',
|
||||
this.env.model.lineIsFaulty(this) ? 'o_faulty' : '',
|
||||
this.isSelected ? 'o_selected o_highlight' : ''
|
||||
].join(' ');
|
||||
}
|
||||
|
||||
_getLocationPath(rootLocation, currentLocation) {
|
||||
let locationName = currentLocation.display_name;
|
||||
if (this.env.model.shouldShortenLocationName) {
|
||||
if (rootLocation && rootLocation.id != currentLocation.id) {
|
||||
const name = rootLocation.display_name;
|
||||
locationName = locationName.replace(name, '...');
|
||||
}
|
||||
}
|
||||
return locationName.replace(new RegExp(currentLocation.name + '$'), '');
|
||||
}
|
||||
|
||||
edit() {
|
||||
bus.trigger('edit-line', { line: this.line });
|
||||
}
|
||||
|
||||
addQuantity(quantity, ev) {
|
||||
this.env.model.updateLineQty(this.line.virtual_id, quantity);
|
||||
}
|
||||
|
||||
select(ev) {
|
||||
ev.stopPropagation();
|
||||
this.env.model.selectLine(this.line);
|
||||
this.env.model.trigger('update');
|
||||
}
|
||||
|
||||
setOnHandQuantity(ev) {
|
||||
this.env.model.setOnHandQuantity(this.line);
|
||||
}
|
||||
}
|
||||
LineComponent.template = 'stock_barcode.LineComponent';
|
||||
128
stock_barcode/static/src/components/line.scss
Normal file
128
stock_barcode/static/src/components/line.scss
Normal file
@@ -0,0 +1,128 @@
|
||||
$o-barcode-completed-color: #befdcc;
|
||||
|
||||
.o_barcode_client_action .o_barcode_lines .o_barcode_line {
|
||||
flex: 0 0 auto;
|
||||
border-width: 1px 0;
|
||||
|
||||
&:first-child {
|
||||
border-top-width: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
box-shadow: 0 3px 10px $gray-300;
|
||||
margin-bottom: 60vh;
|
||||
}
|
||||
|
||||
&_details {
|
||||
.fa:first-child {
|
||||
opacity: 0.5;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&.o_barcode_line_package {
|
||||
.o_barcode_line_details > * {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.o_barcode_line_details > .o_barcode_package_name {
|
||||
flex: 0 1 auto;
|
||||
overflow: hidden;
|
||||
|
||||
> span {
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.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: var(--barcode__line--completed, #befdcc);
|
||||
}
|
||||
|
||||
&.o_line_not_completed {
|
||||
background: var(--barcode__line--notCompleted, #fcf9f2);
|
||||
}
|
||||
|
||||
&.o_selected {
|
||||
box-shadow: inset 0px 0px 0px 3px $o-enterprise-primary-color;
|
||||
}
|
||||
|
||||
.o_barcode_scanner_qty {
|
||||
font-size: 1em;
|
||||
border-color: transparent; // Overwrite default badge color
|
||||
margin-left: -$badge-padding-x; // Compensate badge padding
|
||||
|
||||
&[class*="badge-"] {
|
||||
margin-left: 0; // If a style class is applied, reset compensation margin
|
||||
}
|
||||
|
||||
.qty-done, .inventory_quantity {
|
||||
min-width: 20px;
|
||||
&.o_flash {
|
||||
animation-name: highlighting-flash-primary;
|
||||
animation-duration: 0.5s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_line_buttons {
|
||||
min-width: 132px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
background-color: gray;
|
||||
border-color: gray;
|
||||
}
|
||||
}
|
||||
|
||||
.o_next_expected {
|
||||
color: #00A09D;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
[name=source_location].o_highlight {
|
||||
background-color: $o-barcode-completed-color;
|
||||
& .fa { opacity: 1; }
|
||||
}
|
||||
}
|
||||
136
stock_barcode/static/src/components/line.xml
Normal file
136
stock_barcode/static/src/components/line.xml
Normal file
@@ -0,0 +1,136 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<!-- Line's sub-elements -->
|
||||
<t t-name="stock_barcode.LineTitle" owl="1">
|
||||
<t t-if="props.line.product_id.default_code or props.line.product_id.code">
|
||||
<div class="o_barcode_line_title">
|
||||
<i class="fa fa-fw fa-tags"/>
|
||||
<span t-if="props.line.product_id.default_code"
|
||||
class="o_barcode_product_ref h5 fw-bold"
|
||||
t-esc="props.line.product_id.default_code"/>
|
||||
<span t-if="props.line.product_id.code != props.line.product_id.default_code"
|
||||
class="o_barcode_partner_code ms-1 h5 text-muted"
|
||||
t-esc="props.line.product_id.code"/>
|
||||
</div>
|
||||
<div>
|
||||
<i class="fa fa-fw"/>
|
||||
<span class="product-label" t-esc="props.line.product_id.display_name"/>
|
||||
</div>
|
||||
</t>
|
||||
<div t-else="" class="o_barcode_line_title pb-1">
|
||||
<i class="fa fa-fw fa-tags"/>
|
||||
<span class="product-label" t-esc="props.line.product_id.display_name"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="stock_barcode.LineQuantity" owl="1">
|
||||
<div name="quantity">
|
||||
<i class="fa fa-fw fa-cube" t-attf-class="{{nextExpected === 'quantity' ? 'o_next_expected' : ''}}"/>
|
||||
<span t-attf-class="o_barcode_scanner_qty font-monospace badge #{' '}">
|
||||
<span class="qty-done d-inline-block text-start"
|
||||
t-attf-class="
|
||||
{{nextExpected === 'quantity' && qtyDone ? 'o_flash' : ''}}
|
||||
{{isSelected && qtyDemand && qtyDone && qtyDone < qtyDemand ? 'fw-bolder' : ''}}"
|
||||
t-esc="env.model.IsNotSet(line) ? '?' : qtyDone"/>
|
||||
<span t-if="qtyDemand" t-esc="'/ ' + qtyDemand"/>
|
||||
</span>
|
||||
<span t-if="props.displayUOM" t-esc="line.product_uom_id.name"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="stock_barcode.LineOwner" owl="1">
|
||||
<div t-if="line.owner_id">
|
||||
<i class="fa fa-fw fa-user-o"/>
|
||||
<span class="o_line_owner" t-esc="line.owner_id.display_name"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="stock_barcode.LineSourceLocation" owl="1">
|
||||
<div name="source_location" t-if="displaySourceLocation" title="Source Location"
|
||||
t-attf-class="{{line.location_id.usage != 'internal' ? 'text-danger' : ''}} {{highlightLocation ? 'o_highlight' : ''}}">
|
||||
<i class="fa fa-fw fa-sign-out"/>
|
||||
<span class="o_line_source_location fst-italic text-muted">
|
||||
<t t-esc="sourceLocationPath"/>
|
||||
<span t-esc="line.location_id.name"
|
||||
t-attf-class="
|
||||
{{highlightLocation ? 'fw-bold' : ''}}
|
||||
{{line.location_id.usage != 'internal' ? 'text-danger' : 'text-black'}}"/>
|
||||
</span>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="stock_barcode.LineDestinationLocation" owl="1">
|
||||
<div name="destination_location" t-if="displayDestinationLocation" title="Destination Location"
|
||||
t-att-class="line.location_dest_id.usage != 'internal' ? 'text-danger' : ''">
|
||||
<i class="fa fa-fw fa-sign-in"/>
|
||||
<span class="o_line_destination_location fst-italic text-muted">
|
||||
<t t-esc="destinationLocationPath"/>
|
||||
<span t-esc="line.location_dest_id.name"
|
||||
t-attf-class="
|
||||
{{env.model.lastScanned.destLocation && env.model.lastScanned.destLocation.id == line.location_dest_id.id ? 'fw-bold' : ''}}
|
||||
{{line.location_dest_id.usage != 'internal' ? 'text-danger' : 'text-black'}}"/>
|
||||
</span>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<!-- Line's template -->
|
||||
<t t-name="stock_barcode.LineComponent" owl="1">
|
||||
<div t-on-click="select"
|
||||
class="o_barcode_line list-group-item d-flex flex-row flex-nowrap"
|
||||
t-att-data-virtual-id="line.virtual_id" t-attf-class="{{componentClasses}}"
|
||||
t-att-data-barcode="line.product_id.barcode">
|
||||
<div class="o_barcode_line_details flex-grow-1 flex-column flex-nowrap">
|
||||
<t t-call="stock_barcode.LineSourceLocation"/>
|
||||
<!-- Hides product's name if it's a subline, as it is already on the parent line. -->
|
||||
<t t-if="!props.subline" t-call="stock_barcode.LineTitle"/>
|
||||
<div t-if="isTracked and requireLotNumber" name="lot">
|
||||
<i class="fa fa-fw fa-barcode" t-attf-class="{{nextExpected === 'lot' ? 'o_next_expected' : ''}}"/>
|
||||
<span class="o_line_lot_name" t-esc="lotName"/>
|
||||
</div>
|
||||
<t t-call="stock_barcode.LineQuantity"/>
|
||||
<div t-if="line.package_id || line.result_package_id" name="package">
|
||||
<i class="fa fa-fw fa-archive"/>
|
||||
<span t-if="line.package_id" class="package" t-esc="line.package_id.name"/>
|
||||
<i t-if="displayResultPackage" class="fa fa-long-arrow-right mx-1"/>
|
||||
<span t-if="line.result_package_id" class="result-package" t-esc="line.result_package_id.name"/>
|
||||
<span t-if="line.result_package_id && line.result_package_id.package_type_id"
|
||||
class="fst-italic text-muted">
|
||||
(<t t-esc="line.result_package_id.package_type_id.name"/>)
|
||||
</span>
|
||||
</div>
|
||||
<t t-call="stock_barcode.LineOwner"/>
|
||||
<t t-call="stock_barcode.LineDestinationLocation"/>
|
||||
</div>
|
||||
<div class="o_line_buttons">
|
||||
<button t-on-click="edit" class="o_line_button o_edit btn"
|
||||
t-att-class="this.env.model.lineCanBeEdited(line) ? 'btn-secondary' : ''"
|
||||
t-att-disabled="!this.env.model.lineCanBeEdited(line)">
|
||||
<i class="fa fa-2x fa-pencil"/>
|
||||
</button>
|
||||
<button t-if="env.model.displaySetButton" t-on-click="setOnHandQuantity"
|
||||
class="o_line_button o_set btn ms-2 ms-sm-4"
|
||||
t-attf-class="{{quantityIsSet && qtyDone != qtyDemand ? 'o_difference' : ''}}">
|
||||
<i t-if="quantityIsSet" class="fa fa-2x"
|
||||
t-attf-class="{{qtyDone == qtyDemand ? 'fa-check' : 'fa-times'}}"/>
|
||||
</button>
|
||||
<span t-attf-class="{{env.model.incrementButtonsDisplayStyle}}">
|
||||
<button t-if="env.model.getDisplayDecrementBtn(line)" name="decrementButton" t-on-click="(ev) => this.addQuantity(-1, ev)"
|
||||
class="o_line_button o_remove_unit btn btn-primary ms-2 ms-sm-4"
|
||||
t-attf-disabled="{{qtyDone <= 0 || qtyDone == '?'}}">-1</button>
|
||||
<button t-if="env.model.getDisplayIncrementBtn(line)" name="incrementButton"
|
||||
t-on-click="(ev) => this.addQuantity(incrementQty, ev)" t-esc="'+' + incrementQty"
|
||||
t-att-disabled="!this.env.model.lineCanBeEdited(line)"
|
||||
class="o_line_button o_add_quantity btn btn-primary ms-2 ms-sm-4"/>
|
||||
</span>
|
||||
<button t-if="isSelected and env.model.getDisplayIncrementPackagingBtn(line)" name="incrementPackagingButton"
|
||||
t-on-click="(ev) => this.addQuantity(line.product_packaging_uom_qty, ev)"
|
||||
class="o_line_button w-100 btn btn-primary my-3 d-block">
|
||||
<div class="text-capitalize">
|
||||
+ <t t-esc="line.product_packaging_id.name"/>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
391
stock_barcode/static/src/components/main.js
Normal file
391
stock_barcode/static/src/components/main.js
Normal file
@@ -0,0 +1,391 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { ChatterContainer } from '@mail/components/chatter_container/chatter_container';
|
||||
|
||||
import BarcodePickingModel from '@stock_barcode/models/barcode_picking_model';
|
||||
import BarcodeQuantModel from '@stock_barcode/models/barcode_quant_model';
|
||||
import { bus } from 'web.core';
|
||||
import config from 'web.config';
|
||||
import GroupedLineComponent from '@stock_barcode/components/grouped_line';
|
||||
import LineComponent from '@stock_barcode/components/line';
|
||||
import PackageLineComponent from '@stock_barcode/components/package_line';
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import * as BarcodeScanner from '@web/webclient/barcode/barcode_scanner';
|
||||
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
import { View } from "@web/views/view";
|
||||
|
||||
const { Component, onMounted, onPatched, onWillStart, onWillUnmount, useSubEnv } = owl;
|
||||
|
||||
/**
|
||||
* Main Component
|
||||
* Gather the line information.
|
||||
* Manage the scan and save process.
|
||||
*/
|
||||
|
||||
class MainComponent extends Component {
|
||||
//--------------------------------------------------------------------------
|
||||
// Lifecycle
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
setup() {
|
||||
this.rpc = useService('rpc');
|
||||
this.orm = useService('orm');
|
||||
this.notification = useService('notification');
|
||||
this.props.model = this.props.action.res_model;
|
||||
this.props.id = this.props.action.context.active_id;
|
||||
const model = this._getModel(this.props);
|
||||
useSubEnv({model});
|
||||
this._scrollBehavior = 'smooth';
|
||||
this.isMobile = config.device.isMobile;
|
||||
|
||||
onWillStart(async () => {
|
||||
const barcodeData = await this.rpc(
|
||||
'/stock_barcode/get_barcode_data',
|
||||
{
|
||||
model: this.props.model,
|
||||
res_id: this.props.id || false,
|
||||
}
|
||||
);
|
||||
this.groups = barcodeData.groups;
|
||||
this.env.model.setData(barcodeData);
|
||||
this.env.model.on('process-action', this, this._onDoAction);
|
||||
this.env.model.on('notification', this, this._onNotification);
|
||||
this.env.model.on('refresh', this, this._onRefreshState);
|
||||
this.env.model.on('update', this, () => this.render(true));
|
||||
this.env.model.on('do-action', this, args => bus.trigger('do-action', args));
|
||||
this.env.model.on('history-back', this, () => this.env.config.historyBack());
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
bus.on('barcode_scanned', this, this._onBarcodeScanned);
|
||||
bus.on('edit-line', this, this._onEditLine);
|
||||
bus.on('exit', this, this.exit);
|
||||
bus.on('open-package', this, this._onOpenPackage);
|
||||
bus.on('refresh', this, this._onRefreshState);
|
||||
bus.on('warning', this, this._onWarning);
|
||||
});
|
||||
|
||||
onWillUnmount(() => {
|
||||
bus.off('barcode_scanned', this, this._onBarcodeScanned);
|
||||
bus.off('edit-line', this, this._onEditLine);
|
||||
bus.off('exit', this, this.exit);
|
||||
bus.off('open-package', this, this._onOpenPackage);
|
||||
bus.off('refresh', this, this._onRefreshState);
|
||||
bus.off('warning', this, this._onWarning);
|
||||
});
|
||||
|
||||
onPatched(() => {
|
||||
this._scrollToSelectedLine();
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
get displayHeaderInfoAsColumn() {
|
||||
return this.env.model.isDone || this.env.model.isCancelled;
|
||||
}
|
||||
|
||||
get displayBarcodeApplication() {
|
||||
return this.env.model.view === 'barcodeLines';
|
||||
}
|
||||
|
||||
get displayBarcodeActions() {
|
||||
return this.env.model.view === 'actionsView';
|
||||
}
|
||||
|
||||
get displayBarcodeLines() {
|
||||
return this.displayBarcodeApplication && this.env.model.canBeProcessed;
|
||||
}
|
||||
|
||||
get displayInformation() {
|
||||
return this.env.model.view === 'infoFormView';
|
||||
}
|
||||
|
||||
get displayNote() {
|
||||
return !this._hideNote && this.env.model.record.note;
|
||||
}
|
||||
|
||||
get displayPackageContent() {
|
||||
return this.env.model.view === 'packagePage';
|
||||
}
|
||||
|
||||
get displayProductPage() {
|
||||
return this.env.model.view === 'productPage';
|
||||
}
|
||||
|
||||
get lineFormViewData() {
|
||||
const data = this.env.model.viewsWidgetData;
|
||||
data.context = data.additionalContext;
|
||||
data.resId = this._editedLineParams && this._editedLineParams.currentId;
|
||||
return data;
|
||||
}
|
||||
|
||||
get highlightValidateButton() {
|
||||
return this.env.model.highlightValidateButton;
|
||||
}
|
||||
|
||||
get info() {
|
||||
return this.env.model.barcodeInfo;
|
||||
}
|
||||
|
||||
get isTransfer() {
|
||||
return this.currentSourceLocation && this.currentDestinationLocation;
|
||||
}
|
||||
|
||||
get lines() {
|
||||
return this.env.model.groupedLines;
|
||||
}
|
||||
|
||||
get mobileScanner() {
|
||||
return BarcodeScanner.isBarcodeScannerSupported();
|
||||
}
|
||||
|
||||
get packageLines() {
|
||||
return this.env.model.packageLines;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
_getModel(params) {
|
||||
const { rpc, orm, notification } = this;
|
||||
if (params.model === 'stock.picking') {
|
||||
return new BarcodePickingModel(params, { rpc, orm, notification });
|
||||
} else if (params.model === 'stock.quant') {
|
||||
return new BarcodeQuantModel(params, { rpc, orm, notification });
|
||||
} else {
|
||||
throw new Error('No JS model define');
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
async cancel() {
|
||||
await this.env.model.save();
|
||||
const action = await this.orm.call(
|
||||
this.props.model,
|
||||
'action_cancel_from_barcode',
|
||||
[[this.props.id]]
|
||||
);
|
||||
const onClose = res => {
|
||||
if (res && res.cancelled) {
|
||||
this.env.model._cancelNotification();
|
||||
this.env.config.historyBack();
|
||||
}
|
||||
};
|
||||
bus.trigger('do-action', {
|
||||
action,
|
||||
options: {
|
||||
on_close: onClose.bind(this),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async openMobileScanner() {
|
||||
const barcode = await BarcodeScanner.scanBarcode();
|
||||
if (barcode) {
|
||||
this.env.model.processBarcode(barcode);
|
||||
if ('vibrate' in window.navigator) {
|
||||
window.navigator.vibrate(100);
|
||||
}
|
||||
} else {
|
||||
this.env.services.notification.notify({
|
||||
type: 'warning',
|
||||
message: this.env._t("Please, Scan again !"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async exit(ev) {
|
||||
if (this.displayBarcodeApplication) {
|
||||
await this.env.model.save();
|
||||
this.env.config.historyBack();
|
||||
} else {
|
||||
this.toggleBarcodeLines();
|
||||
}
|
||||
}
|
||||
|
||||
hideNote(ev) {
|
||||
this._hideNote = true;
|
||||
this.render();
|
||||
}
|
||||
|
||||
async openProductPage() {
|
||||
if (!this._editedLineParams) {
|
||||
await this.env.model.save();
|
||||
}
|
||||
this.env.model.displayProductPage();
|
||||
}
|
||||
|
||||
async print(action, method) {
|
||||
await this.env.model.save();
|
||||
const options = this.env.model._getPrintOptions();
|
||||
if (options.warning) {
|
||||
return this.env.model.notification.add(options.warning, { type: 'warning' });
|
||||
}
|
||||
if (!action && method) {
|
||||
action = await this.orm.call(
|
||||
this.props.model,
|
||||
method,
|
||||
[[this.props.id]]
|
||||
);
|
||||
}
|
||||
bus.trigger('do-action', { action, options });
|
||||
}
|
||||
|
||||
putInPack(ev) {
|
||||
ev.stopPropagation();
|
||||
this.env.model._putInPack();
|
||||
}
|
||||
|
||||
saveFormView(lineRecord) {
|
||||
const lineId = (lineRecord && lineRecord.data.id) || (this._editedLineParams && this._editedLineParams.currentId);
|
||||
const recordId = (lineRecord.resModel === this.props.model) ? lineId : undefined
|
||||
this._onRefreshState({ recordId, lineId });
|
||||
}
|
||||
|
||||
toggleBarcodeActions(ev) {
|
||||
ev.stopPropagation();
|
||||
this.env.model.displayBarcodeActions();
|
||||
}
|
||||
|
||||
async toggleBarcodeLines(lineId) {
|
||||
this._editedLineParams = undefined;
|
||||
await this.env.model.displayBarcodeLines(lineId);
|
||||
}
|
||||
|
||||
async toggleInformation() {
|
||||
await this.env.model.save();
|
||||
this.env.model.displayInformation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls `validate` on the model and then triggers up the action because OWL
|
||||
* components don't seem able to manage wizard without doing custom things.
|
||||
*
|
||||
* @param {OdooEvent} ev
|
||||
*/
|
||||
async validate(ev) {
|
||||
ev.stopPropagation();
|
||||
await this.env.model.validate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler called when a barcode is scanned.
|
||||
*
|
||||
* @private
|
||||
* @param {string} barcode
|
||||
*/
|
||||
_onBarcodeScanned(barcode) {
|
||||
if (this.displayBarcodeApplication) {
|
||||
this.env.model.processBarcode(barcode);
|
||||
}
|
||||
}
|
||||
|
||||
_scrollToSelectedLine() {
|
||||
if (!this.displayBarcodeLines) {
|
||||
this._scrollBehavior = 'auto';
|
||||
return;
|
||||
}
|
||||
let selectedLine = document.querySelector('.o_sublines .o_barcode_line.o_highlight');
|
||||
const isSubline = Boolean(selectedLine);
|
||||
if (!selectedLine) {
|
||||
selectedLine = document.querySelector('.o_barcode_line.o_highlight');
|
||||
}
|
||||
if (!selectedLine) {
|
||||
const matchingLine = this.env.model.findLineForCurrentLocation();
|
||||
if (matchingLine) {
|
||||
selectedLine = document.querySelector(`.o_barcode_line[data-virtual-id="${matchingLine.virtual_id}"]`);
|
||||
}
|
||||
}
|
||||
if (selectedLine) {
|
||||
// If a line is selected, checks if this line is on the top of the
|
||||
// page, and if it's not, scrolls until the line is on top.
|
||||
const header = document.querySelector('.o_barcode_header');
|
||||
const lineRect = selectedLine.getBoundingClientRect();
|
||||
const navbar = document.querySelector('.o_main_navbar');
|
||||
const page = document.querySelector('.o_barcode_lines');
|
||||
// Computes the real header's height (the navbar is present if the page was refreshed).
|
||||
const headerHeight = navbar ? navbar.offsetHeight + header.offsetHeight : header.offsetHeight;
|
||||
if (lineRect.top < headerHeight || lineRect.bottom > (headerHeight + lineRect.height)) {
|
||||
let top = lineRect.top - headerHeight + page.scrollTop;
|
||||
if (isSubline) {
|
||||
const parentLine = selectedLine.closest('.o_barcode_lines > .o_barcode_line');
|
||||
const parentSummary = parentLine.querySelector('.o_barcode_line_summary');
|
||||
top -= parentSummary.getBoundingClientRect().height;
|
||||
}
|
||||
page.scroll({ left: 0, top, behavior: this._scrollBehavior });
|
||||
this._scrollBehavior = 'smooth';
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
async _onDoAction(ev) {
|
||||
bus.trigger('do-action', {
|
||||
action: ev,
|
||||
options: {
|
||||
on_close: this._onRefreshState.bind(this),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async _onEditLine(ev) {
|
||||
let { line } = ev;
|
||||
const virtualId = line.virtual_id;
|
||||
await this.env.model.save();
|
||||
// Updates the line id if it's missing, in order to open the line form view.
|
||||
if (!line.id && virtualId) {
|
||||
line = this.env.model.pageLines.find(l => Number(l.dummy_id) === virtualId);
|
||||
}
|
||||
this._editedLineParams = this.env.model.getEditedLineParams(line);
|
||||
await this.openProductPage();
|
||||
}
|
||||
|
||||
_onNotification(notifParams) {
|
||||
const { message } = notifParams;
|
||||
delete notifParams.message;
|
||||
this.env.services.notification.add(message, notifParams);
|
||||
}
|
||||
|
||||
_onOpenPackage(packageId) {
|
||||
this._inspectedPackageId = packageId;
|
||||
this.env.model.displayPackagePage();
|
||||
}
|
||||
|
||||
async _onRefreshState(paramsRefresh) {
|
||||
const { recordId, lineId } = paramsRefresh || {}
|
||||
const { route, params } = this.env.model.getActionRefresh(recordId);
|
||||
const result = await this.rpc(route, params);
|
||||
await this.env.model.refreshCache(result.data.records);
|
||||
await this.toggleBarcodeLines(lineId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles triggered warnings. It can happen from an onchange for example.
|
||||
*
|
||||
* @param {CustomEvent} ev
|
||||
*/
|
||||
_onWarning(ev) {
|
||||
const { title, message } = ev.detail;
|
||||
this.env.services.dialog.add(ConfirmationDialog, { title, body: message });
|
||||
}
|
||||
}
|
||||
MainComponent.template = 'stock_barcode.MainComponent';
|
||||
MainComponent.components = {
|
||||
View,
|
||||
GroupedLineComponent,
|
||||
LineComponent,
|
||||
PackageLineComponent,
|
||||
ChatterContainer,
|
||||
};
|
||||
|
||||
registry.category("actions").add("stock_barcode_client_action", MainComponent);
|
||||
|
||||
export default MainComponent;
|
||||
270
stock_barcode/static/src/components/main.scss
Normal file
270
stock_barcode/static/src/components/main.scss
Normal file
@@ -0,0 +1,270 @@
|
||||
.o_barcode_client_action {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background-color: $o-view-background-color;
|
||||
overflow: auto;
|
||||
|
||||
.o_strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
// Top navbar
|
||||
// =====================================
|
||||
.o_barcode_header {
|
||||
flex: 0 0 46px;
|
||||
color: white;
|
||||
background-color: var(--barcode__header-bg, #{$o-brand-odoo});
|
||||
|
||||
.nav-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
.nav-link, .navbar-text {
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
|
||||
&:hover {
|
||||
color: rgba($color: #FFFFFF, $alpha: 0.75)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Top Block
|
||||
// =====================================
|
||||
.o_barcode_message {
|
||||
box-shadow: inset 0 0 20px $gray-900;
|
||||
|
||||
.o_barcode_pic {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1 1 60%;
|
||||
max-width: 200px;
|
||||
.fa-exclamation-triangle {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Summary
|
||||
// =====================================
|
||||
.o_barcode_lines_header {
|
||||
font-size: 16px;
|
||||
color: white;
|
||||
background-color: var(--barcode__linesHeader-bg, #{$o-gray-800});
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Lines Block
|
||||
// =====================================
|
||||
.o_barcode_lines {
|
||||
clear: both;
|
||||
flex: auto;
|
||||
overflow: auto;
|
||||
color: $gray-800;
|
||||
margin-bottom: 60px;
|
||||
@media (orientation: portrait) {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
&.o_js_has_highlight .o_barcode_line.o_highlight {
|
||||
&.o_highlight_green {
|
||||
box-shadow: inset 0px 0px 0px 3px $o-brand-secondary;
|
||||
}
|
||||
|
||||
.product-label, .o_barcode_scanner_qty {
|
||||
color: $headings-color;
|
||||
}
|
||||
|
||||
.qty-done, .inventory_quantity {
|
||||
font-weight: bold;
|
||||
|
||||
&.o_js_qty_animate {
|
||||
animation: o_barcode_scanner_qty_update .2s alternate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Embedded views
|
||||
// =====================================
|
||||
.o_barcode_generic_view {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.o_view_controller, .o_view_controller .o_form_view.o_form_nosheet {
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.o_field_one2many.o_field_widget .o_kanban_record {
|
||||
font-size: 0.6em;
|
||||
}
|
||||
|
||||
.o_form_view {
|
||||
&.o_xxs_form_view {
|
||||
.o_td_label > .o_form_label {
|
||||
color: $gray-900;
|
||||
font-weight: bold;
|
||||
padding-top: 5px;
|
||||
}
|
||||
.o_field_widget {
|
||||
font-size: 1em;
|
||||
.btn.fa {
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
.o_list_view {
|
||||
th, .o_field_widget {
|
||||
font-size: $font-size-base;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.o_form_nosheet {
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
.o_kanban_record {
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Settings menu
|
||||
// =====================================
|
||||
.o_barcode_settings {
|
||||
display: flex;
|
||||
flex: auto;
|
||||
|
||||
> button {
|
||||
flex: 1 0 auto;
|
||||
border-bottom: 1px solid $border-color;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Control buttons (validate, previous,
|
||||
// next, put in pack, ...)
|
||||
// =====================================
|
||||
.o_barcode_control {
|
||||
flex: 0 0 60px;
|
||||
margin: 0 -1px;
|
||||
width: 100%;
|
||||
> .btn {
|
||||
flex: 1;
|
||||
width: 50%;
|
||||
@media (orientation: portrait) {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
@media (orientation: landscape) {
|
||||
height: 60px;
|
||||
}
|
||||
border-width: 1px 0 0 0;
|
||||
border-style: solid;
|
||||
&.btn-secondary {
|
||||
color: $gray-800;
|
||||
border-color: $gray-400;
|
||||
}
|
||||
&.btn-primary {
|
||||
border-color: $primary;
|
||||
}
|
||||
&.btn-success {
|
||||
border-color: $success;
|
||||
}
|
||||
&[disabled] {
|
||||
opacity: 1;
|
||||
background-color: $gray-200;
|
||||
color: $btn-link-disabled-color;
|
||||
}
|
||||
+ .btn {
|
||||
border-left-width: 1px;
|
||||
border-left-color: $gray-400;
|
||||
}
|
||||
}
|
||||
.fa-angle-left, .fa-angle-right {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
&:first-of-type {
|
||||
box-shadow: 0 -3px 10px $gray-300;
|
||||
}
|
||||
}
|
||||
|
||||
// Line form
|
||||
// =====================================
|
||||
.o_barcode_line_form {
|
||||
margin-left: 24px;
|
||||
margin-bottom: 36px;
|
||||
font-size: 1.4em;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.row {
|
||||
width: 700px;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
&.row-long {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
a.o_field_widget {
|
||||
display: inline-block;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
// Avoids to make the UoM field as long as the quantity done field.
|
||||
.o_field_widget[name="product_uom_id"] input {
|
||||
@include media-breakpoint-up(sm) {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.o_qty_done_field_completed input {
|
||||
background: var(--barcode__input--completed, #f6fdf6);
|
||||
}
|
||||
|
||||
.o_qty_done_field_not_completed input {
|
||||
background: var(--barcode__input--notCompleted, #fcf9f2);
|
||||
}
|
||||
|
||||
& > div {
|
||||
.o_field_float {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.o_input {
|
||||
padding: 8px;
|
||||
border: 1px solid $border-color;
|
||||
}
|
||||
|
||||
.o_required_modifier .o_input {
|
||||
border-bottom: 2px solid $border-color
|
||||
}
|
||||
|
||||
.o_dropdown_button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
i {
|
||||
min-width: 24px;
|
||||
max-width: 24px;
|
||||
color: $o-main-color-muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
127
stock_barcode/static/src/components/main.xml
Normal file
127
stock_barcode/static/src/components/main.xml
Normal file
@@ -0,0 +1,127 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<div t-name="stock_barcode.MainComponent" class="o_content o_barcode_client_action" owl="1">
|
||||
<div class="o_barcode_header">
|
||||
<div class="navbar navbar-expand navbar-dark">
|
||||
<nav class="navbar-nav me-auto">
|
||||
<button t-on-click="exit" class="o_exit btn nav-link me-4">
|
||||
<i class="fa fa-chevron-left"/>
|
||||
</button>
|
||||
<span class="o_title navbar-text" t-esc="env.model.name"/>
|
||||
</nav>
|
||||
<nav class="navbar-nav">
|
||||
<t t-if="displayBarcodeApplication">
|
||||
<button t-if="env.model.formViewId" t-on-click="toggleInformation" class="o_show_information btn nav-link">
|
||||
<i class="fa fa-info-circle"/>
|
||||
</button>
|
||||
<button t-if="mobileScanner" class="o_stock_mobile_barcode btn nav-link" t-on-click="openMobileScanner">
|
||||
<i class="fa fa-barcode"/>
|
||||
</button>
|
||||
<button t-on-click="toggleBarcodeActions" class="o_barcode_actions btn nav-link">
|
||||
<i class="fa fa-cog"/>
|
||||
</button>
|
||||
</t>
|
||||
<button t-else="" t-on-click="() => this.toggleBarcodeLines()" class="o_close btn nav-link">
|
||||
<i class="fa fa-times"/>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
<t t-if="displayBarcodeApplication">
|
||||
<div t-if="displayNote" class="alert alert-warning text-start mb-0">
|
||||
<button type="button" class="close" title="Close" aria-label="Close" t-on-click="hideNote">×</button>
|
||||
<t t-esc="env.model.record.note"/>
|
||||
</div>
|
||||
<div class="o_barcode_lines_header row alert m-0 px-1 "
|
||||
t-attf-class="{{displayHeaderInfoAsColumn ? 'flex-column justify-content-center align-items-center' : 'justify-content-between'}}">
|
||||
<div t-if="info.warning" class="o_barcode_pic position-relative text-center mt-2 mb-1">
|
||||
<i class="fa fa-5x fa-exclamation-triangle"/>
|
||||
</div>
|
||||
<div name="barcode_messages" class="d-flex align-items-center justify-content-center w-100">
|
||||
<span t-if="info.icon" class="fa fa-3x me-3" t-attf-class="fa-{{info.icon}}"/>
|
||||
<span class="o_scan_message" t-attf-class="o_{{info.class}}">
|
||||
<span t-if="info.warning" name="warning" class="fa fa-exclamation-triangle me-1"/>
|
||||
<span t-out="info.message"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<div t-if="displayBarcodeLines && (lines.length || packageLines.length)" class="o_barcode_lines"> <!-- Lines -->
|
||||
<t t-foreach="lines" t-as="line" t-key="line.virtual_id">
|
||||
<GroupedLineComponent t-if="line.lines" line="line" displayUOM="groups.group_uom"/>
|
||||
<LineComponent t-else="" line="line" displayUOM="groups.group_uom"/>
|
||||
</t>
|
||||
<t t-foreach="packageLines" t-as="line" t-key="line.virtual_id">
|
||||
<PackageLineComponent line="line" displayUOM="false"/>
|
||||
</t>
|
||||
</div>
|
||||
<div t-if="displayProductPage"> <!-- Barcode Line Edit Form View -->
|
||||
<View type="'form'" mode="'edit'"
|
||||
viewId="env.model.lineFormViewId"
|
||||
resModel="lineFormViewData.resModel"
|
||||
resId="lineFormViewData.resId"
|
||||
display="{ controlPanel: false }"
|
||||
context="lineFormViewData.context"
|
||||
onSave="(record) => this.saveFormView(record)"
|
||||
onDiscard="() => this.toggleBarcodeLines()"/>
|
||||
</div>
|
||||
<div t-if="displayPackageContent"> <!-- Quants (in package) Kanban View -->
|
||||
<View type="'kanban'"
|
||||
viewId="packageKanbanViewId"
|
||||
display="{ controlPanel: false }"
|
||||
resModel="'stock.quant'"
|
||||
domain="[['package_id', '=', _inspectedPackageId]]"/>
|
||||
</div>
|
||||
<div t-if="displayInformation"> <!-- Res Model Form View -->
|
||||
<View type="'form'" mode="'edit'"
|
||||
viewId="env.model.formViewId"
|
||||
display="{ controlPanel: false }"
|
||||
resModel="props.model"
|
||||
resId="props.id"
|
||||
onSave="() => this._onRefreshState({ lineId: this._editedLineParams && this._editedLineParams.currentId })"
|
||||
onDiscard="() => this.toggleBarcodeLines()"/>
|
||||
<ChatterContainer threadModel="props.model" threadId="props.id"/>
|
||||
</div>
|
||||
<div t-if="displayBarcodeActions" class="o_barcode_settings flex-column h100">
|
||||
<t t-foreach="env.model.printButtons" t-as="button" t-key="button.class">
|
||||
<button class="btn-lg btn btn-light text-uppercase"
|
||||
t-attf-class="{{button.class}}" t-esc="button.name"
|
||||
t-on-click="() => this.print(button.action, button.method)"/>
|
||||
</t>
|
||||
<button t-if="env.model.displayCancelButton"
|
||||
t-on-click="cancel"
|
||||
class="o_cancel_operation btn-lg btn btn-light text-uppercase">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
<div t-if="displayBarcodeLines" class="fixed-bottom"> <!-- Footer -->
|
||||
<div class="o_barcode_control o_action_buttons d-flex">
|
||||
<button class="o_add_line btn btn-secondary text-uppercase" t-on-click="openProductPage">
|
||||
<i class="fa fa-plus me-1"/> Add Product
|
||||
</button>
|
||||
<button t-if="env.model.displayPutInPackButton" t-on-click="putInPack"
|
||||
t-att-disabled="!env.model.canPutInPack"
|
||||
class="o_put_in_pack btn btn-secondary text-uppercase">
|
||||
<i class="fa fa-cube me-1"/> Put In Pack
|
||||
</button>
|
||||
<button t-if="env.model.displayValidateButton" t-on-click="validate"
|
||||
class="btn text-uppercase o_validate_page"
|
||||
t-att-disabled="!env.model.canBeValidate"
|
||||
t-attf-class="{{highlightValidateButton ? 'btn-success' : 'btn-secondary'}}">
|
||||
<i class="fa fa-check me-1"/> Validate
|
||||
</button>
|
||||
<button t-if="env.model.displayApplyButton" t-on-click="() => this.env.model.apply()"
|
||||
class="btn text-uppercase o_apply_page"
|
||||
t-att-disabled="env.model.applyOn === 0"
|
||||
t-attf-class="{{highlightValidateButton ? 'btn-success' : 'btn-secondary'}}">
|
||||
<i class="fa fa-check me-1"/> Apply
|
||||
<span t-attf-class="{{highlightValidateButton ? '' : 'text-muted'}}">
|
||||
(<t t-esc="env.model.applyOn"/>)
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</templates>
|
||||
41
stock_barcode/static/src/components/package_line.js
Normal file
41
stock_barcode/static/src/components/package_line.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { bus } from 'web.core';
|
||||
import LineComponent from './line';
|
||||
|
||||
export default class PackageLineComponent extends LineComponent {
|
||||
get componentClasses() {
|
||||
return [
|
||||
this.qtyDone == 1 ? 'o_line_completed' : 'o_line_not_completed',
|
||||
this.isSelected ? 'o_selected o_highlight' : ''
|
||||
].join(' ');
|
||||
}
|
||||
|
||||
get isSelected() {
|
||||
return this.line.package_id.id === this.env.model.lastScanned.packageId;
|
||||
}
|
||||
|
||||
get qtyDemand() {
|
||||
return this.props.line.reservedPackage ? 1 : false;
|
||||
}
|
||||
|
||||
get qtyDone() {
|
||||
const reservedQuantity = this.line.lines.reduce((r, l) => r + l.reserved_uom_qty, 0);
|
||||
const doneQuantity = this.line.lines.reduce((r, l) => r + l.qty_done, 0);
|
||||
if (reservedQuantity > 0) {
|
||||
return doneQuantity / reservedQuantity;
|
||||
}
|
||||
return doneQuantity >= 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
openPackage() {
|
||||
bus.trigger('open-package', this.line.package_id.id);
|
||||
}
|
||||
|
||||
select(ev) {
|
||||
ev.stopPropagation();
|
||||
this.env.model.selectPackageLine(this.line);
|
||||
this.env.model.trigger('update');
|
||||
}
|
||||
}
|
||||
PackageLineComponent.template = 'stock_barcode.PackageLineComponent';
|
||||
24
stock_barcode/static/src/components/package_line.xml
Normal file
24
stock_barcode/static/src/components/package_line.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<div t-name="stock_barcode.PackageLineComponent" owl="1" t-on-click="select"
|
||||
class="o_barcode_line list-group-item d-flex flex-row flex-nowrap py-3"
|
||||
t-attf-class="{{componentClasses}}" t-att-data-package="line.package_id.name">
|
||||
<div class="o_barcode_line_details flex-grow-1 flex-column flex-nowrap">
|
||||
<t t-call="stock_barcode.LineSourceLocation"/>
|
||||
<div>
|
||||
<i class="fa fa-fw fa-archive"/>
|
||||
<t t-esc="line.package_id.name"/>
|
||||
<i class="fa fa-long-arrow-right mx-1"/>
|
||||
<t t-esc="line.result_package_id.name"/>
|
||||
</div>
|
||||
<t t-call="stock_barcode.LineQuantity"/>
|
||||
<t t-call="stock_barcode.LineOwner"/>
|
||||
<t t-call="stock_barcode.LineDestinationLocation"/>
|
||||
</div>
|
||||
<button t-on-click="openPackage" class="o_line_button o_package_content btn btn-secondary ms-2 ms-sm-4">
|
||||
<i class="fa fa-2x fa-dropbox"/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</templates>
|
||||
Reference in New Issue
Block a user