import logging from odoo import api, fields, models, tools, exceptions import datetime from dateutil.relativedelta import relativedelta from werkzeug import urls import functools import base64 _logger = logging.getLogger(__name__) try: from jinja2.sandbox import SandboxedEnvironment mako_template_env = SandboxedEnvironment( block_start_string="<%", block_end_string="%>", variable_start_string="${", variable_end_string="}", comment_start_string="<%doc>", comment_end_string="", line_statement_prefix="%", line_comment_prefix="##", trim_blocks=True, # do not output newline after blocks autoescape=True, # XML/HTML automatic escaping ) mako_template_env.globals.update({ 'str': str, 'quote': urls.url_quote, 'urlencode': urls.url_encode, 'datetime': datetime, 'len': len, 'abs': abs, 'min': min, 'max': max, 'sum': sum, 'filter': filter, 'reduce': functools.reduce, 'map': map, 'round': round, 'relativedelta': lambda *a, **kw: relativedelta(*a, **kw), }) except ImportError: _logger.warning("jinja2 not available, templating features will not work!") class DmsDocumentChoiseTemplate(models.TransientModel): _name = 'dms.choise_template' _description = 'mklab_dms_document_choise_template' temp_id = fields.Many2one(comodel_name='dms.template', string='Шаблон', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]") doc_id = fields.Many2one(comodel_name='dms.document', string='Документ') company_id = fields.Many2one(comodel_name='res.company', string='Компания', required=True, index=True, default=lambda self: self.env.company) def get_choise(self): for s in self: if s.temp_id: s.doc_id.text = s.temp_id.text class DmsDocumentTemplate(models.Model): _name = 'dms.template' _description = 'mklab_dms_document_template' name = fields.Char(string='Наименование', required=True, tracking=True) text = fields.Html(string='Текст', required=True, tracking=True) company_id = fields.Many2one(comodel_name='res.company', string='Компания', required=True, index=True, default=lambda self: self.env.company, tracking=True) class DmsDocument(models.Model): _name = 'dms.document' _description = 'Документ DMS' _inherit = ['portal.mixin', 'mail.thread', 'mail.activity.mixin'] def create_pdf(self): report_action = self.env.ref('mklab_dms_document.action_report_dms_document').sudo() for s in self: if s.type_document=='outgoing': pdf, _ = report_action._render_qweb_pdf(report_action.report_name, [s.id]) s.incoming_file = base64.b64encode(pdf) s.incoming_file_type = 'pdf' if not s.incoming_file and s.incoming_file_type == 'pdf': raise exceptions.UserError('Не указан файл для сохранения') if not s.incoming_file_other and s.incoming_file_type != 'pdf': raise exceptions.UserError('Не указан файл для сохранения') if not s.directory_id: raise exceptions.UserError('Не указана директория для сохранения файла') content = s.incoming_file if s.incoming_file_type == 'pdf' else s.incoming_file_other save_file = self.env['dms.file'].sudo().create({ 'name': s.name, 'directory_id': s.directory_id.id, 'content': content, }) s.file = save_file s.state = 'done' def open_choise_template(self): company_id = False if self.company_id: company_id = self.company_id.id return { 'type': 'ir.actions.act_window', 'name': "Выберите шаблон", 'res_model': 'dms.choise_template', 'target': 'new', 'view_mode': 'form', 'context': {'default_doc_id': self.id, 'company_id': company_id}, } @api.ondelete(at_uninstall=False) def _unlink_except_done(self): # Штатный механизм запрета удаления (вместо raise прямо в unlink): # корректно уживается с каскадами ondelete и групповыми операциями. for s in self: if s.state == 'done': raise exceptions.ValidationError('Нельзя удалить документ, записанный в хранилище') # Соответствие типа документа коду последовательности нумерации. _SEQUENCE_BY_TYPE = { 'incoming': 'dms.document_number', 'outgoing': 'dms.document_number_out', 'internal': 'dms.document_number_internal', } def _next_document_number(self, type_document): """Следующий номер по типу документа из соответствующей последовательности.""" code = self._SEQUENCE_BY_TYPE.get(type_document) if not code: return False return self.env['ir.sequence'].next_by_code(code) type_document = fields.Selection( [('incoming', 'Входящий документ'), ('outgoing', 'Исходящий документ'), ('internal', 'Внутренний документ')], string='Тип', required=True, tracking=True) name = fields.Char(string='Номер', copy=False, tracking=True) partner_id = fields.Many2one( comodel_name='res.partner', string='Контакт', required=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", tracking=True) date = fields.Date( string='Дата документа', default=fields.Date.context_today, required=True, tracking=True) text = fields.Html(string='Текст') incoming_file = fields.Binary(string='Входящий файл (PDF)') incoming_file_other = fields.Binary(string='Входящий файл') incoming_file_type = fields.Selection( [('pdf', 'PDF'), ('other', 'Другой')], string='Тип входящего файла', default='pdf', tracking=True) text_str = fields.Text(string='Текст шапки') text_render = fields.Html(string='Обработанный текст') file = fields.Many2one( comodel_name='dms.file', string='Связанный файл', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", tracking=True) link_model = fields.Many2one( comodel_name='ir.model', string='Связанная модель', tracking=True) res_id = fields.Integer( string='ID документа в связанной модели', tracking=True) print_head = fields.Boolean( string='Печатать шапку', default=False, tracking=True) state = fields.Selection( [('draft', 'Черновик'), ('done', 'Записано в хранилище')], string='Статус', default='draft', tracking=True) parent_id = fields.Many2one( comodel_name='dms.document', string='Родительский документ', domain="[('id', '!=', id), '|', ('partner_id', '=', partner_id)," " ('partner_id.parent_id', '=', partner_id)," " '|', ('company_id', '=', False), ('company_id', '=', company_id)]", tracking=True) child_ids = fields.One2many( comodel_name='dms.document', inverse_name='parent_id', string='Дочерние документы') subordinate_ids = fields.One2many( comodel_name='dms.document', string='Все связанные документы', compute='_compute_subordinates', compute_sudo=True) company_id = fields.Many2one(comodel_name='res.company', string='Компания', required=True, index=True, default=lambda self: self.env.company, tracking=True) directory_id = fields.Many2one( comodel_name="dms.directory", string="Директория хранения", domain="[('permission_create', '=', True), '|', ('company_id', '=', False), ('company_id', '=', company_id)]", context={'dms_directory_show_path': True}, ondelete="restrict", auto_join=True, index=True, tracking=True #domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]" ) child_all_count = fields.Integer( string='Количество связанных документов', compute='_compute_subordinates', store=False, compute_sudo=True) @api.model_create_multi def create(self, vals_list): for vals in vals_list: if not vals.get('name'): type_document = vals.get('type_document') \ or self.env.context.get('default_type_document') number = self._next_document_number(type_document) if number: vals['name'] = number return super().create(vals_list) def _get_subordinates(self, parents=None): if not parents: parents = self.env[self._name] indirect_subordinates = self.env[self._name] parents |= self direct_subordinates = self.child_ids - parents for child in direct_subordinates: child_subordinate = child._get_subordinates(parents=parents) indirect_subordinates |= child_subordinate return indirect_subordinates | direct_subordinates @api.depends('child_ids', 'child_ids.child_all_count') def _compute_subordinates(self): for doc in self: doc.subordinate_ids = doc._get_subordinates() doc.child_all_count = len(doc.subordinate_ids) def render_template(self): for s in self: template = s.text template_new = s.text model = s.link_model.model res_id = s.res_id render_result = 'Не удалось обработать текст' substrstart = "${" substrend = "}" substr_new_start = "<" substr_new_end = ">" flag = True start = 0 s.text_str = '' end = len(template)-1 while flag: try: #search for the item index = template.index(substrstart,start,end) start = index+len(substrstart) indexend = template.index(substrend,start,end) substr = template[index:indexend+1] substr_new_str = substr #s.text_str += str(substr)+ '; ' if substr.find(substr_new_start)>0 and substr.find(substr_new_end)>0: new_flag = True start_new = 0 end_new = len(substr)-1 while new_flag: try: #search for the item index_new = substr.index(substr_new_start,start_new,end_new) start_new = index_new+len(substr_new_start) indexend_new = substr.index(substr_new_end,start_new,end_new) substr_new = substr[index_new:indexend_new+1] #s.text_str += str(index_new) + '-' + str(indexend_new) +':'+str(substr_new)+ '; ' substr_new_str = substr_new_str.replace(substr_new,'') except ValueError: new_flag=False template_new = template_new.replace(substr,substr_new_str) except ValueError: flag = False parent = '' if s.parent_id: type_doc_parent = dict(self._fields['type_document'].selection).get(s.parent_id.type_document) parent = ' В ответ на {} {} от {}.'.format(type_doc_parent, s.parent_id.name,s.parent_id.date.strftime('%d.%m.%Y')) type_doc = dict(self._fields['type_document'].selection).get(s.type_document) s.text_str = '{} {} от {}.{}'.format(type_doc, s.name,s.date.strftime('%d.%m.%Y'), parent) template = template_new #res = re.findall(r"(${})", template) try: template = mako_template_env.from_string(tools.ustr(template)) except: template = mako_template_env.from_string(tools.ustr('')) user = self.env.user variables = { 'user': user } if model and res_id: record = self.env[model].browse(res_id) variables['object'] = record try: render_result = template.render(variables) except Exception: _logger.error("Failed to render template %r using values %r" % (template, variables)) render_result = "" if render_result == "False": render_result = "" s.text_render = render_result return render_result