[IMP] sale_product_configurator: redesign of the modal
This commit - Cleans and optimize the SCSS - Improves the layout and the spacing of the existing modal - Improves the input's style (`.o_input`, better pills) - Sets a fixed height to the modal so it doesn't grow/shrink when fields are shown/hidden task-3289240 closes odoo/odoo#133056 closes odoo/odoo#140096 Related: odoo/enterprise#49636 Related: odoo/enterprise#49733 Signed-off-by: Valentin Chevalier <vcr@odoo.com> Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com>
This commit is contained in:
committed by
Antoine Vandevenne (anv)
parent
bd187c35c2
commit
5f7aa80c73
-9
@@ -1,9 +0,0 @@
|
||||
.o_product_badge_extra_price {
|
||||
display: inline-block;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
color: map-get($theme-colors, 'primary');
|
||||
background: white;
|
||||
line-height: 1rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="product.badge_extra_price">
|
||||
<span class="badge rounded-pill text-bg-light border ms-1 ps-1">
|
||||
<span t-out="this.props.price > 0 ? '+' : '-'" class="o_product_badge_extra_price"/>
|
||||
<span t-out="getFormattedPrice()" class="text-muted fst-italic"/>
|
||||
<span class="badge ms-1 ps-1 text-bg-info rounded-pill">
|
||||
<span t-out="this.props.price > 0 ? '+' : '-'" class="me-1"/>
|
||||
<i t-out="getFormattedPrice()"/>
|
||||
</span>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -1,47 +1,36 @@
|
||||
.table.o_sale_product_configurator_table {
|
||||
& tr:first-child > td {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
&.o_sale_product_configurator_table_optional > :not(caption) > *:last-child > * {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.o_sale_product_configurator_img {
|
||||
width: 100px;
|
||||
width: 120px;
|
||||
max-height: 240px;
|
||||
}
|
||||
|
||||
.o_sale_product_configurator_qty {
|
||||
width: 200px;
|
||||
width: 160px;
|
||||
|
||||
.input-group {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
div {
|
||||
width: initial; // We don't want the quantity form to be full-width
|
||||
|
||||
.btn, input {
|
||||
border-color: $input-border-color;
|
||||
input {
|
||||
max-width: 4rem;
|
||||
//removing input field=number arrows as their size might
|
||||
//change depending on browser default styling and shift input's position
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
// Needs !important because themes customize btns' padding directly
|
||||
// rather than change '$input-btn-padding-X' (shared with inputs).
|
||||
height: auto !important;
|
||||
max-width: 4rem;
|
||||
text-align: center;
|
||||
//removing input field=number arrows as their size might
|
||||
//change depending on browser default styling and shift input's position
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
&[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
&[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_sale_product_configurator_name {
|
||||
word-wrap: break-word;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.o_sale_product_configurator_price {
|
||||
width: 120px;
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
@@ -1,69 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="sale_product_configurator.product">
|
||||
<td class="o_sale_product_configurator_img">
|
||||
<td class="o_sale_product_configurator_img py-3 px-0">
|
||||
<img
|
||||
t-if="this.props.id"
|
||||
class="w-100"
|
||||
t-att-src="'/web/image/product.product/'+this.props.id+'/image_128'"
|
||||
alt="Product Image"/>
|
||||
<img
|
||||
t-else=""
|
||||
class="w-100"
|
||||
t-att-src="'/web/image/product.template/'+this.props.product_tmpl_id+'/image_128'"
|
||||
alt="Product Image"/>
|
||||
</td>
|
||||
<td t-att-colspan="this.props.optional ? 2:false">
|
||||
<div class="o_sale_product_configurator_name">
|
||||
<strong t-out="this.props.display_name"/>
|
||||
<td class="p-3" t-att-colspan="this.props.optional ? 2:false">
|
||||
<div class="mb-4 text-break" name="o_sale_product_configurator_name">
|
||||
<h5 t-out="this.props.display_name"/>
|
||||
<div
|
||||
t-if="this.props.description_sale"
|
||||
t-out="this.props.description_sale"
|
||||
class="text-muted small"/>
|
||||
<div t-if="!this.env.isPossibleCombination(this.props)" class="alert alert-warning mt-3">
|
||||
<span>This option or combination of options is not available</span>
|
||||
</div>
|
||||
</div>
|
||||
<t t-foreach="this.props.attribute_lines" t-as="ptal" t-key="ptal.id">
|
||||
<PTAL t-props="ptal" productTmplId="this.props.product_tmpl_id"/>
|
||||
</t>
|
||||
</td>
|
||||
<td t-if="!this.props.optional" class="text-center o_sale_product_configurator_qty">
|
||||
<div class="input-group">
|
||||
<td class="o_sale_product_configurator_qty py-3 px-0 text-end">
|
||||
<div t-if="!this.props.optional" class="input-group justify-content-end">
|
||||
<button
|
||||
class="btn btn-primary d-none d-md-inline-block"
|
||||
class="btn btn-secondary d-none d-md-inline-block"
|
||||
aria-label="Remove one"
|
||||
t-on-click="decreaseQuantity">
|
||||
<i class="fa fa-minus"></i>
|
||||
<i class="fa fa-minus"/>
|
||||
</button>
|
||||
<input
|
||||
class="form-control quantity border-bottom border-top text-center"
|
||||
name="product_quantity"
|
||||
type="number"
|
||||
t-att-value="this.props.quantity"
|
||||
t-on-change="setQuantity"/>
|
||||
<button
|
||||
class="btn btn-primary d-none d-md-inline-block"
|
||||
class="btn btn-secondary d-none d-md-inline-block"
|
||||
aria-label="Add one"
|
||||
t-on-click="increaseQuantity">
|
||||
<i class="fa fa-plus"></i>
|
||||
<i class="fa fa-plus"/>
|
||||
</button>
|
||||
</div>
|
||||
<div t-else="">
|
||||
<h5 class="text-nowrap" t-out="getFormattedPrice()"/>
|
||||
</div>
|
||||
<a
|
||||
class="d-block mt-2"
|
||||
role="button"
|
||||
t-if="!this.props.optional && this.env.mainProductTmplId !== this.props.product_tmpl_id"
|
||||
t-on-click="() => this.env.removeProduct(this.props.product_tmpl_id)">
|
||||
Remove product
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-center o_sale_product_configurator_price" name="price">
|
||||
<t t-if="this.env.isPossibleCombination(this.props)">
|
||||
<span style="white-space: nowrap;" t-out="getFormattedPrice()"/>
|
||||
<td class="o_sale_product_configurator_price py-3 px-0 text-end" name="price">
|
||||
<div t-if="!this.props.optional" class="input-group justify-content-end">
|
||||
<h5 class="text-nowrap" t-out="getFormattedPrice()"/>
|
||||
</div>
|
||||
<div t-else="">
|
||||
<button
|
||||
t-if="this.props.optional"
|
||||
class="btn btn-primary btn-sm"
|
||||
class="btn btn-secondary"
|
||||
t-att-class="{'disabled': !this.env.isPossibleCombination(this.props)}"
|
||||
t-on-click="() => this.env.addProduct(this.props.product_tmpl_id)">
|
||||
<i class="fa fa-shopping-cart"></i> Add to cart
|
||||
<i class="fa fa-plus"/> Add
|
||||
</button>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<p class="alert alert-warning">Option not available</p>
|
||||
</t>
|
||||
<div>
|
||||
<a
|
||||
role="button"
|
||||
t-if="!this.props.optional && this.env.mainProductTmplId !== this.props.product_tmpl_id"
|
||||
t-on-click="() => this.env.removeProduct(this.props.product_tmpl_id)">
|
||||
<i class="fa fa-trash-o"/>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</t>
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ export class ProductConfiguratorDialog extends Component {
|
||||
}
|
||||
|
||||
setup() {
|
||||
this.title = _t("Configure");
|
||||
this.title = _t("Configure your product");
|
||||
this.rpc = useService("rpc");
|
||||
this.state = useState({
|
||||
products: [],
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="sale_product_configurator.dialog">
|
||||
<Dialog size="size" title="title">
|
||||
<Dialog size="size" title="title" contentClass="this.state.optionalProducts.length ? 'h-75' : ''">
|
||||
<ProductList t-if="this.state.products.length" products="this.state.products"/>
|
||||
<ProductList
|
||||
t-if="this.state.optionalProducts.length"
|
||||
@@ -15,7 +15,7 @@
|
||||
Confirm
|
||||
</button>
|
||||
<button class="btn btn-secondary" t-on-click="onDiscard">
|
||||
Back
|
||||
Cancel
|
||||
</button>
|
||||
</t>
|
||||
</Dialog>
|
||||
|
||||
@@ -1,31 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="saleProductConfigurator.productList">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr t-if="!this.props.areProductsOptional">
|
||||
<th class="o_product_configurator_img"><span class="label">Product</span></th>
|
||||
<th><span class="label"></span></th>
|
||||
<th class="text-center o_product_configurator_qty">
|
||||
<span class="label">Quantity</span>
|
||||
</th>
|
||||
<th class="text-center o_product_configurator_price">
|
||||
<span class="label">Price</span>
|
||||
</th>
|
||||
<h4 class="mt-4 mb-3" t-if="this.props.areProductsOptional">Add optional products</h4>
|
||||
<table class="o_sale_product_configurator_table table table-sm position-relative mb-0" t-att-class="{'o_sale_product_configurator_table_optional': this.props.areProductsOptional}">
|
||||
<thead t-if="!this.props.areProductsOptional">
|
||||
<tr>
|
||||
<th class="px-0 border-bottom-0" colspan="2">Product</th>
|
||||
<th class="px-0 text-end border-bottom-0">Quantity</th>
|
||||
<th class="px-0 text-end border-bottom-0">Price</th>
|
||||
</tr>
|
||||
<tr t-else=""><th colspan="4"><h4>Available Options:</h4></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="border-top-0">
|
||||
<tr t-foreach="this.props.products" t-as="product" t-key="product.product_tmpl_id">
|
||||
<Product t-props="product" optional="this.props.areProductsOptional"/>
|
||||
</tr>
|
||||
<tr t-if="!this.props.areProductsOptional">
|
||||
<td colspan="4" class="text-end">
|
||||
<strong>Total: </strong>
|
||||
<span class="fw-bold" t-out="getFormattedTotal()"/>
|
||||
<td colspan="4" class="border-bottom-0 text-end">
|
||||
<h4>Total: <span t-out="getFormattedTotal()"/></h4>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
+11
@@ -133,4 +133,15 @@ export class ProductTemplateAttributeLine extends Component {
|
||||
ptav => this.props.selected_attribute_value_ids.includes(ptav.id)
|
||||
)?.is_custom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the line has a custom ptav or not.
|
||||
*
|
||||
* @return {Boolean} - Whether the line has a custom ptav or not.
|
||||
*/
|
||||
hasPTAVCustom() {
|
||||
return this.props.attribute_values.some(
|
||||
ptav => ptav.is_custom
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+8
-42
@@ -1,24 +1,5 @@
|
||||
.o_sale_product_configurator_ptal_name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase;
|
||||
padding-bottom: 0.5rem;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
margin-left: $spacer;
|
||||
flex-grow: 1;
|
||||
border-bottom: 1px solid map-get($grays, '400');
|
||||
}
|
||||
}
|
||||
|
||||
.o_sale_product_configurator_ptav_color {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
border: 5px solid $input-border-color;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
border: 5px solid $border-color;
|
||||
transition: $input-transition;
|
||||
|
||||
@include o-field-pointer();
|
||||
@@ -27,7 +8,7 @@
|
||||
content: "";
|
||||
display: block;
|
||||
@include o-position-absolute(-3px, -3px, -3px, -3px);
|
||||
border: 4px solid white;
|
||||
border: 4px solid $o-view-background-color;
|
||||
border-radius: 50%;
|
||||
box-shadow: inset 0 0 3px rgba(black, 0.3);
|
||||
}
|
||||
@@ -66,27 +47,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.o_sale_product_configurator_ptav_pills {
|
||||
padding: $spacer/2 $spacer;
|
||||
margin-right: 0.2rem;
|
||||
border: none;
|
||||
cursor: default !important;
|
||||
|
||||
&.btn.active {
|
||||
color: $white;
|
||||
background-color: map-get($theme-colors, 'primary');
|
||||
}
|
||||
&:not(.active) {
|
||||
color: map-get($grays, '600');
|
||||
background-color: map-get($grays, '200');
|
||||
}
|
||||
|
||||
input {
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
opacity: 0;
|
||||
}
|
||||
.o_sale_product_configurator_ptav_pills.active label {
|
||||
$-btn-secondary-design: map-get($o-btns-bs-override, "secondary");
|
||||
|
||||
background-color: map-get($-btn-secondary-design, active-background);
|
||||
border-color: map-get($-btn-secondary-design, active-border);
|
||||
color: map-get($-btn-secondary-design, active-color);
|
||||
}
|
||||
|
||||
.css_not_available {
|
||||
|
||||
+57
-52
@@ -2,26 +2,31 @@
|
||||
<templates xml:space="preserve">
|
||||
<!-- Attributes line template -->
|
||||
<t t-name="saleProductConfigurator.ptal">
|
||||
<div name="ptal" t-attf-class="#{this.props.attribute_values.length === 1 && hasPTAVCustom() ? 'd-flex' : ''}">
|
||||
<!-- If the attribute line contains only one attribute value, we don't show the attribute
|
||||
value template or the attribute line title unless the single attribute value is custom,
|
||||
whereas in this case, only the title of the attribute line and the custom value
|
||||
template are rendered. -->
|
||||
<strong
|
||||
t-if="this.props.attribute_values.length === 1 && isSelectedPTAVCustom() || this.props.attribute_values.length > 1"
|
||||
t-out="this.props.attribute.name"
|
||||
class="o_sale_product_configurator_ptal_name"/>
|
||||
<t t-if="this.props.attribute_values.length > 1" t-call="{{getPTAVTemplate()}}"/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="custom"
|
||||
t-if="isSelectedPTAVCustom()"
|
||||
t-att-value="this.props.customValue"
|
||||
t-on-change="updateCustomValue"
|
||||
/>
|
||||
<div class="d-flex flex-column flex-lg-row gap-2 mb-2">
|
||||
<label
|
||||
t-if="this.props.attribute_values.length === 1 && isSelectedPTAVCustom() || this.props.attribute_values.length > 1"
|
||||
t-out="this.props.attribute.name"
|
||||
t-attf-class="fw-bold text-break #{this.props.attribute_values.length === 1 && hasPTAVCustom() ? '' : 'w-lg-25'}"/>
|
||||
<t t-if="this.props.attribute_values.length > 1" t-call="{{getPTAVTemplate()}}"/>
|
||||
</div>
|
||||
<input
|
||||
class="o_input w-75 mb-4 ms-lg-auto"
|
||||
type="text"
|
||||
placeholder="Enter a customized value"
|
||||
t-if="hasPTAVCustom && isSelectedPTAVCustom()"
|
||||
t-att-value="this.props.customValue"
|
||||
t-on-change="updateCustomValue"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
<!-- Attributes value templates -->
|
||||
<t t-name="saleProductConfigurator.ptav-select">
|
||||
<select class="form-select"
|
||||
<select class="o_input w-50 flex-grow-1"
|
||||
t-on-change="updateSelectedPTAV"
|
||||
t-att-name="'ptal-' + this.props.id">
|
||||
<option
|
||||
@@ -35,40 +40,12 @@
|
||||
</select>
|
||||
</t>
|
||||
<t t-name="saleProductConfigurator.ptav-radio">
|
||||
<ul class="list-inline list-unstyled">
|
||||
<ul class="list-unstyled flex-grow-1 m-0">
|
||||
<li t-foreach="this.props.attribute_values" t-as="ptav" t-key="ptav.id"
|
||||
class="list-inline-item mb-3">
|
||||
class="mb-2">
|
||||
<div class="form-check">
|
||||
<label
|
||||
class="col-form-label"
|
||||
t-att-class="{ 'css_not_available': ptav.excluded }"
|
||||
t-att-for="ptav.id">
|
||||
<span t-out="ptav.name"/>
|
||||
<BadgeExtraPrice
|
||||
t-if="ptav.price_extra"
|
||||
price="ptav.price_extra"
|
||||
currencyId="this.env.currencyId"/>
|
||||
<input
|
||||
type="radio"
|
||||
class="form-check-input"
|
||||
t-att-id="ptav.id"
|
||||
t-att-value="ptav.id"
|
||||
t-att-checked="this.props.selected_attribute_value_ids.includes(ptav.id)"
|
||||
t-att-name="'ptal-' + this.props.id"
|
||||
t-on-change="updateSelectedPTAV"/>
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</t>
|
||||
<t t-name="saleProductConfigurator.ptav-pills">
|
||||
<ul t-attf-class="list-inline list-unstyled">
|
||||
<li t-foreach="this.props.attribute_values" t-as="ptav" t-key="ptav.id"
|
||||
t-att-class="{'active': this.props.selected_attribute_value_ids.includes(ptav.id)}"
|
||||
class="o_sale_product_configurator_ptav_pills btn btn-primary list-inline-item mb-3">
|
||||
<div class="form-check">
|
||||
<label
|
||||
class="col-form-label"
|
||||
class="form-check-label d-inline-flex align-items-center"
|
||||
t-att-class="{ 'css_not_available': ptav.excluded }"
|
||||
t-att-for="ptav.id">
|
||||
<span t-out="ptav.name"/>
|
||||
@@ -79,22 +56,50 @@
|
||||
</label>
|
||||
<input
|
||||
type="radio"
|
||||
class="form-check-input"
|
||||
t-att-id="ptav.id"
|
||||
t-att-value="ptav.id"
|
||||
t-att-name="'ptal-' + this.props.id"
|
||||
t-att-checked="this.props.selected_attribute_value_ids.includes(ptav.id)"
|
||||
t-att-name="'ptal-' + this.props.id"
|
||||
t-on-change="updateSelectedPTAV"/>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</t>
|
||||
<t t-name="saleProductConfigurator.ptav-color">
|
||||
<ul class="list-inline">
|
||||
<t t-name="saleProductConfigurator.ptav-pills">
|
||||
<ul class="list-inline list-unstyled flex-grow-1 mb-0">
|
||||
<li t-foreach="this.props.attribute_values" t-as="ptav" t-key="ptav.id"
|
||||
class="list-inline-item me-1">
|
||||
t-att-class="{'active': this.props.selected_attribute_value_ids.includes(ptav.id)}"
|
||||
class="o_sale_product_configurator_ptav_pills list-inline-item">
|
||||
<label
|
||||
class="btn btn-outline-secondary"
|
||||
t-att-class="{ 'css_not_available': ptav.excluded }"
|
||||
t-att-for="ptav.id">
|
||||
<span t-out="ptav.name"/>
|
||||
<BadgeExtraPrice
|
||||
t-if="ptav.price_extra"
|
||||
price="ptav.price_extra"
|
||||
currencyId="this.env.currencyId"/>
|
||||
</label>
|
||||
<input
|
||||
class="btn-check"
|
||||
type="radio"
|
||||
t-att-id="ptav.id"
|
||||
t-att-value="ptav.id"
|
||||
t-att-name="'ptal-' + this.props.id"
|
||||
t-att-checked="this.props.selected_attribute_value_ids.includes(ptav.id)"
|
||||
t-on-change="updateSelectedPTAV"/>
|
||||
</li>
|
||||
</ul>
|
||||
</t>
|
||||
<t t-name="saleProductConfigurator.ptav-color">
|
||||
<ul class="list-inline flex-grow-1 mb-0">
|
||||
<li t-foreach="this.props.attribute_values" t-as="ptav" t-key="ptav.id"
|
||||
class="list-inline-item me-2">
|
||||
<t t-set="img_style" t-value="ptav.image ? 'background:url(/web/image/product.template.attribute.value/'+ptav.id+'/image); background-size:cover;' : ''"/>
|
||||
<t t-set="color_style" t-value="ptav.is_custom ? '' : 'background-color:' + ptav.html_color"/>
|
||||
<label
|
||||
class="position-relative d-inline-block rounded-pill text-center"
|
||||
t-att-title="ptav.name"
|
||||
t-attf-style="#{img_style or color_style}"
|
||||
t-att-class="{'o_sale_product_configurator_ptav_color': true,
|
||||
@@ -114,20 +119,20 @@
|
||||
</ul>
|
||||
</t>
|
||||
<t t-name="saleProductConfigurator.ptav-multi">
|
||||
<ul t-attf-class="list-inline list-unstyled">
|
||||
<ul class="list-unstyled flex-grow-1 m-0">
|
||||
<li t-foreach="this.props.attribute_values" t-as="ptav" t-key="ptav.id"
|
||||
class="list-inline-item mb-3">
|
||||
class="mb-2">
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="me-1"
|
||||
class="form-check-input"
|
||||
t-att-id="ptav.id"
|
||||
t-att-value="ptav.id"
|
||||
t-att-name="'ptal-' + this.props.id"
|
||||
t-att-checked="this.props.selected_attribute_value_ids.includes(ptav.id)"
|
||||
t-on-change="updateSelectedPTAV"/>
|
||||
<label
|
||||
class="col-form-label"
|
||||
class="form-check-label"
|
||||
t-att-for="ptav.id">
|
||||
<span t-out="ptav.name"/>
|
||||
<BadgeExtraPrice
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_tests': [
|
||||
'test_sale_product_configurators/static/src/js/tour_utils.js',
|
||||
'test_sale_product_configurators/static/tests/tours/**/*',
|
||||
],
|
||||
},
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
function addOptionalProduct(productName) {
|
||||
return {
|
||||
trigger: `table.o_sale_product_configurator_table_optional tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("${productName}")) td.o_sale_product_configurator_price button:contains("Add")`,
|
||||
content: `Add ${productName}`,
|
||||
};
|
||||
}
|
||||
|
||||
function increaseProductQuantity(productName) {
|
||||
return {
|
||||
trigger: `table.o_sale_product_configurator_table tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("${productName}")) td.o_sale_product_configurator_qty div>button:has(i.fa-plus)`,
|
||||
content: `Increase quantity of ${productName}`,
|
||||
};
|
||||
}
|
||||
|
||||
function selectAttribute(productName, attributeName, attributeValue, attributeType='radio') {
|
||||
switch (attributeType) {
|
||||
case 'color':
|
||||
return {
|
||||
trigger: `table.o_sale_product_configurator_table tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("${productName}")) td>div[name="ptal"]:has(div>label:contains("${attributeName}")) label[title="${attributeValue}"]`,
|
||||
content: `Select ${attributeValue} for ${productName} ${attributeName}`,
|
||||
};
|
||||
case 'multi':
|
||||
case 'pills':
|
||||
case 'radio':
|
||||
return {
|
||||
trigger: `table.o_sale_product_configurator_table tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("${productName}")) td>div[name="ptal"]:has(div>label:contains("${attributeName}")) span:contains("${attributeValue}")`,
|
||||
content: `Select ${attributeValue} for ${productName} ${attributeName}`,
|
||||
};
|
||||
case 'select':
|
||||
return {
|
||||
trigger: `table.o_sale_product_configurator_table tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("${productName}")) td>div[name="ptal"]:has(div>label:contains("${attributeName}"))`,
|
||||
content: `Select ${attributeValue} for ${productName} ${attributeName}`,
|
||||
run: () => {
|
||||
let $selectElement = $(`table.o_sale_product_configurator_table tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("${productName}")) td>div[name="ptal"]:has(div>label:contains("${attributeName}")) select`)
|
||||
let inputValue = $selectElement.children(`option:contains(${attributeValue})`).val();
|
||||
$selectElement.val(inputValue);
|
||||
$selectElement[0].dispatchEvent(new Event("change"));
|
||||
}
|
||||
};
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function setCustomAttribute(productName, attributeName, customValue) {
|
||||
return {
|
||||
trigger: `table.o_sale_product_configurator_table tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("${productName}")) td>div[name="ptal"]:has(div>label:contains("${attributeName}")) input[type="text"]`,
|
||||
content: `Set ${customValue} as a custom attribute for ${productName} ${attributeName}`,
|
||||
run: `text ${customValue}`,
|
||||
};
|
||||
}
|
||||
|
||||
function selectAndSetCustomAttribute(productName, attributeName, attributeValue, customValue, attributeType=undefined) {
|
||||
return [
|
||||
selectAttribute(productName, attributeName, attributeValue, attributeType),
|
||||
setCustomAttribute(productName, attributeName, customValue),
|
||||
]
|
||||
}
|
||||
|
||||
function assertPriceTotal(total) {
|
||||
return {
|
||||
trigger: `table.o_sale_product_configurator_table tr>td[colspan=4] span:contains("${total}")`,
|
||||
content: `Assert that the total is ${total}`,
|
||||
isCheck: true,
|
||||
};
|
||||
}
|
||||
|
||||
function assertProductNameContains(productName) {
|
||||
return {
|
||||
trigger: `table.o_sale_product_configurator_table tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("${productName}"))`,
|
||||
content: `Assert that the productName contains ${productName}`,
|
||||
isCheck: true,
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
addOptionalProduct,
|
||||
increaseProductQuantity,
|
||||
selectAttribute,
|
||||
setCustomAttribute,
|
||||
selectAndSetCustomAttribute,
|
||||
assertPriceTotal,
|
||||
assertProductNameContains,
|
||||
};
|
||||
+11
-10
@@ -2,6 +2,7 @@
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { stepUtils } from "@web_tour/tour_service/tour_utils";
|
||||
import configuratorTourUtils from "@test_sale_product_configurators/js/tour_utils";
|
||||
|
||||
registry.category("web_tour.tours").add('event_sale_with_product_configurator_tour', {
|
||||
url: '/web',
|
||||
@@ -25,7 +26,7 @@ registry.category("web_tour.tours").add('event_sale_with_product_configurator_to
|
||||
}, {
|
||||
trigger: 'ul.ui-autocomplete a:contains("Registration Event (TEST variants)")',
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Memorabilia")) button:has(i.fa-shopping-cart)',
|
||||
trigger: 'tr:has(div[name="o_sale_product_configurator_name"]:contains("Memorabilia")) button:has(i.fa-plus)',
|
||||
}, {
|
||||
trigger: 'button:contains(Confirm)',
|
||||
}, {
|
||||
@@ -54,13 +55,13 @@ registry.category("web_tour.tours").add('event_sale_with_product_configurator_to
|
||||
}, {
|
||||
trigger: 'ul.ui-autocomplete a:contains("Registration Event (TEST variants)")',
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Registration Event (TEST variants)")) label:contains("Adult")',
|
||||
trigger: 'tr:has(div[name="o_sale_product_configurator_name"]:contains("Registration Event (TEST variants)")) label:contains("Adult")',
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Registration Event (TEST variants)"))>td:nth-child(3) input',
|
||||
trigger: 'tr:has(div[name="o_sale_product_configurator_name"]:contains("Registration Event (TEST variants)")) .o_sale_product_configurator_qty input',
|
||||
run: 'text 5',
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(2)>td>span:contains("150.00")', // to be sure the correct variant is set
|
||||
}, {
|
||||
},
|
||||
configuratorTourUtils.assertPriceTotal("150.00"),
|
||||
{
|
||||
trigger: 'button:contains(Confirm)',
|
||||
}, {
|
||||
trigger: '.o_input_dropdown input',
|
||||
@@ -88,10 +89,10 @@ registry.category("web_tour.tours").add('event_sale_with_product_configurator_to
|
||||
}, {
|
||||
trigger: 'ul.ui-autocomplete a:contains("Registration Event (TEST variants)")',
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Registration Event (TEST variants)")) label:contains("VIP")',
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(2)>td>span:contains("60.00")', // to be sure the correct variant is set
|
||||
}, {
|
||||
trigger: 'tr:has(div[name="o_sale_product_configurator_name"]:contains("Registration Event (TEST variants)")) label:contains("VIP")',
|
||||
},
|
||||
configuratorTourUtils.assertPriceTotal(60.00),
|
||||
{
|
||||
trigger: 'button:contains(Confirm)',
|
||||
}, {
|
||||
trigger: '.o_input_dropdown input',
|
||||
|
||||
+17
-37
@@ -1,7 +1,8 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { stepUtils } from "@web_tour/tour_service/tour_utils";
|
||||
import { stepUtils, TourError } from "@web_tour/tour_service/tour_utils";
|
||||
import configuratorTourUtils from "@test_sale_product_configurators/js/tour_utils";
|
||||
|
||||
let optionVariantImage;
|
||||
|
||||
@@ -27,48 +28,27 @@ registry.category("web_tour.tours").add('sale_product_configurator_advanced_tour
|
||||
run: 'text Custo',
|
||||
}, {
|
||||
trigger: 'ul.ui-autocomplete a:contains("Customizable Desk (TEST)")',
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody span:contains("Custom")'
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1)>td:nth-child(2)>input',
|
||||
run: 'text Custom 1'
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1)>td:nth-child(2)>ul:nth-child(8) span:contains("PAV9")',
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1)>td:nth-child(2)>ul:nth-child(8) ~ input',
|
||||
run: 'text Custom 2'
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1)>td:nth-child(2)>ul:nth-child(11) span:contains("PAV5")',
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1)>td:nth-child(2)>select:nth-child(15)',
|
||||
run: function (){
|
||||
let inputValue = $('main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1)>td:nth-child(2)>select:nth-child(15) option:contains("PAV9")').val();
|
||||
$('main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1)>td:nth-child(2)>select:nth-child(15)').val(inputValue);
|
||||
$('main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1)>td:nth-child(2)>select:nth-child(15)')[0].dispatchEvent(new Event("change"));
|
||||
}
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1)>td:nth-child(2)>select:nth-child(15) ~ input',
|
||||
run: 'text Custom 3'
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1) strong:contains("Custom, White, PAV9, PAV5, PAV1")',
|
||||
isCheck: true,
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(2)>tbody>tr:nth-child(1)>td:nth-child(2):contains("Conference Chair (TEST) (Steel)")',
|
||||
},
|
||||
...configuratorTourUtils.selectAndSetCustomAttribute("Customizable Desk", "Legs", "Custom", "Custom 1"),
|
||||
...configuratorTourUtils.selectAndSetCustomAttribute("Customizable Desk", "PA1", "PAV9", "Custom 2"),
|
||||
configuratorTourUtils.selectAttribute("Customizable Desk", "PA2", "PAV5"),
|
||||
...configuratorTourUtils.selectAndSetCustomAttribute("Customizable Desk", "PA4", "PAV9", "Custom 3", "select"),
|
||||
configuratorTourUtils.assertProductNameContains("Custom, White, PAV9, PAV5, PAV1"),
|
||||
{
|
||||
trigger: 'table.o_sale_product_configurator_table_optional tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("Conference Chair (TEST) (Steel)"))',
|
||||
run: function () {
|
||||
optionVariantImage = $('main.modal-body>table:nth-child(2)>tbody>tr:nth-child(1)>td:nth-child(1)>img').attr('src');
|
||||
optionVariantImage = $('table.o_sale_product_configurator_table_optional tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("Conference Chair (TEST) (Aluminium)")) td[name="o_sale_product_configurator_img"]>img').attr('src');
|
||||
}
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(2)>tbody>tr:nth-child(1) label:contains("Aluminium")',
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(2)>tbody>tr:nth-child(1)>td:nth-child(2):contains("Conference Chair (TEST) (Aluminium)")',
|
||||
},
|
||||
configuratorTourUtils.selectAttribute("Conference Chair", "Legs", "Aluminium"),
|
||||
{
|
||||
trigger: 'table.o_sale_product_configurator_table_optional tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("Conference Chair (TEST) (Aluminium)"))',
|
||||
run: function () {
|
||||
let newVariantImage = $('main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1)>td:nth-child(1)>img').attr('src');
|
||||
let newVariantImage = $('table.o_sale_product_configurator_table_optional tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("Conference Chair (TEST) (Aluminium)")) td[name="o_sale_product_configurator_img"]>img').attr('src');
|
||||
if (newVariantImage !== optionVariantImage) {
|
||||
$('<p>').text('image variant option src changed').insertAfter('main.modal-body>table:nth-child(2)>tbody>tr:nth-child(1)>td:nth-child(2)>div>strong');
|
||||
throw new TourError('image variant option src changed');
|
||||
}
|
||||
}
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(2)>tbody>tr:nth-child(1)>td:nth-child(2)>div:contains("image variant option src changed")',
|
||||
}, {
|
||||
trigger: 'button:contains(Confirm)',
|
||||
}, {
|
||||
|
||||
+19
-21
@@ -2,6 +2,7 @@
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { stepUtils } from "@web_tour/tour_service/tour_utils";
|
||||
import configuratorTourUtils from "@test_sale_product_configurators/js/tour_utils";
|
||||
|
||||
registry.category("web_tour.tours").add('sale_product_configurator_edition_tour', {
|
||||
url: '/web',
|
||||
@@ -25,9 +26,9 @@ registry.category("web_tour.tours").add('sale_product_configurator_edition_tour'
|
||||
}, {
|
||||
trigger: 'ul.ui-autocomplete a:contains("Customizable Desk (TEST)")',
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Customizable Desk")) label:contains("Aluminium")',
|
||||
trigger: 'tr:has(div[name="o_sale_product_configurator_name"]:contains("Customizable Desk")) label:contains("Aluminium")',
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Customizable Desk (TEST) (Aluminium, White)"))',
|
||||
trigger: 'tr:has(div[name="o_sale_product_configurator_name"]:contains("Customizable Desk (TEST) (Aluminium, White)"))',
|
||||
isCheck: true,
|
||||
}, {
|
||||
trigger: 'button:contains(Confirm)',
|
||||
@@ -40,20 +41,20 @@ registry.category("web_tour.tours").add('sale_product_configurator_edition_tour'
|
||||
}, {
|
||||
trigger: '.fa-pencil',
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Customizable Desk"))>td:nth-child(2)>ul:nth-child(3)>li:nth-child(2)>div label:contains("Aluminium") > input:checked',
|
||||
trigger: 'table.o_sale_product_configurator_table tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("Customizable Desk")) td>div[name="ptal"]:has(div>label:contains("Legs")) label:has(span:contains("Aluminium")) ~ input:checked',
|
||||
isCheck: true, // check updated legs
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Customizable Desk"))>td:nth-child(4) span:contains("800.40")',
|
||||
trigger: 'table.o_sale_product_configurator_table tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("Customizable Desk")) td[name="price"] h5:contains("800.40")',
|
||||
isCheck: true, // check updated price
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Customizable Desk")) label:contains("Custom") > input',
|
||||
trigger: 'table.o_sale_product_configurator_table tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("Customizable Desk")) td>div[name="ptal"]:has(div>label:contains("Legs")) label:has(span:contains("Custom")) ~ input',
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Customizable Desk"))>td:nth-child(2)>input',
|
||||
trigger: 'table.o_sale_product_configurator_table tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("Customizable Desk")) td>div[name="ptal"]:has(div>label:contains("Legs")) input[type="text"]',
|
||||
run: 'text nice custom value'
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Customizable Desk")) label[style="background-color:#000000"] input',
|
||||
trigger: 'tr:has(div[name="o_sale_product_configurator_name"]:contains("Customizable Desk")) label[style="background-color:#000000"] input',
|
||||
}, {
|
||||
trigger: '.o_sale_product_configurator_name:contains("Customizable Desk (TEST) (Custom, Black)")',
|
||||
trigger: 'div[name="o_sale_product_configurator_name"]:contains("Customizable Desk (TEST) (Custom, Black)")',
|
||||
isCheck: true, // used to sync with server
|
||||
}, {
|
||||
trigger: 'button:contains(Confirm)',
|
||||
@@ -69,10 +70,9 @@ registry.category("web_tour.tours").add('sale_product_configurator_edition_tour'
|
||||
trigger: 'div[name="product_template_id"]',
|
||||
}, {
|
||||
trigger: '.fa-pencil',
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Customizable Desk"))>td:nth-child(2)>input',
|
||||
run: 'text another nice custom value'
|
||||
}, {
|
||||
},
|
||||
configuratorTourUtils.setCustomAttribute("Customizable Desk", "Legs", "another nice custom value"),
|
||||
{
|
||||
trigger: 'button:contains(Confirm)',
|
||||
}, {
|
||||
trigger: 'td.o_data_cell:contains("Custom: another nice custom value")',
|
||||
@@ -83,15 +83,13 @@ registry.category("web_tour.tours").add('sale_product_configurator_edition_tour'
|
||||
}, {
|
||||
trigger: '.fa-pencil',
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Customizable Desk")) label:contains("Steel") > input',
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Customizable Desk (TEST) (Steel, Black)"))',
|
||||
isCheck: true,
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Customizable Desk"))>td:nth-child(3)>div>button:has(i.fa-plus)',
|
||||
}, { // Mr Tajine Saucisse uses the pricelist that has a rule when 2 or more products. Price is 600
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(2)>td>span:contains("1,200.00")',
|
||||
}, {
|
||||
trigger: 'table.o_sale_product_configurator_table tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("Customizable Desk")) td>div[name="ptal"]:has(div>label:contains("Legs")) label:has(span:contains("Steel")) ~ input',
|
||||
},
|
||||
configuratorTourUtils.assertProductNameContains("Customizable Desk (TEST) (Steel, Black)"),
|
||||
configuratorTourUtils.increaseProductQuantity("Customizable Desk"),
|
||||
// Mr Tajine Saucisse uses the pricelist that has a rule when 2 or more products. Price is 600
|
||||
configuratorTourUtils.assertPriceTotal("1,200.00"),
|
||||
{
|
||||
trigger: 'button:contains(Confirm)',
|
||||
}, {
|
||||
trigger: 'td.o_data_cell:contains("2.00")',
|
||||
|
||||
+6
-6
@@ -25,17 +25,17 @@ registry.category("web_tour.tours").add('sale_product_configurator_optional_prod
|
||||
}, {
|
||||
trigger: 'ul.ui-autocomplete a:contains("Customizable Desk (TEST)")',
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Office Chair Black")) button:has(i.fa-shopping-cart)',
|
||||
trigger: 'tr:has(div[name="o_sale_product_configurator_name"]:contains("Office Chair Black")) button:has(i.fa-plus)',
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Customizable Desk")) button:has(i.fa-plus)'
|
||||
trigger: 'tr:has(div[name="o_sale_product_configurator_name"]:contains("Customizable Desk")) button:has(i.fa-plus)'
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Chair floor protection")) button:has(i.fa-shopping-cart)',
|
||||
trigger: 'tr:has(div[name="o_sale_product_configurator_name"]:contains("Chair floor protection")) button:has(i.fa-plus)',
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Conference Chair")) button:has(i.fa-shopping-cart)',
|
||||
trigger: 'tr:has(div[name="o_sale_product_configurator_name"]:contains("Conference Chair")) button:has(i.fa-plus)',
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Conference Chair")) a:has(i.fa-trash-o)',
|
||||
trigger: 'tr:has(div[name="o_sale_product_configurator_name"]:contains("Conference Chair")) a:contains("Remove product")',
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Conference Chair")) button:has(i.fa-shopping-cart)',
|
||||
trigger: 'tr:has(div[name="o_sale_product_configurator_name"]:contains("Conference Chair")) button:has(i.fa-plus)',
|
||||
}, {
|
||||
trigger: 'button:contains(Confirm)',
|
||||
}, {
|
||||
|
||||
+10
-16
@@ -2,6 +2,7 @@
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { stepUtils } from "@web_tour/tour_service/tour_utils";
|
||||
import configuratorTourUtils from "@test_sale_product_configurators/js/tour_utils";
|
||||
|
||||
registry.category("web_tour.tours").add('sale_product_configurator_pricelist_tour', {
|
||||
url: '/web',
|
||||
@@ -44,29 +45,22 @@ stepUtils.showAppsMenuItem(),
|
||||
trigger: 'ul.ui-autocomplete a:contains("Customizable Desk (TEST)")',
|
||||
}, {
|
||||
content: "check price is correct (USD)",
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1)>td:nth-child(4) span:contains("750.00")',
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1)>td:nth-child(4) h5:contains("750.00")',
|
||||
isCheck: true,
|
||||
}, {
|
||||
content: "add one more",
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1)>td:nth-child(3)>div>button:has(i.fa-plus)',
|
||||
}, {
|
||||
content: "check price for 2",
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1)>td:nth-child(4) span:contains("600.00")',
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1)>td:nth-child(4) h5:contains("600.00")',
|
||||
isCheck: true,
|
||||
}, {
|
||||
content: "add conference chair",
|
||||
trigger: 'main.modal-body>table:nth-child(2)>tbody>tr:nth-child(1)>td:nth-child(3)>button',
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(2)>td:nth-child(3)>div>button:has(i.fa-plus)',
|
||||
}, {
|
||||
content: "add chair floor protection",
|
||||
trigger: 'main.modal-body>table:nth-child(2)>tbody>tr:nth-child(1)>td:nth-child(3)>button',
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(3)>td:nth-child(3)>div>button:has(i.fa-plus)',
|
||||
}, {
|
||||
content: "verify configurator final price", // tax excluded
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(4)>td>span:contains("1,257.00")',
|
||||
}, {
|
||||
},
|
||||
configuratorTourUtils.addOptionalProduct("Conference Chair"),
|
||||
configuratorTourUtils.increaseProductQuantity("Conference Chair"),
|
||||
configuratorTourUtils.addOptionalProduct("Chair floor protection"),
|
||||
configuratorTourUtils.increaseProductQuantity("Chair floor protection"),
|
||||
configuratorTourUtils.assertPriceTotal("1,257.00"),
|
||||
{
|
||||
content: "add to SO",
|
||||
trigger: 'button:contains(Confirm)',
|
||||
}, {
|
||||
|
||||
+6
-7
@@ -2,6 +2,7 @@
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { stepUtils } from "@web_tour/tour_service/tour_utils";
|
||||
import configuratorTourUtils from "@test_sale_product_configurators/js/tour_utils";
|
||||
|
||||
registry.category("web_tour.tours").add('sale_product_configurator_recursive_optional_products_tour', {
|
||||
url: '/web',
|
||||
@@ -18,13 +19,11 @@ registry.category("web_tour.tours").add('sale_product_configurator_recursive_opt
|
||||
run: 'text Custo',
|
||||
}, {
|
||||
trigger: 'ul.ui-autocomplete a:contains("Customizable Desk (TEST)")',
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1) label:contains("Aluminium")',
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Conference Chair")) button:has(i.fa-shopping-cart)',
|
||||
}, {
|
||||
trigger: 'tr:has(.o_sale_product_configurator_name:contains("Chair floor protection")) button:has(i.fa-shopping-cart)',
|
||||
}, {
|
||||
},
|
||||
configuratorTourUtils.selectAttribute("Customizable Desk", "Legs", "Aluminium"),
|
||||
configuratorTourUtils.addOptionalProduct("Conference Chair"),
|
||||
configuratorTourUtils.addOptionalProduct("Chair floor protection"),
|
||||
{
|
||||
trigger: 'button:contains(Confirm)',
|
||||
}, ...stepUtils.discardForm()
|
||||
]});
|
||||
|
||||
+10
-13
@@ -1,7 +1,8 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { stepUtils } from "@web_tour/tour_service/tour_utils";
|
||||
import { stepUtils, TourError } from "@web_tour/tour_service/tour_utils";
|
||||
import configuratorTourUtils from "@test_sale_product_configurators/js/tour_utils";
|
||||
|
||||
registry.category("web_tour.tours").add('sale_product_configurator_single_custom_attribute_tour', {
|
||||
url: '/web',
|
||||
@@ -18,32 +19,28 @@ registry.category("web_tour.tours").add('sale_product_configurator_single_custom
|
||||
run: 'text Custo',
|
||||
}, {
|
||||
trigger: 'ul.ui-autocomplete a:contains("Customizable Desk (TEST)")',
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1)>td:nth-child(2)>input:nth-child(7)',
|
||||
run: 'text great single custom value'
|
||||
}, {
|
||||
},
|
||||
configuratorTourUtils.setCustomAttribute("Customizable Desk (TEST)", "product attribute", "great single custom value"),
|
||||
{
|
||||
trigger: 'button:contains(Confirm)',
|
||||
}, {
|
||||
trigger: 'td.o_data_cell:contains("single product attribute value: great single custom value")',
|
||||
extra_trigger: 'div[name="order_line"]',
|
||||
run: function (){} // check custom value
|
||||
isCheck: true,
|
||||
}, {
|
||||
trigger: 'div[name="product_template_id"]',
|
||||
}, {
|
||||
trigger: '.fa-pencil',
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1)>td:nth-child(2)>input:nth-child(7)',
|
||||
trigger: 'table.o_sale_product_configurator_table tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("Customizable Desk (TEST)")) td>div[name="ptal"]:has(div>label:contains("product attribute")) input[type="text"]',
|
||||
run: function () {
|
||||
// check custom value initialized
|
||||
if ($('main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1)>td:nth-child(2)>input:nth-child(7)').val() === "great single custom value") {
|
||||
$('main').addClass('tour_success_2');
|
||||
if ($('table.o_sale_product_configurator_table tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("Customizable Desk (TEST)")) td>div[name="ptal"]:has(div>label:contains("product attribute")) input[type="text"]').val() !== "great single custom value") {
|
||||
throw new TourError("The value of custom product attribute should be 'great single custom value'.");
|
||||
}
|
||||
}
|
||||
}, {
|
||||
trigger: 'main.tour_success_2',
|
||||
isCheck: true,
|
||||
}, {
|
||||
trigger: 'button:contains(Back)',
|
||||
trigger: 'button:contains("Cancel")',
|
||||
},
|
||||
...stepUtils.discardForm()
|
||||
]});
|
||||
|
||||
+8
-8
@@ -2,6 +2,7 @@
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { stepUtils } from "@web_tour/tour_service/tour_utils";
|
||||
import configuratorTourUtils from "@test_sale_product_configurators/js/tour_utils";
|
||||
|
||||
// Note: please keep this test without pricelist for maximum coverage.
|
||||
// The pricelist is tested on the other tours.
|
||||
@@ -28,12 +29,12 @@ registry.category("web_tour.tours").add('sale_product_configurator_tour', {
|
||||
}, {
|
||||
trigger: 'ul.ui-autocomplete a:contains("Customizable Desk (TEST)")',
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1) label:contains("Steel")',
|
||||
trigger: '.o_sale_product_configurator_table tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("Customizable Desk")) label:contains("Steel")',
|
||||
isCheck: true,
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1) label:contains("Aluminium")',
|
||||
trigger: '.o_sale_product_configurator_table tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("Customizable Desk")) label:contains("Aluminium")',
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(1)>tbody>tr:nth-child(1)>td:nth-child(4)>span:contains("800.40")',
|
||||
trigger: '.o_sale_product_configurator_table tr:has(td>div[name="o_sale_product_configurator_name"] h5:contains("Customizable Desk")) td[name="price"] h5:contains("800.40")',
|
||||
isCheck: true, // check updated price
|
||||
}, {
|
||||
trigger: 'label[style="background-color:#000000"] input'
|
||||
@@ -48,11 +49,10 @@ registry.category("web_tour.tours").add('sale_product_configurator_tour', {
|
||||
isCheck: true, // check confirm is available
|
||||
}, {
|
||||
trigger: 'span:contains("Aluminium"):eq(1)',
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(2)>tbody>tr:nth-child(1)>td:nth-child(3)>button',
|
||||
}, {
|
||||
trigger: 'main.modal-body>table:nth-child(2)>tbody>tr:nth-child(1)>td:nth-child(3)>button',
|
||||
}, {
|
||||
},
|
||||
configuratorTourUtils.addOptionalProduct("Conference Chair"),
|
||||
configuratorTourUtils.addOptionalProduct("Chair floor protection"),
|
||||
{
|
||||
trigger: 'button:contains(Confirm)',
|
||||
id: 'quotation_product_selected',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user