diff --git a/ext/custom-addons/cam_customer_pricelist/__init__.py b/ext/custom-addons/cam_customer_pricelist/__init__.py new file mode 100644 index 00000000..27376f43 --- /dev/null +++ b/ext/custom-addons/cam_customer_pricelist/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP Modul cam_customer_pricelist +# Copyright (C) Camadeus GmbH (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import cam_customer_pricelist + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/ext/custom-addons/cam_customer_pricelist/__openerp__.py b/ext/custom-addons/cam_customer_pricelist/__openerp__.py new file mode 100644 index 00000000..a25b70bc --- /dev/null +++ b/ext/custom-addons/cam_customer_pricelist/__openerp__.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP Modul cam_customer_pricelist +# Copyright (C) Camadeus GmbH (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +{ + 'name': 'Camadeus Customer Pricelist', + 'category': 'Custom', + 'version': '1.0', + 'description': """Prices per user basis""", + 'author': 'Camadeus GmbH', + 'website': 'http://www.camadeus.at', + 'depends': ['product'], + 'data': [ + 'cam_customer_pricelist_view.xml', + 'cam_customer_pricelist_data.xml', + 'security/ir.model.access.csv', + ], + 'installable': True, + 'auto_install': False, +} + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/ext/custom-addons/cam_customer_pricelist/cam_customer_pricelist.py b/ext/custom-addons/cam_customer_pricelist/cam_customer_pricelist.py new file mode 100644 index 00000000..52156d2b --- /dev/null +++ b/ext/custom-addons/cam_customer_pricelist/cam_customer_pricelist.py @@ -0,0 +1,256 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP Modul cam_customer_pricelist +# Copyright (C) Camadeus GmbH (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp.osv import fields, osv +from openerp.tools.translate import _ +import openerp.addons.decimal_precision as dp +import datetime +import time + +class product_pricelist_item(osv.osv): + + _inherit = "product.pricelist.item" + + def _price_field_get(self, cr, uid, context=None): + pt = self.pool.get('product.price.type') + ids = pt.search(cr, uid, [], context=context) + result = [] + for line in pt.browse(cr, uid, ids, context=context): + result.append((line.id, line.name)) + + result.append((-1, _('Other Pricelist'))) + result.append((-2, _('Supplier Prices on the product form'))) + result.append((-3, _('Kundenbezogener Preis'))) + return result + + _columns = { + 'base': fields.selection(_price_field_get, 'Based on', required=True, size=-1, help="Base price for computation."), + } + +class product_pricelist_customer(osv.osv): + + _name = "product.pricelist.customer" + _inherit = ['mail.thread'] + + _columns = { + 'partner_id': fields.many2one('res.partner', 'Kunde', domain="[('customer','=',True)]", required=True), + 'product_id': fields.many2one('product.product', 'Produkt', required=True), + 'unit_price': fields.float('Preis pro Einheit', digits_compute=dp.get_precision('Product Price'), track_visibility='onchange'), + 'min_quantity': fields.integer('Mindestbestellmenge', track_visibility='onchange'), + 'date_start': fields.date('Begindatum', track_visibility='onchange'), + 'date_end': fields.date('Enddatum', track_visibility='onchange'), + } + +class product_pricelist(osv.osv): + + _inherit = "product.pricelist" + + def _price_rule_get_multi(self, cr, uid, pricelist, products_by_qty_by_partner, context=None): + context = context or {} + date = context.get('date') or time.strftime('%Y-%m-%d') + + products = map(lambda x: x[0], products_by_qty_by_partner) + currency_obj = self.pool.get('res.currency') + product_obj = self.pool.get('product.template') + product_uom_obj = self.pool.get('product.uom') + price_type_obj = self.pool.get('product.price.type') + + if not products: + return {} + + version = False + for v in pricelist.version_id: + if ((v.date_start is False) or (v.date_start <= date)) and ((v.date_end is False) or (v.date_end >= date)): + version = v + break + if not version: + raise osv.except_osv(_('Warning!'), _("At least one pricelist has no active version !\nPlease create or activate one.")) + categ_ids = {} + for p in products: + categ = p.categ_id + while categ: + categ_ids[categ.id] = True + categ = categ.parent_id + categ_ids = categ_ids.keys() + + is_product_template = products[0]._name == "product.template" + if is_product_template: + prod_tmpl_ids = [tmpl.id for tmpl in products] + prod_ids = [product.id for product in tmpl.product_variant_ids for tmpl in products] + else: + prod_ids = [product.id for product in products] + prod_tmpl_ids = [product.product_tmpl_id.id for product in products] + + # Load all rules + cr.execute( + 'SELECT i.id ' + 'FROM product_pricelist_item AS i ' + 'WHERE (product_tmpl_id IS NULL OR product_tmpl_id = any(%s)) ' + 'AND (product_id IS NULL OR (product_id = any(%s))) ' + 'AND ((categ_id IS NULL) OR (categ_id = any(%s))) ' + 'AND (price_version_id = %s) ' + 'ORDER BY sequence, min_quantity desc', + (prod_tmpl_ids, prod_ids, categ_ids, version.id)) + + item_ids = [x[0] for x in cr.fetchall()] + items = self.pool.get('product.pricelist.item').browse(cr, uid, item_ids, context=context) + + price_types = {} + + results = {} + for product, qty, partner in products_by_qty_by_partner: + results[product.id] = 0.0 + rule_id = False + price = False + + # Final unit price is computed according to `qty` in the `qty_uom_id` UoM. + # An intermediary unit price may be computed according to a different UoM, in + # which case the price_uom_id contains that UoM. + # The final price will be converted to match `qty_uom_id`. + qty_uom_id = context.get('uom') or product.uom_id.id + price_uom_id = product.uom_id.id + qty_in_product_uom = qty + if qty_uom_id != product.uom_id.id: + try: + qty_in_product_uom = product_uom_obj._compute_qty( + cr, uid, context['uom'], qty, product.uom_id.id or product.uos_id.id) + except except_orm: + # Ignored - incompatible UoM in context, use default product UoM + pass + + for rule in items: + if rule.min_quantity and qty_in_product_uom < rule.min_quantity: + continue + if is_product_template: + if rule.product_tmpl_id and product.id != rule.product_tmpl_id.id: + continue + if rule.product_id: + continue + else: + if rule.product_tmpl_id and product.product_tmpl_id.id != rule.product_tmpl_id.id: + continue + if rule.product_id and product.id != rule.product_id.id: + continue + + if rule.categ_id: + cat = product.categ_id + while cat: + if cat.id == rule.categ_id.id: + break + cat = cat.parent_id + if not cat: + continue + + if rule.base == -1: + if rule.base_pricelist_id: + price_tmp = self._price_get_multi(cr, uid, + rule.base_pricelist_id, [(product, + qty, False)], context=context)[product.id] + ptype_src = rule.base_pricelist_id.currency_id.id + price_uom_id = qty_uom_id + price = currency_obj.compute(cr, uid, + ptype_src, pricelist.currency_id.id, + price_tmp, round=False, + context=context) + elif rule.base == -2: + seller = False + for seller_id in product.seller_ids: + if (not partner) or (seller_id.name.id != partner): + continue + seller = seller_id + if not seller and product.seller_ids: + seller = product.seller_ids[0] + if seller: + qty_in_seller_uom = qty + seller_uom = seller.product_uom.id + if qty_uom_id != seller_uom: + qty_in_seller_uom = product_uom_obj._compute_qty(cr, uid, qty_uom_id, qty, to_uom_id=seller_uom) + price_uom_id = seller_uom + for line in seller.pricelist_ids: + if line.min_quantity <= qty_in_seller_uom: + price = line.price + elif rule.base == -3: + ppc_obj = self.pool.get('product.pricelist.customer') + ppc_ids = ppc_obj.search(cr, uid, [('partner_id','=',partner), ('product_id','=',product.id)], context=context) + + ppc_best = False + today = datetime.date.today() + for ppc in ppc_obj.browse(cr, uid, ppc_ids, context=context): + ppc_start = ppc.date_start or '0001-01-01' + ppc_start = datetime.datetime.strptime(ppc_start, '%Y-%m-%d').date() + ppc_end = ppc.date_end or '9999-01-01' + ppc_end = datetime.datetime.strptime(ppc_end, '%Y-%m-%d').date() + + # 1) Entweder es gibt noch kein best oder die best min_qty muss kleiner sein als die canditate min_qty + # 2) Die qty aus der Zeile muss größer sein als die canditate min_qty + # 3) Heute muss zwischen start und end datum der candite regel sein + if (not ppc_best or ppc_best.min_quantity < ppc.min_quantity) and qty_in_product_uom >= ppc.min_quantity and ppc_start <= today <= ppc_end: + ppc_best = ppc + + if ppc_best: + price = ppc_best.unit_price + else: + continue + + else: + if rule.base not in price_types: + price_types[rule.base] = price_type_obj.browse(cr, uid, int(rule.base)) + price_type = price_types[rule.base] + + # price_get returns the price in the context UoM, i.e. qty_uom_id + price_uom_id = qty_uom_id + price = currency_obj.compute( + cr, uid, + price_type.currency_id.id, pricelist.currency_id.id, + product_obj._price_get(cr, uid, [product], price_type.field, context=context)[product.id], + round=False, context=context) + + if price is not False: + price_limit = price + price = price * (1.0+(rule.price_discount or 0.0)) + if rule.price_round: + price = tools.float_round(price, precision_rounding=rule.price_round) + + convert_to_price_uom = (lambda price: product_uom_obj._compute_price( + cr, uid, product.uom_id.id, + price, price_uom_id)) + if rule.price_surcharge: + price_surcharge = convert_to_price_uom(rule.price_surcharge) + price += price_surcharge + + if rule.price_min_margin: + price_min_margin = convert_to_price_uom(rule.price_min_margin) + price = max(price, price_limit + price_min_margin) + + if rule.price_max_margin: + price_max_margin = convert_to_price_uom(rule.price_max_margin) + price = min(price, price_limit + price_max_margin) + + rule_id = rule.id + break + + # Final price conversion to target UoM + price = product_uom_obj._compute_price(cr, uid, price_uom_id, price, qty_uom_id) + + results[product.id] = (price, rule_id) + return results + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/ext/custom-addons/cam_customer_pricelist/cam_customer_pricelist_data.xml b/ext/custom-addons/cam_customer_pricelist/cam_customer_pricelist_data.xml new file mode 100644 index 00000000..aab2e2f0 --- /dev/null +++ b/ext/custom-addons/cam_customer_pricelist/cam_customer_pricelist_data.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/ext/custom-addons/cam_customer_pricelist/cam_customer_pricelist_view.xml b/ext/custom-addons/cam_customer_pricelist/cam_customer_pricelist_view.xml new file mode 100644 index 00000000..711f5a09 --- /dev/null +++ b/ext/custom-addons/cam_customer_pricelist/cam_customer_pricelist_view.xml @@ -0,0 +1,117 @@ + + + + + + product_pricelist_customer_form + product.pricelist.customer + +
+ + + + + + + + + + +
+ + +
+
+
+
+ + + product_pricelist_customer_tree + product.pricelist.customer + + + + + + + + + + + + + + product_pricelist_customer_search + product.pricelist.customer + + + + + + + + + + + + + + + Kundenbez. Preise + ir.actions.act_window + product.pricelist.customer + form + tree,form + + + + Kundenbez. Preise + ir.actions.act_window + product.pricelist.customer + form + tree,form + [('product_id.product_tmpl_id', '=', active_id)] + {'default_product_id': active_id} + + + + Kundenbez. Preise + ir.actions.act_window + product.pricelist.customer + form + tree,form + [('partner_id', '=', active_id)] + {'default_partner_id': active_id} + + + + + + + product.template.product.form + product.template + + + +