418 lines
14 KiB
Python
418 lines
14 KiB
Python
# 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', [])),
|
|
])
|