Public release from ruodoo-project: 19.0 - 2026-05-10 21:19:01 UTC

This commit is contained in:
CI Publish Bot
2026-05-10 21:19:11 +00:00
commit cbf9e6e6d6
1213 changed files with 183945 additions and 0 deletions

View File

@ -0,0 +1,9 @@
from . import partner_contract_customer
from . import dop_field
from . import res_partner
from . import res_partner_bank
from . import res_company
from . import contract_day
from . import contract_allowed_profiles
from . import contract_line
from . import contract_profile

View File

@ -0,0 +1,50 @@
from odoo import api, fields, models, exceptions, _
class AccountMove(models.Model):
_inherit = 'account.move'
mt_contract_id = fields.Many2one('partner.contract.customer', string=_('Номер договора'))
sf_number = fields.Char(string=_('Номер с'))
osnovanie = fields.Char(string=_('Основание'))
sec_partner_id = fields.Many2one('res.partner', string=_('Контрагент'), store=True, compute='_compute_get_pid')
stamp = fields.Boolean(string=_('Печать и подпись'), related='mt_contract_id.stamp')
@api.depends('partner_id')
def _compute_get_pid(self):
for s in self:
s.sec_partner_id = s.partner_id.parent_id if s.partner_id.parent_id else s.partner_id
@api.onchange('mt_contract_id')
def set_ons(self):
if self.mt_contract_id:
self.osnovanie = 'Договор № ' + self.mt_contract_id.name + ' от ' + fields.Datetime.from_string(
self.mt_contract_id.date_start).strftime("%d.%m.%Y")
@api.constrains('state')
def invoice_fields_check(self):
for s in self:
if s.state == 'posted':
if s.mt_contract_id:
errors_list = []
journal_in_contract = s.mt_contract_id.profile_id.journal_id
payment_term_in_contract = s.mt_contract_id.profile_id.payment_term_id
receivable_in_contract = s.mt_contract_id.profile_id.receivable_account_id
if journal_in_contract != s.journal_id:
errors_list.append(f'Отличается Журнал - [{s.journal_id.name}] '
f'и указанный в договоре №{s.mt_contract_id.name} '
f'Журнал - [{journal_in_contract.name}]\n\n')
if payment_term_in_contract != s.invoice_payment_term_id:
errors_list.append(f'Отличается поле "Условие оплаты" в инвойсе '
f'[Условие оплаты - {s.invoice_payment_term_id.name}] '
f'и указанный в договоре №{s.mt_contract_id.name} '
f'[Условие оплаты - {payment_term_in_contract.name}]\n\n')
if receivable_in_contract not in s.line_ids.account_id:
errors_list.append(f'Отличается поле "Счет дебиторской задолженности" в инвойсе '
f'и указанный в договоре №{s.mt_contract_id.name}')
if errors_list:
raise exceptions.ValidationError(''.join(errors_list))

View File

@ -0,0 +1,16 @@
from odoo import api, fields, models
class AllowedProfiles(models.Model):
_name = 'contract.allowed.profiles'
name = fields.Char(string='Одновременно включены следующие виды договоров:')
allowed_profiles = fields.Many2many('contract.profile', string='Виды договоров', required=True)
@api.onchange('allowed_profiles')
def set_name(self):
for s in self:
s.name = ''
for profile in s.allowed_profiles:
s.name += profile.name + ' + '
if s.name:
if s.name[-2] == '+':
s.name = s.name[:-2]

View File

