Browse Source

Jan 17: [FIX] Bug Fixed 'pos_multi_variant'

pull/364/head
Cybrosys Technologies 3 months ago
parent
commit
06fdea1639
  1. 2
      pos_multi_variant/__init__.py
  2. 2
      pos_multi_variant/__manifest__.py
  3. 5
      pos_multi_variant/doc/RELEASE_NOTES.md
  4. 2
      pos_multi_variant/models/__init__.py
  5. 2
      pos_multi_variant/models/pos_session.py
  6. 2
      pos_multi_variant/models/product_template.py
  7. 2
      pos_multi_variant/models/variants_tree.py
  8. 154
      pos_multi_variant/static/src/css/label.css
  9. 122
      pos_multi_variant/static/src/js/ProductPopup.js
  10. 119
      pos_multi_variant/static/src/js/ProductScreen.js
  11. 30
      pos_multi_variant/static/src/js/models.js
  12. 49
      pos_multi_variant/static/src/js/product_variant_orderline.js
  13. 5
      pos_multi_variant/static/src/xml/label.xml
  14. 102
      pos_multi_variant/static/src/xml/popup.xml

2
pos_multi_variant/__init__.py

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
@ -19,4 +20,5 @@
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from . import models

2
pos_multi_variant/__manifest__.py

