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. 104
      pos_multi_variant/static/src/xml/popup.xml

2
pos_multi_variant/__init__.py

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

2
pos_multi_variant/__manifest__.py

@ -21,7 +21,7 @@
############################################################################# #############################################################################
{ {
'name': "POS Product Multi variant", 'name': "POS Product Multi variant",
'version': '16.0.1.0.0', 'version': '16.0.1.0.1',
'category': 'Point of Sale', 'category': 'Point of Sale',
'summary': """POS Multi-variant module is an advanced way for managing 'summary': """POS Multi-variant module is an advanced way for managing
product variants from the point of sale application""", 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 - 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 -*- # -*- coding: utf-8 -*-
############################################################################# #############################################################################
# #
# Cybrosys Technologies Pvt. Ltd. # Cybrosys Technologies Pvt. Ltd.
@ -19,6 +20,7 @@
# If not, see <http://www.gnu.org/licenses/>. # If not, see <http://www.gnu.org/licenses/>.
# #
############################################################################# #############################################################################
from . import product_template from . import product_template
from . import pos_session from . import pos_session
from . import variants_tree from . import variants_tree

2
pos_multi_variant/models/pos_session.py

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

2
pos_multi_variant/models/product_template.py

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

2
pos_multi_variant/models/variants_tree.py

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

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

@ -38,6 +38,24 @@ position: absolute;
background-color:#d7d7d7; background-color:#d7d7d7;
font-style: italic; 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{ .variant-selected{
position: absolute; position: absolute;
bottom: 23px; bottom: 23px;
@ -64,4 +82,140 @@ font-style: italic;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
color: #03001C; 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) { /** @odoo-module **/
'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');
class ProductsPopup extends ProductItem { import Registries from 'point_of_sale.Registries';
setup() { import { useListener } from "@web/core/utils/hooks";
super.setup(); const ProductItem = require('point_of_sale.ProductItem');
useListener('click','.confirm', this.click_confirm); const { useState } = owl;
useListener('click','.product', this.select_variant);
useListener('click','.cancel', this.click_cancel);
} class ProductsPopup extends ProductItem {
select_variant(e) { setup() {
var order = this.env.pos.get_order() super.setup();
var self = e.composedPath ? e.composedPath()[2] : e.path[2]; this.state = useState({
var action = $(self).find('.action').text(); variant_details: this.props.variant_details,
var category = $(self).find('.action').data('category'); selected_variants: {},
$('.product-img').find('.variant-selected').each(function () price_total: {},
{ if($(this).data('category') == category) })
{ $(this).text("").removeClass('variant-selected'); }
$(self).find('.action').text("Selected").addClass('variant-selected');
} SelectVariant(product,variant) {
}); if (this.state.selected_variants[product.attribute_id[1]] === variant.id){
$(self).find('.action').text("Selected").addClass('variant-selected'); this.state.selected_variants[product.attribute_id[1]] = false
var add = $(self).find('.extra-price').text().substr(1).slice(0, -2); this.state.price_total[product.attribute_id[1]] = 0.0
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`;
} }
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) { /** @odoo-module **/
'use strict';
/* This JavaScript code extends the ProductScreen class from the point_of_sale module. import ProductScreen from 'point_of_sale.ProductScreen';
* It adds functionality for handling product clicks and displaying the ProductsPopup import Registries from 'point_of_sale.Registries';
* for selecting variants. import NumberBuffer from 'point_of_sale.NumberBuffer';
*/ import rpc from 'web.rpc';
var ProductScreen = require('point_of_sale.ProductScreen');
const Registries = require('point_of_sale.Registries'); const ProductScreenExtend = (ProductScreen) =>
const NumberBuffer = require('point_of_sale.NumberBuffer'); class extends ProductScreen {
var rpc = require('web.rpc'); setup() {
const ProductScreenExtend = (ProductScreen) => super.setup();
class extends ProductScreen { }
constructor() {
super(...arguments); async _clickProduct(event) {
await super._clickProduct(...arguments)
if (!this.currentOrder) {
this.env.pos.add_new_order();
} }
async _clickProduct(event) { const product = event.detail;
await super._clickProduct(...arguments) var variant_product = ''
if (!this.currentOrder) { await rpc.query({
this.env.pos.add_new_order(); model: 'variants.tree',
} method: 'search_read',
const product = event.detail; fields: ['extra_price','attribute_id','value_ids', 'variants_id'],
var variant_product = '' args: [[['variants_id','=',event.detail.product_tmpl_id]]]
await rpc.query({ }).then(function (data) {
model: 'variants.tree', variant_product = data
method: 'search_read', });
fields: ['extra_price','attribute_id','value_ids', 'variants_id'], var li=[]
args: [[['variants_id','=',event.detail.product_tmpl_id]]] for(var i=0; i<variant_product.length; ++i) {
}).then(function (data) { variant_product[i].value_ids.forEach(function (field) {
variant_product = data li.push(field)
}); });
var li=[] }
for(var i=0; i<variant_product.length; ++i) { var variant_details = ''
variant_product[i].value_ids.forEach(function (field) { await rpc.query({
li.push(field) model: 'product.attribute.value',
}); method: 'search_read',
} fields: ['name'],
var variant_details = '' domain: [['id', 'in', li]],
await rpc.query({ }).then(function (result) {
model: 'product.attribute.value', variant_details = result
method: 'search_read', });
fields: ['name'], const options = await this._getAddProductOptions(product);
domain: [['id', 'in', li]], // Do not add product if options is undefined.
}).then(function (result) { if (!options) return;
variant_details = result NumberBuffer.reset();
}); if(product.is_pos_variants){
const options = await this._getAddProductOptions(product); this.showPopup('ProductsPopup',{
// Do not add product if options is undefined. title: product.display_name,
if (!options) return; products: variant_product,
NumberBuffer.reset(); product_tmpl_id: event.detail.id,
if(product.is_pos_variants){ variant_details: variant_details,
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);
}
}
};
Registries.Component.extend(ProductScreen, ProductScreenExtend);
return ProductScreen;
});

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

@ -1,21 +1,15 @@
odoo.define('pos_multi_variant.model', function(require) { /** @odoo-module **/
'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) {
await super._processData(...arguments); import { PosGlobalState } from 'point_of_sale.models';
import Registries from 'point_of_sale.Registries';
this.variants_tree = loadedData['variants.tree']; const VariantsPosGlobalState = (PosGlobalState) => class VariantsPosGlobalState extends PosGlobalState {
this.product_attribute_value = loadedData['product.attribute.value']; 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) { /** @odoo-module **/
'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');
const PosMultiVariantOrderline = (Orderline) => class PosMultiVariantOrderline extends Orderline { import { Orderline } from 'point_of_sale.models';
import Registries from 'point_of_sale.Registries';
constructor(obj, options) { export const PosMultiVariantOrderline = (Orderline) => class PosMultiVariantOrderline extends Orderline {
super(...arguments); constructor(obj, options) {
this.product_variants = this.product_variants || []; 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;
}
} }
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"> <templates id="template" xml:space="preserve">
<!-- This XML snippet extends the point_of_sale.ProductItem template --> <!-- This XML snippet extends the point_of_sale.ProductItem template -->
<t t-inherit="point_of_sale.ProductItem" t-inherit-mode="extension" owl="1"> <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"> <t t-if="props.product.is_pos_variants">
<span class="custom-pos-label">Multi-variant</span> <span class="multi_variant">Multi-variant</span>
<link rel="stylesheet" type="text/scss" href="pos_multi_variant/static/src/css/label.css"/>
</t> </t>
</xpath> </xpath>
</t> </t>

104
pos_multi_variant/static/src/xml/popup.xml

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