@ -0,0 +1,498 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, exceptions, tools, _
import pymorphy2
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from .crutch_fields_header import IP_CONTACT_HEADER, ENTITY_CONTRACT_HEADER, INDIVIDUAL_CONTRACT_HEADER
from odoo.exceptions import UserError
import re
import base64
from io import BytesIO
from docx import Document
from docx.shared import Pt, Inches
from docx.enum.text import WD_ALIGN_PARAGRAPH
class PartnerContractCustomer(models.Model):
_name = 'partner.contract.customer'
_inherit = ['mail.thread', 'mail.activity.mixin', 'mail.render.mixin']
def get_dateend(self):
for s in self:
if s.date_start:
six_months = fields.Datetime.from_string(s.date_start) + relativedelta(months=+11)
else:
six_months = datetime.today() + relativedelta(months=+11)
return fields.Datetime.to_string(six_months)
name = fields.Char(string='Номер')
date_start = fields.Date(string='Дата договора', required=True, default=fields.Datetime.now())
partner_id = fields.Many2one('res.partner', string=_('Контрагент'), required=True)
sec_partner_id = fields.Many2one('res.partner', string=_('Контрагент как в заказе'))
company_id = fields.Many2one('res.company', string=_('Компания'), required=True)
name_print = fields.Char(string=_('Имя для печати'), compute='_get_name_print')
name_print1 = fields.Char(string=_('Имя для печати, И.П.'), compute='_get_name_printip')
date_end = fields.Date(string=_('Дата окончания'), required=True, default=get_dateend)
name_dirprint = fields.Char(string=_('Имя нашего директора для печати'))
name_dirprint1 = fields.Char(string=_('Имя нашего директора для печати И.П.'))
lines = fields.One2many('contract.line', 'contract_id', string=_('Договорные цены'))
type = fields.Selection(
[('customer', 'С покупателем'),
('supplier', 'С поставщиком'),
('other', 'Прочие расчеты'),
],
string=_('Тип договора'), default='customer', required=True)
saleorder_id = fields.Many2one('sale.order', string=_('Заказ/Сделка'))
stamp = fields.Boolean(string=_('Печать и подпись'))
signed = fields.Boolean(string=_('Договор подписан'))
state = fields.Selection(
[('draft', 'Черновик'),
('progress', 'На согласовании'),
('signed', 'Подписан, действует'),
('closed', 'Истёк'),
],
string=_('Статус'), default='draft', group_expand='_expand_states', index=True
)
is_template = fields.Boolean(_('Это шаблон'))
copy_from = fields.Many2one('partner.contract.customer', string=_('Копировать из этого шаблона'))
profile_id = fields.Many2one('contract.profile', string=_('Вид договора'), required=True)
credit_limit = fields.Float(string=_('Лимит кредита'))
guid_1s = fields.Char(_('Код договора из 1С'))
buh_code = fields.Char(_('Код договора из бухгалтерии'))
payment_term_id = fields.Many2one('account.payment.term', string=_('Условие оплаты'))
manager_id = fields.Many2one('res.users', string=_('Менеджер по продажам'))
accountant_id = fields.Many2one('res.users', string=_('Бухгалтер по взаиморасчетам'))
time_to_delivery_from = fields.Datetime(_('Время доставки от'))
time_to_delivery_to = fields.Datetime(_('Время доставки до'))
day_of_delivery = fields.Float(_('Дни доставки'))
day_of_otgruzki = fields.Float(_('Дни отгрузки'))
channel_id = fields.Many2one('saleorder.channel', string=_('Канал продаж'))
team_id = fields.Many2one('crm.team', string=_('Команда продаж'))
order_days_ids = fields.Many2many(comodel_name='contract.day', relation='orderdays', string=_('Дни доставки'),
column1='contract_id', column2='day_id')
shipment_days_ids = fields.Many2many(comodel_name='contract.day', relation='shipmentdays', string=_('Дни отгрузки'),
column1='contract_id',
column2='day_id')
# Доработка хедера договора
partner_type = fields.Selection(string=_('Тип контрагента'), selection=[
('person', 'Физ. лицо'),
('company_ip', 'ИП'),
('company', 'Юр. лицо')
], required=True)
contract_header = fields.Html(_('Шапка договора'))
@api.onchange('partner_type')
def generate_contract_header(self):
self.ensure_one()
self.render_model = 'partner.contract.customer'
if self.partner_type == 'company_ip':
self.contract_header = IP_CONTACT_HEADER
elif self.partner_type == 'person':
self.contract_header = INDIVIDUAL_CONTRACT_HEADER
else:
self.contract_header = ENTITY_CONTRACT_HEADER
# # Рендер Jinja выражение типа {{object.field}}
result = self._render_template(self.contract_header, self.render_model, res_ids=[self.id])
result = tools.html_sanitize(result[self.id])
self.contract_header = result
@api.onchange('sec_partner_id')
def set_pid(self):
for s in self:
s.partner_id = s.sec_partner_id.parent_id if s.sec_partner_id.parent_id else s.sec_partner_id
def _expand_states(self, states, domain, order):
return [key for key, val in type(self).state.selection]
def copy_it(self):
for s in self:
if s.copy_from:
for line in s.copy_from.lines:
line.copy({'contract_id': s.id})
def _get_name_print(self):
morph = pymorphy2.MorphAnalyzer()
for s in self:
s.name_print = False
director = self.env['res.partner'].search([('parent_id', '=', s.partner_id.id), ('type', '=', 'director')],
limit=1)
if director:
if len(director.name.split(' ')) == 3:
lastname_old, firstname_old, middlename_old = director.name.split(' ')
if lastname_old:
lastname_n = morph.parse(lastname_old)[0]
if lastname_n.inflect({'gent'}):
lastname_n = lastname_n.inflect({'gent'}).word
else:
lastname_n = lastname_old
else:
lastname_n = ''
if firstname_old:
firstname_n = morph.parse(firstname_old)[0]
firstname_n = firstname_n.inflect({'gent'}).word
else:
firstname_n = ''
if middlename_old:
middlename_n = morph.parse(middlename_old)[0]
middlename_n = middlename_n.inflect({'gent'}).word
else:
middlename_n = ''
name_print = lastname_n + ' ' + firstname_n + ' ' + middlename_n
s.name_print = name_print.title()
def _get_name_print1(self):
morph = pymorphy2.MorphAnalyzer()
for s in self:
director = s.company_id.chief_id.partner_id if s.company_id.chief_id else False
# raise exceptions.UserError(str(director))
s.name_dirprint = False
if director:
if len(director.name.split(' ')) == 3:
lastname_old, firstname_old, middlename_old = director.name.split(' ')
if lastname_old:
lastname_n = morph.parse(lastname_old)[0]
lastname_n = lastname_n.inflect({'gent'}).word
else:
lastname_n = ''
if firstname_old:
firstname_n = morph.parse(firstname_old)[0]
firstname_n = firstname_n.inflect({'gent'}).word
else:
firstname_n = ''
if middlename_old:
middlename_n = morph.parse(middlename_old)[0]
middlename_n = middlename_n.inflect({'gent'}).word
else:
middlename_n = ''
name_print = lastname_n + ' ' + firstname_n + ' ' + middlename_n
s.name_dirprint = name_print.title()
@api.model
def create(self, vals_list):
if isinstance(vals_list, dict):
vals_list = [vals_list]
records = super().create(vals_list)
for record, vals in zip(records, vals_list):
if vals.get('is_template'):
continue
if vals.get('type') == 'customer':
sequence_code = 'partner.contract.customer.sequence'
elif vals.get('type') == 'supplier':
sequence_code = 'partner.contract.supplier.sequence'
else:
continue
name = self.env['ir.sequence'].next_by_code(sequence_code)
record.name = name
return records
# @api.model
def write(self, values):
if 'state' in values:
if self.state != values['state']:
msg = 'Статус: ' + dict(self._fields['state'].selection).get(self.state) + ' -> ' + dict(
self._fields['state'].selection).get(values['state'])
self.message_post(body=msg)
res = super(PartnerContractCustomer, self).write(values)
return res
def _get_name_print1ip(self):
for s in self:
s.name_dirprint1 = s.company_id.chief_id.partner_id.name if s.company_id.chief_id else False
def _get_name_printip(self):
for s in self:
s.name_print1 = False
director = self.env['res.partner'].search([('parent_id', '=', s.partner_id.id), ('type', '=', 'director')],
limit=1)
if director:
s.name_print1 = director.name
def print_supp(self):
# self.filtered(lambda s: s.state == 'draft').write({'state': 'sent'})
return self.env['report'].get_action(self, 'mta_base.action_mtacontractsupp_report')
def print_contract_cust(self):
for s in self:
if s.saleorder_id:
return s.saleorder_id.print_contract()
else:
raise exceptions.UserError(
'Вы не можете напечатать договор с Клиентом, потому что нет связи с Заказом. Нужно зайти в Заказ и привязать этот договор.')
def contract_action_confirm(self):
for s in self:
if s.state == 'draft':
s.state = 'progress'
elif s.state == 'progress':
s.state = 'signed'
def contract_in_draft(self):
for s in self:
s.state = 'draft'
@api.onchange('name')
def set_comp_and_partn(self):
for s in self:
context = s._context
order_id = context.get('sale_order_id')
if order_id:
sale_order = self.env['sale.order'].browse(order_id)
s.company_id = sale_order.company_id
s.partner_id = sale_order.partner_id
@api.onchange('profile_id')
def set_payment(self):
for s in self:
if s.profile_id.payment_term_id:
s.payment_term_id = s.profile_id.payment_term_id
# @api.constrains('name')
def check_name(self):
obj = self.search([('name', '=', self.name), ('id', '!=', self.id), ('state', '!=', 'closed')])
if obj:
raise exceptions.ValidationError('Договор с таким номером уже существует')
"""
@api.constrains('profile_id')
def check_profile_id(self):
contracts = self.search([('partner_id', '=', self.partner_id.id), ('id', '!=', self.id)])
if contracts:
profiles_in_contracts = contracts.profile_id
# raise exceptions.ValidationError(profiles_in_contracts.ids)
if profiles_in_contracts:
ads = self.env['contract.allowed.profiles'].search(
[('allowed_profiles', 'in', profiles_in_contracts.ids)])
if ads:
raise exceptions.ValidationError((self.profile_id.name, ads.name))
# raise exceptions.ValidationError(contracts)
"""
def action_report_contract_with_format(self, report_format):
if report_format == 'docx':
file_data, filename = self._generate_contract_docx()
mimetype = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
elif report_format == 'md':
file_data, filename = self._generate_contract_md()
mimetype = 'application/md'
else:
raise UserError("Неверный формат отчёта.")
attachment = self.env['ir.attachment'].create({
'name': filename,
'datas': base64.b64encode(file_data),
'res_model': self._name,
'res_id': self.id,
'mimetype': mimetype or 'application/octet-stream',
})
return {
'type': 'ir.actions.act_url',
'url': '/web/content/%s?download=true' % attachment.id,
'target': 'new',
}
def _strip_html_tags(self, html):
clean = re.compile('<.*?>')
return re.sub(clean, '', html)
def _generate_contract_docx(self):
doc = Document()
style = doc.styles['Normal']
font = style.font
font.name = 'Times New Roman'
font.size = Pt(12)
company = self.env.company
partner = self.partner_id
table_header = doc.add_table(rows=1, cols=2)
rc_header = table_header.rows[0].cells
run_left = rc_header[0].paragraphs[0].add_run(f"{company.name or ''}")
run_left.font.bold = True
header_paragraph_right = rc_header[1].paragraphs[0]
header_paragraph_right.alignment = WD_ALIGN_PARAGRAPH.RIGHT
header_run_right = header_paragraph_right.add_run(f"Договор {self.name or ''} от {self.date_start or ''}")
header_run_right.font.bold = True
header_paragraph_centre = doc.add_paragraph(f"Договор №{self.name or ''} от {self.date_start or ''}\n на поставку продукции")
header_paragraph_centre.alignment = WD_ALIGN_PARAGRAPH.CENTER
table_city_date = doc.add_table(rows=1, cols=2)
row = table_city_date.rows[0].cells
run_city = row[0].paragraphs[0].add_run(company.city or '')
run_city.font.bold = True
paragraph_date = row[1].paragraphs[0]
paragraph_date.alignment = WD_ALIGN_PARAGRAPH.RIGHT
run_date = paragraph_date.add_run(self.get_date_text(self.date_start) or '')
run_date.font.bold = True
text_contract_header = self._strip_html_tags(self.contract_header)
doc.add_paragraph(text_contract_header)
for line in self.lines.sorted(key=lambda r: r.sequence, reverse=False):
doc.add_paragraph(line.name or '')
doc.add_paragraph(line.punct or '')
doc.add_paragraph("Реквизиты и подписи сторон.").alignment = WD_ALIGN_PARAGRAPH.CENTER
table = doc.add_table(rows=7, cols=6)
table.style = 'Table Grid'
table.cell(0, 0).merge(table.cell(0, 2)).text = "Поставщик:"
table.cell(0, 3).merge(table.cell(0, 5)).text = "Покупатель:"
table.cell(1, 0).merge(table.cell(1, 2)).text = company.name or ''
table.cell(1, 3).merge(table.cell(1, 5)).text = partner.name or ''
table.cell(2, 0).merge(table.cell(2, 2)).text = f"ИНН {company.inn or ''}"
table.cell(2, 3).merge(table.cell(2, 5)).text = f"ИНН {partner.inn or ''}"
table.cell(3, 0).merge(table.cell(3, 1)).text = f"ОГРН {company.partner_id.ogrn or ''}"
table.cell(3, 3).merge(table.cell(3, 5)).text = f"ОГРН {partner.ogrn or ''}"
table.cell(4, 0).merge(table.cell(4, 2)).text = f"КПП {company.kpp or ''}"
table.cell(4, 3).merge(table.cell(4, 5)).text = f"КПП {partner.kpp or ''}"
company_address = self.address(company) or ''
partner_address = self.address(partner) or ''
table.cell(5, 0).merge(table.cell(5, 2)).text = f"Юридический адрес: {company_address}"
table.cell(5, 3).merge(table.cell(5, 5)).text = f"Юридический адрес: {partner_address}"
table.cell(6, 0).merge(table.cell(6, 2)).text = "Подпись Исполнителя:"
table.cell(6, 3).merge(table.cell(6, 5)).text = "Подпись Заказчика:"
f = BytesIO()
doc.save(f)
f.seek(0)
return f.read(), 'Договор (DOCX).docx'
def _generate_contract_md(self):
company = self.env.company
partner = self.partner_id
lines = []
lines.append(f"| **{company.name or ''}** | **Договор {self.name or ''} от {self.date_start or ''}** |")
lines.append("| --- | --- |")
lines.append(f"**Договор {self.name or ''} от {self.date_start or ''}**\n")
lines.append("**на поставку продукции**\n")
lines.append("| Город | Дата |")
lines.append("| --- | --- |")
lines.append(f"| {company.city or ''} | {self.get_date_text(self.date_start)} |\n")
text_contract_header = self._strip_html_tags(self.contract_header or "")
lines.append(text_contract_header + "\n")
for line in self.lines.sorted(key=lambda r: r.sequence):
lines.append(f"**{line.name or ''}**")
lines.append((line.punct or '') + "\n")
lines.append("**Реквизиты и подписи сторон.**\n")
lines.append("| Поставщик | | Покупатель | |")
lines.append("| --- | --- | --- | --- |")
lines.append(f"| {company.name or ''} | | {partner.name or ''} | |")
lines.append(f"| ИНН {company.inn or ''} | | ИНН {partner.inn or ''} | |")
lines.append(f"| ОГРН {company.ogrn or ''} | | ОГРН {partner.ogrn or ''} | |")
lines.append(f"| КПП {company.kpp or ''} | | КПП {partner.kpp or ''} | |")
company_address = self.address(company) or ''
partner_address = self.address(partner) or ''
lines.append(f"| Юридический адрес: {company_address} | | Юридический адрес: {partner_address} | |")
lines.append(f"| Подпись Исполнителя | | Подпись Заказчика | |\n")
md_content = "\n".join(lines)
return md_content.encode('utf-8'), 'Договор (MD).md'
def action_print_by_template(self):
return {
'name': 'Печать по шаблону',
'type': 'ir.actions.act_window',
'res_model': 'report.template.wizard',
'view_mode': 'form',
'target': 'new',
'context': {'default_contract_id': self.id},
}
class Partner(models.Model):
_inherit = 'res.partner'
contract_count = fields.Integer(string='Договоры', compute='get_count_contract')
pol = fields.Selection(string="Пол", selection=[('m', 'Муж.'), ('j', 'Жен'), ], required=False)
type = fields.Selection(selection_add=[('director', 'Директор')])
def get_count_contract(self):
contract = self.env['partner.contract.customer']
self.contract_count = contract.search_count([('partner_id', '=', self.id)])
def action_view_contract(self):
action = self.env.ref('contract.contract_customer_action').read()[0]
action['domain'] = [('partner_id', '=', self.id)]
return action
class ContractLine(models.Model):
_name = 'contract.line'
contract_id = fields.Many2one('partner.contract.customer', string='Order Reference', required=True,
ondelete='cascade', index=True, copy=False)
_order = "sequence desc"
# name = fields.Text(string='Название для договора')
# price_unit = fields.Float('Цена', default=0.0)
# product_uom = fields.Many2one('uom.uom', string='Единица измерения')
# product_id = fields.Many2one('product.product', string='Услуга', domain=[('sale_ok', '=', True)], change_default=True, ondelete='restrict')
sequence = fields.Integer('Порядок')
name = fields.Char('Номер пункта')
punct = fields.Html('Текст пункта')
@api.onchange('product_id')
def set_name(self):
for s in self:
s.name = s.product_id.name
class AllowedProfiles(models.Model):
_name = 'contract.allowed.profiles'
name = fields.Char(string='Одновременно включены следующие виды договоров:')
allowed_profiles = fields.Many2many('contract.profile', string='Виды договоров', required=True)
@api.onchange('allowed_profiles')
def set_name(self):
for s in self:
s.name = ''
for profile in s.allowed_profiles:
s.name += profile.name + ' + '
if s.name:
if s.name[-2] == '+':
s.name = s.name[:-2]
class ContractProfile(models.Model):
_name = 'contract.profile'
name = fields.Char(string='Вид договора', required=True)
payable_account_id = fields.Many2one('account.account', string='Счет кредиторской задолженности', required=True)
receivable_account_id = fields.Many2one('account.account', string='Счет дебиторской задолженности', required=True)
max_receivable_id = fields.Float(string='Максимальная деб. задолженность', required=True)
payment_term_id = fields.Many2one('account.payment.term', string='Условие оплаты', required=True)
journal_id = fields.Many2one('account.journal', string='Журнал', required=True)
class ContractDay(models.Model):
_name = 'contract.day'
name = fields.Char('День')

