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,21 @@
# Российская локализация - Доверенность
name: l10n_ru_attorney
## Описание
Создание списка доверенностей на получение ТМЦ и их печать.
###Создание доверенности:
1. Меню Покупки - Доверенности - кнопка "Создать";
2. На форме указываем:
2.1. Контрагент - поставщик;
2.2. Заказ на закупку;
2.3. Даты действия доверенности ("дата выдачи" и "действительно по").
###Для печати:
1. Меню Настройки - Техническое - Отчеты;
2. Находим в списке l10n_ru_attorney и добавляем в меню "Печать";
3. Открываем созданную запись доверенности - Действие - "Доверенность".

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import models

View File

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
{
'name': "Российская локализация - Доверенность",
'summary': """
Печать доверенности на получение ТМЦ
""",
'description': """
Создание списка доверенностей на получение ТМЦ и их печать.
Создание доверенности:
1. Меню Покупки - Доверенности - кнопка "Создать";
2. На форме указываем:
2.1. Контрагент - поставщик;
2.2. Заказ на закупку;
2.3. Даты действия доверенности ("дата выдачи" и "действительно по").
Для печати:
1. Меню Настройки - Техническое - Отчеты;
2. Находим в списке l10n_ru_attorney и добавляем в меню "Печать";
3. Открываем созданную запись доверенности - Действие - "Доверенность".
""",
'author': "MK.Lab",
'website': "https://www.inf-centre.ru/",
'category': 'Uncategorized',
'version': '0.1',
# any module necessary for this one to work correctly
'depends': ['base', 'account', 'sale', 'purchase', 'hr', 'l10n_ru_base'],
# always loaded
'data': [
'security/ir.model.access.csv',
'views/base_consent_views.xml',
'views/hr_employee_views.xml',
'views/purchase_order_views.xml',
'report/consent_report.xml',
],
'demo': [
'demo/demo.xml',
],
}

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Демо партнёр-поставщик для доверенности -->
<record id="demo_attorney_partner_supplier" model="res.partner">
<field name="name">ООО "Снабжение Плюс"</field>
<field name="is_company" eval="True"/>
<field name="city">Москва</field>
</record>
<!-- Демо сотрудник для доверенности -->
<record id="demo_employee_ivanov" model="hr.employee">
<field name="name">Иванов Иван Иванович</field>
<field name="job_title">Менеджер по закупкам</field>
</record>
<!-- Демо заказ на закупку -->
<record id="demo_purchase_order_001" model="purchase.order">
<field name="partner_id" ref="demo_attorney_partner_supplier"/>
<field name="date_order">2026-01-15 10:00:00</field>
</record>
<!-- Доверенность -->
<record id="demo_consent_001" model="base.consent">
<field name="name">ДОВ-2026-001</field>
<field name="date_from">2026-01-15</field>
<field name="date_to">2026-07-15</field>
<field name="partner_id" ref="demo_attorney_partner_supplier"/>
<field name="employee_id" ref="demo_employee_ivanov"/>
<field name="purchaseorder_id" ref="demo_purchase_order_001"/>
<field name="company_id" ref="base.main_company"/>
</record>
</data>
</odoo>

View File

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
from . import base_consent
from . import hr_employee
from . import purchase_order

View File

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
from datetime import datetime, timedelta
class BaseConsent(models.Model):
_name = 'base.consent'
_inherit = ['mail.thread', 'utm.mixin']
_description = 'Consent'
_order = 'date_from desc'
name = fields.Char(string=_('Номер'))
date_from = fields.Date(string=_('Дата выдачи'), default=lambda self: fields.Datetime.now())
date_to = fields.Date(string=_('Действительна по'), default=lambda self: datetime.today() + timedelta(days=180))
partner_id = fields.Many2one('res.partner', string=_('Контрагент'), required=1)
employee_id = fields.Many2one('hr.employee', string=_('Сотрудник'), required=1)
purchaseorder_id = fields.Many2one('purchase.order', _('Заказ на закупку'), domain="[('partner_id','=',partner_id)]",
required=1)
company_id = fields.Many2one('res.company', string=_('Компания'),
default=lambda self: self.env.company,
required=1)
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if not vals.get("name"):
vals["name"] = self.env["ir.sequence"].next_by_code("base.consent")
records = super().create(vals_list)
for record in records:
if record.purchaseorder_id:
record.purchaseorder_id.sudo().write({
"consent_id": record.id
})
return records
@api.onchange('purchaseorder_id')
def set_partner(self):
if self.purchaseorder_id:
self.partner_id = self.purchaseorder_id.partner_id
@api.constrains('purchaseorder_id')
def fill_order(self):
p_orders = self.env['purchase.order'].sudo().browse(self.purchaseorder_id.id)
for order in p_orders:
order.consent_id = self.id

