mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-27 23:52:04 +02:00
Initial commit: Sale packages
This commit is contained in:
commit
14e3d26998
6469 changed files with 2479670 additions and 0 deletions
|
|
@ -0,0 +1,26 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import tour from 'web_tour.tour';
|
||||
import wTourUtils from 'website.tour_utils';
|
||||
|
||||
tour.register('website_sale_stock_reorder_from_portal', {
|
||||
test: true,
|
||||
url: '/my/orders',
|
||||
},
|
||||
[
|
||||
{
|
||||
content: 'Select first order',
|
||||
trigger: '.o_portal_my_doc_table a:first',
|
||||
},
|
||||
wTourUtils.clickOnElement('Reorder Again', '.o_wsale_reorder_button'),
|
||||
{
|
||||
content: "Check that there is one out of stock product",
|
||||
trigger: "#o_wsale_reorder_body div.text-warning span:contains('This product is out of stock.')",
|
||||
},
|
||||
{
|
||||
content: "Check that there is one product that does not have enough stock",
|
||||
trigger: "#o_wsale_reorder_body div.text-warning:contains('You ask for 2.0 Units but only 1.0 are available.')",
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
odoo.define('website_sale_stock.VariantMixin', function (require) {
|
||||
'use strict';
|
||||
|
||||
const {Markup} = require('web.utils');
|
||||
const field_utils = require('web.field_utils');
|
||||
var VariantMixin = require('sale.VariantMixin');
|
||||
var publicWidget = require('web.public.widget');
|
||||
var core = require('web.core');
|
||||
var QWeb = core.qweb;
|
||||
|
||||
require('website_sale.website_sale');
|
||||
|
||||
/**
|
||||
* Addition to the variant_mixin._onChangeCombination
|
||||
*
|
||||
* This will prevent the user from selecting a quantity that is not available in the
|
||||
* stock for that product.
|
||||
*
|
||||
* It will also display various info/warning messages regarding the select product's stock.
|
||||
*
|
||||
* This behavior is only applied for the web shop (and not on the SO form)
|
||||
* and only for the main product.
|
||||
*
|
||||
* @param {MouseEvent} ev
|
||||
* @param {$.Element} $parent
|
||||
* @param {Array} combination
|
||||
*/
|
||||
VariantMixin._onChangeCombinationStock = function (ev, $parent, combination) {
|
||||
let product_id = 0;
|
||||
// needed for list view of variants
|
||||
if ($parent.find('input.product_id:checked').length) {
|
||||
product_id = $parent.find('input.product_id:checked').val();
|
||||
} else {
|
||||
product_id = $parent.find('.product_id').val();
|
||||
}
|
||||
const isMainProduct = combination.product_id &&
|
||||
($parent.is('.js_main_product') || $parent.is('.main_product')) &&
|
||||
combination.product_id === parseInt(product_id);
|
||||
|
||||
if (!this.isWebsite || !isMainProduct) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $addQtyInput = $parent.find('input[name="add_qty"]');
|
||||
let qty = $addQtyInput.val();
|
||||
let ctaWrapper = $parent[0].querySelector('#o_wsale_cta_wrapper');
|
||||
ctaWrapper.classList.replace('d-none', 'd-flex');
|
||||
ctaWrapper.classList.remove('out_of_stock');
|
||||
|
||||
if (combination.product_type === 'product' && !combination.allow_out_of_stock_order) {
|
||||
combination.free_qty -= parseInt(combination.cart_qty);
|
||||
$addQtyInput.data('max', combination.free_qty || 1);
|
||||
if (combination.free_qty < 0) {
|
||||
combination.free_qty = 0;
|
||||
}
|
||||
if (qty > combination.free_qty) {
|
||||
qty = combination.free_qty || 1;
|
||||
$addQtyInput.val(qty);
|
||||
}
|
||||
if (combination.free_qty < 1) {
|
||||
ctaWrapper.classList.replace('d-flex', 'd-none');
|
||||
ctaWrapper.classList.add('out_of_stock');
|
||||
}
|
||||
}
|
||||
|
||||
// needed xml-side for formatting of remaining qty
|
||||
combination.formatQuantity = (qty) => {
|
||||
if (Number.isInteger(qty)) {
|
||||
return qty;
|
||||
} else {
|
||||
const decimals = Math.max(
|
||||
0,
|
||||
Math.ceil(-Math.log10(combination.uom_rounding))
|
||||
);
|
||||
return field_utils.format.float(qty, {digits: [false, decimals]});
|
||||
}
|
||||
}
|
||||
|
||||
$('.oe_website_sale')
|
||||
.find('.availability_message_' + combination.product_template)
|
||||
.remove();
|
||||
combination.has_out_of_stock_message = $(combination.out_of_stock_message).text() !== '';
|
||||
combination.out_of_stock_message = Markup(combination.out_of_stock_message);
|
||||
const $message = $(QWeb.render(
|
||||
'website_sale_stock.product_availability',
|
||||
combination
|
||||
));
|
||||
$('div.availability_messages').html($message);
|
||||
};
|
||||
|
||||
publicWidget.registry.WebsiteSale.include({
|
||||
/**
|
||||
* Adds the stock checking to the regular _onChangeCombination method
|
||||
* @override
|
||||
*/
|
||||
_onChangeCombination: function () {
|
||||
this._super.apply(this, arguments);
|
||||
VariantMixin._onChangeCombinationStock.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* Recomputes the combination after adding a product to the cart
|
||||
* @override
|
||||
*/
|
||||
_onClickAdd(ev) {
|
||||
return this._super.apply(this, arguments).then(() => {
|
||||
if ($('div.availability_messages').length) {
|
||||
this._getCombinationInfo(ev);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return VariantMixin;
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/** @odoo-module alias=website_sale_stock.website_sale**/
|
||||
|
||||
import { WebsiteSale } from 'website_sale.website_sale';
|
||||
import { is_email } from 'web.utils';
|
||||
|
||||
WebsiteSale.include({
|
||||
events: Object.assign({}, WebsiteSale.prototype.events, {
|
||||
'click #product_stock_notification_message': '_onClickProductStockNotificationMessage',
|
||||
'click #product_stock_notification_form_submit_button': '_onClickSubmitProductStockNotificationForm',
|
||||
}),
|
||||
|
||||
_onClickProductStockNotificationMessage: function (ev) {
|
||||
const partnerEmail = document.querySelector('#wsale_user_email').value;
|
||||
const emailInputEl = document.querySelector('#stock_notification_input');
|
||||
|
||||
emailInputEl.value = partnerEmail;
|
||||
this._handleClickStockNotificationMessage(ev);
|
||||
},
|
||||
|
||||
_onClickSubmitProductStockNotificationForm: function (ev) {
|
||||
const formEl = ev.currentTarget.closest('#stock_notification_form');
|
||||
const productId = parseInt(formEl.querySelector('input[name="product_id"]').value);
|
||||
this._handleClickSubmitStockNotificationForm(ev, productId);
|
||||
},
|
||||
|
||||
|
||||
_handleClickStockNotificationMessage(ev) {
|
||||
ev.currentTarget.classList.add('d-none');
|
||||
ev.currentTarget.parentElement.querySelector('#stock_notification_form').classList.remove('d-none');
|
||||
},
|
||||
|
||||
_handleClickSubmitStockNotificationForm(ev, productId) {
|
||||
const stockNotificationEl = ev.currentTarget.closest('#stock_notification_div');
|
||||
const formEl = stockNotificationEl.querySelector('#stock_notification_form');
|
||||
const email = stockNotificationEl.querySelector('#stock_notification_input').value.trim();
|
||||
|
||||
if (!is_email(email)) {
|
||||
return this._displayEmailIncorrectMessage(stockNotificationEl);
|
||||
}
|
||||
|
||||
this._rpc({
|
||||
route: "/shop/add/stock_notification",
|
||||
params: {
|
||||
product_id: productId,
|
||||
email,
|
||||
},
|
||||
}).then((data) => {
|
||||
const message = stockNotificationEl.querySelector('#stock_notification_success_message');
|
||||
|
||||
message.classList.remove('d-none');
|
||||
formEl.classList.add('d-none');
|
||||
}).catch((error) => {
|
||||
this._displayEmailIncorrectMessage(stockNotificationEl);
|
||||
});
|
||||
},
|
||||
|
||||
_displayEmailIncorrectMessage(stockNotificationEl) {
|
||||
const incorrectIconEl = stockNotificationEl.querySelector('#stock_notification_input_incorrect');
|
||||
incorrectIconEl.classList.remove('d-none');
|
||||
}
|
||||
});
|
||||
|
||||
export default WebsiteSale;
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { ReorderDialog } from "@website_sale/js/website_sale_reorder";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { sprintf } from "@web/core/utils/strings";
|
||||
|
||||
patch(ReorderDialog.prototype, "website_sale_stock_reorder", {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async onWillStartHandler() {
|
||||
const res = await this._super(...arguments);
|
||||
for (const product of this.content.products) {
|
||||
this.stockCheckCombinationInfo(product);
|
||||
}
|
||||
return res;
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async loadProductCombinationInfo(product) {
|
||||
await this._super(...arguments);
|
||||
},
|
||||
|
||||
stockCheckCombinationInfo(product) {
|
||||
// Products that should have a max quantity available should be limited by default.
|
||||
if (product.combinationInfo.allow_out_of_stock_order || product.type !== "product") {
|
||||
return;
|
||||
}
|
||||
product.max_quantity_available = product.combinationInfo.free_qty;
|
||||
if (!product.max_quantity_available) {
|
||||
product.add_to_cart_allowed = false;
|
||||
}
|
||||
if (product.max_quantity_available < product.qty) {
|
||||
product.qty_warning = sprintf(
|
||||
this.env._t("You ask for %s Units but only %s are available."),
|
||||
product.qty.toFixed(1),
|
||||
product.max_quantity_available.toFixed(1)
|
||||
);
|
||||
product.qty = product.max_quantity_available;
|
||||
product.stock_warning = true;
|
||||
} else if (product.combinationInfo.cart_qty) {
|
||||
product.qty_warning = sprintf(
|
||||
this.env._t("You already have %s Units in your cart."),
|
||||
product.combinationInfo.cart_qty.toFixed(1)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getWarningForProduct(product) {
|
||||
if (product.hasOwnProperty("max_quantity_available") && !product.max_quantity_available) {
|
||||
return this.env._t("This product is out of stock.");
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
changeProductQty(product, newQty) {
|
||||
if (product.max_quantity_available && newQty > product.max_quantity_available) {
|
||||
product.qty_warning = sprintf(
|
||||
this.env._t("You ask for %s Units but only %s are available."),
|
||||
newQty.toFixed(1),
|
||||
product.max_quantity_available.toFixed(1)
|
||||
);
|
||||
product.stock_warning = true;
|
||||
newQty = product.max_quantity_available;
|
||||
} else if (product.stock_warning) {
|
||||
product.qty_warning = false;
|
||||
product.stock_warning = false;
|
||||
}
|
||||
this._super(product, newQty);
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<templates>
|
||||
<t t-name="website_sale_stock.product_availability">
|
||||
<t t-if="product_type == 'product' and !prevent_zero_price_sale">
|
||||
<div t-if="free_qty lte 0 and !cart_qty" t-attf-class="availability_message_#{product_template} mb-1">
|
||||
<div id="out_of_stock_message">
|
||||
<t t-if='has_out_of_stock_message' t-out='out_of_stock_message'/>
|
||||
<t t-elif="!allow_out_of_stock_order">
|
||||
<div class="text-danger fw-bold">
|
||||
<i class="fa fa-times me-1"/>
|
||||
Out of Stock
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<div id="stock_notification_div" t-if="!allow_out_of_stock_order">
|
||||
<div class="btn btn-link px-0" t-if="!has_stock_notification"
|
||||
id="product_stock_notification_message">
|
||||
<i class="fa fa-envelope-o me-1"/>
|
||||
Get notified when back in stock
|
||||
</div>
|
||||
<div id="stock_notification_form" class="d-none">
|
||||
<div class="input-group">
|
||||
<input class="form-control"
|
||||
id="stock_notification_input" name="email"
|
||||
type="text" placeholder="youremail@gmail.com" t-value="stock_notification_email if stock_notification_email else None"/>
|
||||
<input name="product_id" type="hidden" t-att-value="product_id"/>
|
||||
|
||||
<div id="product_stock_notification_form_submit_button" class="btn btn-secondary">
|
||||
<i class="fa fa-paper-plane"/>
|
||||
</div>
|
||||
<div id="stock_notification_input_incorrect" class="btn d-none">
|
||||
<i class="fa fa-times text-danger"/>
|
||||
Invalid email
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="stock_notification_success_message"
|
||||
t-att-class="has_stock_notification ? '' : 'd-none'">
|
||||
<div class="text-muted">
|
||||
<i class="fa fa-bell"/>
|
||||
We'll notify you once the product is back in stock.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="threshold_message" t-elif="show_availability and free_qty lte available_threshold" t-attf-class="availability_message_#{product_template} text-warning fw-bold">
|
||||
Only <t t-esc="formatQuantity(free_qty)"/> <t t-esc="uom_name" /> left in stock.
|
||||
</div>
|
||||
|
||||
<div id="already_in_cart_message" t-if="!allow_out_of_stock_order and show_availability and cart_qty" t-attf-class="availability_message_#{product_template} text-warning mt8">
|
||||
<t t-if='!free_qty'>
|
||||
You already added all the available product in your cart.
|
||||
</t>
|
||||
<t t-else=''>
|
||||
You already added <t t-esc="cart_qty" /> <t t-esc="uom_name" /> in your cart.
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</templates>
|
||||
Loading…
Add table
Add a link
Reference in a new issue