View File

@ -0,0 +1,20 @@
from odoo import models, fields, api
import os
class ReportTemplate(models.Model):
_name = 'contract.report.template'
_description = 'Шаблоны отчетов'
name = fields.Char(string='Название', required=True)
attachment = fields.Binary(string='Файл шаблона', required=True)
attachment_filename = fields.Char(string='Имя файла')
typeformat = fields.Char(string='Формат', compute='_compute_format', store=True)
create_date = fields.Datetime(string='Дата создания', default=fields.Datetime.now)
@api.depends('attachment_filename')
def _compute_typeformat(self):
for rec in self:
if rec.attachment_filename:
rec.typeformat = os.path.splitext(rec.attachment_filename)[1].lstrip('.').lower()
else:
rec.typeformat = False

View File

@ -0,0 +1,5 @@
from odoo import api, fields, models, exceptions, _
class ContractDay(models.Model):
_name = 'contract.day'
name = fields.Char(_('День'))

View File

@ -0,0 +1,12 @@
from odoo import api, fields, models, exceptions, tools, _
class ContractLine(models.Model):
_name = 'contract.line'
_order = "sequence desc"
contract_id = fields.Many2one('partner.contract.customer', string='Order Reference', required=True,
ondelete='cascade', index=True, copy=False)
sequence = fields.Integer('Порядок')
name = fields.Char('Номер пункта')
punct = fields.Html('Текст пункта')