View File

@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
from odoo import fields, models, _
class HrEmployee(models.Model):
_inherit = 'hr.employee'
inn = fields.Char(string=_("ИНН"))
pass_kem = fields.Char(string=_("Кем выдан паспорт"))
pass_date = fields.Date(string=_('Дата выдачи паспорта'))

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
from odoo import fields, models, _
class PurchaseOrder(models.Model):
_inherit = 'purchase.order'
consent_id = fields.Many2one('base.consent', string=_('Доверенность'))

View File

@ -0,0 +1,305 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<report
string="Доверенность"
id="action_report_consent"
model="base.consent"
report_type="qweb-pdf"
name="l10n_ru_attorney.report_consent"
file="l10n_ru_attorney.report_consent"
/>
<record model="ir.actions.report" id="l10n_ru_attorney.action_report_consent">
<field name="name">Доверенность</field>
<field name="model">base.consent</field>
<field name="print_report_name">(u'Доверенность - %s.pdf' % (object.name))</field>
<field name="binding_model_id" ref="model_base_consent"/>
<field name="report_type">qweb-pdf</field>
<field name="report_name">l10n_ru_attorney.report_consent</field>
</record>
<record id="paperformat_a4new" model="report.paperformat">
<field name="name">A4</field>
<field name="default" eval="True"/>
<field name="format">A4</field>
<field name="page_height">0</field>
<field name="page_width">0</field>
<field name="orientation">Portrait</field>
<field name="margin_top">15</field>
<field name="margin_bottom">15</field>
<field name="margin_left">7</field>
<field name="margin_right">7</field>
<field name="header_line" eval="False"/>
<field name="header_spacing">10</field>
<field name="dpi">90</field>
</record>
<template id="l10n_ru_attorney.report_consent">
<t t-call="web.basic_layout">
<t t-foreach="docs" t-as="o">
<div class="page">
<style type="text/css">
/*----------------Invoice classes-------------------*/
body { background: #ffffff; font-family: Arial; font-size: 8pt; font-style: normal; }
p { font-family: Arial; font-size: 13px; }
.tb-numbers { width: 600px; }
.tb-numbers td { padding: 4px 10px; font-family: Arial; }
.tb-numbers label {display: inline-block;padding: 4px 0px 0px;line-height: 20px;font-size:
12px;font-family: Arial;}
.tb-numbers input {width: 100px;}
.company-info {width: 100%;margin: 20px auto;}
.company-info td {padding: 5px 2px;font-size: 13px;font-family: Arial;}
.approver-info {width: 100%;margin: 20px auto;text-align: center;}
.approver-info .director {font-weight: bold;}
.approver-info .title {font-weight: bold;font-size: 18px;padding-bottom: 20px;}
.approver-info .signature {font-size: 11px;padding-top: 40px;}
.actDate {float: right;}
.actDescription {width: 100%;padding-top: 30px;}
.act-info p {font-weight: bold;}
.buyer-box {width: 50%;}
.buyer-box table {width: 100%;}
.buyer-box .tb-info {width: 310px;}
.buyer-box .tb-info td {padding: 2px 5px;}
.buyer-box .tb-info td.lbl {text-align: right;font-weight: bold;width: 40%;}
.tb-invoice {width: 100%;border-collapse: collapse;}
.tb-invoice td {padding: 2px 4px;border: 1px solid #ccc;font-size: 11px;text-align:
center;font-family: Arial;}
.tb-invoice td input {border: 0px;text-align: center;}
.tb-invoice .head td {background: #f3f3f3;font-weight: bold;}
.deleteRow {color: Red;font-size: 13px;font-weight: bold;cursor: pointer;}
.tb-act {width: 100%;border-collapse: collapse;margin-bottom: 15px;clear: both;margin: 20px
0px 10px 0px;}
.tb-act td {padding: 2px 4px;border: 1px solid #ccc;font-size:
13px;text-align:center;font-family: Arial;}
.tb-act td input {border: 0px;text-align:center;}
.tb-act .head td {background:#f3f3f3;}
.tb-act td .deleteActRow {color: Red;font-size :13 px;font-weight:bold;cursor:pointer;}
.description {font-size :12 px;text-align :justify;text-indent :1.5 em;white-space
:pre-wrap;}
.tb-total {width :405 px;margin: 30px 0px 20px auto;}
.tb-total td {padding: 2px 5px;font-size :12 px;}
.tb-total td.lbl {font-weight:bold;text-align:right;}
.tb-total td.val {border :1 px solid #ccc ;min-width :50 px ;}
.tb-total td input {padding :1 px ;font-size :11 px ;}
.act-link-addrow-container {margin-bottom :30 px ;}
.link-addrow, .act-link-addrow {text-decoration:none !important ;border-bottom :1 px dashed
#777 ;font-size :13 px ;}
.btn-box {margin: 10px 0px;}
.signer-box {width: 400px;}
.buyer-box .tb-info {width: 400px;}
.btn-box a {font-size: 14px;margin-left: 10px;}
.podpis-box {clear: both;display: inline-block;font-size: 11px;margin: 20px auto 30px;width:
100%;}
.tb-podpis {width: 100%;}
.podpis-box .box1 {float: left;width: 48%;}
/*SIGNATURES PLACE BEGIN*/
.signatures-box {clear: both;display: inline-block;font-size: 11px;margin: 20px auto
30px;width: 100%;}
.signatures-box .box1 {float: left;width: 35%;}
.signatures-box .box2 {float: right;width: 35%;}
.signatures-box .box1 table,
.signatures-box .box2 table { width: 100%; }
.signatures-box table td { padding: 3px; text-align: center; }
/*SIGNATURES PLACE END*/
.podpis-box .box2 { float: right; width: 48%; }
.podpis-box .box1 table,
.podpis-box .box2 table { width: 100%; }
.podpis-box table td { padding: 3px; text-align: center; }
.special-line { border-bottom: 1px solid #000; display: block; line-height: 22px
!important;text-align: center; }
.w120 { width: 120px; }
.w130 { width: 130px; }
.note { font-size: 11px !important; }
.l { text-align: left !important; }
.r { text-align: right !important; }
.c { text-align: center !important; }
</style>
<div>
<h4 style="text-align:center; margin:10px 5px 5px;">
Доверенность №
<span t-field="o.name"/>
</h4>
</div>
<table style="width:55%">
<tr>
<td>Дата выдачи:</td>
<td>
<span t-field="o.date_from" t-options="{&quot;widget&quot;: &quot;date&quot;}"/>
г.
</td>
</tr>
<tr>
<td>Доверенность действительна по:</td>
<td>
<span t-field="o.date_from" t-options="{&quot;widget&quot;: &quot;date&quot;}"/>
г.
</td>
</tr>
</table>
<div class="special-line" style="width:100%;margin-top:15px;text-align:center">
<b>
<span t-field="o.company_id.name"/>, адрес:<span t-field="o.company_id.street"/>, ИНН
<span t-field="o.company_id.vat"/>,
</b>
</div>
<div style="margin:0 auto;text-align:center;">наименование предприятия и его адрес</div>
<table style="width:100%;margin-top:10px">
<tr>
<td style="width:30%">
Доверенность выдана
</td>
<td style="width:100%;text-align:center;">
<span style="border-bottom:1px solid;display:block;clear:both;text-align:center;">
<span t-field="o.employee_id"/>,<span t-field="o.employee_id.job_id.name"/>, ИНН
<span t-field="o.employee_id.inn"/>
</span>
<span style="">
должность и Ф.И.О.
</span>
</td>
</tr>
</table>
<table style="width:100%">
<tr>
<td>Паспорт серия и №
<span style="padding:0 5px 0 5px;margin-left:12px;margin-right:12px;display:inline-table;"
class="special-line">
<span t-field="o.employee_id.passport_id"/>
</span>
</td>
</tr>
<tr>
<td>Кем выдан
<span style="padding:0 5px 0 5px;margin-left:12px;margin-right:12px;display:inline-table;"
class="special-line">
<span t-field="o.employee_id.pass_kem"/>
</span>
</td>
</tr>
<tr>
<td>Дата выдачи
<span style="padding:0 5px 0 5px;margin-left:12px;margin-right:12px;display:inline-table;"
class="special-line">
<span t-field="o.employee_id.pass_date"/>
</span>
</td>
</tr>
</table>
<table style="width:100%">
<tr>
<td style="width:25%">
На получение от
</td>
<td style="width:75%;text-align:center;">
<span style="border-bottom:1px solid;display:block;clear:both;">
<span t-field="o.partner_id"/>
</span>
<span style="">
наименование поставщика
</span>
</td>
</tr>
<tr>
<td style="width:25%">
Материальных ценностей по
</td>
<td style="width:75%;text-align:center;">
<span style="border-bottom:1px solid;display:block;clear:both;">
Заказу на закупку №
<span t-field="o.purchaseorder_id.name"/>
от
<span t-field="o.purchaseorder_id.date_order"
t-options="{&quot;widget&quot;: &quot;date&quot;}"/>
г.
</span>
<span style="">
наименование, номер и дата документа
</span>
</td>
</tr>
</table>
<div>
<h4 style="text-align:center; margin:15px 5px 15px;">
Перечень материальных ценностей,
<br/>
подлежащих получению
</h4>
</div>
<table class="tb-invoice">
<tr class="head">
<td></td>
<td style="min-width:200px;">Наименование товаров (работ, услуг)</td>
<td>Ед. изм.</td>
<td>Количество</td>
</tr>
<t t-set="num" t-value="1"/>
<t t-foreach="o.purchaseorder_id.order_line" t-as="line">
<tr class="data-row">
<td>
<t t-esc="num"/>
</td>
<td class="l" style="white-space: pre-wrap;">
<span t-field="line.name"/>
</td>
<td>
<span t-field="line.product_uom_id.name"/>
</td>
<td>
<span t-field="line.product_qty"/>
</td>
</tr>
<t t-set="num" t-value="num+1"/>
</t>
</table>
<div class="clear h100"></div>
<div>
<p></p>
<p>Подпись лица, получившего доверенность ___________________ удостоверяем</p>
</div>
<div class="podpis-box">
<table style="width:50%;">
<tr>
<td style="width: 13%;">Руководитель предприятия</td>
<td style="width: 19%; vertical-align: bottom;">
<span class="special-line"></span>
<span class="note">(подпись)</span>
</td>
<td style="width: 16%; vertical-align: bottom;">
<span class="special-line"></span>
<span class="note">(Ф.И.О)</span>
</td>
<!-- <td style="width: 16%; vertical-align: bottom;"></td>-->
</tr>
<tr>
<td style="width: 13%;">Гл. бухгалтер:</td>
<td style="width: 19%; vertical-align: bottom;">
<span class="special-line"></span>
<span class="note">(подпись)</span>
</td>
<td style="width: 16%; vertical-align: bottom;">
<span class="special-line"></span>
<span class="note">(Ф.И.О)</span>
</td>
</tr>
</table>
</div>
</div>
</t>
</t>
</template>
</data>
</odoo>

View File

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_base_consent,base.consent,model_base_consent,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_base_consent base.consent model_base_consent base.group_user 1 1 1 1

View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import test_base_consent

View File

@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
"""
Tests for l10n_ru_attorney — attorney (base.consent) creation, autonumbering,
and linkage to purchase orders.
Validates: Requirements 17.1, 17.2, 17.3
"""
from odoo.tests.common import TransactionCase
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _make_partner(env):
"""Create a company partner (supplier)."""
return env['res.partner'].create({
'name': 'Test Supplier Attorney',
'is_company': True,
})
def _make_employee(env):
"""Create a minimal hr.employee."""
return env['hr.employee'].create({
'name': 'Test Employee Attorney',
})
def _make_purchase_order(env, partner):
"""Create a minimal purchase.order."""
return env['purchase.order'].create({
'partner_id': partner.id,
})
def _make_consent(env, partner, employee, purchase_order, name=None):
"""Create a base.consent record."""
vals = {
'partner_id': partner.id,
'employee_id': employee.id,
'purchaseorder_id': purchase_order.id,
}
if name:
vals['name'] = name
return env['base.consent'].create(vals)
# ---------------------------------------------------------------------------
# TestBaseConsent
# ---------------------------------------------------------------------------
class TestBaseConsent(TransactionCase):
"""
Tests for base.consent creation: autonumbering via ir.sequence,
write-back of consent_id to purchase.order, and fill_order constraint.
Validates: Requirements 17.1, 17.2, 17.3
"""
def setUp(self):
super().setUp()
self.partner = _make_partner(self.env)
self.employee = _make_employee(self.env)
self.purchase_order = _make_purchase_order(self.env, self.partner)
# ------------------------------------------------------------------
# Requirement 17.1 — autonumber via ir.sequence when name is absent
# ------------------------------------------------------------------
def test_create_autonumber(self):
"""
Req 17.1 — creating base.consent without a name assigns a number
via ir.sequence with code 'base.consent'.
"""
consent = _make_consent(
self.env, self.partner, self.employee, self.purchase_order
)
self.assertTrue(
consent.name,
"consent.name should be set automatically via ir.sequence",
)
# A manually-supplied name must be preserved as-is
consent2 = _make_consent(
self.env, self.partner, self.employee, self.purchase_order,
name='MANUAL-001',
)
self.assertEqual(
consent2.name, 'MANUAL-001',
"Explicitly supplied name should not be overwritten",
)
# ------------------------------------------------------------------
# Requirement 17.2 — purchase.order.consent_id set after create
# ------------------------------------------------------------------
def test_create_writes_back_to_purchase_order(self):
"""
Req 17.2 — after creating base.consent linked to a purchase.order,
purchase.order.consent_id equals the created consent.
"""
consent = _make_consent(
self.env, self.partner, self.employee, self.purchase_order
)
self.purchase_order.invalidate_recordset()
self.assertEqual(
self.purchase_order.consent_id.id,
consent.id,
"purchase.order.consent_id should be set to the created consent",
)
# ------------------------------------------------------------------
# Requirement 17.3 — fill_order constraint writes consent_id to order
# ------------------------------------------------------------------
def test_constrains_fill_order(self):
"""
Req 17.3 — the @api.constrains('purchaseorder_id') method fill_order
writes consent_id into the linked purchase.order.
"""
# Create a second purchase order to reassign to
second_order = _make_purchase_order(self.env, self.partner)
consent = _make_consent(
self.env, self.partner, self.employee, self.purchase_order
)
# Reassign to second_order — this triggers fill_order constraint
consent.write({'purchaseorder_id': second_order.id})
second_order.invalidate_recordset()
self.assertEqual(
second_order.consent_id.id,
consent.id,
"fill_order should write consent_id into the newly assigned purchase.order",
)

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="base_consent_tree" model="ir.ui.view">
<field name="name">Consents</field>
<field name="model">base.consent</field>
<field name="arch" type="xml">
<list>
<field name="name"/>
<field name="partner_id"/>
<field name="employee_id"/>
<field name="date_from"/>
<field name="date_to"/>
</list>
</field>
</record>
<record id="base_consent_form" model="ir.ui.view">
<field name="name">consent.form</field>
<field name="model">base.consent</field>
<field name="arch" type="xml">
<form>
<header></header>
<sheet>
<group>
<group>
<field name="name" placeholder="Для автонумерации оставьте пустым"/>
<field name="partner_id"/>
<field name="employee_id"/>
</group>
<group>
<field name="date_from"/>
<field name="date_to"/>
<field name="purchaseorder_id"/>
<field name="company_id"/>
</group>
</group>
</sheet>
<chatter/>
</form>
</field>
</record>
<record id="l10n_ru_attorney.action_consent" model="ir.actions.act_window">
<field name="name">Доверенности</field>
<field name="res_model">base.consent</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="l10n_ru_attorney.menu_1" name="Доверенности" parent="purchase.menu_purchase_root"
action="l10n_ru_attorney.action_consent"/>
<record id="seq_consent" model="ir.sequence">
<field name="name">Consents</field>
<field name="code">base.consent</field>
<field name="prefix">CON</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
</data>
</odoo>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_employee_form" model="ir.ui.view">
<field name="name">view_employee_form.inherit</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='bank_account_ids']" position="after">
<field name="inn"/>
</xpath>
<xpath expr="//field[@name='passport_id']" position="after">
<field name="pass_kem"/>
<field name="pass_date"/>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="purchase_order_form" model="ir.ui.view">
<field name="name">purchase.order.form.inherit</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='currency_id']" position="after">
<field name="consent_id" context="{'default_partner_id':partner_id,'default_purchaseorder_id':id}"
domain="[('purchaseorder_id','=',id)]"/>
</xpath>
</field>
</record>
</data>
</odoo>