añadido pos_product_available desde odoo app

https://apps.odoo.com/apps/modules/12.0/pos_product_available/
This commit is contained in:
santiky 2021-08-22 22:57:36 +02:00
parent 89d58a2aff
commit f1c8cf8414
Signed by: snt
GPG key ID: A9FD34930EADBE71
24 changed files with 1030 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,145 @@
<section class="container">
<div class="row">
<div class="col-md-offset-1 col-md-8 mb64 mt64">
<div class="">
<h2 class="display-3">POS: show product qty</h2>
<h4 class="text-default">Let a cashier know how many products are available</h4>
<p class="text-default mt16">Version: v<b>12.0</b>.1.1.1</p>
</div>
</div>
<div class="col-md-3 text-right">
<p>
<img src="https://itpp.dev/images/favicon.png"/>
<br/>Tested and maintained by
<br/><b>IT Projects Labs</b>
<br/>Assitance: <a href="mailto:help@itpp.dev">help@itpp.dev</a>
</p>
</div>
</div>
</section>
<section class="oe_container"><div class="oe_row oe_spaced"> <div class="oe_span12">
</div>
<div class="oe_span6">
<div class="oe_demo oe_picture oe_screenshot">
<img src="screenshot.png"/>
</div>
</div>
<div class="oe_span6">
<p class="oe_mt32">
Adds available quantity at order panel
</p>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="oe_span6">
<p class="oe_mt32">
Adds available quantity at products.
</p>
</div>
<div class="oe_span6">
<div class="oe_demo oe_picture oe_screenshot">
<img src="screenshot-2.png"/>
</div>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2>Usage</h2>
<p class="oe_mt32">
There no any settings. Just install the module and start POS.
</p>
<p class="oe_mt32">
Works in offline mode.
</p>
</div>
</div>
</section>
<section class="container text-center">
<div class="mt-4 mb-3">
<h2>Let our expertise work for you!</h2>
<h4 class="d-none d-md-block">(Doors and windows below are clickable)</h4>
<div class="mb32 d-md-none">
<ul class="list-unstyled">
<li><a target="_blank" href="https://apps.odoo.com/apps/browse?repo_maintainer_id=59928">All modules</a></li>
<li><a target="_blank" href="https://apps.odoo.com/apps/browse?price=Free&order=Downloads&repo_maintainer_id=59928">Free Modules</a></li>
<li><a target="_blank" href="https://apps.odoo.com/apps/modules/category/Point%20of%20Sale/browse?repo_maintainer_id=59928">All POS modules</a></li>
<li><a target="_blank" href="https://apps.odoo.com/apps/modules/category/Discuss/browse?repo_maintainer_id=59928">All Mail modules</a></li>
<li><a target="_blank" href="https://apps.odoo.com/apps/modules/category/Website/browse?repo_maintainer_id=59928">All Website modules</a></li>
<li><a target="_blank" href="https://apps.odoo.com/apps/modules/category/eCommerce/browse?repo_maintainer_id=59928">All eCommerce modules</a></li>
<li><a target="_blank" href="https://apps.odoo.com/apps/modules/browse?search=sync+studio&repo_maintainer_id=59928">Sync Studio</a></li>
<li><a target="_blank" href="https://apps.odoo.com/apps/modules/14.0/openapi/">REST API</a></li>
<li><a target="_blank" href="https://apps.odoo.com/apps/modules/14.0/base_automation_webhook/">Webhooks</a></li>
<li><a target="_blank" href="https://apps.odoo.com/apps/modules/14.0/pos_product_available/">POS: show product qty</a></li>
<li><a target="_blank" href="https://apps.odoo.com/apps/modules/14.0/web_login_background/">Web Login Background</a></li>
</ul>
</div>
<div class="mb64 d-none d-xl-block">
<img class="img img-responsive" src="https://itpp.dev/images/apps/;-)/itpp-labs-1100.jpg" usemap="#itpp-xl" />
<map name="itpp-xl">
<area alt="Free Modules" target="_blank" href="https://apps.odoo.com/apps/browse?price=Free&order=Downloads&repo_maintainer_id=59928" shape="rect" class="" coords="900,220,1080,625" />
<area alt="Sync Studio" target="_blank" href="https://apps.odoo.com/apps/modules/browse?search=sync+studio&repo_maintainer_id=59928" shape="rect" class="" coords="552,186,721,274" />
<area alt="REST API" target="_blank" href="https://apps.odoo.com/apps/modules/14.0/openapi/" shape="rect" class="" coords="646,279,720,365" />
<area alt="Webhooks" target="_blank" href="https://apps.odoo.com/apps/modules/14.0/base_automation_webhook/" shape="rect" class="" coords="646,370,720,458" />
<area alt="POS: show product qty" target="_blank" href="https://apps.odoo.com/apps/modules/14.0/pos_product_available/" shape="rect" class="" coords="82,373,251,459" />
<area alt="All modules" target="_blank" href="https://apps.odoo.com/apps/browse?repo_maintainer_id=59928" shape="rect" class="" coords="326,267,381,456" />
<area alt="All POS modules" target="_blank" href="https://apps.odoo.com/apps/modules/category/Point%20of%20Sale/browse?repo_maintainer_id=59928" shape="rect" class="" coords="82,191,154,276" />
<area alt="All Mail modules" target="_blank" href="https://apps.odoo.com/apps/modules/category/Discuss/browse?repo_maintainer_id=59928" shape="rect" class="" coords="82,282,155,368" />
<area alt="All Website modules" target="_blank" href="https://apps.odoo.com/apps/modules/category/Website/browse?repo_maintainer_id=59928" shape="rect" class="" coords="176,282,250,368" />
<area alt="All eCommerce modules" target="_blank" href="https://apps.odoo.com/apps/modules/category/eCommerce/browse?repo_maintainer_id=59928" shape="rect" class="" coords="175,189,250,275" />
<area alt="Ivan Yelizariev" target="_blank" href="https://apps.odoo.com/apps/modules/browse?search=Ivan+Yelizariev&repo_maintainer_id=59928" shape="rect" class="" coords="554,280,625,456" />
<area alt="Web Login Background" target="_blank" href="https://apps.odoo.com/apps/modules/14.0/web_login_background/" shape="rect" class="" coords="420,267,477,456" />
</map>
</div>
<div class="mb64 d-none d-lg-block d-xl-none">
<img class="img img-responsive" src="https://itpp.dev/images/apps/;-)/itpp-labs-930.jpg" usemap="#itpp-lg" />
<map name="itpp-lg">
<area alt="Free Modules" target="_blank" href="https://apps.odoo.com/apps/browse?price=Free&order=Downloads&repo_maintainer_id=59928" shape="rect" class="" coords="760,186,913,528" />
<area alt="Sync Studio" target="_blank" href="https://apps.odoo.com/apps/modules/browse?search=sync+studio&repo_maintainer_id=59928" shape="rect" class="" coords="466,157,609,231" />
<area alt="REST API" target="_blank" href="https://apps.odoo.com/apps/modules/14.0/openapi/" shape="rect" class="" coords="546,235,608,308" />
<area alt="Webhooks" target="_blank" href="https://apps.odoo.com/apps/modules/14.0/base_automation_webhook/" shape="rect" class="" coords="546,312,608,387" />
<area alt="POS: show product qty" target="_blank" href="https://apps.odoo.com/apps/modules/14.0/pos_product_available/" shape="rect" class="" coords="69,315,212,388" />
<area alt="All modules" target="_blank" href="https://apps.odoo.com/apps/browse?repo_maintainer_id=59928" shape="rect" class="" coords="275,225,322,385" />
<area alt="All POS modules" target="_blank" href="https://apps.odoo.com/apps/modules/category/Point%20of%20Sale/browse?repo_maintainer_id=59928" shape="rect" class="" coords="69,161,130,233" />
<area alt="All Mail modules" target="_blank" href="https://apps.odoo.com/apps/modules/category/Discuss/browse?repo_maintainer_id=59928" shape="rect" class="" coords="69,238,131,311" />
<area alt="All Website modules" target="_blank" href="https://apps.odoo.com/apps/modules/category/Website/browse?repo_maintainer_id=59928" shape="rect" class="" coords="148,238,211,311" />
<area alt="All eCommerce modules" target="_blank" href="https://apps.odoo.com/apps/modules/category/eCommerce/browse?repo_maintainer_id=59928" shape="rect" class="" coords="147,159,211,232" />
<area alt="Ivan Yelizariev" target="_blank" href="https://apps.odoo.com/apps/modules/browse?search=Ivan+Yelizariev&repo_maintainer_id=59928" shape="rect" class="" coords="468,236,528,385" />
<area alt="Web Login Background" target="_blank" href="https://apps.odoo.com/apps/modules/14.0/web_login_background/" shape="rect" class="" coords="355,225,403,385" />
</map>
</div>
<div class="mb64 d-lg-none">
<img class="img img-responsive" src="https://itpp.dev/images/apps/;-)/itpp-labs-690.jpg" usemap="#itpp-md" />
<map name="itpp-md">
<area alt="Free Modules" target="_blank" href="https://apps.odoo.com/apps/browse?price=Free&order=Downloads&repo_maintainer_id=59928" shape="rect" class="" coords="564,138,677,392" />
<area alt="Sync Studio" target="_blank" href="https://apps.odoo.com/apps/modules/browse?search=sync+studio&repo_maintainer_id=59928" shape="rect" class="" coords="346,116,452,171" />
<area alt="REST API" target="_blank" href="https://apps.odoo.com/apps/modules/14.0/openapi/" shape="rect" class="" coords="405,175,451,228" />
<area alt="Webhooks" target="_blank" href="https://apps.odoo.com/apps/modules/14.0/base_automation_webhook/" shape="rect" class="" coords="405,232,451,287" />
<area alt="POS: show product qty" target="_blank" href="https://apps.odoo.com/apps/modules/14.0/pos_product_available/" shape="rect" class="" coords="51,233,157,287" />
<area alt="All modules" target="_blank" href="https://apps.odoo.com/apps/browse?repo_maintainer_id=59928" shape="rect" class="" coords="204,167,238,286" />
<area alt="All POS modules" target="_blank" href="https://apps.odoo.com/apps/modules/category/Point%20of%20Sale/browse?repo_maintainer_id=59928" shape="rect" class="" coords="51,119,96,173" />
<area alt="All Mail modules" target="_blank" href="https://apps.odoo.com/apps/modules/category/Discuss/browse?repo_maintainer_id=59928" shape="rect" class="" coords="51,176,97,230" />
<area alt="All Website modules" target="_blank" href="https://apps.odoo.com/apps/modules/category/Website/browse?repo_maintainer_id=59928" shape="rect" class="" coords="110,176,156,230" />
<area alt="All eCommerce modules" target="_blank" href="https://apps.odoo.com/apps/modules/category/eCommerce/browse?repo_maintainer_id=59928" shape="rect" class="" coords="109,118,156,172" />
<area alt="Ivan Yelizariev" target="_blank" href="https://apps.odoo.com/apps/modules/browse?search=Ivan+Yelizariev&repo_maintainer_id=59928" shape="rect" class="" coords="347,175,392,286" />
<area alt="Web Login Background" target="_blank" href="https://apps.odoo.com/apps/modules/14.0/web_login_background/" shape="rect" class="" coords="263,167,299,286" />
</map>
</div>
</div>
</section>

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View file

