# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from collections import defaultdict from sql import Literal from sql.operators import Equal from trytond.cache import Cache from trytond.model import ( DeactivableMixin, Exclude, ModelSQL, ModelView, fields) from trytond.pool import Pool, PoolMeta from trytond.pyson import Eval from trytond.transaction import Transaction class Many2ManyInactive(fields.Many2Many): def get(self, ids, model, name, values=None): with Transaction().set_context(inactive_test=True): return super().get(ids, model, name, values=values) def set(self, Model, name, ids, values, *args): with Transaction().set_context(inactive_test=True): return super().set(Model, name, ids, values, *args) class Inactivate(ModelSQL, DeactivableMixin): @classmethod def search_domain(cls, domain, active_test=None, tables=None): context = Transaction().context if context.get('inactive_test'): domain = [domain, ('active', '=', False)] return super().search_domain( domain, active_test=active_test, tables=tables) @classmethod def on_modification(cls, mode, records, field_names=None): super().on_modification(mode, records, field_names=field_names) if mode == 'delete': cls.copy([r for r in records if r.active and r.shop.to_sync], default={ 'active': False, }) class Shop(DeactivableMixin, ModelSQL, ModelView): __name__ = 'web.shop' name = fields.Char("Name", required=True) company = fields.Many2One('company.company', "Company", required=True) currency = fields.Many2One('currency.currency', "Currency", required=True) language = fields.Many2One( 'ir.lang', "Language", domain=[ ('translatable', '=', True), ]) type = fields.Selection([ (None, ""), ], "Type", help="The front-end used for the web shop.") warehouses = fields.Many2Many( 'web.shop-stock.location', 'shop', 'warehouse', "Warehouses", domain=[ ('type', '=', 'warehouse'), ]) guest_party = fields.Many2One( 'party.party', "Guest Party", context={ 'company': Eval('company', -1), }, depends={'company'}) countries = fields.Many2Many( 'web.shop-country.country', 'shop', 'country', "Countries") products = fields.Many2Many( 'web.shop-product.product', 'shop', 'product', "Products", domain=[ ('salable', '=', True), ], context={ 'company': Eval('company', -1), }, depends={'company'}, help="The list of products to publish.") products_removed = Many2ManyInactive( 'web.shop-product.product', 'shop', 'product', "Products Removed", context={ 'company': Eval('company', -1), }, depends={'company'}, help="The list of products to unpublish.") categories = fields.Many2Many( 'web.shop-product.category', 'shop', 'category', "Categories", context={ 'company': Eval('company', -1), }, depends={'company'}, help="The list of categories to publish.") categories_removed = Many2ManyInactive( 'web.shop-product.category', 'shop', 'category', "Categories Removed", context={ 'company': Eval('company', -1), }, depends={'company'}, help="The list of categories to unpublish.") _name_cache = Cache('web.shop.name', context=False) @property def warehouse(self): if self.warehouses: return self.warehouses[0] @classmethod def __setup__(cls): super().__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_unique', Exclude(t, (t.name, Equal), where=t.active == Literal(True)), 'web_shop.msg_shop_name_unique'), ] @classmethod def default_company(cls): return Transaction().context.get('company') @classmethod def default_currency(cls): pool = Pool() Company = pool.get('company.company') company_id = cls.default_company() if company_id is not None and company_id >= 0: company = Company(company_id) return company.currency.id @classmethod def get(cls, name): shop_id = cls._name_cache.get(name) if not shop_id: shop, = cls.search([('name', '=', name)]) cls._name_cache.set(name, shop.id) else: shop = cls(shop_id) return shop @classmethod def copy(cls, shops, default=None): default = default.copy() if default is not None else {} default.setdefault('warehouses') default.setdefault('products') default.setdefault('products_removed') default.setdefault('categories') default.setdefault('categories_removed') return super().copy(shops, default=default) @classmethod def on_modification(cls, mode, shops, field_names=None): super().on_modification(mode, shops, field_names=field_names) if mode == 'delete' or (mode == 'write' and 'name' in field_names): cls._name_cache.clear() @property def to_sync(self): return False def _customer_taxe_rule(self): pool = Pool() Configuration = pool.get('account.configuration') config = Configuration(1) return config.get_multivalue( 'default_customer_tax_rule', company=self.company.id) def get_context(self): pool = Pool() Date = pool.get('ir.date') with Transaction().set_context(company=self.company.id): today = Date.today() return { 'language': self.language.code if self.language else None, 'company': self.company.id, 'currency': self.currency.id, 'locations': [w.id for w in self.warehouses], 'stock_date_end': today, 'stock_assign': True, } def get_products(self, pattern=None, key=None): """Return a list of products with their corresponding dictionaries of prices and taxes according to the tax pattern. If a key function is supplied, the products will be sorted in ascending order based on the key applied to each product.""" pool = Pool() Date = pool.get('ir.date') Product = pool.get('product.product') Tax = pool.get('account.tax') if pattern is None: pattern = {} with Transaction().set_context(**self.get_context()): if key is not None: all_products = sorted(self.products, key=key) else: all_products = self.products all_products = Product.browse(all_products) today = Date.today() customer_tax_rule = self._customer_taxe_rule() taxes2products = defaultdict(list) for product in all_products: taxes = set() for tax in product.customer_taxes_used: if customer_tax_rule: tax_ids = customer_tax_rule.apply(tax, pattern) if tax_ids: taxes.update(tax_ids) continue taxes.add(tax.id) if customer_tax_rule: tax_ids = customer_tax_rule.apply(None, pattern) if tax_ids: taxes.update(tax_ids) taxes2products[tuple(sorted(taxes))].append(product) prices, taxes = {}, {} for tax_ids, products in taxes2products.items(): with Transaction().set_context(**self.get_context()): products = Product.browse(products) taxes_ = Tax.browse(tax_ids) with Transaction().set_context(taxes=tax_ids): prices.update(Product.get_sale_price(products)) for product in products: price = prices[product.id] if price is not None: taxes[product.id] = sum( t['amount'] for t in Tax.compute( taxes_, price, 1, today)) else: taxes[product.id] = None return all_products, prices, taxes def get_categories(self): "Return the list of categories" pool = Pool() Category = pool.get('product.category') with Transaction().set_context(**self.get_context()): return Category.browse(self.categories) def get_sale(self, party=None): pool = Pool() Sale = pool.get('sale.sale') if not party: party = self.guest_party sale = Sale(party=party) sale.company = self.company sale.warehouse = self.warehouse sale.web_shop = self sale.on_change_party() sale.on_change_web_shop() sale.currency = self.currency sale.invoice_method = 'order' sale.shipment_method = 'order' return sale def update_sale_ids(self, sale_ids): pool = Pool() Sale = pool.get('sale.sale') return self.update_sales(Sale.browse(sale_ids)) def update_sales(self, sales): assert all(s.web_shop == self for s in sales) class Shop_PriceList(metaclass=PoolMeta): __name__ = 'web.shop' sale_price_list = fields.Many2One( 'product.price_list', "Sale Price List", domain=[ ('company', '=', Eval('company', -1)), ], help="The price list to compute sale price of products.") non_sale_price_list = fields.Many2One( 'product.price_list', "Non-Sale Price List", domain=[ ('company', '=', Eval('company', -1)), ], help="The price list to compute the price of products " "when it is not on sale.") def get_context(self): context = super().get_context() if self.sale_price_list: context['price_list'] = self.sale_price_list.id if (self.non_sale_price_list and Transaction().context.get('_non_sale_price', False)): context['price_list'] = self.non_sale_price_list.id return context def get_sale(self, party=None): sale = super().get_sale(party=party) if self.sale_price_list: sale.price_list = self.sale_price_list return sale class Shop_ShipmentCost(metaclass=PoolMeta): __name__ = 'web.shop' def get_sale(self, party=None): sale = super().get_sale(party=party) sale.shipment_cost_method = 'order' return sale class Shop_TaxRuleCountry(metaclass=PoolMeta): __name__ = 'web.shop' def get_products(self, pattern=None, key=None): pattern = pattern.copy() if pattern is not None else {} if (self.warehouse and self.warehouse.address and self.warehouse.address.country): pattern.setdefault( 'from_country', self.warehouse.address.country.id) else: pattern.setdefault('from_country') pattern.setdefault('to_country') return super().get_products(pattern=pattern, key=key) class Shop_Warehouse(ModelSQL): __name__ = 'web.shop-stock.location' shop = fields.Many2One( 'web.shop', "Shop", ondelete='CASCADE', required=True) warehouse = fields.Many2One( 'stock.location', "Warehouse", ondelete='CASCADE', required=True, domain=[ ('type', '=', 'warehouse'), ]) class Shop_Country(ModelSQL): __name__ = 'web.shop-country.country' shop = fields.Many2One( 'web.shop', "Shop", ondelete='CASCADE', required=True) country = fields.Many2One( 'country.country', "Country", ondelete='CASCADE', required=True) class Shop_Product(Inactivate): __name__ = 'web.shop-product.product' shop = fields.Many2One( 'web.shop', "Shop", ondelete='CASCADE', required=True) product = fields.Many2One( 'product.product', "Product", ondelete='RESTRICT', required=True) class Shop_ProductCategory(Inactivate): __name__ = 'web.shop-product.category' shop = fields.Many2One( 'web.shop', "Shop", ondelete='CASCADE', required=True) category = fields.Many2One( 'product.category', "Category", ondelete='RESTRICT', required=True) class ShopAttribute(metaclass=PoolMeta): __name__ = 'web.shop' attributes = fields.Many2Many( 'web.shop-product.attribute', 'shop', 'attribute', "Attributes", help="The list of attributes to publish.") attributes_removed = Many2ManyInactive( 'web.shop-product.attribute', 'shop', 'attribute', "Attributes Removed", help="The list of attributes to unpublish.") def get_attributes(self): "Return the list of attributes" pool = Pool() Attribute = pool.get('product.attribute') with Transaction().set_context(**self.get_context()): return Attribute.browse(self.attributes) class Shop_Attribute(Inactivate): __name__ = 'web.shop-product.attribute' shop = fields.Many2One( 'web.shop', "Shop", ondelete='CASCADE', required=True) attribute = fields.Many2One( 'product.attribute', "Attribute", ondelete='RESTRICT', required=True) class User(metaclass=PoolMeta): __name__ = 'web.user' invoice_address = fields.Many2One( 'party.address', "Invoice Address", domain=['OR', ('party', '=', Eval('party', -1)), ('party', 'in', Eval('secondary_parties', [])), ]) shipment_address = fields.Many2One( 'party.address', "Shipment Address", domain=['OR', ('party', '=', Eval('party', -1)), ('party', 'in', Eval('secondary_parties', [])), ])