# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime from sql import Null from trytond.model import ModelSQL, ModelView, ValueMixin, fields from trytond.modules.currency.fields import Monetary from trytond.modules.product import price_digits, round_price from trytond.pool import Pool, PoolMeta from trytond.pyson import Eval, TimeDelta from trytond.transaction import Transaction default_lead_time = fields.TimeDelta( "Default Lead Time", domain=['OR', ('default_lead_time', '=', None), ('default_lead_time', '>=', TimeDelta()), ], help="The time from confirming the sales order to sending the " "products.\n" "Used for products without a lead time.") class Configuration(metaclass=PoolMeta): __name__ = 'product.configuration' default_lead_time = fields.MultiValue(default_lead_time) class DefaultLeadTime(ModelSQL, ValueMixin): __name__ = 'product.configuration.default_lead_time' default_lead_time = default_lead_time class Template(metaclass=PoolMeta): __name__ = 'product.template' salable = fields.Boolean("Salable") sale_uom = fields.Many2One( 'product.uom', "Sale UoM", states={ 'invisible': ~Eval('salable', False), 'required': Eval('salable', False), }, domain=[ ('category', '=', Eval('default_uom_category', -1)), ], help="The default Unit of Measure for sales.") lead_time = fields.MultiValue(fields.TimeDelta( "Lead Time", domain=['OR', ('lead_time', '=', None), ('lead_time', '>=', TimeDelta()), ], states={ 'invisible': ~Eval('salable', False), }, help="The time from confirming the sales order to sending the " "products.\n" "If empty the default lead time from the configuration is used.")) lead_times = fields.One2Many( 'product.lead_time', 'template', "Lead Times") @classmethod def multivalue_model(cls, field): pool = Pool() if field == 'lead_time': return pool.get('product.lead_time') return super().multivalue_model(field) @fields.depends('default_uom', 'sale_uom', 'salable') def on_change_default_uom(self): try: super().on_change_default_uom() except AttributeError: pass if self.default_uom: if self.sale_uom: if self.default_uom.category != self.sale_uom.category: self.sale_uom = self.default_uom else: self.sale_uom = self.default_uom @classmethod def view_attributes(cls): return super().view_attributes() + [ ('//page[@id="customers"]', 'states', { 'invisible': ~Eval('salable'), })] class ProductLeadTime(ModelSQL, ValueMixin): __name__ = 'product.lead_time' template = fields.Many2One( 'product.template', "Template", ondelete='CASCADE') lead_time = fields.TimeDelta( "Lead Time", domain=['OR', ('lead_time', '=', None), ('lead_time', '>=', TimeDelta()), ]) @classmethod def __register__(cls, module_name): pool = Pool() Template = pool.get('product.template') template = Template.__table__() table = cls.__table__() super().__register__(module_name) cursor = Transaction().connection.cursor() template_h = Template.__table_handler__(module_name) if template_h.column_exist('lead_time'): cursor.execute(*table.insert( columns=[table.template, table.lead_time], values=template.select( template.id, template.lead_time, where=template.lead_time != Null))) template_h.drop_column('lead_time') class Product(metaclass=PoolMeta): __name__ = 'product.product' sale_price_uom = fields.Function(Monetary( "Sale Price", digits=price_digits), 'get_sale_price_uom') @classmethod def get_sale_price_uom(cls, products, name): quantity = Transaction().context.get('quantity') or 0 return cls.get_sale_price(products, quantity=quantity) def _get_sale_unit_price(self, quantity=0): return self.list_price_used @classmethod def get_sale_price(cls, products, quantity=0): ''' Return the sale price for products and quantity. It uses if exists from the context: uom: the unit of measure or the sale uom of the product currency: the currency id for the returned price ''' pool = Pool() Uom = pool.get('product.uom') Company = pool.get('company.company') Currency = pool.get('currency.currency') Date = pool.get('ir.date') context = Transaction().context prices = {} assert len(products) == len(set(products)), "Duplicate products" uom = None if context.get('uom'): uom = Uom(context['uom']) currency = None if context.get('currency'): currency = Currency(context.get('currency')) company = None if context.get('company'): company = Company(context['company']) date = context.get('sale_date') or Date.today() for product in products: unit_price = product._get_sale_unit_price(quantity=quantity) if unit_price is not None: if uom and product.default_uom.category == uom.category: unit_price = Uom.compute_price( product.default_uom, unit_price, uom) elif product.sale_uom: unit_price = Uom.compute_price( product.default_uom, unit_price, product.sale_uom) if currency and company and unit_price is not None: if company.currency != currency: with Transaction().set_context(date=date): unit_price = Currency.compute( company.currency, unit_price, currency, round=False) if unit_price is not None: unit_price = round_price(unit_price) prices[product.id] = unit_price return prices @property def lead_time_used(self): pool = Pool() Configuration = pool.get('product.configuration') if self.lead_time is None: with Transaction().set_context(self._context): config = Configuration(1) return config.get_multivalue('default_lead_time') else: return self.lead_time def compute_shipping_date(self, date=None): ''' Compute the shipping date at the given date ''' Date = Pool().get('ir.date') if not date: with Transaction().set_context(context=self._context): date = Date.today() lead_time = self.lead_time_used if lead_time is None: return datetime.date.max return date + lead_time class SaleContext(ModelView): __name__ = 'product.sale.context' locations = fields.Many2Many( 'stock.location', None, None, "Warehouses", domain=[('type', '=', 'warehouse')]) company = fields.Many2One('company.company', "Company") currency = fields.Many2One('currency.currency', "Currency") customer = fields.Many2One( 'party.party', "Customer", context={ 'company': Eval('company', -1), }, depends={'company'}) sale_date = fields.Date("Sale Date") quantity = fields.Float("Quantity") stock_date_end = fields.Function( fields.Date("Stock End Date"), 'on_change_with_stock_date_end') @classmethod def default_locations(cls): pool = Pool() Location = pool.get('stock.location') context = Transaction().context locations = context.get('locations') if locations is None: locations = [] warehouse = Location.get_default_warehouse() if warehouse: locations.append(warehouse) return locations @classmethod def default_company(cls): return Transaction().context.get('company') @classmethod def default_currency(cls): pool = Pool() Company = pool.get('company.company') context = Transaction().context currency = context.get('currency') if currency is None: company_id = cls.default_company() if company_id is not None and company_id >= 0: company = Company(company_id) currency = company.currency.id return currency @classmethod def default_customer(cls): return Transaction().context.get('customer') @classmethod def default_sale_date(cls): return Transaction().context.get('sale_date') @classmethod def default_quantity(cls): return Transaction().context.get('quantity') @fields.depends('sale_date') def on_change_with_stock_date_end(self, name=None): return self.sale_date