140 lines
6.3 KiB
Python
140 lines
6.3 KiB
Python
# -*- coding: utf-8 -*-
|
||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||
from odoo import api, fields, models, _
|
||
from odoo.exceptions import UserError
|
||
from itertools import groupby
|
||
from operator import itemgetter
|
||
from datetime import date
|
||
|
||
|
||
class ProductTemplate(models.Model):
|
||
_inherit = 'product.template'
|
||
|
||
available_in_pos = fields.Boolean(string='Available in POS', help='Check if you want this product to appear in the Point of Sale.', default=False)
|
||
to_weight = fields.Boolean(string='To Weigh With Scale', help="Check if the product should be weighted using the hardware scale integration.")
|
||
pos_categ_ids = fields.Many2many(
|
||
'pos.category', string='Point of Sale Category',
|
||
help="Category used in the Point of Sale.")
|
||
combo_ids = fields.Many2many('pos.combo', string='Combinations')
|
||
detailed_type = fields.Selection(selection_add=[
|
||
('combo', 'Combo')
|
||
], ondelete={'combo': 'set consu'})
|
||
type = fields.Selection(selection_add=[
|
||
('combo', 'Combo')
|
||
], ondelete={'combo': 'set consu'})
|
||
|
||
@api.ondelete(at_uninstall=False)
|
||
def _unlink_except_open_session(self):
|
||
product_ctx = dict(self.env.context or {}, active_test=False)
|
||
if self.with_context(product_ctx).search_count([('id', 'in', self.ids), ('available_in_pos', '=', True)]):
|
||
if self.env['pos.session'].sudo().search_count([('state', '!=', 'closed')]):
|
||
raise UserError(_("To delete a product, make sure all point of sale sessions are closed.\n\n"
|
||
"Deleting a product available in a session would be like attempting to snatch a"
|
||
"hamburger from a customer’s hand mid-bite; chaos will ensue as ketchup and mayo go flying everywhere!"))
|
||
|
||
@api.onchange('sale_ok')
|
||
def _onchange_sale_ok(self):
|
||
if not self.sale_ok:
|
||
self.available_in_pos = False
|
||
|
||
@api.constrains('available_in_pos')
|
||
def _check_combo_inclusions(self):
|
||
for product in self:
|
||
if not product.available_in_pos:
|
||
combo_name = self.env['pos.combo.line'].search([('product_id', 'in', product.product_variant_ids.ids)], limit=1).combo_id.name
|
||
if combo_name:
|
||
raise UserError(_('You must first remove this product from the %s combo', combo_name))
|
||
|
||
class ProductProduct(models.Model):
|
||
_inherit = 'product.product'
|
||
|
||
@api.ondelete(at_uninstall=False)
|
||
def _unlink_except_active_pos_session(self):
|
||
product_ctx = dict(self.env.context or {}, active_test=False)
|
||
if self.env['pos.session'].sudo().search_count([('state', '!=', 'closed')]):
|
||
if self.with_context(product_ctx).search_count([('id', 'in', self.ids), ('product_tmpl_id.available_in_pos', '=', True)]):
|
||
raise UserError(_("To delete a product, make sure all point of sale sessions are closed.\n\n"
|
||
"Deleting a product available in a session would be like attempting to snatch a"
|
||
"hamburger from a customer’s hand mid-bite; chaos will ensue as ketchup and mayo go flying everywhere!"))
|
||
|
||
def get_product_info_pos(self, price, quantity, pos_config_id):
|
||
self.ensure_one()
|
||
config = self.env['pos.config'].browse(pos_config_id)
|
||
|
||
# Tax related
|
||
taxes = self.taxes_id.compute_all(price, config.currency_id, quantity, self)
|
||
grouped_taxes = {}
|
||
for tax in taxes['taxes']:
|
||
if tax['id'] in grouped_taxes:
|
||
grouped_taxes[tax['id']]['amount'] += tax['amount']/quantity if quantity else 0
|
||
else:
|
||
grouped_taxes[tax['id']] = {
|
||
'name': tax['name'],
|
||
'amount': tax['amount']/quantity if quantity else 0
|
||
}
|
||
|
||
all_prices = {
|
||
'price_without_tax': taxes['total_excluded']/quantity if quantity else 0,
|
||
'price_with_tax': taxes['total_included']/quantity if quantity else 0,
|
||
'tax_details': list(grouped_taxes.values()),
|
||
}
|
||
|
||
# Pricelists
|
||
if config.use_pricelist:
|
||
pricelists = config.available_pricelist_ids
|
||
else:
|
||
pricelists = config.pricelist_id
|
||
price_per_pricelist_id = pricelists._price_get(self, quantity) if pricelists else False
|
||
pricelist_list = [{'name': pl.name, 'price': price_per_pricelist_id[pl.id]} for pl in pricelists]
|
||
|
||
# Warehouses
|
||
warehouse_list = [
|
||
{'name': w.name,
|
||
'available_quantity': self.with_context({'warehouse': w.id}).qty_available,
|
||
'forecasted_quantity': self.with_context({'warehouse': w.id}).virtual_available,
|
||
'uom': self.uom_name}
|
||
for w in self.env['stock.warehouse'].search([])]
|
||
|
||
# Suppliers
|
||
key = itemgetter('partner_id')
|
||
supplier_list = []
|
||
for key, group in groupby(sorted(self.seller_ids, key=key), key=key):
|
||
for s in list(group):
|
||
if not((s.date_start and s.date_start > date.today()) or (s.date_end and s.date_end < date.today()) or (s.min_qty > quantity)):
|
||
supplier_list.append({
|
||
'name': s.partner_id.name,
|
||
'delay': s.delay,
|
||
'price': s.price
|
||
})
|
||
break
|
||
|
||
# Variants
|
||
variant_list = [{'name': attribute_line.attribute_id.name,
|
||
'values': list(map(lambda attr_name: {'name': attr_name, 'search': '%s %s' % (self.name, attr_name)}, attribute_line.value_ids.mapped('name')))}
|
||
for attribute_line in self.attribute_line_ids]
|
||
|
||
return {
|
||
'all_prices': all_prices,
|
||
'pricelists': pricelist_list,
|
||
'warehouses': warehouse_list,
|
||
'suppliers': supplier_list,
|
||
'variants': variant_list
|
||
}
|
||
|
||
class ProductAttributeCustomValue(models.Model):
|
||
_inherit = "product.attribute.custom.value"
|
||
|
||
pos_order_line_id = fields.Many2one('pos.order.line', string="PoS Order Line", ondelete='cascade')
|
||
|
||
class UomCateg(models.Model):
|
||
_inherit = 'uom.category'
|
||
|
||
is_pos_groupable = fields.Boolean(string='Group Products in POS',
|
||
help="Check if you want to group products of this category in point of sale orders")
|
||
|
||
|
||
class Uom(models.Model):
|
||
_inherit = 'uom.uom'
|
||
|
||
is_pos_groupable = fields.Boolean(related='category_id.is_pos_groupable', readonly=False)
|