diff --git a/CHANGELOG b/CHANGELOG index 05526f6577..b3a9e82d67 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +* Add company and employee administrator group +* Add top level menu entry + Version 5.4.0 - 2019-11-04 * Bug fixes (see mercurial logs for details) diff --git a/__init__.py b/__init__.py index 5a960224f6..ed915a6a5f 100644 --- a/__init__.py +++ b/__init__.py @@ -2,35 +2,39 @@ # this repository contains the full copyright notices and license terms. from trytond.pool import Pool -from .company import * -from .cron import * +from . import company +from . import ir from . import party +from . import res +from .company import CompanyReport + +__all__ = ['register', 'CompanyReport'] def register(): Pool.register( - Company, - Employee, - UserEmployee, - User, - Sequence, - SequenceStrict, - Date, - CompanyConfigStart, - Cron, - CronCompany, + company.Company, + company.Employee, + company.CompanyConfigStart, + res.UserEmployee, + res.User, + ir.Sequence, + ir.SequenceStrict, + ir.Date, + ir.Rule, + ir.Cron, + ir.CronCompany, party.Configuration, party.PartyConfigurationLang, party.Party, party.PartyLang, party.ContactMechanism, - Rule, module='company', type_='model') Pool.register( - CompanyConfig, + company.CompanyConfig, party.PartyReplace, party.PartyErase, module='company', type_='wizard') Pool.register( - LetterReport, + party.LetterReport, module='company', type_='report') diff --git a/company.xml b/company.xml index 8224737030..0e7c058c8a 100644 --- a/company.xml +++ b/company.xml @@ -3,11 +3,37 @@ this repository contains the full copyright notices and license terms. --> + + Company Administration + + + + + + + + Employee Administration + + + + + + tryton-company icons/tryton-company.svg + + + + + + + + + + company.company form @@ -44,7 +70,7 @@ this repository contains the full copyright notices and license terms. --> - + Companies @@ -62,8 +88,7 @@ this repository contains the full copyright notices and license terms. --> - + @@ -74,7 +99,7 @@ this repository contains the full copyright notices and license terms. --> - + @@ -92,49 +117,6 @@ this repository contains the full copyright notices and license terms. --> user_form_preferences - - ir.sequence - - sequence_form - - - ir.sequence - - sequence_tree - - - - User in company - - - - - - - - - - - - - - User in company - - - - - - - - - - - - company.company.config.start form @@ -180,8 +162,7 @@ this repository contains the full copyright notices and license terms. --> - + @@ -192,7 +173,7 @@ this repository contains the full copyright notices and license terms. --> - + diff --git a/cron.py b/cron.py deleted file mode 100644 index 02dde296d9..0000000000 --- a/cron.py +++ /dev/null @@ -1,40 +0,0 @@ -# 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 trytond.model import ModelSQL, ModelView, fields, dualmethod -from trytond.pool import Pool, PoolMeta -from trytond.transaction import Transaction - - -__all__ = ['Cron', 'CronCompany'] - - -class Cron(metaclass=PoolMeta): - __name__ = "ir.cron" - companies = fields.Many2Many('ir.cron-company.company', 'cron', 'company', - 'Companies', help='Companies registered for this cron') - - @dualmethod - @ModelView.button - def run_once(cls, crons): - for cron in crons: - if not cron.companies: - super(Cron, cls).run_once([cron]) - else: - for company in cron.companies: - with Transaction().set_context(company=company.id): - super(Cron, cls).run_once([cron]) - - @staticmethod - def default_companies(): - Company = Pool().get('company.company') - return list(map(int, Company.search([]))) - - -class CronCompany(ModelSQL): - 'Cron - Company' - __name__ = 'ir.cron-company.company' - _table = 'cron_company_rel' - cron = fields.Many2One('ir.cron', 'Cron', ondelete='CASCADE', - required=True, select=True) - company = fields.Many2One('company.company', 'Company', ondelete='CASCADE', - required=True, select=True) diff --git a/cron.xml b/cron.xml deleted file mode 100644 index 292c7e30e9..0000000000 --- a/cron.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - ir.cron - - cron_form - - - diff --git a/ir.py b/ir.py new file mode 100644 index 0000000000..7a3b65d066 --- /dev/null +++ b/ir.py @@ -0,0 +1,89 @@ +# 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 pytz + +from trytond.model import ModelSQL, ModelView, fields, dualmethod +from trytond.pool import PoolMeta, Pool +from trytond.pyson import Eval, If +from trytond.transaction import Transaction + + +class Sequence(metaclass=PoolMeta): + __name__ = 'ir.sequence' + company = fields.Many2One('company.company', 'Company', + domain=[ + ('id', If(Eval('context', {}).contains('company'), '=', '!='), + Eval('context', {}).get('company', -1)), + ], help="Restrict the sequence usage to the company.") + + @classmethod + def __setup__(cls): + super(Sequence, cls).__setup__() + cls._order.insert(0, ('company', 'ASC')) + + @staticmethod + def default_company(): + return Transaction().context.get('company') + + +class SequenceStrict(Sequence): + __name__ = 'ir.sequence.strict' + + +class Date(metaclass=PoolMeta): + __name__ = 'ir.date' + + @classmethod + def today(cls, timezone=None): + pool = Pool() + Company = pool.get('company.company') + company_id = Transaction().context.get('company') + if timezone is None and company_id: + company = Company(company_id) + if company.timezone: + timezone = pytz.timezone(company.timezone) + return super(Date, cls).today(timezone=timezone) + + +class Rule(metaclass=PoolMeta): + __name__ = 'ir.rule' + + @classmethod + def _get_cache_key(cls): + key = super(Rule, cls)._get_cache_key() + # XXX Use company from context instead of browse to prevent infinite + # loop, but the cache is cleared when User is written. + return key + (Transaction().context.get('company'), + Transaction().context.get('employee')) + + +class Cron(metaclass=PoolMeta): + __name__ = "ir.cron" + companies = fields.Many2Many('ir.cron-company.company', 'cron', 'company', + 'Companies', help='Companies registered for this cron') + + @dualmethod + @ModelView.button + def run_once(cls, crons): + for cron in crons: + if not cron.companies: + super(Cron, cls).run_once([cron]) + else: + for company in cron.companies: + with Transaction().set_context(company=company.id): + super(Cron, cls).run_once([cron]) + + @staticmethod + def default_companies(): + Company = Pool().get('company.company') + return list(map(int, Company.search([]))) + + +class CronCompany(ModelSQL): + 'Cron - Company' + __name__ = 'ir.cron-company.company' + _table = 'cron_company_rel' + cron = fields.Many2One('ir.cron', 'Cron', ondelete='CASCADE', + required=True, select=True) + company = fields.Many2One('company.company', 'Company', ondelete='CASCADE', + required=True, select=True) diff --git a/ir.xml b/ir.xml new file mode 100644 index 0000000000..de5ef43c60 --- /dev/null +++ b/ir.xml @@ -0,0 +1,56 @@ + + + + + + ir.sequence + + sequence_form + + + + ir.sequence + + sequence_tree + + + + User in company + + + + + + + + + + + + + + User in company + + + + + + + + + + + + + + ir.cron + + cron_form + + + diff --git a/party.py b/party.py index 06694837a7..8fb8d53d2e 100644 --- a/party.py +++ b/party.py @@ -5,6 +5,7 @@ from trytond.pool import PoolMeta, Pool from trytond.tools.multivalue import migrate_property from trytond.transaction import Transaction +from .company import CompanyReport from .model import CompanyMultiValueMixin, CompanyValueMixin __all__ = ['Configuration', 'PartyConfigurationLang', 'Party', 'PartyLang', @@ -110,3 +111,12 @@ class ContactMechanism(CompanyMultiValueMixin, metaclass=PoolMeta): for address in company.party.addresses: if address.country: yield address.country.code + + +class LetterReport(CompanyReport): + __name__ = 'party.letter' + + @classmethod + def execute(cls, ids, data): + with Transaction().set_context(address_with_party=True): + return super(LetterReport, cls).execute(ids, data) diff --git a/res.py b/res.py new file mode 100644 index 0000000000..ce844d8dec --- /dev/null +++ b/res.py @@ -0,0 +1,201 @@ +# 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 copy + +from trytond.model import ModelSQL, fields +from trytond.pool import PoolMeta, Pool +from trytond.pyson import Eval +from trytond.transaction import Transaction + + +class UserEmployee(ModelSQL): + 'User - Employee' + __name__ = 'res.user-company.employee' + user = fields.Many2One('res.user', 'User', ondelete='CASCADE', select=True, + required=True) + employee = fields.Many2One('company.employee', 'Employee', + ondelete='CASCADE', select=True, required=True) + + +class User(metaclass=PoolMeta): + __name__ = 'res.user' + main_company = fields.Many2One('company.company', 'Main Company', + help="Grant access to the company and its children.") + company = fields.Many2One('company.company', 'Current Company', + domain=[('parent', 'child_of', [Eval('main_company')], 'parent')], + depends=['main_company'], + help="Select the company to work for.") + companies = fields.Function(fields.One2Many('company.company', None, + 'Companies'), 'get_companies') + employees = fields.Many2Many('res.user-company.employee', 'user', + 'employee', 'Employees', + help="Add employees to grant the user access to them.") + employee = fields.Many2One('company.employee', 'Current Employee', + domain=[ + ('company', '=', Eval('company', -1)), + ('id', 'in', Eval('employees', [])), + ], + depends=['company', 'employees'], + help="Select the employee to make the user behave as such.") + + @classmethod + def __setup__(cls): + super(User, cls).__setup__() + cls._context_fields.insert(0, 'company') + cls._context_fields.insert(0, 'employee') + + @staticmethod + def default_main_company(): + return Transaction().context.get('company') + + @classmethod + def default_company(cls): + return cls.default_main_company() + + @classmethod + def get_companies(cls, users, name): + Company = Pool().get('company.company') + companies = {} + company_childs = {} + for user in users: + companies[user.id] = [] + company = None + if user.company: + company = user.company + elif user.main_company: + company = user.main_company + if company: + if company in company_childs: + company_ids = company_childs[company] + else: + company_ids = list(map(int, Company.search([ + ('parent', 'child_of', [company.id]), + ]))) + company_childs[company] = company_ids + if company_ids: + companies[user.id].extend(company_ids) + return companies + + def get_status_bar(self, name): + status = super(User, self).get_status_bar(name) + if self.company: + status += ' - %s [%s]' % (self.company.rec_name, + self.company.currency.name) + return status + + @fields.depends('main_company') + def on_change_main_company(self): + self.company = self.main_company + self.employee = None + + @fields.depends('company', 'employees') + def on_change_company(self): + Employee = Pool().get('company.employee') + self.employee = None + if self.company and self.employees: + employees = Employee.search([ + ('id', 'in', [e.id for e in self.employees]), + ('company', '=', self.company.id), + ]) + if employees: + self.employee = employees[0] + + @classmethod + def _get_preferences(cls, user, context_only=False): + res = super(User, cls)._get_preferences(user, + context_only=context_only) + if not context_only: + res['main_company'] = None + if user.main_company: + res['main_company'] = user.main_company.id + res['main_company.rec_name'] = user.main_company.rec_name + res['employees'] = [e.id for e in user.employees] + if user.employee: + res['employee'] = user.employee.id + res['employee.rec_name'] = user.employee.rec_name + if user.company: + res['company'] = user.company.id + res['company.rec_name'] = user.company.rec_name + return res + + @classmethod + def get_preferences_fields_view(cls): + pool = Pool() + Company = pool.get('company.company') + + res = super(User, cls).get_preferences_fields_view() + res = copy.deepcopy(res) + + def convert2selection(definition, name): + del definition[name]['relation'] + definition[name]['type'] = 'selection' + selection = [] + definition[name]['selection'] = selection + return selection + + if 'company' in res['fields']: + selection = convert2selection(res['fields'], 'company') + selection.append((None, '')) + user = cls(Transaction().user) + if user.main_company: + companies = Company.search([ + ('parent', 'child_of', [user.main_company.id], + 'parent'), + ]) + for company in companies: + selection.append((company.id, company.rec_name)) + return res + + @classmethod + def read(cls, ids, fields_names=None): + Company = Pool().get('company.company') + user_id = Transaction().user + if user_id == 0 and 'user' in Transaction().context: + user_id = Transaction().context['user'] + result = super(User, cls).read(ids, fields_names=fields_names) + if (fields_names + and (('company' in fields_names + and 'company' in Transaction().context) + or ('employee' in fields_names + and 'employee' in Transaction().context))): + values = None + if int(user_id) in ids: + for vals in result: + if vals['id'] == int(user_id): + values = vals + break + if values: + if ('company' in fields_names + and 'company' in Transaction().context): + main_company_id = values.get('main_company') + if not main_company_id: + main_company_id = cls.read([user_id], + ['main_company'])[0]['main_company'] + companies = Company.search([ + ('parent', 'child_of', [main_company_id]), + ]) + company_id = Transaction().context['company'] + if ((company_id and company_id in map(int, companies)) + or not company_id + or Transaction().user == 0): + values['company'] = company_id + if ('employee' in fields_names + and 'employee' in Transaction().context): + employees = values.get('employees') + if not employees: + employees = cls.read([user_id], + ['employees'])[0]['employees'] + employee_id = Transaction().context['employee'] + if ((employee_id and employee_id in employees) + or not employee_id + or Transaction().user == 0): + values['employee'] = employee_id + return result + + @classmethod + def write(cls, *args): + pool = Pool() + Rule = pool.get('ir.rule') + super(User, cls).write(*args) + # Restart the cache on the domain_get method + Rule._domain_get_cache.clear() diff --git a/tryton.cfg b/tryton.cfg index 7b6b04f7d7..f50b88fadb 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -7,4 +7,4 @@ depends: res xml: company.xml - cron.xml + ir.xml