@ -0,0 +1,24 @@
.pos .product .qty-tag {
position: absolute;
top: 2px;
left: 2px;
vertical-align: top;
color: white;
line-height: 13px;
background: #7f82ac;
padding: 2px 5px;
border-radius: 2px;
font-weight: bold;
}
.pos .product .qty-tag.not-available {
background: #d882ac;
}
.pos .order .orderline .qty-info {
color: #777;
font-weight: bold;
font-style: normal;
}
.pos .order .orderline .qty-info.not-available {
color: red;
}

View file

@ -0,0 +1,233 @@
/* Copyright 2014-2017 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
Copyright 2016 gaelTorrecillas <https://github.com/gaelTorrecillas>
Copyright 2016 manawi <https://github.com/manawi>
Copyright 2017 Ilmir Karamov <https://it-projects.info/team/ilmir-k>
Copyright 2018,2019 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr>
License MIT (https://opensource.org/licenses/MIT). */
odoo.define("pos_product_available.PosModel", function(require) {
"use strict";
var rpc = require("web.rpc");
var models = require("point_of_sale.models");
var field_utils = require("web.field_utils");
var PosModelSuper = models.PosModel.prototype;
models.PosModel = models.PosModel.extend({
load_server_data: function() {
var self = this;
var loaded = PosModelSuper.load_server_data.call(this);
var prod_model = _.find(this.models, function(model) {
return model.model === "product.product";
});
if (prod_model) {
prod_model.fields.push("qty_available", "type");
var context_super = prod_model.context;
prod_model.context = function(that) {
var ret = context_super(that);
ret.location = that.config.stock_location_id[0];
return ret;
};
var loaded_super = prod_model.loaded;
prod_model.loaded = function(that, products) {
loaded_super(that, products);
self.db.product_qtys = products;
};
return loaded;
}
return loaded.then(function() {
return rpc
.query({
model: "product.product",
method: "search_read",
args: [],
fields: ["qty_available", "type"],
domain: [
["sale_ok", "=", true],
["available_in_pos", "=", true],
],
context: {location: self.config.stock_location_id[0]},
})
.then(function(products) {
self.db.product_qtys = products;
});
});
},
set_product_qty_available: function(product, qty) {
product.qty_available = qty;
this.refresh_qty_available(product);
},
update_product_qty_from_order_lines: function(order) {
var self = this;
order.orderlines.each(function(line) {
var product = line.get_product();
product.qty_available = Math.round(
product.qty_available - line.get_quantity(),
{digits: [69, 3]}
);
self.refresh_qty_available(product);
});
// Compatibility with pos_multi_session
order.trigger("new_updates_to_send");
},
after_load_server_data: function() {
var self = this;
var res = PosModelSuper.after_load_server_data.apply(this, arguments);
_.each(this.db.product_qtys, function(v) {
_.extend(self.db.get_product_by_id(v.id), v);
});
return res;
},
refresh_qty_available: function(product) {
var $elem = $("[data-product-id='" + product.id + "'] .qty-tag");
$elem.html(product.rounded_qty());
if (product.qty_available <= 0 && !$elem.hasClass("not-available")) {
$elem.addClass("not-available");
}
},
push_order: function(order, opts) {
var pushed = PosModelSuper.push_order.call(this, order, opts);
if (order) {
this.update_product_qty_from_order_lines(order);
}
return pushed;
},
push_and_invoice_order: function(order) {
var invoiced = PosModelSuper.push_and_invoice_order.call(this, order);
if (order && order.get_client() && order.orderlines) {
this.update_product_qty_from_order_lines(order);
}
return invoiced;
},
});
models.Product = models.Product.extend({
get_price: function(pricelist, quantity) {
var self = this;
var date = moment().startOf("day");
// In case of nested pricelists, it is necessary that all pricelists are made available in
// the POS. Display a basic alert to the user in this case.
if (pricelist === undefined) {
// eslint-disable-next-line
alert(
"An error occurred when loading product prices. " +
"Make sure all pricelists are available in the POS."
);
}
var category_ids = [];
var category = this.categ;
while (category) {
category_ids.push(category.id);
category = category.parent;
}
/*
So, with pos_product_available it doesnt work correctly, cause
'item.product_tmpl_id || item.product_tmpl_id[0] === self.product_tmpl_id'
in this string are comparing integer with an array of two elements.
Changed: 'self.product_tmpl_id -> self.product_tmpl_id[0]',
where 'self.product_tmpl_id[0]' product template id.
By the way, here's comparing products ids.
IMPORTANT: Changed line - (!item.product_tmpl_id || item.product_tmpl_id[0] === self.product_tmpl_id[0]).
*/
var pricelist_items = _.filter(pricelist.items, function(item) {
return (
(!item.product_tmpl_id ||
item.product_tmpl_id[0] === self.product_tmpl_id[0]) &&
(!item.product_id || item.product_id[0] === self.id) &&
(!item.categ_id || _.contains(category_ids, item.categ_id[0])) &&
(!item.date_start ||
moment(item.date_start).isSameOrBefore(date)) &&
(!item.date_end || moment(item.date_end).isSameOrAfter(date))
);
});
var price = self.lst_price;
_.find(pricelist_items, function(rule) {
if (rule.min_quantity && quantity < rule.min_quantity) {
return false;
}
if (rule.base === "pricelist") {
price = self.get_price(rule.base_pricelist, quantity);
} else if (rule.base === "standard_price") {
price = self.standard_price;
}
if (rule.compute_price === "fixed") {
price = rule.fixed_price;
return true;
} else if (rule.compute_price === "percentage") {
price -= price * (rule.percent_price / 100);
return true;
}
var price_limit = price;
price -= price * (rule.price_discount / 100);
if (rule.price_round) {
// eslint-disable-next-line
price = round_pr(price, rule.price_round);
}
if (rule.price_surcharge) {
price += rule.price_surcharge;
}
if (rule.price_min_margin) {
price = Math.max(price, price_limit + rule.price_min_margin);
}
if (rule.price_max_margin) {
price = Math.min(price, price_limit + rule.price_max_margin);
}
return true;
});
// This return value has to be rounded with round_di before
// being used further. Note that this cannot happen here,
// because it would cause inconsistencies with the backend for
// pricelist that have base == 'pricelist'.
return price;
},
});
var OrderlineSuper = models.Orderline;
models.Orderline = models.Orderline.extend({
export_as_JSON: function() {
var data = OrderlineSuper.prototype.export_as_JSON.apply(this, arguments);
data.qty_available = this.product.qty_available;
return data;
},
// Compatibility with pos_multi_session
apply_ms_data: function(data) {
if (OrderlineSuper.prototype.apply_ms_data) {
OrderlineSuper.prototype.apply_ms_data.apply(this, arguments);
}
var product = this.pos.db.get_product_by_id(data.product_id);
if (product.qty_available !== data.qty_available) {
this.pos.set_product_qty_available(product, data.qty_available);
}
},
});
models.Product = models.Product.extend({
format_float_value: function(val) {
var value = parseFloat(val);
value = field_utils.format.float(value, {digits: [69, 3]});
return String(parseFloat(value));
},
/*
Commented this code, cause it works incorrect
Example: "this.format_float_value(2366) === 2"
rounded_qty: function() {
return this.format_float_value(this.qty_available);
},
*/
rounded_qty: function() {
return Math.round(this.qty_available);
},
});
});