View File

@ -0,0 +1,6 @@
from odoo import api, fields, models, exceptions, _
class ContractProfile(models.Model):
_name = 'contract.profile'
name = fields.Char(string=_('Вид договора'), required=True)

View File

@ -0,0 +1,33 @@
ENTITY_CONTRACT_HEADER = """
<b>{{object.company_id.partner_id.name}}</b>,
именуемое в дальнейшем <b>«Поставщик»</b>, в лице
{{(object.company_id.chief_id.partner_id.function or '').lower()}}
{{(object.name_dirprint1 or '').title()}},
действующего на основании ОГРНИП № {{object.company_id.company_registry or ''}}, с одной стороны, и <b>{{object.partner_id.name or ''}}</b>,
именуемое в дальнейшем <b>«Покупатель»</b>, в лице
{{(object.get_function_partner1(object.partner_id.id) or '').lower()}}
{{(object.name_print1 or '').title()}}, действующего на основании устава общества, с другой стороны, вместе именуемые в дальнейшем <b>«Стороны»</b> заключили
настоящий Договор о нижеследующем:
"""
IP_CONTACT_HEADER = """
<b>{{object.company_id.partner_id.name}}</b>,
именуемое в дальнейшем <b>«Поставщик»</b>, в лице
{{(object.company_id.chief_id.partner_id.function or '').lower()}}
{{(object.name_dirprint1 or '').title()}},
действующего на основании ОГРНИП № {{object.company_id.company_registry or ''}}, с одной стороны, и <b>{{object.partner_id.name or ''}}</b>,
именуемое в дальнейшем <b>«Покупатель»</b>, в лице
{{(object.get_function_partner1(object.partner_id.id) or '').lower()}}
{{(object.name_print1 or '').title()}}, действующего на основании ОГРНИП №{{object.partner_id.ogrn or ''}},
с другой стороны, вместе именуемые в дальнейшем <b>«Стороны»</b> заключили
настоящий Договор о нижеследующем:
"""
INDIVIDUAL_CONTRACT_HEADER = """
<b>{{object.company_id.partner_id.name}}</b>,
именуемое в дальнейшем <b>«Поставщик»</b>, в лице
{{(object.company_id.chief_id.partner_id.function or '').lower()}}
{{(object.name_dirprint1 or '').title()}},
действующего на основании ОГРНИП № {{object.company_id.company_registry or ''}}, с одной стороны, и <b>{{object.partner_id.name or ''}}</b>,
именуемое в дальнейшем <b>«Покупатель»</b>, вместе именуемые в дальнейшем <b>«Стороны»</b> заключили
настоящий Договор о нижеследующем:
"""

View File

@ -0,0 +1,313 @@
from odoo import models
from datetime import datetime
import re
import pymorphy2
from odoo.tools import pycompat
FRACTIONS = (
(u"десятая", u"десятых", u"десятых"),
(u"сотая", u"сотых", u"сотых"),
(u"тысячная", u"тысячных", u"тысячных"),
(u"десятитысячная", u"десятитысячных", u"десятитысячных"),
(u"стотысячная", u"стотысячных", u"стотысячных"),
(u"миллионная", u"милллионных", u"милллионных"),
(u"десятимиллионная", u"десятимилллионных", u"десятимиллионных"),
(u"стомиллионная", u"стомилллионных", u"стомиллионных"),
(u"миллиардная", u"миллиардных", u"миллиардных"),
)
ONES = {
0: (u"", u"", u""),
1: (u"один", u"одна", u"одно"),
2: (u"два", u"две", u"два"),
3: (u"три", u"три", u"три"),
4: (u"четыре", u"четыре", u"четыре"),
5: (u"пять", u"пять", u"пять"),
6: (u"шесть", u"шесть", u"шесть"),
7: (u"семь", u"семь", u"семь"),
8: (u"восемь", u"восемь", u"восемь"),
9: (u"девять", u"девять", u"девять"),
}
TENS = {
0: u"",
10: u"десять",
11: u"одиннадцать",
12: u"двенадцать",
13: u"тринадцать",
14: u"четырнадцать",
15: u"пятнадцать",
16: u"шестнадцать",
17: u"семнадцать",
18: u"восемнадцать",
19: u"девятнадцать",
2: u"двадцать",
3: u"тридцать",
4: u"сорок",
5: u"пятьдесят",
6: u"шестьдесят",
7: u"семьдесят",
8: u"восемьдесят",
9: u"девяносто",
}
HUNDREDS = {
0: u"",
1: u"сто",
2: u"двести",
3: u"триста",
4: u"четыреста",
5: u"пятьсот",
6: u"шестьсот",
7: u"семьсот",
8: u"восемьсот",
9: u"девятьсот",
}
MALE = 1
FEMALE = 2
import sys
import types
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
if PY3:
string_types = str,
integer_types = int,
class_types = type,
text_type = str
binary_type = bytes
MAXSIZE = sys.maxsize
else:
string_types = basestring,
integer_types = (int, long)
class_types = (type, types.ClassType)
text_type = unicode
binary_type = str
class Report_contract_customer(models.Model):
_inherit = 'partner.contract.customer'
def img(self, img, type='png', width=0, height=0):
if width:
width = "width='%spx'" % (width)
else:
width = " "
if height:
height = "height='%spx'" % (height)
else:
height = " "
toreturn = "<img %s %s src='data:image/%s;base64,%s' />" % (
width,
height,
type,
str(pycompat.to_text(img)))
return toreturn
def numer(self, name):
if name:
numeration = re.findall('\d+$', name)
if numeration: return numeration[0]
return ''
def ru_date(self, date):
if date and date != 'False':
return dt.ru_strftime(u'"%d" %B %Y года', date=datetime.strptime(str(date), "%Y-%m-%d"), inflected=True)
return ''
def ru_date2(self, date):
if date and date != 'False':
return dt.ru_strftime(u'%d %B %Y г.', date=datetime.strptime(str(date), "%Y-%m-%d %H:%M:%S"),
inflected=True)
return ''
def in_words(self, number):
return numeral.in_words(number)
def rubles(self, sum):
"Transform sum number in rubles to text"
text_rubles = self.numeral_rubles(int(sum))
copeck = round((sum - int(sum)) * 100)
text_copeck = self.numeral_choose_plural(int(copeck), (u"копейка", u"копейки", u"копеек"))
return ("%s %02d %s") % (text_rubles, copeck, text_copeck)
def numeral_rubles(self, amount, zero_for_kopeck=False):
self.check_positive(amount)
pts = []
amount = round(amount, 2)
pts.append(self.sum_string(int(amount), 1, (u"рубль", u"рубля", u"рублей")))
remainder = self._get_float_remainder(amount, 2)
iremainder = int(remainder)
if iremainder != 0 or zero_for_kopeck:
if iremainder < 10 and len(remainder) == 1:
iremainder *= 10
pts.append(self.sum_string(iremainder, 2,
(u"копейка", u"копейки", u"копеек")))
return u" ".join(pts)
def _get_float_remainder(self, fvalue, signs=9):
self.check_positive(fvalue)
if isinstance(fvalue, integer_types):
return "0"
if isinstance(fvalue, Decimal) and fvalue.as_tuple()[2] == 0:
return "0"
def sum_string(self, amount, gender, items=None):
if isinstance(items, text_type):
items = split_values(items)
if items is None:
items = (u"", u"", u"")
try:
one_item, two_items, five_items = items
except ValueError:
raise ValueError("Items must be 3-element sequence")
self.check_positive(amount)
if amount == 0:
return u"ноль %s" % five_items
into = u''
tmp_val = amount
into, tmp_val = self._sum_string_fn(into, tmp_val, gender, items)
into, tmp_val = self._sum_string_fn(into, tmp_val, FEMALE,
(u"тысяча", u"тысячи", u"тысяч"))
into, tmp_val = self._sum_string_fn(into, tmp_val, MALE,
(u"миллион", u"миллиона", u"миллионов"))
into, tmp_val = self._sum_string_fn(into, tmp_val, MALE,
(u"миллиард", u"миллиарда", u"миллиардов"))
if tmp_val == 0:
return into
else:
raise ValueError("Cannot operand with numbers bigger than 10**11")
def _sum_string_fn(self, into, tmp_val, gender, items=None):
if items is None:
items = (u"", u"", u"")
one_item, two_items, five_items = items
self.check_positive(tmp_val)
if tmp_val == 0:
return into, tmp_val
words = []
rest = tmp_val % 1000
tmp_val = tmp_val // 1000
if rest == 0:
if into == u"":
into = u"%s " % five_items
return into, tmp_val
end_word = five_items
words.append(HUNDREDS[rest // 100])
rest = rest % 100
rest1 = rest // 10
tens = rest1 == 1 and TENS[rest] or TENS[rest1]
words.append(tens)
if rest1 < 1 or rest1 > 1:
amount = rest % 10
end_word = self.numeral_choose_plural(amount, items)
words.append(ONES[amount][gender - 1])
words.append(end_word)
words.append(into)
words = filter(lambda x: len(x) > 0, words)
return u" ".join(words).strip(), tmp_val
def check_positive(self, value, strict=False):
if not strict and value < 0:
raise ValueError("Value must be positive or zero, not %s" % str(value))
if strict and value <= 0:
raise ValueError("Value must be positive, not %s" % str(value))
def numeral_choose_plural(self, amount, variants):
if isinstance(variants, text_type):
variants = split_values(variants)
self.check_length(variants, 3)
amount = abs(amount)
if amount % 10 == 1 and amount % 100 != 11:
variant = 0
elif amount % 10 >= 2 and amount % 10 <= 4 and \
(amount % 100 < 10 or amount % 100 >= 20):
variant = 1
else:
variant = 2
return variants[variant]
def check_length(self, value, length):
_length = len(value)
if _length != length:
raise ValueError("length must be %d, not %d" % \
(length, _length))
def initials(self, fio):
if fio:
return (fio.split()[0] + ' ' + ''.join([fio[0:1] + '.' for fio in fio.split()[1:]])).strip()
return ''
def address(self, partner):
repr = []
if partner.zip: repr.append(partner.zip)
if partner.city: repr.append(partner.city)
if partner.street: repr.append(partner.street)
if partner.street2: repr.append(partner.street2)
return ', '.join(repr)
def address_delivery(self, partner):
if partner:
addr = self.env['res.partner'].search([('parent_id', '=', partner), ('type', '=', 'delivery')], limit=1)
repr = []
if addr:
if addr.zip: repr.append(addr.zip)
if addr.city: repr.append(addr.city)
if addr.street: repr.append(addr.street)
if addr.street2: repr.append(addr.street2)
return ', '.join(repr)
def get_function_print(self, function):
morph = pymorphy2.MorphAnalyzer()
if function:
f = morph.parse(function)[0]
f = f.inflect({'gent'}).word
return f.title()
def get_function_partnerip(self, partner):
director = self.env['res.partner'].search([('parent_id', '=', partner), ('type', '=', 'director')], limit=1)
if director:
if director.function:
return director.function
def get_function_partner(self, partner):
res = []
morph = pymorphy2.MorphAnalyzer()
if partner:
director = self.env['res.partner'].search([('parent_id', '=', partner), ('type', '=', 'director')], limit=1)
if director:
if director.function:
list_f = str(director.function).split(' ')
for func in list_f:
f = morph.parse(func)[0]
f = f.inflect({'gent'}).word
res.append(f)
return ' '.join(res)
def get_date_text(self, date):
month_list = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября',
'ноября', 'декабря']
if date:
date_list = str(date).split('-')
if date_list[0] and date_list[1] and date_list[2]:
return ('"' + date_list[2] + '" ' + month_list[int(date_list[1]) - 1] + ' ' + date_list[0] + ' г.')
def get_bank(self, partner):
repr = []
bank = None
if partner.bank_ids:
bank = partner.bank_ids[0]
elif partner.parent_id.bank_ids:
bank = partner.parent_id.bank_ids[0]
if bank and bank.bank_name: repr.append(bank.bank_name)
if bank and bank.acc_number: repr.append(u"Р/счет " + bank.acc_number)
if bank and bank.bank_bic: repr.append(u"БИК " + bank.bank_bic)
if bank and bank.bank_corr_acc: repr.append(u"к/с " + bank.bank_corr_acc)
return '<br/>'.join(repr)

View File

@ -0,0 +1,130 @@
from odoo import api, fields, models, exceptions
from datetime import datetime
class contract_sale_order(models.Model):
_inherit = 'sale.order'
mt_contractid = fields.Many2one('partner.contract.customer', string='Номер договора')
sec_partner_id = fields.Many2one('res.partner', string='Контрагент', store=True, compute='get_pid')
stamp = fields.Boolean(string='Печать и подпись', related='mt_contractid.stamp')
@api.depends('partner_id')
def get_pid(self):
for s in self:
s.sec_partner_id = s.partner_id.parent_id if s.partner_id.parent_id else s.partner_id
@api.onchange('mt_contractid')
def set_ons(self):
for s in self:
if s.mt_contractid:
s.payment_term_id = s.mt_contractid.payment_term_id
@api.constrains('state')
def late_payment_check(self):
for s in self:
if s.mt_contractid:
if s.state == 'sale':
late_invoices_count = 0
max_receivable = s.mt_contractid.profile_id.max_receivable_id # макс. деб. задолженность в договоре
# ищу просроченные инвойсы контрагента указанного в заказе со стейтом "Подтверждено"
invoices_obj = self.env['account.move'].search([('partner_id', '=', s.partner_id.id),
('state', '=', 'posted'),
('payment_state', 'not in', ['paid','reversed']),
('move_type','in',['out_invoice']),
('invoice_date_due', '<', datetime.now().date())])
for invoice in invoices_obj:
late_invoices_count += invoice.amount_residual # складываю деб. задолженность по просроченным инвойсам
if late_invoices_count > max_receivable:
raise exceptions.ValidationError(
f'Нельзя подтвердить заказ, так как у контрагента {s.sec_partner_id.name} нарушено '
f'условие по дебиторской задолженности.\n\n'
f'Контрагент {s.sec_partner_id.name} должен {late_invoices_count}руб.\n'
f'Максимальная дебиторская задолженность указанная в '
f'договоре №{s.mt_contractid.name} - {max_receivable}руб.\n\n'
f'Проверьте следующие неоплаченные счета контрагента:\n'
f'{", ".join([invoice.name for invoice in invoices_obj])}')
# def _get_invoice_grouping_keys(self):
# res = super(contract_sale_order, self)._get_invoice_grouping_keys()
# res.append('mt_contractid')
# return res
def _prepare_invoice(self):
invoice_vals = super(contract_sale_order, self)._prepare_invoice()
for s in self:
if s.mt_contractid:
invoice_vals['mt_contractid'] = s.mt_contractid.id
invoice_vals['osnovanie'] = 'Договор № ' + s.mt_contractid.name + ' от ' + fields.Datetime.from_string(
s.mt_contractid.date_start).strftime("%d.%m.%Y")
return invoice_vals
class ContractCreateInvoice(models.TransientModel):
_inherit = 'sale.advance.payment.inv'
def _prepare_invoice_values(self, order, so_lines):
invoice_vals = super(ContractCreateInvoice, self)._prepare_invoice_values(order, so_lines)
if order.mt_contractid:
invoice_vals['mt_contractid'] = order.mt_contractid.id
invoice_vals['osnovanie'] = 'Договор № ' + order.mt_contractid.name + ' от ' + fields.Datetime.from_string(order.mt_contractid.date_start).strftime("%d.%m.%Y")
return invoice_vals
class contract_invoice(models.Model):
_inherit = 'account.move'
mt_contractid = fields.Many2one('partner.contract.customer', string='Номер договора')
sf_number = fields.Char(string='Номер с')
osnovanie = fields.Char(string='Основание')
sec_partner_id = fields.Many2one('res.partner', string='Контрагент', store=True, compute='get_pid')
stamp = fields.Boolean(string='Печать и подпись', related='mt_contractid.stamp')
@api.depends('partner_id')
def get_pid(self):
for s in self:
s.sec_partner_id = s.partner_id.parent_id if s.partner_id.parent_id else s.partner_id
@api.onchange('mt_contractid')
def set_ons(self):
if self.mt_contractid:
self.osnovanie = 'Договор № ' + self.mt_contractid.name + ' от ' + fields.Datetime.from_string(
self.mt_contractid.date_start).strftime("%d.%m.%Y")
@api.constrains('state')
def invoice_fields_check(self):
for s in self:
if s.state == 'posted':
if s.mt_contractid:
errors_list = []
journal_in_contract = s.mt_contractid.profile_id.journal_id
payment_term_in_contract = s.mt_contractid.profile_id.payment_term_id
receivable_in_contract = s.mt_contractid.profile_id.receivable_account_id
if journal_in_contract != s.journal_id:
errors_list.append(f'Отличается Журнал - [{s.journal_id.name}] '
f'и указанный в договоре №{s.mt_contractid.name} '
f'Журнал - [{journal_in_contract.name}]\n\n')
if payment_term_in_contract != s.invoice_payment_term_id:
errors_list.append(f'Отличается поле "Условие оплаты" в инвойсе '
f'[Условие оплаты - {s.invoice_payment_term_id.name}] '
f'и указанный в договоре №{s.mt_contractid.name} '
f'[Условие оплаты - {payment_term_in_contract.name}]\n\n')
if receivable_in_contract not in s.line_ids.account_id:
errors_list.append(f'Отличается поле "Счет дебиторской задолженности" в инвойсе '
f'и указанный в договоре №{s.mt_contractid.name}')
if errors_list:
raise exceptions.ValidationError(''.join(errors_list))
class contact_purchase_order(models.Model):
_inherit = 'purchase.order'
mt_contractid = fields.Many2one('partner.contract.customer', string='Номер договора')
sec_partner_id = fields.Many2one('res.partner', string='Контрагент', store=True, compute='get_pid')
@api.depends('partner_id')
def get_pid(self):
for s in self:
s.sec_partner_id = s.partner_id.parent_id if s.partner_id.parent_id else s.partner_id

View File

@ -0,0 +1,231 @@
from odoo import api, fields, models, tools, _
import logging
_logger = logging.getLogger(__name__)
class PartnerContractCustomer(models.Model):
_name = 'partner.contract.customer'
_inherit = ['mail.thread', 'mail.activity.mixin', 'mail.render.mixin']
name = fields.Char(string=_('Номер'))
date_start = fields.Date(string=_('Дата договора'), required=True, default=fields.Datetime.now())
date_end = fields.Date(string=_('Дата окончания'), required=True)
type = fields.Selection(
[('customer', 'С покупателем'),
('supplier', 'С поставщиком'),
('other', 'Прочие расчеты'),
],
string=_('Тип договора'), default='customer', required=True)
profile_id = fields.Many2one('contract.profile', string=_('Вид договора'), required=True)
partner_id = fields.Many2one(
'res.partner',
string='Контрагент',
required=True,
domain="[('id', 'in', possible_partner_ids)]",
)
partner_type = fields.Selection(string=_('Тип контрагента'), selection=[
('person', 'Физ. лицо'),
('company_ip', 'ИП'),
('company', 'Юр. лицо')
], required=True)
company_id = fields.Many2one('res.company', string=_('Компания'), required=True)
state = fields.Selection(
[
('draft', 'Черновик'),
('progress', 'На согласовании'),
('signed', 'Подписан, действует'),
('closed', 'Истёк'),
],
string=_('Статус'),
default='draft',
group_expand=lambda self, states, domain: [
key for key, _ in self._fields['state'].selection
],
)
stamp = fields.Boolean(string=_('Печать и подпись'))
signed = fields.Boolean(string=_('Договор подписан'))
lines_ids = fields.One2many('contract.line', 'contract_id', string=_('Пункты договора'))
is_template = fields.Boolean(_('Это шаблон'))
copy_from = fields.Many2one('partner.contract.customer', string=_('Копировать из этого шаблона'))
director_name_partner = fields.Char(
string=_('ФИО директора (от партнёра)'),
related='partner_id.parent_id.name',
readonly=True,
)
director_name_company = fields.Char(
string=_('ФИО руководителя (от компании)'),
related='company_id.chief_id.partner_id.name',
readonly=True,
)
contract_header = fields.Html(
string=_("Шапка договора"),
compute="_compute_contract_header",
)
contract_header_template_id = fields.Many2one(
"ir.ui.view",
string=_("Шаблон шапки договора"),
domain='['
'("type", "=", "qweb"), '
'"|", '
'"&", ("model", "!=", False), ("model", "=", "partner.contract.customer"), '
'"&", ("model", "=", False), ("key", "ilike", "l10n_ru_contract.contract_header_")'
']',
help=_("QWeb-шаблон, по которому генерируется поле «Шапка договора»."),
)
use_custom_contract_header = fields.Boolean(
string=_("Использовать кастомный шаблон"),
default=False,
help=_(
"Если включено, шапка договора редактируется вручную и "
"не перегенерируется автоматически при изменении реквизитов."
),
)
possible_partner_ids = fields.Many2many(
'res.partner',
compute='_compute_possible_partners',
compute_sudo=True,
)
@api.depends('partner_type')
def _compute_possible_partners(self):
company_ids = self.env.companies.ids
internal_user_partners = self.env['res.users'].sudo().search([
('share', '=', False)
]).mapped('partner_id.id')
for rec in self:
if rec.partner_type in ('company', 'company_ip'):
rec.possible_partner_ids = self.env['res.partner'].search([
('is_company', '=', True),
('id', 'not in', company_ids),
])
elif rec.partner_type == 'person':
rec.possible_partner_ids = self.env['res.partner'].search([
('is_company', '=', False),
('id', 'not in', internal_user_partners),
])
else:
rec.possible_partner_ids = self.env['res.partner'].search([])
@api.onchange('partner_type')
def _onchange_partner_type(self):
if self.partner_id:
if self.partner_type in ('company', 'company_ip'):
if not self.partner_id.is_company or self.partner_id.id in self.env.companies.ids:
self.partner_id = False
elif self.partner_type == 'person':
if self.partner_id.is_company or self.partner_id.id in self.env['res.users'].sudo().search(
[('share', '=', False)]).mapped('partner_id.id'):
self.partner_id = False
def _get_default_header_xmlid(self):
self.ensure_one()
if self.partner_type == "company_ip":
xmlid = "l10n_ru_contract.contract_header_ip"
elif self.partner_type == "person":
xmlid = "l10n_ru_contract.contract_header_individual"
else:
xmlid = "l10n_ru_contract.contract_header_entity"
return xmlid
def _render_header_qweb(self):
self.ensure_one()
view = self.contract_header_template_id
if not view:
xmlid = self._get_default_header_xmlid()
view = self.env.ref(xmlid, raise_if_not_found=False)
self.contract_header_template_id = view
try:
html = self.env["ir.qweb"]._render(view.key, {"object": self})
except Exception as e:
return ""
return tools.html_sanitize(html or "")
@api.depends(
"partner_type",
"partner_id",
"company_id",
"contract_header_template_id",
"use_custom_contract_header",
)
def _compute_contract_header(self):
for rec in self:
rec.contract_header = rec._render_header_qweb()
@api.onchange("partner_type")
def _onchange_partner_type_set_template(self):
for rec in self:
xmlid = rec._get_default_header_xmlid()
view = rec.env.ref(xmlid, raise_if_not_found=False)
rec.contract_header_template_id = view
def generate_contract_header(self):
for rec in self:
rec.contract_header = rec._render_header_qweb()
@api.onchange("use_custom_contract_header")
def _onchange_use_custom_contract_header(self):
for rec in self:
if rec.use_custom_contract_header:
if not rec.contract_header:
rec.contract_header = rec._render_header_qweb()
else:
rec.contract_header = rec._render_header_qweb()
@api.onchange('sec_partner_id')
def set_pid(self):
for s in self:
s.partner_id = s.sec_partner_id.parent_id if s.sec_partner_id.parent_id else s.sec_partner_id
def copy_it(self):
for s in self:
if s.copy_from:
for line in s.copy_from.lines_ids:
line.copy({'contract_id': s.id})
@api.model
def create(self, vals_list):
if not isinstance(vals_list, list):
vals_list = [vals_list]
for vals in vals_list:
if not vals.get('is_template'):
if vals.get('type') == 'customer':
vals['name'] = self.env['ir.sequence'].next_by_code('partner.contract.customer.sequence')
elif vals.get('type') == 'supplier':
vals['name'] = self.env['ir.sequence'].next_by_code('partner.contract.supplier.sequence')
records = super().create(vals_list)
return records
def write(self, values):
if 'state' in values:
if self.state != values['state']:
msg = 'Статус: ' + dict(self._fields['state'].selection).get(self.state) + ' -> ' + dict(
self._fields['state'].selection).get(values['state'])
self.message_post(body=msg)
return super().write(values)
def action_set_on_approval(self):
for rec in self:
rec.state = 'progress'
def action_confirm(self):
for rec in self:
rec.state = 'signed'
def action_reset_to_draft(self):
for rec in self:
rec.state = 'draft'

View File

@ -0,0 +1,13 @@
from odoo import api, fields, models, exceptions, _
class PurchaseOrder(models.Model):
_inherit = 'purchase.order'
mt_contract_id = fields.Many2one('partner.contract.customer', string=_('Номер договора'))
sec_partner_id = fields.Many2one('res.partner', string=_('Контрагент'), store=True, compute='_compute_get_pid')
@api.depends('partner_id')
def _compute_get_pid(self):
for s in self:
s.sec_partner_id = s.partner_id.parent_id if s.partner_id.parent_id else s.partner_id

View File

@ -0,0 +1,10 @@
from odoo import api, fields, models, exceptions, _
class Company(models.Model):
_inherit = 'res.company'
inn = fields.Char(related='partner_id.inn', readonly=False)
kpp = fields.Char(related='partner_id.kpp', readonly=False)
okpo = fields.Char(related='partner_id.okpo', readonly=False)
chief_id = fields.Many2one('res.users', 'Имя директора')
stamp = fields.Binary("Stamp")

View File

@ -0,0 +1,24 @@
from odoo import api, fields, models, exceptions, _
class Partner(models.Model):
_inherit = 'res.partner'
ogrn = fields.Char('ОГРН')
okpo = fields.Char('ОКПО')
inn = fields.Char('ИНН')
kpp = fields.Char('KPP')
passport = fields.Char('Паспорт')
contract_count = fields.Integer(string=_('Договоры'), compute='get_count_contract')
pol = fields.Selection(string=_("Пол"), selection=[('m', 'Муж.'), ('j', 'Жен'), ], required=False)
type = fields.Selection(selection_add=[('director', 'Директор')])
def get_count_contract(self):
contract = self.env['partner.contract.customer']
for s in self:
s.contract_count = contract.search_count([('partner_id', '=', s.id)])
def action_view_contract(self):
action = self.env.ref('l10n_ru_contract.contract_customer_action').read()[0]
action['domain'] = [('partner_id', '=', self.id)]
return action

View File

@ -0,0 +1,6 @@
from odoo import api, fields, models, exceptions, _
class Partner_Bank(models.Model):
_inherit = 'res.partner.bank'
bank_corr_acc = fields.Char('Кор.счет')

View File

@ -0,0 +1,14 @@
from odoo import api, fields, models
class ContractCreateInvoice(models.TransientModel):
_inherit = 'sale.advance.payment.inv'
# при выбора счета "Авансовый платеж"
@api.model
def _create_invoice(self, order, so_line, amount):
res = super(ContractCreateInvoice, self)._create_invoice(order, so_line, amount)
if order.mt_contract_id:
res.write({'mt_contract_id': order.mt_contract_id,
'journal_id': order.mt_contract_id.profile_id.journal_id, })
return res

View File

@ -0,0 +1,53 @@
from odoo import api, fields, models, exceptions, _
from datetime import datetime
class SaleOrder(models.Model):
_inherit = 'sale.order'
mt_contract_id = fields.Many2one('partner.contract.customer', string=_('Номер договора'))
sec_partner_id = fields.Many2one('res.partner', string=_('Контрагент'), store=True, compute='_compute_get_pid')
stamp = fields.Boolean(string=_('Печать и подпись'), related='mt_contract_id.stamp')
@api.depends('partner_id')
def _compute_get_pid(self):
for s in self:
s.sec_partner_id = s.partner_id.parent_id if s.partner_id.parent_id else s.partner_id
@api.onchange('mt_contract_id')
def set_ons(self):
if self.mt_contract_id:
self.payment_term_id = self.mt_contract_id.payment_term_id
@api.constrains('state')
def late_payment_check(self):
if self.mt_contract_id:
if self.state == 'sale':
late_invoices_count = 0
max_receivable = self.mt_contract_id.profile_id.max_receivable_id # макс. деб. задолженность в договоре
# ищу просроченные инвойсы контрагента указанного в заказе со стейтом "Подтверждено"
invoices_obj = self.env['account.move'].search([('partner_id', '=', self.partner_id.id),
('state', '=', 'posted'),
('invoice_date_due', '<', datetime.now().date())])
for invoice in invoices_obj:
late_invoices_count += invoice.amount_residual # складываю деб. задолженность по просроченным инвойсам
if late_invoices_count > max_receivable:
raise exceptions.ValidationError(
f'Нельзя подтвердить заказ, так как у контрагента {self.sec_partner_id.name} нарушено '
f'условие по дебиторской задолженности.\n\n'
f'Контрагент {self.sec_partner_id.name} должен {late_invoices_count}руб.\n'
f'Максимальная дебиторская задолженность указанная в '
f'договоре №{self.mt_contract_id.name} - {max_receivable}руб.\n\n'
f'Проверьте следующие неоплаченные счета контрагента:\n'
f'{", ".join([invoice.name for invoice in invoices_obj])}')
# при выбора счета "Обычный счет"
@api.model
def _create_invoices(self, grouped=False, final=False, date=None):
res = super(SaleOrder, self)._create_invoices(grouped, final, date)
if self.mt_contract_id:
res.write({'mt_contract_id': self.mt_contract_id,
'journal_id': self.mt_contract_id.profile_id.journal_id})
return res