mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-25 07:52:02 +02:00
19.0 vanilla
This commit is contained in:
parent
79f83631d5
commit
73afc09215
6267 changed files with 1534193 additions and 1130106 deletions
|
|
@ -0,0 +1,52 @@
|
|||
import { useService } from "@web/core/utils/hooks";
|
||||
import { ProductMatrixDialog } from "./product_matrix_dialog";
|
||||
|
||||
export function useMatrixConfigurator() {
|
||||
const dialog = useService("dialog");
|
||||
|
||||
const openDialog = (rootRecord, jsonInfo, productTemplateId, editedCellAttributes) => {
|
||||
const infos = JSON.parse(jsonInfo);
|
||||
dialog.add(ProductMatrixDialog, {
|
||||
header: infos.header,
|
||||
rows: infos.matrix,
|
||||
editedCellAttributes: editedCellAttributes.toString(),
|
||||
product_template_id: productTemplateId,
|
||||
record: rootRecord,
|
||||
});
|
||||
};
|
||||
|
||||
const open = async (record, edit) => {
|
||||
const rootRecord = record.model.root;
|
||||
|
||||
// fetch matrix information from server;
|
||||
await rootRecord.update({
|
||||
grid_product_tmpl_id: record.data.product_template_id,
|
||||
});
|
||||
|
||||
const updatedLineAttributes = [];
|
||||
if (edit) {
|
||||
// provide attributes of edited line to automatically focus on matching cell in the matrix
|
||||
for (const ptnvav of record.data.product_no_variant_attribute_value_ids.records) {
|
||||
updatedLineAttributes.push(ptnvav.resId);
|
||||
}
|
||||
for (const ptav of record.data.product_template_attribute_value_ids.records) {
|
||||
updatedLineAttributes.push(ptav.resId);
|
||||
}
|
||||
updatedLineAttributes.sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
openDialog(
|
||||
rootRecord,
|
||||
rootRecord.data.grid,
|
||||
record.data.product_template_id.id,
|
||||
updatedLineAttributes
|
||||
);
|
||||
|
||||
if (!edit) {
|
||||
// remove new line used to open the matrix
|
||||
rootRecord.data.order_line.delete(record);
|
||||
}
|
||||
};
|
||||
|
||||
return { open };
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
import { Dialog } from '@web/core/dialog/dialog';
|
||||
import { formatMonetary } from "@web/views/fields/formatters";
|
||||
import { useHotkey } from "@web/core/hotkeys/hotkey_hook";
|
||||
import { Component, onMounted, markup, useRef } from "@odoo/owl";
|
||||
|
||||
export class ProductMatrixDialog extends Component {
|
||||
static template = "product_matrix.dialog";
|
||||
static props = {
|
||||
header: { type: Object },
|
||||
rows: { type: Object },
|
||||
editedCellAttributes: { type: String },
|
||||
product_template_id: { type: Number },
|
||||
record: { type: Object },
|
||||
close: { type: Function },
|
||||
};
|
||||
static components = { Dialog };
|
||||
|
||||
setup() {
|
||||
this.size = 'xl';
|
||||
|
||||
const productMatrixRef = useRef('productMatrix');
|
||||
useHotkey("enter", () => this._onConfirm(), {
|
||||
/***
|
||||
* By default, Hotkeys don't work in input fields. As the matrix table is composed of
|
||||
* input fields, the `bypassEditableProtection` param will allow Hotkeys to work from
|
||||
* the input fields.
|
||||
*
|
||||
* To avoid triggering the confirmation when pressing 'enter' on the close or the
|
||||
* discard button, we only set the hotkey area on the matrix table.
|
||||
*/
|
||||
bypassEditableProtection: true,
|
||||
area: () => productMatrixRef.el,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if(this.props.editedCellAttributes.length) {
|
||||
const inputs = document.getElementsByClassName('o_matrix_input');
|
||||
const relevantInput = Array.from(inputs).filter((matrixInput) =>
|
||||
matrixInput.attributes.ptav_ids.nodeValue === this.props.editedCellAttributes
|
||||
)[0];
|
||||
if (relevantInput) {
|
||||
relevantInput.select();
|
||||
} else {
|
||||
// Based on the record creation, it may ignore the 'no_variant' attributes
|
||||
// (e.g. from a stock.move), thus finding no match in the matrix.
|
||||
inputs[0].select();
|
||||
}
|
||||
} else {
|
||||
document.getElementsByClassName('o_matrix_input')[0].select();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_format({price, currency_id}) {
|
||||
if (!price) { return ""; }
|
||||
const sign = price < 0 ? '-' : '+';
|
||||
const formatted = formatMonetary(
|
||||
Math.abs(price),
|
||||
{
|
||||
currencyId: currency_id,
|
||||
},
|
||||
);
|
||||
return markup(` ${sign} ${formatted} `);
|
||||
}
|
||||
|
||||
_onConfirm() {
|
||||
const inputs = document.getElementsByClassName('o_matrix_input');
|
||||
let matrixChanges = [];
|
||||
for (let matrixInput of inputs) {
|
||||
if (matrixInput.value && matrixInput.value !== matrixInput.attributes.value.nodeValue) {
|
||||
matrixChanges.push({
|
||||
qty: parseFloat(matrixInput.value),
|
||||
ptav_ids: matrixInput.attributes.ptav_ids.nodeValue.split(",").map(
|
||||
id => parseInt(id)
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
if (matrixChanges.length > 0) {
|
||||
// NB: server also removes current line opening the matrix
|
||||
this.props.record.update({
|
||||
grid: JSON.stringify({
|
||||
changes: matrixChanges,
|
||||
product_template_id: this.props.product_template_id
|
||||
}),
|
||||
grid_update: true // to say that the changes to grid have to be applied to the SO.
|
||||
});
|
||||
}
|
||||
this.props.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +1,35 @@
|
|||
.o_web_client .o_matrix_input_table {
|
||||
table {
|
||||
margin-bottom: 0;
|
||||
table-layout: fixed;
|
||||
min-width: 100%;
|
||||
width: auto;
|
||||
max-width: none;
|
||||
.o_matrix_input_table {
|
||||
.o_matrix_ps {
|
||||
padding-left: $modal-inner-padding;
|
||||
}
|
||||
th, td {
|
||||
border: 0 !important;
|
||||
vertical-align: middle;
|
||||
width: 5em;
|
||||
.o_matrix_pe {
|
||||
padding-right: $modal-inner-padding;
|
||||
}
|
||||
.o_matrix_title_header {
|
||||
width: 10em;
|
||||
//removing input field=number arrows as their size might
|
||||
//change depending on browser default styling and shift input's position
|
||||
.o_matrix_input {
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
&[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
}
|
||||
.o_matrix_input_td {
|
||||
border: none;
|
||||
&:focus-within {
|
||||
--table-accent-bg: none;
|
||||
background-color: $o-view-background-color;
|
||||
border-bottom: $o-black 1px solid;
|
||||
}
|
||||
}
|
||||
// Sticky header styles
|
||||
thead {
|
||||
color: $o-main-text-color;
|
||||
background-color: $o-brand-lightsecondary;
|
||||
th {
|
||||
text-align: center;
|
||||
white-space: pre-line;
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
background-color: $o-view-background-color;
|
||||
text-align: right;
|
||||
tr {
|
||||
border-top: 1px solid $o-form-lightsecondary;
|
||||
border-bottom: 1px solid $o-form-lightsecondary;
|
||||
}
|
||||
.o_matrix_input {
|
||||
text-align: right;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
.o_matrix_text_muted{
|
||||
color: lighten($o-main-text-color, 15%);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
// ensure white background completely surrounds nocontent bubble
|
||||
.o_matrix_nocontent_container {
|
||||
overflow: auto;
|
||||
|
||||
.oe_view_nocontent_img_link {
|
||||
padding:10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_product_variant_matrix {
|
||||
.form-control {
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
border: 1px solid $gray-400;
|
||||
}
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10; // Ensure it stays on top of other content
|
||||
background-color: $o-view-background-color; // Background for visibility
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,51 +1,67 @@
|
|||
<template>
|
||||
<div t-name="product_matrix.matrix">
|
||||
<table class="o_matrix_input_table o_product_variant_matrix table table-sm table-striped table-bordered cursor-default">
|
||||
<thead>
|
||||
<tr>
|
||||
<t t-foreach="header" t-as="column_header">
|
||||
<th t-attf-class="o_matrix_title_header {{column_header_first?'text-start':'text-center'}}">
|
||||
<span t-esc="column_header.name"/>
|
||||
<t t-call="product_matrix.extra_price">
|
||||
<t t-set="cell" t-value="column_header"/>
|
||||
</t>
|
||||
<div t-name="product_matrix.matrix" t-ref="productMatrix">
|
||||
<table class="o_matrix_input_table table table-sm table-striped table-hover table-bordered cursor-default mb-0 h-100">
|
||||
<thead class="border-0">
|
||||
<tr class="border-0 h-100">
|
||||
<t t-foreach="header" t-as="column_header" t-key="column_header_index">
|
||||
<th class="border-0"
|
||||
t-attf-class="{{column_header_first?'o_matrix_ps':''}} {{column_header_last?'o_matrix_pe':''}}">
|
||||
<div class="d-flex flex-column justify-content-start h-100"
|
||||
t-attf-class="{{column_header_first?'align-items-start':'align-items-end'}}">
|
||||
<span t-esc="column_header.name"/>
|
||||
<t t-call="product_matrix.extra_price">
|
||||
<t t-set="cell" t-value="column_header"/>
|
||||
</t>
|
||||
</div>
|
||||
</th>
|
||||
</t>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="rows" t-as="row">
|
||||
<t t-foreach="row" t-as="cell">
|
||||
<th t-if="cell.name" class="text-start">
|
||||
<strong t-esc="cell.name"/>
|
||||
<t t-call="product_matrix.extra_price"/>
|
||||
</th>
|
||||
<td t-else="">
|
||||
<div t-if="cell.is_possible_combination" class="input-group">
|
||||
<input type="number"
|
||||
class="o_matrix_input"
|
||||
t-att-ptav_ids="cell.ptav_ids"
|
||||
t-att-value="cell.qty"/>
|
||||
<tr t-foreach="rows" t-as="row" t-key="row_index" class="border-1 border-end-0 border-start-0">
|
||||
<t t-foreach="row" t-as="cell" t-key="cell_index">
|
||||
<th t-if="cell.name"
|
||||
class="border-0"
|
||||
t-attf-class="{{cell_first?'o_matrix_ps o_matrix_pe':''}} {{cell_last?'o_matrix_pe':''}}">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<strong t-esc="cell.name" class="pe-2"/>
|
||||
<t t-call="product_matrix.extra_price"/>
|
||||
</div>
|
||||
<span t-else="" class="o_matrix_cell o_matrix_text_muted o_matrix_nocontent_container"> Not available </span>
|
||||
</th>
|
||||
<td t-else=""
|
||||
class="o_matrix_input_td text-end"
|
||||
t-attf-class="{{cell_last?'o_matrix_pe':''}}">
|
||||
<div t-if="cell.is_possible_combination" class="input-group">
|
||||
<input type="number"
|
||||
class="o_input o_field_number o_matrix_input border-0 text-end"
|
||||
t-att="{'ptav_ids': cell.ptav_ids,'value': cell.qty}"
|
||||
onClick="this.select();"/>
|
||||
</div>
|
||||
<span t-else=""
|
||||
class="text-muted overflow-auto">
|
||||
Not available
|
||||
</span>
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<span t-name="product_matrix.extra_price" t-if="cell.price" class="badge rounded-pill text-bg-secondary">
|
||||
<!--
|
||||
price_extra is displayed as catalog price instead of
|
||||
price after pricelist because it is impossible to
|
||||
compute. Indeed, the pricelist rule might depend on the
|
||||
selected variant, so the price_extra will be different
|
||||
depending on the selected combination. The price of an
|
||||
attribute is therefore variable and it's not very
|
||||
accurate to display it.
|
||||
-->
|
||||
<span class="variant_price_extra" style="white-space: nowrap;">
|
||||
<t t-out="format(cell)"/>
|
||||
</span>
|
||||
<span t-name="product_matrix.extra_price"
|
||||
t-if="cell.price"
|
||||
class="badge rounded-pill text-bg-secondary"
|
||||
>
|
||||
<!--
|
||||
price_extra is displayed as catalog price instead of
|
||||
price after pricelist because it is impossible to
|
||||
compute. Indeed, the pricelist rule might depend on the
|
||||
selected variant, so the price_extra will be different
|
||||
depending on the selected combination. The price of an
|
||||
attribute is therefore variable and it's not very
|
||||
accurate to display it.
|
||||
-->
|
||||
<span class="variant_price_extra" style="white-space: nowrap;">
|
||||
<t t-out="format(cell)"/>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
<templates>
|
||||
<t t-name="product_matrix.dialog">
|
||||
<Dialog size="size" title.translate="Choose Product Variants" withBodyPadding="false">
|
||||
<t t-call="product_matrix.matrix">
|
||||
<t t-set="header" t-value="props.header"/>
|
||||
<t t-set="rows" t-value="props.rows"/>
|
||||
<t t-set="format" t-value="_format"/>
|
||||
</t>
|
||||
<t t-set-slot="footer">
|
||||
<button class="btn btn-primary" t-on-click="_onConfirm">Confirm</button>
|
||||
<button class="btn btn-secondary" t-on-click="() => this.props.close()">
|
||||
Discard
|
||||
</button>
|
||||
</t>
|
||||
</Dialog>
|
||||
</t>
|
||||
</templates>
|
||||
Loading…
Add table
Add a link
Reference in a new issue