mirror of
https://github.com/bringout/oca-ocb-technical.git
synced 2026-04-18 22:52:03 +02:00
Initial commit: Technical packages
This commit is contained in:
commit
3473fa71a0
873 changed files with 297766 additions and 0 deletions
BIN
odoo-bringout-oca-ocb-barcodes/barcodes/static/img/barcode.png
Normal file
BIN
odoo-bringout-oca-ocb-barcodes/barcodes/static/img/barcode.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -0,0 +1,23 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { standardFieldProps } from "@web/views/fields/standard_field_props";
|
||||
import { useBus, useService } from "@web/core/utils/hooks";
|
||||
|
||||
const { Component, xml } = owl;
|
||||
|
||||
export class BarcodeHandlerField extends Component {
|
||||
setup() {
|
||||
const barcode = useService("barcode");
|
||||
useBus(barcode.bus, "barcode_scanned", this.onBarcodeScanned);
|
||||
}
|
||||
onBarcodeScanned(event) {
|
||||
const { barcode } = event.detail;
|
||||
this.props.update(barcode);
|
||||
}
|
||||
}
|
||||
|
||||
BarcodeHandlerField.template = xml``;
|
||||
BarcodeHandlerField.props = { ...standardFieldProps };
|
||||
|
||||
registry.category("fields").add("barcode_handler", BarcodeHandlerField);
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { getVisibleElements } from "@web/core/utils/ui";
|
||||
import { MacroEngine } from "@web/core/macro";
|
||||
|
||||
function clickOnButton(selector) {
|
||||
const button = document.body.querySelector(selector);
|
||||
if (button) {
|
||||
button.click();
|
||||
}
|
||||
}
|
||||
function updatePager(position) {
|
||||
const pager = document.body.querySelector("nav.o_pager");
|
||||
if (!pager || pager.innerText.includes("-")) {
|
||||
// we don't change pages if we are in a multi record view
|
||||
return;
|
||||
}
|
||||
let next;
|
||||
if (position === "first") {
|
||||
next = 1;
|
||||
} else {
|
||||
next = parseInt(pager.querySelector(".o_pager_limit").textContent, 10);
|
||||
}
|
||||
let current = parseInt(pager.innerText.split('/')[0], 10);
|
||||
if (current === next) {
|
||||
return;
|
||||
}
|
||||
const engine = new MacroEngine();
|
||||
engine.activate({
|
||||
name: "updating pager",
|
||||
timeout: 1000,
|
||||
interval: 0,
|
||||
steps: [
|
||||
{
|
||||
trigger: "span.o_pager_value",
|
||||
action: "click"
|
||||
},
|
||||
{
|
||||
trigger: "input.o_pager_value",
|
||||
action: "text",
|
||||
value: next
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
export const COMMANDS = {
|
||||
"O-CMD.EDIT": () => clickOnButton(".o_form_button_edit"),
|
||||
"O-CMD.DISCARD": () => clickOnButton(".o_form_button_cancel"),
|
||||
"O-CMD.SAVE": () => clickOnButton(".o_form_button_save"),
|
||||
"O-CMD.PREV": () => clickOnButton(".o_pager_previous"),
|
||||
"O-CMD.NEXT": () => clickOnButton(".o_pager_next"),
|
||||
"O-CMD.PAGER-FIRST": () => updatePager("first"),
|
||||
"O-CMD.PAGER-LAST": () => updatePager("last"),
|
||||
};
|
||||
|
||||
export const barcodeGenericHandlers = {
|
||||
dependencies: ["ui", "barcode", "notification"],
|
||||
start(env, { ui, barcode, notification }) {
|
||||
|
||||
barcode.bus.addEventListener("barcode_scanned", (ev) => {
|
||||
const barcode = ev.detail.barcode;
|
||||
if (barcode.startsWith("O-BTN.")) {
|
||||
let targets = [];
|
||||
try {
|
||||
// the scanned barcode could be anything, and could crash the queryselectorall
|
||||
// function
|
||||
targets = getVisibleElements(ui.activeElement, `[barcode_trigger=${barcode.slice(6)}]`);
|
||||
} catch (_e) {
|
||||
console.warn(`Barcode '${barcode}' is not valid`);
|
||||
}
|
||||
for (let elem of targets) {
|
||||
elem.click();
|
||||
}
|
||||
}
|
||||
if (barcode.startsWith("O-CMD.")) {
|
||||
const fn = COMMANDS[barcode];
|
||||
if (fn) {
|
||||
fn();
|
||||
} else {
|
||||
notification.add(env._t("Barcode: ") + `'${barcode}'`, {
|
||||
title: env._t("Unknown barcode command"),
|
||||
type: "danger"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
registry.category("services").add("barcode_handlers", barcodeGenericHandlers);
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { isBrowserChrome, isMobileOS } from "@web/core/browser/feature_detection";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { session } from "@web/session";
|
||||
|
||||
const { EventBus, whenReady } = owl;
|
||||
|
||||
function isEditable(element) {
|
||||
return element.matches('input,textarea,[contenteditable="true"]');
|
||||
}
|
||||
|
||||
function makeBarcodeInput() {
|
||||
const inputEl = document.createElement('input');
|
||||
inputEl.setAttribute("style", "position:fixed;top:50%;transform:translateY(-50%);z-index:-1;opacity:0");
|
||||
inputEl.setAttribute("autocomplete", "off");
|
||||
inputEl.setAttribute("inputmode", "none"); // magic! prevent native keyboard from popping
|
||||
inputEl.classList.add("o-barcode-input");
|
||||
inputEl.setAttribute('name', 'barcode');
|
||||
return inputEl;
|
||||
}
|
||||
|
||||
export const barcodeService = {
|
||||
// Keys from a barcode scanner are usually processed as quick as possible,
|
||||
// but some scanners can use an intercharacter delay (we support <= 50 ms)
|
||||
maxTimeBetweenKeysInMs: session.max_time_between_keys_in_ms || 100,
|
||||
|
||||
// this is done here to make it easily mockable in mobile tests
|
||||
isMobileChrome: isMobileOS() && isBrowserChrome(),
|
||||
|
||||
cleanBarcode: function(barcode) {
|
||||
return barcode.replace(/Alt|Shift|Control/g, '');
|
||||
},
|
||||
|
||||
start() {
|
||||
const bus = new EventBus();
|
||||
let timeout = null;
|
||||
|
||||
let bufferedBarcode = "";
|
||||
let currentTarget = null;
|
||||
let barcodeInput = null;
|
||||
|
||||
function handleBarcode(barcode, target) {
|
||||
bus.trigger('barcode_scanned', {barcode,target});
|
||||
if (target.getAttribute('barcode_events') === "true") {
|
||||
const barcodeScannedEvent = new CustomEvent("barcode_scanned", { detail: { barcode, target } });
|
||||
target.dispatchEvent(barcodeScannedEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check if we have a barcode, and trigger appropriate events
|
||||
*/
|
||||
function checkBarcode(ev) {
|
||||
let str = barcodeInput ? barcodeInput.value : bufferedBarcode;
|
||||
str = barcodeService.cleanBarcode(str);
|
||||
if (str.length >= 3) {
|
||||
if (ev) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
handleBarcode(str, currentTarget);
|
||||
}
|
||||
if (barcodeInput) {
|
||||
barcodeInput.value = "";
|
||||
}
|
||||
bufferedBarcode = "";
|
||||
currentTarget = null;
|
||||
}
|
||||
|
||||
function keydownHandler(ev) {
|
||||
if (!ev.key) {
|
||||
// Chrome may trigger incomplete keydown events under certain circumstances.
|
||||
// E.g. when using browser built-in autocomplete on an input.
|
||||
// See https://stackoverflow.com/questions/59534586/google-chrome-fires-keydown-event-when-form-autocomplete
|
||||
return;
|
||||
}
|
||||
// Ignore 'Shift', 'Escape', 'Backspace', 'Insert', 'Delete', 'Home', 'End', Arrow*, F*, Page*, ...
|
||||
// meta is often used for UX purpose (like shortcuts)
|
||||
// Notes:
|
||||
// - shiftKey is not ignored because it can be used by some barcode scanner for digits.
|
||||
// - altKey/ctrlKey are not ignored because it can be used in some barcodes (e.g. GS1 separator)
|
||||
const isSpecialKey = !['Control', 'Alt'].includes(ev.key) && (ev.key.length > 1 || ev.metaKey);
|
||||
const isEndCharacter = ev.key.match(/(Enter|Tab)/);
|
||||
|
||||
// Don't catch non-printable keys except 'enter' and 'tab'
|
||||
if (isSpecialKey && !isEndCharacter) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentTarget = ev.target;
|
||||
// Don't catch events targeting elements that are editable because we
|
||||
// have no way of redispatching 'genuine' key events. Resent events
|
||||
// don't trigger native event handlers of elements. So this means that
|
||||
// our fake events will not appear in eg. an <input> element.
|
||||
if (currentTarget !== barcodeInput && isEditable(currentTarget) &&
|
||||
!currentTarget.dataset.enableBarcode &&
|
||||
currentTarget.getAttribute("barcode_events") !== "true") {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(timeout);
|
||||
if (isEndCharacter) {
|
||||
checkBarcode(ev);
|
||||
} else {
|
||||
bufferedBarcode += ev.key;
|
||||
timeout = setTimeout(checkBarcode, barcodeService.maxTimeBetweenKeysInMs);
|
||||
}
|
||||
}
|
||||
|
||||
function mobileChromeHandler(ev) {
|
||||
if (ev.key === "Unidentified") {
|
||||
return;
|
||||
}
|
||||
if ($(document.activeElement).not('input:text, textarea, [contenteditable], ' +
|
||||
'[type="email"], [type="number"], [type="password"], [type="tel"], [type="search"]').length) {
|
||||
barcodeInput.focus();
|
||||
}
|
||||
keydownHandler(ev);
|
||||
}
|
||||
|
||||
whenReady(() => {
|
||||
const isMobileChrome = barcodeService.isMobileChrome;
|
||||
if (isMobileChrome) {
|
||||
barcodeInput = makeBarcodeInput();
|
||||
document.body.appendChild(barcodeInput);
|
||||
}
|
||||
const handler = isMobileChrome ? mobileChromeHandler : keydownHandler;
|
||||
document.body.addEventListener('keydown', handler);
|
||||
});
|
||||
|
||||
return {
|
||||
bus,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
registry.category("services").add("barcode", barcodeService);
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { FloatField } from "@web/views/fields/float/float_field";
|
||||
|
||||
export class FloatScannableField extends FloatField {
|
||||
onBarcodeScanned() {
|
||||
this.inputRef.el.dispatchEvent(new InputEvent("input"));
|
||||
}
|
||||
}
|
||||
FloatScannableField.template = "barcodes.FloatScannableField";
|
||||
|
||||
registry.category("fields").add("field_float_scannable", FloatScannableField);
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="barcodes.FloatScannableField" t-inherit="web.FloatField" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//input" position="attributes">
|
||||
<attribute name="t-on-barcode_scanned">onBarcodeScanned</attribute>
|
||||
<attribute name="t-att-data-enable-barcode">true</attribute>
|
||||
<attribute name="barcode_events">true</attribute>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
/**
|
||||
* BarcodeEvents has been removed and replaced by the barcode service.
|
||||
*
|
||||
* This file is a temporary service to remap barcode events from new barcode
|
||||
* service to core.bus (which was the purpose of BarcodeEvents).
|
||||
*
|
||||
* @TODO: remove this as soon as all barcode code is using new barcode service
|
||||
*/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import core from "web.core";
|
||||
|
||||
export const barcodeRemapperService = {
|
||||
dependencies: ["barcode"],
|
||||
start(env, { barcode }) {
|
||||
barcode.bus.addEventListener("barcode_scanned", ev => {
|
||||
const { barcode, target } = ev.detail;
|
||||
core.bus.trigger('barcode_scanned', barcode, target);
|
||||
});
|
||||
},
|
||||
};
|
||||
registry.category("services").add("barcode_remapper", barcodeRemapperService);
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
odoo.define('barcodes.field', function(require) {
|
||||
"use strict";
|
||||
|
||||
var AbstractField = require('web.AbstractField');
|
||||
var basicFields = require('web.basic_fields');
|
||||
var fieldRegistry = require('web.field_registry');
|
||||
var core = require('web.core');
|
||||
|
||||
// Field in which the user can both type normally and scan barcodes
|
||||
|
||||
var FieldFloatScannable = basicFields.FieldFloat.extend({
|
||||
events: _.extend({}, basicFields.FieldFloat.prototype.events, {
|
||||
barcode_scanned: '_onBarcodeScan',
|
||||
}),
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_renderEdit: function () {
|
||||
var self = this;
|
||||
return Promise.resolve(this._super()).then(function () {
|
||||
self.$input[0].dataset.enableBarcode = true;
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
_onBarcodeScan() {
|
||||
// trigger an 'input' event to make sure that the widget is call
|
||||
// notifyChanges
|
||||
this.$input.trigger('input');
|
||||
}
|
||||
});
|
||||
|
||||
var FormViewBarcodeHandler = AbstractField.extend({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function() {
|
||||
this._super.apply(this, arguments);
|
||||
core.bus.on('barcode_scanned', this, this._barcodeScanned);
|
||||
},
|
||||
destroy: function () {
|
||||
core.bus.off('barcode_scanned', this, this._barcodeScanned);
|
||||
this._super();
|
||||
},
|
||||
_barcodeScanned(barcode) {
|
||||
this._setValue(barcode);
|
||||
},
|
||||
});
|
||||
|
||||
fieldRegistry.add('field_float_scannable', FieldFloatScannable);
|
||||
fieldRegistry.add('barcode_handler', FormViewBarcodeHandler);
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,281 @@
|
|||
odoo.define('barcodes.BarcodeParser', function (require) {
|
||||
"use strict";
|
||||
|
||||
var Class = require('web.Class');
|
||||
var rpc = require('web.rpc');
|
||||
|
||||
// The BarcodeParser is used to detect what is the category
|
||||
// of a barcode (product, partner, ...) and extract an encoded value
|
||||
// (like weight, price, etc.)
|
||||
var BarcodeParser = Class.extend({
|
||||
init: function(attributes) {
|
||||
this.nomenclature_id = attributes.nomenclature_id;
|
||||
this.nomenclature = attributes.nomenclature;
|
||||
this.loaded = this.load();
|
||||
},
|
||||
|
||||
// This loads the barcode nomenclature and barcode rules which are
|
||||
// necessary to parse the barcodes. The BarcodeParser is operational
|
||||
// only when those data have been loaded
|
||||
load: function(){
|
||||
if (!this.nomenclature_id) {
|
||||
return this.nomenclature ? Promise.resolve() : Promise.reject();
|
||||
}
|
||||
var id = this.nomenclature_id[0];
|
||||
return rpc.query({
|
||||
model: 'barcode.nomenclature',
|
||||
method: 'read',
|
||||
args: [[id], this._barcodeNomenclatureFields()],
|
||||
}).then(nomenclatures => {
|
||||
this.nomenclature = nomenclatures[0];
|
||||
var args = [
|
||||
[['barcode_nomenclature_id', '=', this.nomenclature.id]],
|
||||
this._barcodeRuleFields(),
|
||||
];
|
||||
return rpc.query({
|
||||
model: 'barcode.rule',
|
||||
method: 'search_read',
|
||||
args: args,
|
||||
});
|
||||
}).then(rules => {
|
||||
rules = rules.sort(function(a, b){ return a.sequence - b.sequence; });
|
||||
this.nomenclature.rules = rules;
|
||||
});
|
||||
},
|
||||
|
||||
// resolves when the barcode parser is operational.
|
||||
is_loaded: function() {
|
||||
return this.loaded;
|
||||
},
|
||||
|
||||
/**
|
||||
* This algorithm is identical for all fixed length numeric GS1 data structures.
|
||||
*
|
||||
* It is also valid for EAN-8, EAN-12 (UPC-A), EAN-13 check digit after sanitizing.
|
||||
* https://www.gs1.org/sites/default/files/docs/barcodes/GS1_General_Specifications.pdf
|
||||
*
|
||||
* @param {String} numericBarcode Need to have a length of 18
|
||||
* @returns {number} Check Digit
|
||||
*/
|
||||
get_barcode_check_digit(numericBarcode) {
|
||||
let oddsum = 0, evensum = 0, total = 0;
|
||||
// Reverses the barcode to be sure each digit will be in the right place
|
||||
// regardless the barcode length.
|
||||
const code = numericBarcode.split('').reverse();
|
||||
// Removes the last barcode digit (should not be took in account for its own computing).
|
||||
code.shift();
|
||||
|
||||
// Multiply value of each position by
|
||||
// N1 N2 N3 N4 N5 N6 N7 N8 N9 N10 N11 N12 N13 N14 N15 N16 N17 N18
|
||||
// x3 X1 x3 x1 x3 x1 x3 x1 x3 x1 x3 x1 x3 x1 x3 x1 x3 CHECK_DIGIT
|
||||
for (let i = 0; i < code.length; i++) {
|
||||
if (i % 2 === 0) {
|
||||
evensum += parseInt(code[i]);
|
||||
} else {
|
||||
oddsum += parseInt(code[i]);
|
||||
}
|
||||
}
|
||||
total = evensum * 3 + oddsum;
|
||||
return (10 - total % 10) % 10;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the barcode string is encoded with the provided encoding.
|
||||
*
|
||||
* @param {String} barcode
|
||||
* @param {String} encoding could be 'any' (no encoding rules), 'ean8', 'upca' or 'ean13'
|
||||
* @returns {boolean}
|
||||
*/
|
||||
check_encoding: function(barcode, encoding) {
|
||||
if (encoding === 'any') {
|
||||
return true;
|
||||
}
|
||||
const barcodeSizes = {
|
||||
ean8: 8,
|
||||
ean13: 13,
|
||||
upca: 12,
|
||||
};
|
||||
return barcode.length === barcodeSizes[encoding] && /^\d+$/.test(barcode) &&
|
||||
this.get_barcode_check_digit(barcode) === parseInt(barcode[barcode.length - 1]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sanitizes a EAN-13 prefix by padding it with chars zero.
|
||||
*
|
||||
* @param {String} ean
|
||||
* @returns {String}
|
||||
*/
|
||||
sanitize_ean: function(ean){
|
||||
ean = ean.substr(0, 13);
|
||||
ean = "0".repeat(13 - ean.length) + ean;
|
||||
return ean.substr(0, 12) + this.get_barcode_check_digit(ean);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sanitizes a UPC-A prefix by padding it with chars zero.
|
||||
*
|
||||
* @param {String} upc
|
||||
* @returns {String}
|
||||
*/
|
||||
sanitize_upc: function(upc) {
|
||||
return this.sanitize_ean(upc).substr(1, 12);
|
||||
},
|
||||
|
||||
// Checks if barcode matches the pattern
|
||||
// Additionnaly retrieves the optional numerical content in barcode
|
||||
// Returns an object containing:
|
||||
// - value: the numerical value encoded in the barcode (0 if no value encoded)
|
||||
// - base_code: the barcode in which numerical content is replaced by 0's
|
||||
// - match: boolean
|
||||
match_pattern: function (barcode, pattern, encoding){
|
||||
var match = {
|
||||
value: 0,
|
||||
base_code: barcode,
|
||||
match: false,
|
||||
};
|
||||
barcode = barcode.replace("\\", "\\\\").replace("{", '\{').replace("}", "\}").replace(".", "\.");
|
||||
|
||||
var numerical_content = pattern.match(/[{][N]*[D]*[}]/); // look for numerical content in pattern
|
||||
var base_pattern = pattern;
|
||||
if(numerical_content){ // the pattern encodes a numerical content
|
||||
var num_start = numerical_content.index; // start index of numerical content
|
||||
var num_length = numerical_content[0].length; // length of numerical content
|
||||
var value_string = barcode.substr(num_start, num_length-2); // numerical content in barcode
|
||||
var whole_part_match = numerical_content[0].match("[{][N]*[D}]"); // looks for whole part of numerical content
|
||||
var decimal_part_match = numerical_content[0].match("[{N][D]*[}]"); // looks for decimal part
|
||||
var whole_part = value_string.substr(0, whole_part_match.index+whole_part_match[0].length-2); // retrieve whole part of numerical content in barcode
|
||||
var decimal_part = "0." + value_string.substr(decimal_part_match.index, decimal_part_match[0].length-1); // retrieve decimal part
|
||||
if (whole_part === ''){
|
||||
whole_part = '0';
|
||||
}
|
||||
match.value = parseInt(whole_part) + parseFloat(decimal_part);
|
||||
|
||||
// replace numerical content by 0's in barcode and pattern
|
||||
match.base_code = barcode.substr(0, num_start);
|
||||
base_pattern = pattern.substr(0, num_start);
|
||||
for(var i=0;i<(num_length-2);i++) {
|
||||
match.base_code += "0";
|
||||
base_pattern += "0";
|
||||
}
|
||||
match.base_code += barcode.substr(num_start + num_length - 2, barcode.length - 1);
|
||||
base_pattern += pattern.substr(num_start + num_length, pattern.length - 1);
|
||||
|
||||
match.base_code = match.base_code
|
||||
.replace("\\\\", "\\")
|
||||
.replace("\{", "{")
|
||||
.replace("\}","}")
|
||||
.replace("\.",".");
|
||||
|
||||
var base_code = match.base_code.split('');
|
||||
if (encoding === 'ean13') {
|
||||
base_code[12] = '' + this.get_barcode_check_digit(match.base_code);
|
||||
} else if (encoding === 'ean8') {
|
||||
base_code[7] = '' + this.get_barcode_check_digit(match.base_code);
|
||||
} else if (encoding === 'upca') {
|
||||
base_code[11] = '' + this.get_barcode_check_digit(match.base_code);
|
||||
}
|
||||
match.base_code = base_code.join('');
|
||||
}
|
||||
|
||||
base_pattern = base_pattern.split('|')
|
||||
.map(part => part.startsWith('^') ? part : '^' + part)
|
||||
.join('|');
|
||||
match.match = match.base_code.match(base_pattern);
|
||||
|
||||
return match;
|
||||
},
|
||||
|
||||
/**
|
||||
* Attempts to interpret a barcode (string encoding a barcode Code-128)
|
||||
*
|
||||
* @param {string} barcode
|
||||
* @returns {Object} the returned object containing informations about the barcode:
|
||||
* - code: the barcode
|
||||
* - type: the type of the barcode (e.g. alias, unit product, weighted product...)
|
||||
* - value: if the barcode encodes a numerical value, it will be put there
|
||||
* - base_code: the barcode with all the encoding parts set to zero; the one put on the product in the backend
|
||||
*/
|
||||
parse_barcode: function(barcode){
|
||||
var parsed_result = {
|
||||
encoding: '',
|
||||
type:'error',
|
||||
code:barcode,
|
||||
base_code: barcode,
|
||||
value: 0,
|
||||
};
|
||||
|
||||
if (!this.nomenclature) {
|
||||
return parsed_result;
|
||||
}
|
||||
|
||||
var rules = this.nomenclature.rules;
|
||||
for (var i = 0; i < rules.length; i++) {
|
||||
var rule = rules[i];
|
||||
var cur_barcode = barcode;
|
||||
|
||||
if ( rule.encoding === 'ean13' &&
|
||||
this.check_encoding(barcode,'upca') &&
|
||||
this.nomenclature.upc_ean_conv in {'upc2ean':'','always':''} ){
|
||||
cur_barcode = '0' + cur_barcode;
|
||||
} else if (rule.encoding === 'upca' &&
|
||||
this.check_encoding(barcode,'ean13') &&
|
||||
barcode[0] === '0' &&
|
||||
this.nomenclature.upc_ean_conv in {'ean2upc':'','always':''} ){
|
||||
cur_barcode = cur_barcode.substr(1,12);
|
||||
}
|
||||
|
||||
if (!this.check_encoding(cur_barcode,rule.encoding)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var match = this.match_pattern(cur_barcode, rules[i].pattern, rule.encoding);
|
||||
if (match.match) {
|
||||
if(rules[i].type === 'alias') {
|
||||
barcode = rules[i].alias;
|
||||
parsed_result.code = barcode;
|
||||
parsed_result.type = 'alias';
|
||||
}
|
||||
else {
|
||||
parsed_result.encoding = rules[i].encoding;
|
||||
parsed_result.type = rules[i].type;
|
||||
parsed_result.value = match.value;
|
||||
parsed_result.code = cur_barcode;
|
||||
if (rules[i].encoding === "ean13"){
|
||||
parsed_result.base_code = this.sanitize_ean(match.base_code);
|
||||
}
|
||||
else{
|
||||
parsed_result.base_code = match.base_code;
|
||||
}
|
||||
return parsed_result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return parsed_result;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
_barcodeNomenclatureFields: function () {
|
||||
return [
|
||||
'name',
|
||||
'rule_ids',
|
||||
'upc_ean_conv',
|
||||
];
|
||||
},
|
||||
|
||||
_barcodeRuleFields: function () {
|
||||
return [
|
||||
'name',
|
||||
'sequence',
|
||||
'type',
|
||||
'encoding',
|
||||
'pattern',
|
||||
'alias',
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
return BarcodeParser;
|
||||
});
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { barcodeGenericHandlers } from "@barcodes/barcode_handlers";
|
||||
import { barcodeService } from "@barcodes/barcode_service";
|
||||
import {
|
||||
editInput,
|
||||
getFixture,
|
||||
mockTimeout,
|
||||
nextTick,
|
||||
patchWithCleanup,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { simulateBarCode } from "../helpers";
|
||||
|
||||
const serviceRegistry = registry.category("services");
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
|
||||
QUnit.module("Barcodes", (hooks) => {
|
||||
hooks.beforeEach(() => {
|
||||
target = getFixture();
|
||||
serverData = {
|
||||
models: {
|
||||
order: {
|
||||
fields: {
|
||||
_barcode_scanned: { string: "Barcode scanned", type: "char" },
|
||||
line_ids: {
|
||||
string: "Order lines",
|
||||
type: "one2many",
|
||||
relation: "order_line",
|
||||
},
|
||||
},
|
||||
records: [{ id: 1, line_ids: [1, 2] }],
|
||||
},
|
||||
order_line: {
|
||||
fields: {
|
||||
product_id: { string: "Product", type: "many2one", relation: "product" },
|
||||
product_barcode: { string: "Product Barcode", type: "char" },
|
||||
quantity: { string: "Quantity", type: "integer" },
|
||||
},
|
||||
records: [
|
||||
{ id: 1, product_id: 1, quantity: 0, product_barcode: "1234567890" },
|
||||
{ id: 2, product_id: 2, quantity: 0, product_barcode: "0987654321" },
|
||||
],
|
||||
},
|
||||
product: {
|
||||
fields: {
|
||||
name: { string: "Product name", type: "char" },
|
||||
int_field: { string: "Integer", type: "integer" },
|
||||
int_field_2: { string: "Integer", type: "integer" },
|
||||
barcode: { string: "Barcode", type: "char" },
|
||||
},
|
||||
records: [
|
||||
{ id: 1, name: "Large Cabinet", barcode: "1234567890" },
|
||||
{ id: 2, name: "Cabinet with Doors", barcode: "0987654321" },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
setupViewRegistries();
|
||||
serviceRegistry.add("barcode", barcodeService, { force: true });
|
||||
serviceRegistry.add("barcode_autoclick", barcodeGenericHandlers, { force: true });
|
||||
});
|
||||
|
||||
QUnit.test("Button with barcode_trigger", async (assert) => {
|
||||
const form = await makeView({
|
||||
type: "form",
|
||||
resModel: "product",
|
||||
serverData,
|
||||
arch: `<form>
|
||||
<header>
|
||||
<button name="do_something" string="Validate" type="object" barcode_trigger="doit"/>
|
||||
<button name="do_something_else" string="Validate" type="object" invisible="1" barcode_trigger="dothat"/>
|
||||
</header>
|
||||
</form>`,
|
||||
resId: 2,
|
||||
});
|
||||
|
||||
patchWithCleanup(form.env.services.action, {
|
||||
doActionButton: (data) => {
|
||||
assert.step(data.name);
|
||||
},
|
||||
});
|
||||
patchWithCleanup(form.env.services.notification, {
|
||||
add: (params) => {
|
||||
assert.step(params.type);
|
||||
},
|
||||
});
|
||||
|
||||
// O-BTN.doit
|
||||
simulateBarCode(["O", "-", "B", "T", "N", ".", "d", "o", "i", "t", "Enter"]);
|
||||
await nextTick();
|
||||
// O-BTN.dothat (should not call execute_action as the button isn't visible)
|
||||
simulateBarCode(["O", "-", "B", "T", "N", ".", "d", "o", "t", "h", "a", "t", "Enter"]);
|
||||
await nextTick();
|
||||
assert.verifySteps(["do_something"]);
|
||||
|
||||
assert.containsOnce(target, ".o_form_statusbar > .o_statusbar_buttons");
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"Two buttons with same barcode_trigger and the same string and action",
|
||||
async function (assert) {
|
||||
const form = await makeView({
|
||||
type: "form",
|
||||
resModel: "product",
|
||||
serverData,
|
||||
arch: `<form>
|
||||
<header>
|
||||
<button name="do_something" string="Validate" type="object" invisible="0" barcode_trigger="doit"/>
|
||||
<button name="do_something" string="Validate" type="object" invisible="1" barcode_trigger="doit"/>
|
||||
</header>
|
||||
</form>`,
|
||||
resId: 2,
|
||||
});
|
||||
|
||||
patchWithCleanup(form.env.services.action, {
|
||||
doActionButton: (data) => {
|
||||
assert.step(data.name);
|
||||
},
|
||||
});
|
||||
patchWithCleanup(form.env.services.notification, {
|
||||
add: (params) => {
|
||||
assert.step(params.type);
|
||||
},
|
||||
});
|
||||
// O-BTN.doit should call execute_action as the first button is visible
|
||||
simulateBarCode(["O", "-", "B", "T", "N", ".", "d", "o", "i", "t", "Enter"]);
|
||||
await nextTick();
|
||||
assert.verifySteps(["do_something"]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("edit, save and cancel buttons", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "product",
|
||||
serverData,
|
||||
arch: '<form><field name="display_name"/></form>',
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === "write") {
|
||||
assert.step("save");
|
||||
}
|
||||
},
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
// O-CMD.EDIT
|
||||
simulateBarCode(["O", "-", "C", "M", "D", ".", "E", "D", "I", "T", "Enter"]);
|
||||
await nextTick();
|
||||
assert.containsOnce(target, ".o_form_editable", "should have switched to 'edit' mode");
|
||||
// dummy change to check that it actually saves
|
||||
await editInput(target.querySelector(".o_field_widget input"), null, "test");
|
||||
// O-CMD.SAVE
|
||||
simulateBarCode(["O", "-", "C", "M", "D", ".", "S", "A", "V", "E", "Enter"]);
|
||||
await nextTick();
|
||||
assert.verifySteps(["save"], "should have saved");
|
||||
|
||||
// O-CMD.EDIT
|
||||
simulateBarCode(["O", "-", "C", "M", "D", ".", "E", "D", "I", "T", "Enter"]);
|
||||
await nextTick();
|
||||
// dummy change to check that it correctly discards
|
||||
await editInput(target.querySelector(".o_field_widget input"), null, "test");
|
||||
// O-CMD.CANCEL
|
||||
simulateBarCode(["O", "-", "C", "M", "D", ".", "D", "I", "S", "C", "A", "R", "D", "Enter"]);
|
||||
await nextTick();
|
||||
assert.verifySteps([], "should not have saved");
|
||||
});
|
||||
|
||||
QUnit.test("pager buttons", async function (assert) {
|
||||
const mock = mockTimeout();
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "product",
|
||||
serverData,
|
||||
arch: '<form><field name="display_name"/></form>',
|
||||
resId: 1,
|
||||
resIds: [1, 2],
|
||||
});
|
||||
|
||||
assert.strictEqual(target.querySelector(".o_field_widget input").value, "Large Cabinet");
|
||||
|
||||
// O-CMD.PAGER-NEXT
|
||||
simulateBarCode(["O", "-", "C", "M", "D", ".", "N", "E", "X", "T", "Enter"]);
|
||||
await nextTick();
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget input").value,
|
||||
"Cabinet with Doors"
|
||||
);
|
||||
|
||||
// O-CMD.PAGER-PREV
|
||||
simulateBarCode(["O", "-", "C", "M", "D", ".", "P", "R", "E", "V", "Enter"]);
|
||||
await nextTick();
|
||||
assert.strictEqual(target.querySelector(".o_field_widget input").value, "Large Cabinet");
|
||||
|
||||
// O-CMD.PAGER-LAST
|
||||
simulateBarCode(["O","-","C","M","D",".","P","A","G","E","R","-","L","A","S","T","Enter"]);
|
||||
// need to await 2 macro steps
|
||||
await mock.advanceTime(20);
|
||||
await mock.advanceTime(20);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget input").value,
|
||||
"Cabinet with Doors"
|
||||
);
|
||||
|
||||
// O-CMD.PAGER-FIRST
|
||||
simulateBarCode(["O","-","C","M","D",".","P","A","G","E","R","-","F","I","R","S","T","Enter"]);
|
||||
// need to await 2 macro steps
|
||||
await mock.advanceTime(20);
|
||||
await mock.advanceTime(20);
|
||||
assert.strictEqual(target.querySelector(".o_field_widget input").value, "Large Cabinet");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { nextTick, patchWithCleanup, getFixture } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { barcodeGenericHandlers } from "@barcodes/barcode_handlers";
|
||||
import { barcodeService } from "@barcodes/barcode_service";
|
||||
import { simulateBarCode } from "../helpers";
|
||||
import { FormController } from "@web/views/form/form_controller";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const maxTimeBetweenKeysInMs = barcodeService.maxTimeBetweenKeysInMs;
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
|
||||
QUnit.module("Fields", (hooks) => {
|
||||
hooks.beforeEach(() => {
|
||||
target = getFixture();
|
||||
barcodeService.maxTimeBetweenKeysInMs = 0;
|
||||
registry.category("services").add("barcode", barcodeService, { force: true });
|
||||
registry
|
||||
.category("services")
|
||||
.add("barcode_autoclick", barcodeGenericHandlers, { force: true });
|
||||
|
||||
serverData = {
|
||||
models: {
|
||||
product: {
|
||||
fields: {
|
||||
name: { string: "Product name", type: "char" },
|
||||
float_field: { string: "Float", type: "float" },
|
||||
float_field_2: { string: "Float", type: "float" },
|
||||
barcode: { string: "Barcode", type: "char" },
|
||||
},
|
||||
records: [
|
||||
{ id: 1, name: "Large Cabinet", barcode: "1234567890" },
|
||||
{ id: 2, name: "Cabinet with Doors", barcode: "0987654321" },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
setupViewRegistries();
|
||||
});
|
||||
hooks.afterEach(() => {
|
||||
barcodeService.maxTimeBetweenKeysInMs = maxTimeBetweenKeysInMs;
|
||||
});
|
||||
|
||||
QUnit.module("FloatScannableField");
|
||||
|
||||
QUnit.test("widget field_float_scannable", async function (assert) {
|
||||
serverData.models.product.records[0].float_field = 4;
|
||||
|
||||
const onBarcodeScanned = (event) => {
|
||||
assert.step(`barcode scanned ${event.detail.barcode}`);
|
||||
};
|
||||
|
||||
const view = await makeView({
|
||||
type: "form",
|
||||
resModel: "product",
|
||||
serverData,
|
||||
resId: 1,
|
||||
arch: /*xml*/ `
|
||||
<form>
|
||||
<field name="display_name"/>
|
||||
<field name="float_field" widget="field_float_scannable"/>
|
||||
<field name="float_field_2"/>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
view.env.services.barcode.bus.addEventListener("barcode_scanned", onBarcodeScanned);
|
||||
|
||||
const inputDiv1 = target.querySelector(".o_field_widget[name=float_field]");
|
||||
assert.strictEqual(
|
||||
inputDiv1.querySelector("input").value,
|
||||
"4.00",
|
||||
"should display the correct value"
|
||||
);
|
||||
|
||||
// we check here that a scan on the field_float_scannable widget triggers a
|
||||
// barcode event
|
||||
inputDiv1.querySelector("input").focus();
|
||||
simulateBarCode(["6", "0", "1", "6", "4", "7", "8", "5"], inputDiv1, "input");
|
||||
await nextTick();
|
||||
assert.verifySteps(["barcode scanned 60164785"]);
|
||||
assert.strictEqual(
|
||||
inputDiv1.querySelector("input"),
|
||||
document.activeElement,
|
||||
"float field should stay focused"
|
||||
);
|
||||
|
||||
// we check here that a scan on the field without widget does not trigger a
|
||||
// barcode event
|
||||
const inputDiv2 = target.querySelector(".o_field_widget[name=float_field_2]");
|
||||
inputDiv2.querySelector("input").focus();
|
||||
simulateBarCode(["6", "0", "1", "6", "4", "7", "8", "5"], inputDiv2, "input");
|
||||
await nextTick();
|
||||
assert.verifySteps([]);
|
||||
|
||||
view.env.services.barcode.bus.removeEventListener("barcode_scanned", onBarcodeScanned);
|
||||
});
|
||||
|
||||
QUnit.test("do no update form twice after a command barcode scanned", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
patchWithCleanup(FormController.prototype, {
|
||||
onPagerUpdate() {
|
||||
assert.step("update");
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "product",
|
||||
serverData,
|
||||
arch: /*xml*/ `
|
||||
<form>
|
||||
<field name="display_name"/>
|
||||
<field name="float_field" widget="field_float_scannable"/>
|
||||
</form>
|
||||
`,
|
||||
mockRPC(_route, args) {
|
||||
if (args.method === "read") {
|
||||
assert.step("read");
|
||||
}
|
||||
},
|
||||
resId: 1,
|
||||
resIds: [1, 2],
|
||||
});
|
||||
|
||||
assert.verifySteps(["read"], "update should not have been called yet");
|
||||
|
||||
// switch to next record
|
||||
simulateBarCode(
|
||||
["O", "-", "C", "M", "D", ".", "N", "E", "X", "T", "Enter"],
|
||||
document.activeElement
|
||||
);
|
||||
await nextTick();
|
||||
// a first update is done to reload the data (thus followed by a read), but
|
||||
// update shouldn't be called afterwards
|
||||
assert.verifySteps(["update", "read"]);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
odoo.define('barcodes/static/tests/barcode_parser_tests.js', function (require) {
|
||||
"use strict";
|
||||
|
||||
const BarcodeParser = require('barcodes.BarcodeParser');
|
||||
|
||||
|
||||
QUnit.module('Barcodes', {}, function () {
|
||||
QUnit.module('Barcode Parser', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
'barcode.nomenclature': {
|
||||
fields: {
|
||||
name: {type: 'char', string: 'Barcode Nomenclature'},
|
||||
rule_ids: {type: 'one2many', relation: 'barcode.rule'},
|
||||
upc_ean_conv: {type: 'selection', string: 'UPC/EAN Conversion'},
|
||||
},
|
||||
records: [
|
||||
{id: 1, name: "normal", upc_ean_conv: "always"},
|
||||
],
|
||||
},
|
||||
'barcode.rule': {
|
||||
fields: {
|
||||
name: {type: 'char', string: 'Barcode Nomenclature'},
|
||||
barcode_nomenclature_id: {type: 'many2one', relation: 'barcode.nomenclature'},
|
||||
sequence: {type: 'integer', string: 'Sequence'},
|
||||
encoding: {type: 'selection', string: 'Encoding'},
|
||||
type: {type: 'selection', string: 'Type'},
|
||||
pattern: {type: 'Char', string: 'Pattern'},
|
||||
alias: {type: 'Char', string: 'Alias'},
|
||||
},
|
||||
records: [
|
||||
{id: 1, name: "Product Barcodes", barcode_nomenclature_id: 1, sequence: 90, type: 'product', encoding: 'any', pattern: ".*"},
|
||||
],
|
||||
}
|
||||
};
|
||||
}
|
||||
}, function () {
|
||||
QUnit.test('Test check digit', async function (assert) {
|
||||
assert.expect(6);
|
||||
const barcodeNomenclature = new BarcodeParser({'nomenclature_id': 1});
|
||||
await barcodeNomenclature.loaded;
|
||||
|
||||
let ean8 = "87111125";
|
||||
assert.equal(barcodeNomenclature.get_barcode_check_digit(ean8), ean8[ean8.length - 1]);
|
||||
ean8 = "4725992";
|
||||
assert.equal(barcodeNomenclature.get_barcode_check_digit(ean8 + "0"), 8);
|
||||
let ean13 = "1234567891231";
|
||||
assert.equal(barcodeNomenclature.get_barcode_check_digit(ean13), ean13[ean13.length - 1]);
|
||||
ean13 = "962434754318";
|
||||
assert.equal(barcodeNomenclature.get_barcode_check_digit(ean13 + "0"), 4);
|
||||
let utca = "692771981161";
|
||||
assert.equal(barcodeNomenclature.get_barcode_check_digit(utca), utca[utca.length - 1]);
|
||||
utca = "71679131569";
|
||||
assert.equal(barcodeNomenclature.get_barcode_check_digit(utca + "0"), 7);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,337 @@
|
|||
odoo.define('barcodes.tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
const {barcodeService} = require("@barcodes/barcode_service");
|
||||
const {barcodeGenericHandlers} = require("@barcodes/barcode_handlers");
|
||||
const {barcodeRemapperService} = require("@barcodes/js/barcode_events");
|
||||
const { makeTestEnv } = require("@web/../tests/helpers/mock_env");
|
||||
const { registry } = require("@web/core/registry");
|
||||
const { mockTimeout } = require("@web/../tests/helpers/utils");
|
||||
const { simulateBarCode } = require("@barcodes/../tests/helpers");
|
||||
|
||||
var FormController = require('web.FormController');
|
||||
var FormView = require('web.FormView');
|
||||
var testUtils = require('web.test_utils');
|
||||
|
||||
var createView = testUtils.createView;
|
||||
var core = require('web.core');
|
||||
|
||||
const maxTimeBetweenKeysInMs = barcodeService.maxTimeBetweenKeysInMs;
|
||||
|
||||
QUnit.module('Legacy Barcodes', {
|
||||
beforeEach: function () {
|
||||
barcodeService.maxTimeBetweenKeysInMs = 0;
|
||||
registry.category("services").add("barcode", barcodeService, { force: true});
|
||||
registry.category("services").add("barcode_autoclick", barcodeGenericHandlers, { force: true});
|
||||
// remove this one later
|
||||
registry.category("services").add("barcode_remapper", barcodeRemapperService);
|
||||
this.env = makeTestEnv();
|
||||
|
||||
this.data = {
|
||||
order: {
|
||||
fields: {
|
||||
_barcode_scanned: {string: 'Barcode scanned', type: 'char'},
|
||||
line_ids: {string: 'Order lines', type: 'one2many', relation: 'order_line'},
|
||||
},
|
||||
records: [
|
||||
{id: 1, line_ids: [1, 2]},
|
||||
],
|
||||
},
|
||||
order_line: {
|
||||
fields: {
|
||||
product_id: {string: 'Product', type: 'many2one', relation: 'product'},
|
||||
product_barcode: {string: 'Product Barcode', type: 'char'},
|
||||
quantity: {string: 'Quantity', type: 'integer'},
|
||||
},
|
||||
records: [
|
||||
{id: 1, product_id: 1, quantity: 0, product_barcode: '1234567890'},
|
||||
{id: 2, product_id: 2, quantity: 0, product_barcode: '0987654321'},
|
||||
],
|
||||
},
|
||||
product: {
|
||||
fields: {
|
||||
name: {string : "Product name", type: "char"},
|
||||
int_field: {string : "Integer", type: "integer"},
|
||||
int_field_2: {string : "Integer", type: "integer"},
|
||||
barcode: {string: "Barcode", type: "char"},
|
||||
},
|
||||
records: [
|
||||
{id: 1, name: "Large Cabinet", barcode: '1234567890'},
|
||||
{id: 2, name: "Cabinet with Doors", barcode: '0987654321'},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
afterEach: function () {
|
||||
barcodeService.maxTimeBetweenKeysInMs = maxTimeBetweenKeysInMs;
|
||||
},
|
||||
});
|
||||
|
||||
QUnit.test('Button with barcode_trigger', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var form = await createView({
|
||||
View: FormView,
|
||||
model: 'product',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
'<header>' +
|
||||
'<button name="do_something" string="Validate" type="object" barcode_trigger="doit"/>' +
|
||||
'<button name="do_something_else" string="Validate" type="object" invisible="1" barcode_trigger="dothat"/>' +
|
||||
'</header>' +
|
||||
'</form>',
|
||||
res_id: 2,
|
||||
services: {
|
||||
notification: {
|
||||
notify: function (params) {
|
||||
assert.step(params.type);
|
||||
}
|
||||
},
|
||||
},
|
||||
intercepts: {
|
||||
execute_action: function (event) {
|
||||
assert.strictEqual(event.data.action_data.name, 'do_something',
|
||||
"do_something method call verified");
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// O-BTN.doit
|
||||
simulateBarCode(['O','-','B','T','N','.','d','o','i','t','Enter']);
|
||||
// O-BTN.dothat (should not call execute_action as the button isn't visible)
|
||||
simulateBarCode(['O','-','B','T','N','.','d','o','t','h','a','t','Enter']);
|
||||
await testUtils.nextTick();
|
||||
assert.verifySteps([], "no warning should be displayed");
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('Two buttons with same barcode_trigger and the same string and action', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var form = await createView({
|
||||
View: FormView,
|
||||
model: 'product',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
'<header>' +
|
||||
'<button name="do_something" string="Validate" type="object" invisible="0" barcode_trigger="doit"/>' +
|
||||
'<button name="do_something" string="Validate" type="object" invisible="1" barcode_trigger="doit"/>' +
|
||||
'</header>' +
|
||||
'</form>',
|
||||
res_id: 2,
|
||||
|
||||
services: {
|
||||
notification: {
|
||||
notify: function (params) {
|
||||
assert.step(params.type);
|
||||
}
|
||||
},
|
||||
},
|
||||
intercepts: {
|
||||
execute_action: function (event) {
|
||||
assert.strictEqual(event.data.action_data.name, 'do_something',
|
||||
"do_something method call verified");
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// O-BTN.doit should call execute_action as the first button is visible
|
||||
simulateBarCode(['O','-','B','T','N','.','d','o','i','t','Enter']);
|
||||
await testUtils.nextTick();
|
||||
assert.verifySteps([], "no warning should be displayed");
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('edit, save and cancel buttons', async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
var form = await createView({
|
||||
View: FormView,
|
||||
model: 'product',
|
||||
data: this.data,
|
||||
arch: '<form><field name="display_name"/></form>',
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'write') {
|
||||
assert.step('save');
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
res_id: 1,
|
||||
});
|
||||
|
||||
// O-CMD.EDIT
|
||||
simulateBarCode(["O","-","C","M","D",".","E","D","I","T","Enter"], document.body);
|
||||
await testUtils.nextTick();
|
||||
assert.containsOnce(form, ".o_form_editable",
|
||||
"should have switched to 'edit' mode");
|
||||
// dummy change to check that it actually saves
|
||||
await testUtils.fields.editInput(form.$('.o_field_widget'), 'test');
|
||||
// O-CMD.SAVE
|
||||
simulateBarCode(["O","-","C","M","D",".","S","A","V","E","Enter"], document.body);
|
||||
await testUtils.nextTick();
|
||||
assert.containsOnce(form, ".o_form_readonly",
|
||||
"should have switched to 'readonly' mode");
|
||||
assert.verifySteps(['save'], 'should have saved');
|
||||
|
||||
// O-CMD.EDIT
|
||||
simulateBarCode(["O","-","C","M","D",".","E","D","I","T","Enter"], document.body);
|
||||
await testUtils.nextTick();
|
||||
// dummy change to check that it correctly discards
|
||||
await testUtils.fields.editInput(form.$('.o_field_widget'), 'test');
|
||||
// O-CMD.CANCEL
|
||||
simulateBarCode(["O","-","C","M","D",".","D","I","S","C","A","R","D","Enter"], document.body);
|
||||
await testUtils.nextTick();
|
||||
assert.containsOnce(form, ".o_form_readonly",
|
||||
"should have switched to 'readonly' mode");
|
||||
assert.verifySteps([], 'should not have saved');
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('pager buttons', async function (assert) {
|
||||
const mock = mockTimeout();
|
||||
assert.expect(5);
|
||||
|
||||
var form = await createView({
|
||||
View: FormView,
|
||||
model: 'product',
|
||||
data: this.data,
|
||||
arch: '<form><field name="display_name"/></form>',
|
||||
res_id: 1,
|
||||
viewOptions: {
|
||||
ids: [1, 2],
|
||||
index: 0,
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(form.$('.o_field_widget').text(), 'Large Cabinet');
|
||||
// O-CMD.PAGER-NEXT
|
||||
simulateBarCode(["O","-","C","M","D",".","N","E","X","T","Enter"]);
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual(form.$('.o_field_widget').text(), 'Cabinet with Doors');
|
||||
// O-CMD.PAGER-PREV
|
||||
simulateBarCode(["O","-","C","M","D",".","P","R","E","V","Enter"]);
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual(form.$('.o_field_widget').text(), 'Large Cabinet');
|
||||
// O-CMD.PAGER-LAST
|
||||
simulateBarCode(["O","-","C","M","D",".","P","A","G","E","R","-","L","A","S","T","Enter"]);
|
||||
// need to await 2 macro steps
|
||||
await mock.advanceTime(20);
|
||||
await mock.advanceTime(20);
|
||||
assert.strictEqual(form.$('.o_field_widget').text(), 'Cabinet with Doors');
|
||||
// O-CMD.PAGER-FIRST
|
||||
simulateBarCode(["O","-","C","M","D",".","P","A","G","E","R","-","F","I","R","S","T","Enter"]);
|
||||
// need to await 2 macro steps
|
||||
await mock.advanceTime(20);
|
||||
await mock.advanceTime(20);
|
||||
assert.strictEqual(form.$('.o_field_widget').text(), 'Large Cabinet');
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('do no update form twice after a command barcode scanned', async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
testUtils.mock.patch(FormController, {
|
||||
update: function () {
|
||||
assert.step('update');
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
var form = await createView({
|
||||
View: FormView,
|
||||
model: 'product',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
'<field name="display_name"/>' +
|
||||
'<field name="int_field" widget="field_float_scannable"/>' +
|
||||
'</form>',
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'read') {
|
||||
assert.step('read');
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
res_id: 1,
|
||||
viewOptions: {
|
||||
ids: [1, 2],
|
||||
index: 0,
|
||||
},
|
||||
});
|
||||
|
||||
assert.verifySteps(['read'], "update should not have been called yet");
|
||||
|
||||
// switch to next record
|
||||
simulateBarCode(["O","-","C","M","D",".","N","E","X","T","Enter"]);
|
||||
await testUtils.nextTick();
|
||||
// a first update is done to reload the data (thus followed by a read), but
|
||||
// update shouldn't be called afterwards
|
||||
assert.verifySteps(['update', 'read']);
|
||||
|
||||
form.destroy();
|
||||
testUtils.mock.unpatch(FormController);
|
||||
});
|
||||
|
||||
QUnit.test('widget field_float_scannable', async function (assert) {
|
||||
this.data.product.records[0].int_field = 4;
|
||||
|
||||
function _onBarcodeScanned (code) {
|
||||
assert.step(`barcode scanned ${code}`)
|
||||
}
|
||||
core.bus.on('barcode_scanned', null, _onBarcodeScanned);
|
||||
|
||||
var form = await createView({
|
||||
View: FormView,
|
||||
model: 'product',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
'<field name="display_name"/>' +
|
||||
'<field name="int_field" widget="field_float_scannable"/>' +
|
||||
'<field name="int_field_2"/>' +
|
||||
'</form>',
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'onchange') {
|
||||
assert.step('onchange');
|
||||
assert.strictEqual(args.args[1].int_field, 426,
|
||||
"should send correct value for int_field");
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
fieldDebounce: 1000,
|
||||
res_id: 1,
|
||||
});
|
||||
|
||||
assert.strictEqual(form.$('.o_field_widget[name=int_field]').text(), '4',
|
||||
"should display the correct value in readonly");
|
||||
|
||||
await testUtils.form.clickEdit(form);
|
||||
|
||||
assert.strictEqual(form.$('.o_field_widget[name=int_field]').val(), '4',
|
||||
"should display the correct value in edit");
|
||||
|
||||
// simulates keypress events in the input to replace 0.00 by 26 (should not trigger onchanges)
|
||||
form.$('.o_field_widget[name=int_field]').focus();
|
||||
|
||||
// we check here that a scan on the fieldflotscannable widget triggers a
|
||||
// barcode event
|
||||
simulateBarCode(["6", "0", "1", "6", "4", "7", "8", "5"], document.activeElement)
|
||||
await testUtils.nextTick();
|
||||
assert.verifySteps(['barcode scanned 60164785']);
|
||||
assert.strictEqual(form.$('.o_field_widget[name=int_field]').get(0), document.activeElement,
|
||||
"int field should be focused");
|
||||
|
||||
// we check here that a scan on the field without widget does not trigger a
|
||||
// barcode event
|
||||
form.$('.o_field_widget[name=int_field_2]').focus();
|
||||
simulateBarCode(["6", "0", "1", "6", "4", "7", "8", "5"], document.activeElement)
|
||||
await testUtils.nextTick();
|
||||
assert.verifySteps([]);
|
||||
|
||||
form.destroy();
|
||||
core.bus.off('barcode_scanned', null, _onBarcodeScanned)
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/* @odoo-module */
|
||||
|
||||
import { triggerEvent } from "@web/../tests/helpers/utils";
|
||||
|
||||
export function simulateBarCode(chars, target = document.body, selector = undefined) {
|
||||
for (let char of chars) {
|
||||
let keycode;
|
||||
if (char === "Enter") {
|
||||
keycode = $.ui.keyCode.ENTER;
|
||||
} else if (char === "Tab") {
|
||||
keycode = $.ui.keyCode.TAB;
|
||||
} else {
|
||||
keycode = char.charCodeAt(0);
|
||||
}
|
||||
triggerEvent(target, selector, "keydown", {
|
||||
key: char,
|
||||
keyCode: keycode,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
odoo.define('barcodes.barcode_mobile_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
const {barcodeService} = require("@barcodes/barcode_service");
|
||||
const {barcodeRemapperService} = require("@barcodes/js/barcode_events");
|
||||
const { makeTestEnv } = require("@web/../tests/helpers/mock_env");
|
||||
const { registry } = require("@web/core/registry");
|
||||
var testUtils = require('web.test_utils');
|
||||
|
||||
const maxTimeBetweenKeysInMs = barcodeService.maxTimeBetweenKeysInMs;
|
||||
const isMobileChrome = barcodeService.isMobileChrome;
|
||||
var triggerEvent = testUtils.dom.triggerEvent;
|
||||
|
||||
function triggerKeyDown(char, target = document.body) {
|
||||
let keycode;
|
||||
if (char === 'Enter') {
|
||||
keycode = $.ui.keyCode.ENTER;
|
||||
} else if (char === "Tab") {
|
||||
keycode = $.ui.keyCode.TAB;
|
||||
} else {
|
||||
keycode = char.charCodeAt(0);
|
||||
}
|
||||
triggerEvent(target, 'keydown', {
|
||||
key: char,
|
||||
keyCode: keycode,
|
||||
});
|
||||
}
|
||||
|
||||
QUnit.module('Barcodes', {
|
||||
before() {
|
||||
barcodeService.maxTimeBetweenKeysInMs = 0;
|
||||
barcodeService.isMobileChrome = true;
|
||||
registry.category("services").add("barcode", barcodeService, { force: true});
|
||||
// remove this one later
|
||||
registry.category("services").add("barcode_remapper", barcodeRemapperService);
|
||||
this.env = makeTestEnv();
|
||||
},
|
||||
after() {
|
||||
barcodeService.maxTimeBetweenKeysInMs = maxTimeBetweenKeysInMs;
|
||||
barcodeService.isMobileChrome = isMobileChrome;
|
||||
},
|
||||
}, function () {
|
||||
|
||||
QUnit.module('Barcodes Mobile');
|
||||
|
||||
QUnit.test('barcode field automatically focus behavior', function (assert) {
|
||||
assert.expect(10);
|
||||
|
||||
var $form = $(
|
||||
'<form>' +
|
||||
'<input name="email" type="email"/>' +
|
||||
'<input name="number" type="number"/>' +
|
||||
'<input name="password" type="password"/>' +
|
||||
'<input name="tel" type="tel"/>' +
|
||||
'<input name="text"/>' +
|
||||
'<input name="explicit_text" type="text"/>' +
|
||||
'<textarea></textarea>' +
|
||||
'<div contenteditable="true"></div>' +
|
||||
'<select name="select">' +
|
||||
'<option value="option1">Option 1</option>' +
|
||||
'<option value="option2">Option 2</option>' +
|
||||
'</select>' +
|
||||
'</form>');
|
||||
$('#qunit-fixture').append($form);
|
||||
|
||||
// Some elements doesn't need to keep the focus
|
||||
triggerKeyDown('a', document.body)
|
||||
assert.strictEqual(document.activeElement.name, 'barcode',
|
||||
"hidden barcode input should have the focus");
|
||||
|
||||
var $element = $form.find('select');
|
||||
$element.focus();
|
||||
triggerKeyDown('b', $element[0]);
|
||||
assert.strictEqual(document.activeElement.name, 'barcode',
|
||||
"hidden barcode input should have the focus");
|
||||
|
||||
// Those elements absolutely need to keep the focus:
|
||||
// inputs elements:
|
||||
var keepFocusedElements = ['email', 'number', 'password', 'tel',
|
||||
'text', 'explicit_text'];
|
||||
for (var i = 0; i < keepFocusedElements.length; ++i) {
|
||||
$element = $form.find('input[name=' + keepFocusedElements[i] + ']');
|
||||
$element.focus();
|
||||
triggerKeyDown('c', $element[0]);
|
||||
|
||||
assert.strictEqual(document.activeElement, $element[0],
|
||||
"input " + keepFocusedElements[i] + " should keep focus");
|
||||
}
|
||||
// textarea element
|
||||
$element = $form.find('textarea');
|
||||
$element.focus().keydown();
|
||||
assert.strictEqual(document.activeElement, $element[0],
|
||||
"textarea should keep focus");
|
||||
// contenteditable elements
|
||||
$element = $form.find('[contenteditable=true]');
|
||||
$element.focus();
|
||||
triggerKeyDown('d', $element[0]);
|
||||
assert.strictEqual(document.activeElement, $element[0],
|
||||
"contenteditable should keep focus");
|
||||
|
||||
$('#qunit-fixture').empty();
|
||||
document.querySelector('input[name=barcode]').remove();
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue