You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

272 lines
10 KiB

/* Copyright 2018 Tecnativa - Jairo Llopis
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). */
odoo.define("pos_pricelist.models", function (require) {
"use strict";
var core = require("web.core");
var models = require("point_of_sale.models");
var utils = require('web.utils');
var moment = window.moment;
var round_pr = utils.round_precision;
var exports = {};
// Patch PosModel (it's a Backbone.Model, not a usual odoo model)
var _PosModel_initialize = models.PosModel.prototype.initialize;
models.PosModel.prototype.initialize = function () {
_PosModel_initialize.apply(this, arguments);
this.default_pricelist = null;
};
// Patch res.partner
models.load_fields("res.partner", ["property_product_pricelist"]);
// Patch product.pricelist
models.load_fields("product.pricelist", ['name', 'display_name']);
var _product_pricelist = _.findWhere(
models.PosModel.prototype.models,
{model: "product.pricelist"}
),
_product_pricelist_domain = _product_pricelist.domain,
_product_pricelist_loaded = _product_pricelist.loaded;
delete _product_pricelist.ids;
_product_pricelist.domain = function (self) {
return _.union(
_product_pricelist_domain,
[['id', 'in', self.config.available_pricelist_ids]]
);
};
_product_pricelist.loaded = function (self, pricelists) {
_product_pricelist_loaded.apply(this, arguments);
_.map(pricelists, function (pricelist) {
pricelist.items = [];
});
self.default_pricelist = _.findWhere(
pricelists,
{id: self.config.pricelist_id[0]}
);
self.pricelists = pricelists;
self.pricelist = self.default_pricelist;
};
// New models to load after product.pricelist
models.load_models(
[
{
model: 'product.pricelist.item',
domain: function(self) {
return [['pricelist_id', 'in',
_.pluck(self.pricelists, 'id')]];
},
loaded: function(self, pricelist_items){
var pricelist_by_id = {};
_.each(self.pricelists, function (pricelist) {
pricelist_by_id[pricelist.id] = pricelist;
});
_.each(pricelist_items, function (item) {
var pricelist = pricelist_by_id[item.pricelist_id[0]];
pricelist.items.push(item);
item.base_pricelist = pricelist_by_id[
item.base_pricelist_id[0]
];
});
},
}, {
model: 'product.category',
fields: ['name', 'parent_id'],
loaded: function (self, product_categories) {
var category_by_id = {};
_.each(product_categories, function (category) {
category_by_id[category.id] = category;
});
_.each(product_categories, function (category) {
category.parent = category_by_id[
category.parent_id[0]
];
});
self.product_categories = product_categories;
}
},
], {
after: "product.pricelist",
}
);
// Patch product.product
models.load_fields(
"product.product",
['lst_price', 'standard_price', 'categ_id']
);
var _product_product = _.findWhere(
models.PosModel.prototype.models,
{model: "product.product"}
);
_product_product.loaded = function (self, products) {
self.db.add_products(_.map(products, function (product) {
product.categ = _.findWhere(
self.product_categories,
{'id': product.categ_id[0]}
);
return new exports.Product(product);
}));
};
// New model Product
exports.Product = core.Class.extend({
init: function(options) {
_.extend(this, options);
},
// Port of get_product_price on product.pricelist.
//
// Anything related to UOM can be ignored, the POS will always use
// the default UOM set on the product and the user cannot change
// it.
//
// Pricelist items do not have to be sorted. All
// product.pricelist.item records are loaded with a search_read
// and were automatically sorted based on their _order by the
// ORM. After that they are added in this order to the pricelists.
get_price: function(pricelist, quantity) {
var self = this;
var date = moment().startOf('day');
var category_ids = [];
var category = this.categ;
while (category) {
category_ids.push(category.id);
category = category.parent;
}
var pricelist_items = _.filter(pricelist.items, function (item) {
return (! item.product_tmpl_id || item.product_tmpl_id[0] === self.product_tmpl_id) &&
(! 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) {
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;
},
});
// Patch Orderline
var _Orderline_initialize = models.Orderline.prototype.initialize,
_Orderline_init_from_JSON = models.Orderline.prototype.init_from_JSON,
_Orderline_set_quantity = models.Orderline.prototype.set_quantity;
models.Orderline.prototype.initialize = function (attr, options) {
_Orderline_initialize.apply(this, arguments);
if (options.product) {
this.set_unit_price(
options.product.price ||
this.product.get_price(
this.order.pricelist,
this.get_quantity()
)
);
}
};
models.Orderline.prototype.init_from_JSON = function () {
this.keep_price = 'do not recompute unit price';
return _Orderline_init_from_JSON.apply(this, arguments);
};
models.Orderline.prototype.set_quantity = function () {
_Orderline_set_quantity.apply(this, arguments);
var keep_price = this.keep_price;
delete this.keep_price;
// just like in sale.order changing the quantity will recompute the unit price
if (!keep_price) {
this.set_unit_price(this.product.get_price(
this.order.pricelist,
this.get_quantity()
));
this.order.fix_tax_included_price(this);
}
};
// Patch Order
var _Order_initialize = models.Order.prototype.initialize,
_Order_init_from_JSON = models.Order.prototype.init_from_JSON,
_Order_export_as_JSON = models.Order.prototype.export_as_JSON;
models.Order.prototype.initialize = function () {
_Order_initialize.apply(this, arguments);
this.set_pricelist(this.pos.default_pricelist);
};
models.Order.prototype.init_from_JSON = function (json) {
_Order_init_from_JSON.apply(this, arguments);
if (json.pricelist_id) {
this.pricelist = _.find(this.pos.pricelists, function (pricelist) {
return pricelist.id === json.pricelist_id;
});
} else {
this.pricelist = this.pos.default_pricelist;
}
};
models.Order.prototype.export_as_JSON = function () {
var result = _Order_export_as_JSON.apply(this, arguments);
result.pricelist_id = this.pricelist ? this.pricelist.id : false;
return result;
};
models.Order.prototype.set_pricelist = function (pricelist) {
var self = this;
this.pricelist = pricelist;
_.each(this.get_orderlines(), function (line) {
line.set_unit_price(
line.product.get_price(self.pricelist, line.get_quantity())
);
self.fix_tax_included_price(line);
});
this.trigger('change');
};
return exports;
});