@ -21,7 +21,7 @@
#############################################################################
{
'name': "POS Product Multi variant",
'version': '16.0.1.0.0',
'version': '16.0.1.0.1',
'category': 'Point of Sale',
'summary': """POS Multi-variant module is an advanced way for managing
product variants from the point of sale application""",

5
pos_multi_variant/doc/RELEASE_NOTES.md

@ -6,4 +6,7 @@
- Initial commit for POS Product Multi variant
#### 14.01.2025
#### Version 16.0.1.0.1
##### ADD
- Fixed issue related to the amount total in pos orders.

2
pos_multi_variant/models/__init__.py

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
@ -19,6 +20,7 @@
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from . import product_template
from . import pos_session
from . import variants_tree

2
pos_multi_variant/models/pos_session.py

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
@ -19,6 +20,7 @@
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import models

2
pos_multi_variant/models/product_template.py

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
@ -19,6 +20,7 @@
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import fields, models

2
pos_multi_variant/models/variants_tree.py

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
@ -19,6 +20,7 @@
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import fields, models

154
pos_multi_variant/static/src/css/label.css

@ -38,6 +38,24 @@ position: absolute;
background-color:#d7d7d7;
font-style: italic;
}
.multi_variant {
position: absolute;
top: 51px;
left: 10px;
padding: 5px 10px;
background-color: #007bff;
color: white;
font-size: 12px;
font-weight: bold;
border-radius: 3px;
border: 1px solid #0056b3;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
text-transform: uppercase;
cursor: default;
white-space: nowrap;
}
.variant-selected{
position: absolute;
bottom: 23px;
@ -65,3 +83,139 @@ font-style: italic;
align-items: center;
color: #03001C;
}
.pos_multi_variant .product-category {
padding: 0.4rem 0;
max-width: 1200px;
margin: 0 auto;
}
.pos_multi_variant .category-title {
font-size: 1.8rem;
color: #333;
margin-bottom: 1.5rem;
text-align: center;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.pos_multi_variant .product-grid {
display: grid;
/* Three items per row by default */
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
padding: 0 1rem;
margin: 0 auto;
max-width: 1000px;
}
.pos_multi_variant .product-card {
position: relative;
background: white;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
overflow: hidden;
cursor: pointer;
width: 100%;
}
.pos_multi_variant .product-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15);
}
.pos_multi_variant .product-image {
position: relative;
width: 100%;
padding-top: 100%; /* 1:1 Aspect Ratio */
overflow: hidden;
}
.pos_multi_variant .product-image img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.pos_multi_variant .product-card:hover .product-image img {
transform: scale(1.05);
}
.pos_multi_variant .ribbon {
position: absolute;
top: 20px;
left: -5px;
background: #40bb4e;
color: white;
padding: 8px 20px;
font-size: 0.85rem;
font-weight: 600;
text-transform: uppercase;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
z-index: 100;
clip-path: polygon(0 0, 100% 0, calc(100% - 10px) 100%, 0 100%, 10px 50%);
}
.pos_multi_variant .price-tag {
position: absolute;
bottom: 60px;
right: 10px;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 5px 10px;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 500;
}
.pos_multi_variant .variant-name {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 12px;
text-align: center;
font-size: 1rem;
font-weight: 500;
}
.pos_multi_variant .section-divider {
border: none;
height: 1px;
background: linear-gradient(to right, transparent, #ddd, transparent);
margin: 2rem 0;
}
/* Tablet view - 2 items per row */
@media (max-width: 900px) {
.pos_multi_variant .product-grid {
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
padding: 0 1rem;
}
.pos_multi_variant .category-title {
font-size: 1.5rem;
}
}
/* Mobile view - 1 item per row */
@media (max-width: 600px) {
.pos_multi_variant .product-grid {
grid-template-columns: 1fr;
gap: 1rem;
}
.pos_multi_variant .product-card {
max-width: 350px;
margin: 0 auto;
}
}

122
pos_multi_variant/static/src/js/ProductPopup.js

@ -1,75 +1,57 @@
odoo.define('pos_multi_variant.ProductsPopup', function(require) {
'use strict';
/* This JavaScript code defines the ProductsPopup component, which extends
* the ProductItem class from the point_of_sale module. It represents a popup
* for selecting product variants.
*/
const Registries = require('point_of_sale.Registries');
const { useListener } = require("@web/core/utils/hooks");
const ProductItem = require('point_of_sale.ProductItem');
/** @odoo-module **/
class ProductsPopup extends ProductItem {
setup() {
super.setup();
useListener('click','.confirm', this.click_confirm);
useListener('click','.product', this.select_variant);
useListener('click','.cancel', this.click_cancel);
}
select_variant(e) {
var order = this.env.pos.get_order()
var self = e.composedPath ? e.composedPath()[2] : e.path[2];
var action = $(self).find('.action').text();
var category = $(self).find('.action').data('category');
$('.product-img').find('.variant-selected').each(function ()
{ if($(this).data('category') == category)
{ $(this).text("").removeClass('variant-selected');
$(self).find('.action').text("Selected").addClass('variant-selected');
}
});
$(self).find('.action').text("Selected").addClass('variant-selected');
var add = $(self).find('.extra-price').text().substr(1).slice(0, -2);
var type = $(self).find('.variants').text();
$(self).find('.variant-selected').attr('data-price', add);
$(self).find('.variant-selected').attr('data-type', type);
}
click_confirm(e){
var price = 0.00
var order = this.env.pos.get_order()
var selected_orderline = order.get_selected_orderline()
var variants = []
this.env.pos.selectedOrder.selected_orderline.product_variants=variants
$('.product-img').find('.variant-selected').each(function ()
{
var add = this.previousSibling.innerHTML;
add = add.slice(3)
price += parseFloat(add)
if (order.selected_orderline.product.is_pos_variants){
variants.push({
'extra_price': add,
'type': $(this).data('type'),
})
};
})
selected_orderline.price_manually_set = true;
selected_orderline.price += price
this.env.posbus.trigger('close-popup', {
popupId: this.props.id
});
}
click_cancel(){
this.env.posbus.trigger('close-popup', {
popupId: this.props.id
});
}
imageUrl() {
return `/web/image?model=product.product&field=image_1920&id=${this.props.product_tmpl_id}&unique=1`;
import Registries from 'point_of_sale.Registries';
import { useListener } from "@web/core/utils/hooks";
const ProductItem = require('point_of_sale.ProductItem');
const { useState } = owl;
class ProductsPopup extends ProductItem {
setup() {
super.setup();
this.state = useState({
variant_details: this.props.variant_details,
selected_variants: {},
price_total: {},
})
}
SelectVariant(product,variant) {
if (this.state.selected_variants[product.attribute_id[1]] === variant.id){
this.state.selected_variants[product.attribute_id[1]] = false
this.state.price_total[product.attribute_id[1]] = 0.0
}
async _clickProduct(event) {
else{
this.state.selected_variants[product.attribute_id[1]] = variant.id
this.state.price_total[product.attribute_id[1]] = product.extra_price
}
}
ProductsPopup.template = 'ProductsPopUp';
ProductsPopup.defaultProps = {};
Registries.Component.add(ProductsPopup);
return ProductsPopup;
});
clickConfirm(e){
const total = Object.values(this.state.price_total).reduce((sum, value) => sum + value, 0);
var order = this.env.pos.get_order()
var selected_orderline = order.get_selected_orderline()
selected_orderline.price += total
this.env.posbus.trigger('close-popup', {
popupId: this.props.id
});
}
clickCancel(){
this.env.pos.get_order().orderlines.remove(this.env.pos.get_order().selected_orderline)
this.env.posbus.trigger('close-popup', {
popupId: this.props.id
});
}
imageUrl() {
return `/web/image?model=product.product&field=image_1920&id=${this.props.product_tmpl_id}&unique=1`;
}
getSelected(attr, variant) {
return this.state.selected_variants[attr] === variant.id
}
}
ProductsPopup.template = 'ProductsPopUp';
ProductsPopup.defaultProps = {};
Registries.Component.add(ProductsPopup);

119
pos_multi_variant/static/src/js/ProductScreen.js

@ -1,62 +1,59 @@
odoo.define('pos_multi_variant.ProductScreen', function(require) {
'use strict';
/* This JavaScript code extends the ProductScreen class from the point_of_sale module.
* It adds functionality for handling product clicks and displaying the ProductsPopup
* for selecting variants.
*/
var ProductScreen = require('point_of_sale.ProductScreen');
const Registries = require('point_of_sale.Registries');
const NumberBuffer = require('point_of_sale.NumberBuffer');
var rpc = require('web.rpc');
const ProductScreenExtend = (ProductScreen) =>
class extends ProductScreen {
constructor() {
super(...arguments);
/** @odoo-module **/
import ProductScreen from 'point_of_sale.ProductScreen';
import Registries from 'point_of_sale.Registries';
import NumberBuffer from 'point_of_sale.NumberBuffer';
import rpc from 'web.rpc';
const ProductScreenExtend = (ProductScreen) =>
class extends ProductScreen {
setup() {
super.setup();
}
async _clickProduct(event) {
await super._clickProduct(...arguments)
if (!this.currentOrder) {
this.env.pos.add_new_order();
}
async _clickProduct(event) {
await super._clickProduct(...arguments)
if (!this.currentOrder) {
this.env.pos.add_new_order();
}
const product = event.detail;
var variant_product = ''
await rpc.query({
model: 'variants.tree',
method: 'search_read',
fields: ['extra_price','attribute_id','value_ids', 'variants_id'],
args: [[['variants_id','=',event.detail.product_tmpl_id]]]
}).then(function (data) {
variant_product = data
});
var li=[]
for(var i=0; i<variant_product.length; ++i) {
variant_product[i].value_ids.forEach(function (field) {
li.push(field)
});
}
var variant_details = ''
await rpc.query({
model: 'product.attribute.value',
method: 'search_read',
fields: ['name'],
domain: [['id', 'in', li]],
}).then(function (result) {
variant_details = result
});
const options = await this._getAddProductOptions(product);
// Do not add product if options is undefined.
if (!options) return;
NumberBuffer.reset();
if(product.is_pos_variants){
this.showPopup('ProductsPopup',{
title: product.display_name,
products: variant_product,
product_tmpl_id: event.detail.id,
variant_details: variant_details,
});
}
}
};
Registries.Component.extend(ProductScreen, ProductScreenExtend);
return ProductScreen;
});
const product = event.detail;
var variant_product = ''
await rpc.query({
model: 'variants.tree',
method: 'search_read',
fields: ['extra_price','attribute_id','value_ids', 'variants_id'],
args: [[['variants_id','=',event.detail.product_tmpl_id]]]
}).then(function (data) {
variant_product = data
});
var li=[]
for(var i=0; i<variant_product.length; ++i) {
variant_product[i].value_ids.forEach(function (field) {
li.push(field)
});
}
var variant_details = ''
await rpc.query({
model: 'product.attribute.value',
method: 'search_read',
fields: ['name'],
domain: [['id', 'in', li]],
}).then(function (result) {
variant_details = result
});
const options = await this._getAddProductOptions(product);
// Do not add product if options is undefined.
if (!options) return;
NumberBuffer.reset();
if(product.is_pos_variants){
this.showPopup('ProductsPopup',{
title: product.display_name,
products: variant_product,
product_tmpl_id: event.detail.id,
variant_details: variant_details,
});
}
}
};
Registries.Component.extend(ProductScreen, ProductScreenExtend);

30
pos_multi_variant/static/src/js/models.js

@ -1,21 +1,15 @@
odoo.define('pos_multi_variant.model', function(require) {
'use strict';
/* This JavaScript code defines the VariantsPosGlobalState class
* extending the PosGlobalState class from the point_of_sale.models module.
* It adds additional functionality to process variants data.
*/
var {
PosGlobalState
} = require('point_of_sale.models');
const Registries = require('point_of_sale.Registries');
const VariantsPosGlobalState = (PosGlobalState) => class VariantsPosGlobalState extends PosGlobalState {
async _processData(loadedData) {
/** @odoo-module **/
await super._processData(...arguments);
import { PosGlobalState } from 'point_of_sale.models';
import Registries from 'point_of_sale.Registries';
this.variants_tree = loadedData['variants.tree'];
this.product_attribute_value = loadedData['product.attribute.value'];
}
const VariantsPosGlobalState = (PosGlobalState) => class VariantsPosGlobalState extends PosGlobalState {
async _processData(loadedData) {
await super._processData(...arguments);
this.variants_tree = loadedData['variants.tree'];
this.product_attribute_value = loadedData['product.attribute.value'];
}
Registries.Model.extend(PosGlobalState, VariantsPosGlobalState);
});
}
Registries.Model.extend(PosGlobalState, VariantsPosGlobalState);

49
pos_multi_variant/static/src/js/product_variant_orderline.js

@ -1,34 +1,23 @@
odoo.define('pos_order_question.Orderline', function(require) {
'use strict';
/* This JavaScript code defines the PosMultiVariantOrderline class
* extending the Orderline class from the point_of_sale.models module.
* It adds additional functionality related to product variants.
*/
var {
Orderline,
} = require('point_of_sale.models');
var utils = require('web.utils');
const Registries = require('point_of_sale.Registries');
/** @odoo-module **/
const PosMultiVariantOrderline = (Orderline) => class PosMultiVariantOrderline extends Orderline {
import { Orderline } from 'point_of_sale.models';
import Registries from 'point_of_sale.Registries';
constructor(obj, options) {
super(...arguments);
this.product_variants = this.product_variants || [];
}
export_as_JSON() {
const json = super.export_as_JSON(...arguments);
json.product_variants = this.product_variants || [];
return json;
}
export_for_printing() {
var line = super.export_for_printing(...arguments);
line.product_variants = this.product_variants;
return line;
}
export const PosMultiVariantOrderline = (Orderline) => class PosMultiVariantOrderline extends Orderline {
constructor(obj, options) {
super(...arguments);
this.product_variants = this.product_variants || [];
}
Registries.Model.extend(Orderline, PosMultiVariantOrderline);
});
export_as_JSON() {
const json = super.export_as_JSON(...arguments);
json.product_variants = this.product_variants || [];
return json;
}
export_for_printing() {
var line = super.export_for_printing(...arguments);
line.product_variants = this.product_variants;
return line;
}
}
Registries.Model.extend(Orderline, PosMultiVariantOrderline);

5
pos_multi_variant/static/src/xml/label.xml

@ -2,10 +2,9 @@
<templates id="template" xml:space="preserve">
<!-- This XML snippet extends the point_of_sale.ProductItem template -->
<t t-inherit="point_of_sale.ProductItem" t-inherit-mode="extension" owl="1">
<xpath expr="//div[hasclass('product-img')]" position="inside">
<xpath expr="//div[hasclass('product-content')]" position="inside">
<t t-if="props.product.is_pos_variants">
<span class="custom-pos-label">Multi-variant</span>
<link rel="stylesheet" type="text/scss" href="pos_multi_variant/static/src/css/label.css"/>
<span class="multi_variant">Multi-variant</span>
</t>
</xpath>
</t>

102
pos_multi_variant/static/src/xml/popup.xml

@ -1,57 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<!-- This XML snippet defines the ProductsPopUp template -->
<t t-name="ProductsPopUp" owl="1">
<div role="dialog" class="modal-dialog">
<div class="popup popup-selection">
<header class="title">
<t t-esc="props.title || 'Product'" />
</header>
<div class="selection scrollable-y touch-scrollable">
<div id="notify"/></div>
<t t-foreach="props.products" t-as="product" t-key="product.id">
<div>
<h2>
<t t-esc="product['attribute_id'][1]"/>
</h2>
</div>
<t t-set="attribute" t-value="product['attribute_id'][1]"/>
<div class="row">
<div class="" style="display:flex; flex-wrap: wrap;justify-content:center; padding:0 1.5rem;">
<t t-foreach="props.variant_details" t-as="variant"
t-key="variant.id">
<t t-foreach="product['value_ids']" t-as="val" t-key="val">
<t t-if="variant['id'] == val">
<!-- Display the product variant -->
<article class='product' style="flex-grow: 1;">
<div class='product-img'>
<img t-att-src="imageUrl()"/>
<span class='extra-price'>+
<t
t-esc="env.pos.format_currency(product['extra_price'])"/>
</span>
<h2 class='action' data-price=''
data-type=''
t-att-data-category='attribute'/>
<span class='variants'>
<t t-esc="variant['name']"/>
</span>
</div>
</article>
</t>
</t>
</t>
</div>
</div>
<hr/>
</t>
<footer class="footer">
<div class="button confirm"> Confirm </div>
<div class="button cancel">
Cancel
</div>
</footer>
</div>
</div>
</t>
<t t-name="ProductsPopUp" owl="1">
<Draggable>
<div class="popup popup-selection pos_multi_variant" style="">
<header class="title">
<t t-esc="props.title || 'Product'"/>
</header>
<div class="" style="max-height: 25rem; overflow: auto;">
<t t-foreach="props.products" t-as="product" t-key="product.id">
<div class="product-category">
<h2 class="category-title">
<t t-esc="product['attribute_id'][1]"/>
</h2>
<t t-set="attribute" t-value="product['attribute_id'][1]"/>
<div class="product-grid">
<t t-foreach="props.variant_details" t-as="variant" t-key="variant.id">
<t t-foreach="product['value_ids']" t-as="val" t-key="val">
<t t-if="variant['id'] == val">
<article class="product-card"
t-on-click="()=>this.SelectVariant(product,variant)">
<div t-if="getSelected(attribute, variant)" class="ribbon">Selected</div>
<div class="product-image">
<img t-att-src="imageUrl()" alt="Product Variant"/>
<div class="price-tag">
+ <t t-esc="env.pos.format_currency(product['extra_price'])"/>
</div>
<div class="variant-name">
<t t-esc="variant['name']"/>
</div>
</div>
</article>
</t>
</t>
</t>
</div>
</div>
<hr class="section-divider"/>
</t>
</div>
<footer class="footer">
<div class="button confirm" t-on-click="clickConfirm">Confirm</div>
<div class="button cancel" t-on-click="clickCancel">
Cancel
</div>
</footer>
</div>
</Draggable>
</t>
</templates>
Loading…
Cancel
Save