View file

@ -0,0 +1,142 @@
/* Copyright 2018-2019 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr>
License MIT (https://opensource.org/licenses/MIT). */
odoo.define("pos_product_available.tour", function(require) {
"use strict";
var tour = require("web_tour.tour");
var core = require("web.core");
var _t = core._t;
function open_pos_neworder() {
return [
{
trigger: ".o_pos_kanban button.oe_kanban_action_button",
content: _t(
"<p>Click to start the point of sale interface. It <b>runs on tablets</b>, laptops, or industrial hardware.</p><p>Once the session launched, the system continues to run without an internet connection.</p>"
),
position: "bottom",
},
{
content: "Switch to table or make dummy action",
trigger: ".table, .order-button.selected",
position: "bottom",
timeout: 30000,
},
{
content: "waiting for loading to finish",
trigger: ".order-button.neworder-button",
},
];
}
function add_product_to_order(product_name) {
return [
{
content: "buy " + product_name,
trigger: '.product-list .product-name:contains("' + product_name + '")',
},
{
content: "the " + product_name + " have been added to the order",
trigger: '.orderline:contains("' + product_name + '")',
run: function() {
// It's a check
},
},
];
}
function payment(pay_method) {
return [
{
trigger: ".button.pay",
content: _t("Open the payment screen"),
},
{
content: "Choose Administrator like a cashier or make a dummy action",
trigger:
'.modal-dialog.cashier:not(.oe_hidden) .cashier .selection-item:contains("Mitchell Admin"), .payment-screen:not(.oe_hidden) h1:contains("Payment")',
},
{
trigger: '.button.paymentmethod:contains("' + pay_method + '")',
content: _t("Click the payment method"),
},
{
trigger: '.payment-screen .input-button.number-char:contains("2")',
content: "Set payment amount",
},
{
trigger: '.button.next.highlight:contains("Validate")',
content: "Validate payment",
},
{
extra_trigger: ".pos-sale-ticket",
trigger: '.button.next.highlight:contains("Next Order")',
content: "Check proceeded validation",
},
];
}
function close_pos() {
return [
{
trigger: '.header-button:contains("Close")',
content: _t("Close POS"),
},
{
trigger: '.header-button.confirm:contains("Confirm")',
content: _t("Close POS"),
},
{
extra_trigger: ".o_pos_kanban button.oe_kanban_action_button",
trigger: ".breadcrumb li.active",
content: _t(
"<p>Click to start the point of sale interface. It <b>runs on tablets</b>, laptops, or industrial hardware.</p><p>Once the session launched, the system continues to run without an internet connection.</p>"
),
position: "bottom",
},
];
}
function check_quantity(qty) {
return [
{
content: "check quantity",
extra_trigger:
'.product-list .product:contains("LED Lamp") .qty-tag:contains(' +
String(qty) +
"):not(.not-available)",
trigger: ".order-button.selected",
},
];
}
var steps = [
tour.STEPS.SHOW_APPS_MENU_ITEM,
{
trigger: '.o_app[data-menu-xmlid="point_of_sale.menu_point_root"]',
content: _t(
"Ready to launch your <b>point of sale</b>? <i>Click here</i>."
),
position: "right",
edition: "community",
},
{
trigger: '.o_app[data-menu-xmlid="point_of_sale.menu_point_root"]',
content: _t(
"Ready to launch your <b>point of sale</b>? <i>Click here</i>."
),
position: "bottom",
edition: "enterprise",
},
];
steps = steps.concat(
open_pos_neworder(),
add_product_to_order("LED Lamp"),
payment("Cash (USD)"),
close_pos(),
open_pos_neworder(),
check_quantity(2)
);
tour.register("tour_pos_product_available", {test: true, url: "/web"}, steps);
});

View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--Copyright 2014-2015 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
Copyright 2018-2019 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr>
License MIT (https://opensource.org/licenses/MIT). -->
<templates id="template" xml:space="preserve">
<t t-extend="Product">
<t t-jquery=".product-img" t-operation="append">
<t t-if="widget.pos.config.show_qtys &amp;&amp; product.type == 'product'">
<span
t-attf-class="qty-tag #{product.qty_available lte 0 ? 'not-available':''}"
>
<t t-esc="product.rounded_qty()" />
</span>
</t>
</t>
</t>
<t t-extend="Orderline">
<t t-jquery=".info-list>t:first-child">
this.attr('t-if', "line.get_quantity_str() !== '1' || line.selected || line.get_product().qty_available lt line.quantity ");
</t>
<t t-jquery="t[t-esc='line.get_quantity_str()']" t-operation="after">
<t
t-if="widget.pos.config.show_qtys &amp;&amp; line.get_product().type == 'product'"
>
<t t-set='qty_available' t-value='line.get_product().qty_available' />
(of <span
t-attf-class="qty-info #{qty_available lt line.quantity ? 'not-available':''}"
>
<t t-esc="line.get_product().rounded_qty()" />
</span>)
</t>
</t>
</t>
</templates>