From 1f2911c1f503d344db86c7cfbedcff07e46c2d6c Mon Sep 17 00:00:00 2001 From: Alexander Shitov Date: Thu, 8 Nov 2018 17:16:01 +0500 Subject: [PATCH] Initial commit --- .gitignore | 2 + __init__.py | 4 + __manifest__.py | 35 ++ controllers/__init__.py | 3 + controllers/controllers.py | 16 + demo/demo.xml | 30 ++ models/__init__.py | 3 + models/contract_wizard.py | 372 +++++++++++++++++ models/models.py | 115 ++++++ security/ir.model.access.csv | 2 + views/contract_wizard.xml | 83 ++++ views/res_partner.xml | 147 +++++++ views/templates.xml | 773 +++++++++++++++++++++++++++++++++++ 13 files changed, 1585 insertions(+) create mode 100644 .gitignore create mode 100644 __init__.py create mode 100644 __manifest__.py create mode 100644 controllers/__init__.py create mode 100644 controllers/controllers.py create mode 100644 demo/demo.xml create mode 100644 models/__init__.py create mode 100644 models/contract_wizard.py create mode 100644 models/models.py create mode 100644 security/ir.model.access.csv create mode 100644 views/contract_wizard.xml create mode 100644 views/res_partner.xml create mode 100644 views/templates.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b6acd2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +idea/ diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..aa4d0fd --- /dev/null +++ b/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import controllers +from . import models diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..573b7c5 --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +{ + 'name': "client_contracts", + + 'summary': """ + Module for storing and creating print forms for contracts with clients""", + + 'description': """ + Module for storing and creating print forms for contracts with clients + """, + + 'author': "RYDLAB", + 'website': "http://rydlab.ru", + + # Categories can be used to filter modules in modules listing + # Check https://github.com/odoo/odoo/blob/master/odoo/addons/base/module/module_data.xml + # for the full list + 'category': 'hr', + 'version': '0.1', + + # any module necessary for this one to work correctly + 'depends': ['base', 'sale'], + + # always loaded + 'data': [ + # 'security/ir.model.access.csv', + 'views/templates.xml', + 'views/res_partner.xml', + 'views/contract_wizard.xml', + ], + # only loaded in demonstration mode + 'demo': [ + 'demo/demo.xml', + ], +} diff --git a/controllers/__init__.py b/controllers/__init__.py new file mode 100644 index 0000000..b0f26a9 --- /dev/null +++ b/controllers/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import controllers diff --git a/controllers/controllers.py b/controllers/controllers.py new file mode 100644 index 0000000..eb3ea3a --- /dev/null +++ b/controllers/controllers.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from werkzeug import datastructures + +from odoo import http + + +class ResPartnerContractBinary(http.Controller): + + @http.route('/web/binary/get_compiled_contract') + def download_compiled_contract(self, doc_id, doc_name): + contract_wizard = http.request.env['res.partner.contract.wizard'].sudo().browse(int(doc_id)) + file_content = contract_wizard.get_docx_contract_1() + headers = datastructures.Headers() + headers.add('Content-Disposition', 'attachment', filename=doc_name) + return http.request.make_response(file_content, headers) diff --git a/demo/demo.xml b/demo/demo.xml new file mode 100644 index 0000000..41cfe8a --- /dev/null +++ b/demo/demo.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..57f6dd7 --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models, contract_wizard diff --git a/models/contract_wizard.py b/models/contract_wizard.py new file mode 100644 index 0000000..c19cf0c --- /dev/null +++ b/models/contract_wizard.py @@ -0,0 +1,372 @@ +# -*- coding: utf-8 -*- + +import math + +from datetime import datetime +from docxtpl import DocxTemplate +from pytils import numeral + +from odoo import models, fields, api +from odoo.tools.config import config + + +class AnnexLine(models.TransientModel): + _name = 'res.partner.contract.annex.line' + + @api.onchange('annex_type') + def _get_default_description(self): + self.description = self.annex_type.description + + annex_type = fields.Many2one('res.partner.contract.annex.type') + description = fields.Text() + + +class ContractWizard(models.TransientModel): + _name = 'res.partner.contract.wizard' + + def _get_default_template(self): + _template = self.env['res.partner.contract.template'].search([('is_contract_template', '=', True)]) + if _template: + return _template[0].id + else: + return False + + def _get_default_partner(self): + current_id = self.env.context.get('active_ids') + return self.env['res.partner.contract'].browse([current_id[0]]).partner_id.id + + def _get_default_contract(self): + current_id = self.env.context.get('active_ids') + return self.env['res.partner.contract'].browse(current_id[0]) + + def _get_partner_representer(self): + return self.partner_id.representative_id + + type = fields.Selection(string='Type of contract', + selection=[('person', 'With person'), ('company', 'With company')], + default='company') + + template = fields.Many2one('res.partner.contract.template', + string='Template', + help='Template for contract', + default=_get_default_template) + partner_id = fields.Many2one('res.partner', + string='Partner', + help='Partner to render contract', + default=_get_default_partner) + order_id = fields.Many2one('sale.order', + string='Appex order', + help='Appex', + ) + company_id = fields.Many2one('res.partner', + string='Company', + help='Seller company', + default=lambda self: self.env.user.company_id.partner_id) + + contract_id = fields.Many2one('res.partner.contract', + string='Contract', + default=_get_default_contract) + payment_terms = fields.Integer(string='Payment term', + help='When customer must pay', + default=45) + delivery_terms = fields.Integer(string='Delivery terms', + help='When product must be delivered', + default=10) + + annex_lines = fields.One2many('res.partner.contract.annex.line', 'id', auto_join=True, copy=True) + + @api.onchange('contract_id') + def _compute_context_name(self): + self._context_name = self.contract_id.name + + @api.onchange('contract_id') + def _compute_context_date(self): + contract_date = datetime.strptime(self.contract_id.date, '%Y-%m-%d') + self._context_date = contract_date.strftime('%d %b %Y') + + @api.onchange('partner_id') + def _compute_context_partner_contract_name(self): + self._context_partner_contract_name = self.partner_id.contract_name + + @api.onchange('partner_id') + def _compute_context_partner_adress(self): + self._compute_context_partner_adress = self.partner_id.full_adress + + @api.onchange('partner_id') + def _compute_context_partner_representer_contract_name(self): + if self.partner_id.representative_id: + partner_representer_contract_name = self.partner_id.representative_id.contract_name + else: + partner_representer_contract_name = '' + self._context_partner_representer_contract_name = partner_representer_contract_name + + @api.onchange('partner_id') + def _compute_context_partner_inn(self): + self._context_partner_inn = self.partner_id.inn + + @api.onchange('partner_id') + def _compute_context_partner_kpp(self): + self._context_partner_kpp = self.partner_id.kpp + + @api.onchange('partner_id') + def _compute_context_partner_rs(self): + self._context_partner_rs = self.partner_id.bank_account.acc_number + + @api.onchange('partner_id') + def _compute_context_partner_bik(self): + self._context_partner_bik = self.partner_id.bank_account.bank_id.bic + + @api.onchange('partner_id') + def _compute_context_partner_bank(self): + self._context_partner_bank = self.partner_id.bank_account.bank_id.name + + @api.onchange('partner_id') + def _compute_context_partner_phone(self): + self._context_partner_phone = self.partner_id.phone + + @api.onchange('partner_id') + def _compute_context_partner_representer_name(self): + self._context_partner_representer_name = self.partner_id.representative_id.name + + @api.onchange('company_id') + def _compute_context_seller_contract_name(self): + self._context_seller_contract_name = self.company_id.contract_name + + @api.onchange('company_id') + def _compute_context_seller_adress(self): + self._context_seller_adress = self.company_id.full_adress + + @api.onchange('company_id') + def _compute_context_seller_representer_contract_job_name(self): + if self.company_id.representative_id: + seller_represent_contract_job_name = self.company_id.representative_id.contract_job_name + else: + seller_represent_contract_job_name = '' + self._context_seller_representer_contract_job_name = seller_represent_contract_job_name + + @api.onchange('company_id') + def _compute_context_seller_representer_contract_name(self): + if self.company_id.representative_id: + seller_represent_contract_name = self.company_id.representative_id.contract_name + else: + seller_represent_contract_name = '' + self._context_seller_representer_contract_name = seller_represent_contract_name + + @api.onchange('company_id') + def _compute_context_seller_inn(self): + self._context_seller_inn = self.company_id.inn + + @api.onchange('company_id') + def _compute_context_seller_kpp(self): + self._context_seller_kpp = self.company_id.kpp + + @api.onchange('company_id') + def _compute_context_seller_rs(self): + self._context_seller_rs = self.company_id.bank_account.acc_number + + @api.onchange('company_id') + def _compute_context_seller_bik(self): + self._context_seller_bik = self.company_id.bank_account.bank_id.bic + + @api.onchange('company_id') + def _compute_context_seller_bank(self): + self._context_seller_bank = self.company_id.bank_account.bank_id.name + + @api.onchange('company_id') + def _compute_context_seller_phone(self): + self._context_seller_phone = self.company_id.phone + + @api.onchange('company_id') + def _compute_context_seller_representer_job_name(self): + if self.company_id.representative_id: + seller_represent_job_name = self.company_id.representative_id.function + else: + seller_represent_job_name = '' + self._context_seller_representer_job_name = seller_represent_job_name + + @api.onchange('company_id') + def _compute_context_seller_representer_name(self): + if self.company_id.representative_id: + seller_represent_name = self.company_id.representative_id.name + else: + seller_represent_name = '' + self._context_seller_representer_name = seller_represent_name + + @api.onchange('order_id') + def _compute_context_summ_rub(self): + if self.order_id: + amount = math.modf(self.order_id.amount_total) + else: + amount = math.modf(0.0) + self._context_summ_rub = str(int(amount[1])) + + @api.onchange('order_id') + def _compute_context_summ_rub_word(self): + if self.order_id: + amount = math.modf(self.order_id.amount_total) + else: + amount = math.modf(0.0) + self._context_summ_rub_word = numeral.in_words(int(amount[1])) + + @api.onchange('order_id') + def _compute_context_summ_kop(self): + if self.order_id: + amount = math.modf(self.order_id.amount_total) + self._context_summ_kop = str(int(amount[0])) + else: + self._context_summ_kop = '0' + + @api.onchange('order_id') + def _compute_context_summ_word(self): + self._context_summ_word = numeral.rubles(self.order_id.amount_total) + + @api.onchange('delivery_terms') + def _compute_context_delivery_term(self): + self._context_delivery_term = self.delivery_terms + + @api.onchange('delivery_terms') + def _compute_context_delivery_term_word(self): + self._context_delivery_term_word = numeral.in_words(self.delivery_terms) + + @api.onchange('payment_terms') + def _compute_context_payment_term(self): + self._context_payment_term = self.payment_terms + + @api.onchange('payment_terms') + def _compute_context_payment_term_word(self): + self._context_payment_term_word = numeral.in_words(self.payment_terms) + + _context_name = fields.Char(compute='_compute_context_name', readonly=True) + _context_date = fields.Char(compute='_compute_context_date', readonly=True) + _context_partner_contract_name = fields.Char(compute='_compute_context_partner_contract_name', readonly=True) + _context_partner_adress = fields.Char(compute='_compute_context_partner_adress', readonly=True) + _context_partner_representer_contract_name = fields.Char( + compute='_compute_context_partner_representer_contract_name', readonly=True) + _context_partner_inn = fields.Char(compute='_compute_context_partner_inn', readonly=True) + _context_partner_kpp = fields.Char(compute='_compute_context_partner_kpp', readonly=True) + _context_partner_rs = fields.Char(compute='_compute_context_partner_rs', readonly=True) + _context_partner_bik = fields.Char(compute='_compute_context_partner_bik', readonly=True) + _context_partner_bank = fields.Char(compute='_compute_context_partner_bank', readonly=True) + _context_partner_phone = fields.Char(compute='_compute_context_partner_phone', readonly=True) + _context_partner_representer_name = fields.Char(compute='_compute_context_partner_representer_name', readonly=True) + _context_seller_contract_name = fields.Char(compute='_compute_context_seller_contract_name', readonly=True) + _context_seller_adress = fields.Char(compute='_compute_context_seller_adress', readonly=True) + _context_seller_representer_contract_job_name = fields.Char( + compute='_compute_context_seller_representer_contract_job_name', readonly=True) + _context_seller_representer_contract_name = fields.Char(compute='_compute_context_seller_representer_contract_name', + readonly=True) + _context_seller_inn = fields.Char(compute='_compute_context_seller_inn', readonly=True) + _context_seller_kpp = fields.Char(compute='_compute_context_seller_kpp', readonly=True) + _context_seller_rs = fields.Char(compute='_compute_context_seller_rs', readonly=True) + _context_seller_bik = fields.Char(compute='_compute_context_seller_bik', readonly=True) + _context_seller_bank = fields.Char(compute='_compute_context_seller_bank', readonly=True) + _context_seller_phone = fields.Char(compute='_compute_context_seller_phone', readonly=True) + _context_seller_representer_job_name = fields.Char(compute='_compute_context_seller_representer_job_name', + readonly=True) + _context_seller_representer_name = fields.Char(compute='_compute_context_seller_representer_name', readonly=True) + _context_summ_rub = fields.Char(compute='_compute_context_summ_rub', readonly=True) + _context_summ_rub_word = fields.Char(compute='_compute_context_summ_rub_word', readonly=True) + _context_summ_kop = fields.Char(compute='_compute_context_summ_kop', readonly=True) + _context_summ_word = fields.Char(compute='_compute_context_summ_word', readonly=True) + _context_delivery_term = fields.Char(compute='_compute_context_delivery_term', readonly=True) + _context_delivery_term_word = fields.Char(compute='_compute_context_delivery_term_word', readonly=True) + _context_payment_term = fields.Char(compute='_compute_context_payment_term', readonly=True) + _context_payment_term_word = fields.Char(compute='_compute_context_payment_term_word', readonly=True) + + @api.onchange('partner_id') + def _set_order_domain(self): + current_id = self.env.context.get('active_ids') + domain = [('contract_id', '=', current_id)] + return {'domain': {'order_id': domain}} + + def _generate_context(self): + contract_date = datetime.strptime(self.contract_id.date, '%Y-%m-%d') + if self.partner_id.representative_id: + partner_representer_contract_name = self.partner_id.representative_id.contract_name + else: + partner_representer_contract_name = '' + + if self.company_id.representative_id: + seller_represent_contract_name = self.company_id.representative_id.contract_name + seller_represent_contract_job_name = self.company_id.representative_id.contract_job_name + seller_represent_name = self.company_id.representative_id.name + seller_represent_job_name = self.company_id.representative_id.function + else: + seller_represent_contract_name = '' + seller_represent_contract_job_name = '' + seller_represent_name = '' + seller_represent_job_name = '' + + amount = math.modf(self.order_id.amount_total) + + order_goods = [] + counter = 1 + for line in self.order_id.order_line: + order_line_values = {'label': counter, + 'description': line.name, + 'count': line.product_qty, + 'mesure': line.product_uom.name, + 'price': line.price_unit, + 'amount': line.price_total} + order_goods.append(order_line_values) + counter += 1 + + annex_terms = '' + counter = 1 + for line in self.annex_lines: + annex_terms = annex_terms + '{}) {}\n'.format(counter, line.description) + counter += 1 + context = {'name': self.contract_id.name, + 'current_date': contract_date.strftime('%d %b %Y'), + 'partner_contract_name': self.partner_id.contract_name, + 'partner_adress': self.partner_id.full_adress, + 'partner_representer_contract_name': partner_representer_contract_name, + 'partner_inn': self.partner_id.inn, + 'partner_kpp': self.partner_id.kpp, + 'partner_rs': self.partner_id.bank_account.acc_number, + 'partner_bik': self.partner_id.bank_account.bank_id.bic, + 'partner_bank': self.partner_id.bank_account.bank_id.name, + 'partner_phone': self.partner_id.phone, + 'partner_representer_name': self.partner_id.representative_id.name, + 'seller_contract_name': self.company_id.contract_name, + 'seller_adress': self.company_id.full_adress, + 'seller_representer_contract_job_name': seller_represent_contract_job_name, + 'seller_representer_contract_name': seller_represent_contract_name, + 'seller_inn': self.company_id.inn, + 'seller_kpp': self.company_id.kpp, + 'seller_rs': self.company_id.bank_account.acc_number, + 'seller_bik': self.company_id.bank_account.bank_id.bic, + 'seller_bank': self.company_id.bank_account.bank_id.name, + 'seller_phone': self.company_id.phone, + 'seller_representer_job_name': seller_represent_job_name, + 'seller_representer_name': seller_represent_name, + 'summ_rub': int(amount[1]), + 'summ_rub_word': numeral.in_words(int(amount[1])), + 'summ_kop': int(amount[0]), + 'delivery_term': self.delivery_terms, + 'delivery_term_word': numeral.in_words(self.delivery_terms), + 'payment_term': self.payment_terms, + 'payment_term_word': numeral.in_words(self.payment_terms), + 'annex_terms': annex_terms, + 'order_goods': order_goods, + } + return context + + def get_docx_contract_1(self): + odoo_data_dir = config.get("data_dir") + odoo_bd = config.get("dbfilter") + filename = self.template.store_fname + full_path = '{}/filestore/{}/{}'.format(odoo_data_dir, odoo_bd, filename) + context = self._generate_context() + doc = DocxTemplate(full_path) + doc.render(context) + doc.save('tmp.docx') + return open('tmp.docx', 'rb').read() + + def get_docx_contract(self): + return { + 'type': 'ir.actions.act_url', + 'url': '/web/binary/get_compiled_contract?doc_id={}&doc_name={}.docx'.format(self.id, + self.contract_id.name), + 'target': 'self', + } diff --git a/models/models.py b/models/models.py new file mode 100644 index 0000000..5a59a0b --- /dev/null +++ b/models/models.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- + +from datetime import datetime + +from odoo import models, fields, api + + +class PartnerContract(models.Model): + + _name = 'res.partner.contract' + + @api.onchange('date') + def _change_contract_name(self): + """ + Procedure for forming contract name + :return: contract name in format "DDMM-YY-№" + """ + contract_date = datetime.strptime(self.date, '%Y-%m-%d') + date_part = contract_date.strftime('%d%m-%y') + today_contracts = self.search([('date', '=', contract_date.date())]) + if len(today_contracts) > 0: + last_contract_number = int(today_contracts[-1].name.split('-')[2]) + 1 + else: + last_contract_number = 1 + self.name = '{}-{}'.format(date_part, last_contract_number) + + name = fields.Char( + string='Contract number', + help='Number of contract, letters and digits',) + date = fields.Date( + string='Date of conclusion', + help='Date, when contract was concluded', + default=datetime.now().date(), + required=True) + partner_id = fields.Many2one( + 'res.partner', + string='Contract Partner', + help='Contract partner', + default=lambda self: self.env.context['active_id'], + required=True) + order_ids = fields.One2many( + 'sale.order', + 'contract_id', + string='Annexes', + help='Annexes to this contract') + + @api.model + def create(self, vals): + contract_date = datetime.now() + date_part = contract_date.strftime('%d%m-%y') + today_contracts = self.search([('date', '=', contract_date.date())]) + if len(today_contracts) > 0: + last_contract_number = int(today_contracts[-1].name.split('-')[2]) + 1 + else: + last_contract_number = 1 + vals['name'] = '{}-{}'.format(date_part, last_contract_number) + return super(PartnerContract, self).create(vals) + + +class AnnexType(models.Model): + + _name = 'res.partner.contract.annex.type' + + name = fields.Char(string='Annex template name') + description = fields.Text(string='Annex template description') + + +class ResPartner(models.Model): + + _inherit = 'res.partner' + + client_contract_ids = fields.One2many( + 'res.partner.contract', + 'partner_id', + string='Contracts', + help='Contracts for this partner') + contract_count = fields.Integer( + compute='_compute_contract_count', + string='# of contracts') + contract_name = fields.Char(string='Contract name', help='Name, as it would be in contract') + contract_job_name = fields.Char(string='Contract job name', help='Job position as it would be in contract') + representative_id = fields.Many2one('res.partner', string='Representative', help='Person, who represents company') + passport_data = fields.Char(string='Passport', help='Passport data') + full_adress = fields.Char(compute='_compute_full_adress') + bank_account = fields.Many2one('res.partner.bank', string='Bank account') + signature = fields.Binary(string='Client signature') + + @api.one + @api.depends('street', 'street2', 'city', 'state_id', 'zip', 'country_id') + def _compute_full_adress(self): + full_adress = '{}, {}, {}, {} {}'.format(self.zip, self.country_id.name, self.city, self.street, self.street2) + self.full_adress = full_adress + + @api.one + @api.depends('self.client_contract_ids') + def _compute_contract_count(self): + self.contract_count = len(self.client_contract_ids) + + +class SaleOrder(models.Model): + + _inherit = 'sale.order' + + contract_id = fields.Many2one( + 'res.partner.contract', + string='Contract', + help='Contract, assigned to this order') + + +class ContractTemplate(models.Model): + + _name = 'res.partner.contract.template' + _inherit = 'ir.attachment' + + is_contract_template = fields.Boolean(srting='Is this document contract template?', default=True) diff --git a/security/ir.model.access.csv b/security/ir.model.access.csv new file mode 100644 index 0000000..ba1df10 --- /dev/null +++ b/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_client_contracts_client_contracts,client_contracts.client_contracts,model_client_contracts_client_contracts,,1,0,0,0 \ No newline at end of file diff --git a/views/contract_wizard.xml b/views/contract_wizard.xml new file mode 100644 index 0000000..cb064f0 --- /dev/null +++ b/views/contract_wizard.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + Contract print wizard + res.partner.contract.wizard + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + + res.partner.inherit + res.partner + + + + + + + + + + + + + + + + annex_action + res.partner.contract.annex.type + form + tree,form,graph + + + + res_partner_contract_annex_type_view + res.partner.contract.annex.type + form + tree,form,graph + + + + + + + + + + res_partner_contract_template_action + res.partner.contract.template + form + tree,form,graph + + + + res_partner_contract_template_view + res.partner.contract.template + form + tree,form,graph + + + + + + + + + + + + +
\ No newline at end of file diff --git a/views/templates.xml b/views/templates.xml new file mode 100644 index 0000000..4dde9f8 --- /dev/null +++ b/views/templates.xml @@ -0,0 +1,773 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +