Public release from ruodoo-project: 19.0 - 2026-05-10 21:19:01 UTC
This commit is contained in:
9
l10n_ru_upd_xml/README.md
Normal file
9
l10n_ru_upd_xml/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Российская локализация - УПД в xml-формате
|
||||
name: l10n_ru_upd_xml
|
||||
|
||||
## Описание
|
||||
Формирует универсальный передаточный документ в формате XML.
|
||||
|
||||
###Для печати:
|
||||
1. Меню Бухгалтерия - Клиенты - Счета (account.move);
|
||||
2. Кнопка "Печать УПД в xml-формате".
|
||||
3
l10n_ru_upd_xml/__init__.py
Normal file
3
l10n_ru_upd_xml/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from . import controllers
|
||||
from . import models
|
||||
from . import reports
|
||||
47
l10n_ru_upd_xml/__manifest__.py
Normal file
47
l10n_ru_upd_xml/__manifest__.py
Normal file
@ -0,0 +1,47 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': "Российская локализация - УПД в xml-формате",
|
||||
|
||||
'summary': """
|
||||
Формирует УПД в формате XML, формат 5.01""",
|
||||
|
||||
'description': """
|
||||
Формирует УПД в формате XML.
|
||||
|
||||
Для печати:
|
||||
1. Меню Бухгалтерия - Клиенты - Счета (account.move);
|
||||
2. Кнопка "Печать УПД в xml-формате".
|
||||
""",
|
||||
|
||||
'author': "MK.Lab",
|
||||
'website': "https://www.inf-centre.ru/",
|
||||
|
||||
'category': 'Uncategorized',
|
||||
'version': '19.0.2025.12.11',
|
||||
"depends": ["web", "base", "account", "account_payment", "l10n_ru_doc", "l10n_ru_base", "uom"],
|
||||
"data": [
|
||||
"views/ir_actions_report_view.xml",
|
||||
"views/res_partner_view.xml",
|
||||
"views/res_company_view.xml",
|
||||
"views/res_users_view.xml",
|
||||
"views/views_uom_okei.xml",
|
||||
"views/view_account_move.xml",
|
||||
"reports/report.xml",
|
||||
"reports/upd_report.xml",
|
||||
],
|
||||
|
||||
"assets": {
|
||||
"web.assets_backend": [
|
||||
"l10n_ru_upd_xml/static/src/js/report/action_manager_report.js",
|
||||
],
|
||||
},
|
||||
|
||||
"external_dependencies": {
|
||||
"python": [
|
||||
"lxml"
|
||||
]
|
||||
},
|
||||
"demo": [
|
||||
"demo/demo.xml",
|
||||
],
|
||||
}
|
||||
3
l10n_ru_upd_xml/controllers/__init__.py
Normal file
3
l10n_ru_upd_xml/controllers/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import main
|
||||
91
l10n_ru_upd_xml/controllers/main.py
Normal file
91
l10n_ru_upd_xml/controllers/main.py
Normal file
@ -0,0 +1,91 @@
|
||||
# Copyright (C) 2014-2015 Grupo ESOC <www.grupoesoc.es>
|
||||
# License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html).
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from werkzeug.urls import url_parse
|
||||
|
||||
from odoo.http import content_disposition, request, route, serialize_exception
|
||||
from odoo.tools import html_escape
|
||||
from odoo.tools.safe_eval import safe_eval, time
|
||||
|
||||
from odoo.addons.web.controllers import report
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ReportController(report.ReportController):
|
||||
@route()
|
||||
def report_routes(
|
||||
self, reportname, docids=None, converter=None, options=None, **kwargs
|
||||
):
|
||||
if converter != "xml":
|
||||
return super().report_routes(
|
||||
reportname,
|
||||
docids=docids,
|
||||
converter=converter,
|
||||
options=options,
|
||||
**kwargs,
|
||||
)
|
||||
if docids:
|
||||
docids = [int(_id) for _id in docids.split(",")]
|
||||
data = {**json.loads(options or "{}"), **kwargs}
|
||||
context = dict(request.env.context)
|
||||
if "context" in data:
|
||||
data["context"] = json.loads(data["context"] or "{}")
|
||||
# Ignore 'lang' here, because the context in data is the one from the
|
||||
# webclient *but* if the user explicitely wants to change the lang, this
|
||||
# mechanism overwrites it.
|
||||
if "lang" in data["context"]:
|
||||
del data["context"]["lang"]
|
||||
context.update(data["context"])
|
||||
report_Obj = request.env["ir.actions.report"]
|
||||
xml = report_Obj.with_context(**context)._render_qweb_xml(
|
||||
reportname, docids, data=data
|
||||
)[0]
|
||||
xmlhttpheaders = [("Content-Type", "text/xml"), ("Content-Length", len(xml))]
|
||||
return request.make_response(xml, headers=xmlhttpheaders)
|
||||
|
||||
@route()
|
||||
def report_download(self, data, context=None, token=None):
|
||||
requestcontent = json.loads(data)
|
||||
url, report_type = requestcontent[0], requestcontent[1]
|
||||
reportname = "???"
|
||||
if report_type != "qweb-xml":
|
||||
return super().report_download(data, context=context, token=token)
|
||||
try:
|
||||
reportname = url.split("/report/xml/")[1].split("?")[0]
|
||||
docids = None
|
||||
if "/" in reportname:
|
||||
reportname, docids = reportname.split("/")
|
||||
report = request.env["ir.actions.report"]._get_report_from_name(reportname)
|
||||
filename = None
|
||||
if docids:
|
||||
response = self.report_routes(
|
||||
reportname, docids=docids, converter="xml", context=context
|
||||
)
|
||||
ids = [int(x) for x in docids.split(",")]
|
||||
obj = request.env[report.model].browse(ids)
|
||||
if report.print_report_name and not len(obj) > 1:
|
||||
report_name = safe_eval(
|
||||
report.print_report_name, {"object": obj, "time": time}
|
||||
)
|
||||
filename = f"{report_name}.{report.xml_extension}"
|
||||
else:
|
||||
data = url_parse(url).decode_query(cls=dict)
|
||||
if "context" in data:
|
||||
context = json.loads(context or "{}")
|
||||
data_context = json.loads(data.pop("context"))
|
||||
context = json.dumps({**context, **data_context})
|
||||
response = self.report_routes(
|
||||
reportname, converter="xml", context=context, **data
|
||||
)
|
||||
filename = filename or f"{report.name}.{report.xml_extension}"
|
||||
response.headers.add("Content-Disposition", content_disposition(filename))
|
||||
return response
|
||||
except Exception as e:
|
||||
_logger.exception(f"Error while generating report {reportname}")
|
||||
se = serialize_exception(e)
|
||||
error = {"code": 200, "message": "Odoo Server Error", "data": se}
|
||||
return request.make_response(html_escape(json.dumps(error)))
|
||||
11
l10n_ru_upd_xml/demo/demo.xml
Normal file
11
l10n_ru_upd_xml/demo/demo.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<!-- Добавляем код ОКЕИ 796 (штука) к стандартной единице измерения -->
|
||||
<record id="uom.product_uom_unit" model="uom.uom">
|
||||
<field name="okei">796</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
7
l10n_ru_upd_xml/models/__init__.py
Normal file
7
l10n_ru_upd_xml/models/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
from . import ir_actions_report
|
||||
from . import res_company
|
||||
from . import res_partner
|
||||
from . import res_users
|
||||
from . import uom_uom
|
||||
from . import account_move
|
||||
from . import account_move_line
|
||||
207
l10n_ru_upd_xml/models/account_move.py
Normal file
207
l10n_ru_upd_xml/models/account_move.py
Normal file
@ -0,0 +1,207 @@
|
||||
from odoo import api, fields, models, _
|
||||
import hashlib
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = 'account.move'
|
||||
|
||||
edi = fields.Char(string=_('ID EDI'), compute='_compute_sh1_edi')
|
||||
kpp = fields.Char(string=_('КПП'), compute='_compute_get_kpp')
|
||||
|
||||
def _compute_get_kpp(self):
|
||||
for s in self:
|
||||
pid = self.partner_id.parent_id or self.partner_id
|
||||
|
||||
s.kpp = pid.kpp if (s.partner_id == s.partner_shipping_id or not s.partner_shipping_id) else s.partner_shipping_id.kpp
|
||||
|
||||
@api.depends('name')
|
||||
def _compute_sh1_edi(self):
|
||||
hash_object = hashlib.sha1((self.name).encode('utf-8'))
|
||||
pid = self.partner_id.parent_id or self.partner_id
|
||||
self.edi = 'ON_NSCHFDOPPR_2BM-' + str(pid.edi) + '_' + str(self.company_id.edi) + '_' + hash_object.hexdigest()
|
||||
|
||||
def print_upd(self):
|
||||
for s in self:
|
||||
mes = str(s.check_correct_upd()).strip()
|
||||
if mes != "":
|
||||
raise UserError(_(u"Не удалось сформировать УПД. Выявлены следующие ошибки:\n{}".format(mes)))
|
||||
else:
|
||||
return self.env.ref('l10n_ru_upd_xml.upd_xml_report').sudo().report_action(s.id) # render_qweb_xml(s.id)
|
||||
|
||||
def check_correct_upd(self, manually=True):
|
||||
for s in self:
|
||||
mes = ""
|
||||
company = s.company_id
|
||||
if s.name == '/':
|
||||
mes += u"Отсутствует наименование документа. Проверидите документ, чтобы назваие сформировалось автоматически.\n"
|
||||
if not s.only_service and s.get_delivery_doc_name() == '0':
|
||||
mes += u"Отсутствуют связанные отгрузки.\n"
|
||||
if not company:
|
||||
company = self.env.company
|
||||
if not company:
|
||||
mes += u"Не указана компания.\n"
|
||||
else:
|
||||
if not company.edi:
|
||||
mes += u"Не указан идентификатор компании для Diadoc.\n"
|
||||
if not company.name:
|
||||
mes += u"Не указано наименование компании.\n"
|
||||
if not company.okpo:
|
||||
mes += u"Не указано ОКПО компании.\n"
|
||||
if not company.inn:
|
||||
mes += u"Не указан ИНН компании.\n"
|
||||
else:
|
||||
if len(company.inn) == 12:
|
||||
if not company.partner_id.last_name_IP:
|
||||
mes += u"Не указана фамилия ИП для вашей компании.\n"
|
||||
if not company.partner_id.first_name_IP:
|
||||
mes += u"Не указано имя ИП для вашей компании.\n"
|
||||
if not company.partner_id.middle_name_IP:
|
||||
mes += u"Не указано отчество ИП для вашей компании.\n"
|
||||
elif len(company.inn) == 10:
|
||||
if not company.kpp:
|
||||
mes += u"Не указан КПП компании.\n"
|
||||
else:
|
||||
mes += u"Некорректный ИНН компании.\n"
|
||||
if not company.city:
|
||||
mes += u"Не указан город компании.\n"
|
||||
if not company.street:
|
||||
mes += u"Не указан адрес компании.\n"
|
||||
if not company.chief_id:
|
||||
mes += u"Не указан руководитель компании.\n"
|
||||
else:
|
||||
if not company.chief_id.function:
|
||||
mes += u"Не указана должность руководителя компании.\n"
|
||||
if not company.chief_id.last_name:
|
||||
mes += u"Не указана фамилия руководителя компании.\n"
|
||||
if not company.chief_id.first_name:
|
||||
mes += u"Не указано имя руководителя компании.\n"
|
||||
if not company.chief_id.second_name:
|
||||
mes += u"Не указано отчество руководителя компании.\n"
|
||||
pid = s.partner_id.parent_id
|
||||
if not pid:
|
||||
pid = s.partner_id
|
||||
if not pid:
|
||||
mes += u"Не указан контрагент.\n"
|
||||
else:
|
||||
if not pid.edi:
|
||||
mes += u"Не указан идентификатор контрагента для Diadoc.\n"
|
||||
if not pid.name:
|
||||
mes += u"Не указано наименование контрагента.\n"
|
||||
if not pid.okpo:
|
||||
mes += u"Не указано ОКПО контрагента.\n"
|
||||
if not pid.inn:
|
||||
mes += u"Не указан ИНН контрагента.\n"
|
||||
else:
|
||||
if len(pid.inn) == 12:
|
||||
if not pid.last_name_IP:
|
||||
mes += u"Не указана фамилия ИП для контрагента.\n"
|
||||
if not pid.first_name_IP:
|
||||
mes += u"Не указано имя ИП для контрагента.\n"
|
||||
if not pid.middle_name_IP:
|
||||
mes += u"Не указано отчество ИП для контрагента.\n"
|
||||
elif len(pid.inn) == 10:
|
||||
if not pid.kpp:
|
||||
mes += u"Не указан КПП контрагента.\n"
|
||||
else:
|
||||
mes += u"Некорректный ИНН контрагента.\n"
|
||||
if not pid.city:
|
||||
mes += u"Не указан город контрагента.\n"
|
||||
if not pid.street:
|
||||
mes += u"Не указан адрес контрагента.\n"
|
||||
if manually:
|
||||
if not s.edi:
|
||||
mes += u"Не указан идентификатор документа для Diadoc.\n"
|
||||
if not s.name:
|
||||
mes += u"Не указано наименование документа\n"
|
||||
if not s.invoice_date:
|
||||
mes += u"Не указана дата документа\n"
|
||||
if not s.only_service:
|
||||
gruzootpr = s.gruzootpr
|
||||
if not gruzootpr:
|
||||
gruzootpr = pid
|
||||
if gruzootpr.parent_id:
|
||||
gruzootpr = gruzootpr.parent_id
|
||||
if not gruzootpr:
|
||||
mes += u"Не указан грузоотправитель.\n"
|
||||
else:
|
||||
if not gruzootpr.name:
|
||||
mes += u"Не указано наименование грузоотправителя.\n"
|
||||
if not gruzootpr.okpo:
|
||||
mes += u"Не указано ОКПО грузоотправителя.\n"
|
||||
if not gruzootpr.inn:
|
||||
mes += u"Не указан ИНН грузоотправителя.\n"
|
||||
else:
|
||||
if len(gruzootpr.inn) == 12:
|
||||
if not gruzootpr.last_name_IP:
|
||||
mes += u"Не указана фамилия ИП для грузоотправителя.\n"
|
||||
if not gruzootpr.first_name_IP:
|
||||
mes += u"Не указано имя ИП для грузоотправителя.\n"
|
||||
if not gruzootpr.middle_name_IP:
|
||||
mes += u"Не указано отчество ИП для грузоотправителя.\n"
|
||||
elif len(gruzootpr.inn) == 10:
|
||||
if not gruzootpr.kpp:
|
||||
mes += u"Не указан КПП грузоотправителя.\n"
|
||||
else:
|
||||
mes += u"Некорректный ИНН грузоотправителя.\n"
|
||||
if not gruzootpr.city:
|
||||
mes += u"Не указан город грузоотправителя.\n"
|
||||
if not gruzootpr.street:
|
||||
mes += u"Не указан адрес грузоотправителя.\n"
|
||||
gruzopol = s.gruzopol
|
||||
if not gruzopol:
|
||||
gruzopol = pid
|
||||
if gruzopol.parent_id:
|
||||
gruzopol = gruzopol.parent_id
|
||||
if not gruzopol:
|
||||
mes += u"Не указан грузополучатель.\n"
|
||||
else:
|
||||
if not gruzopol.name:
|
||||
mes += u"Не указано наименование грузополучателя.\n"
|
||||
if not gruzopol.okpo:
|
||||
mes += u"Не указано ОКПО грузополучателя.\n"
|
||||
if not gruzopol.inn:
|
||||
mes += u"Не указан ИНН грузополучателя.\n"
|
||||
else:
|
||||
if len(gruzopol.inn) == 12:
|
||||
if not gruzopol.last_name_IP:
|
||||
mes += u"Не указана фамилия ИП для грузополучателя.\n"
|
||||
if not gruzopol.first_name_IP:
|
||||
mes += u"Не указано имя ИП для грузополучателя.\n"
|
||||
if not gruzopol.middle_name_IP:
|
||||
mes += u"Не указано отчество ИП для грузополучателя.\n"
|
||||
elif len(gruzopol.inn) == 10:
|
||||
if not gruzopol.kpp:
|
||||
mes += u"Не указан КПП грузополучателя.\n"
|
||||
else:
|
||||
mes += u"Некорректный ИНН грузополучателя.\n"
|
||||
if not gruzopol.city:
|
||||
mes += u"Не указан город грузополучателя.\n"
|
||||
if not gruzopol.street:
|
||||
mes += u"Не указан адрес грузополучателя.\n"
|
||||
if s.payment_num:
|
||||
if not s.payment_date:
|
||||
mes += u"Не указана дата платежки в УПД.\n"
|
||||
if not s.invoice_line_ids:
|
||||
mes += u"Отсутствуют строки заказа.\n"
|
||||
else:
|
||||
for line in s.invoice_line_ids:
|
||||
if not line.price_unit:
|
||||
mes += u"Не указана цена за единицу для товара {}.\n".format(line.name)
|
||||
if not line.quantity:
|
||||
mes += u"Не указано количество для товара {}.\n".format(line.name)
|
||||
if not line.product_uom_id.okei:
|
||||
mes += u"Не указан код ОКЕИ для единицы измерения {}.\n".format(line.product_uom_id.name)
|
||||
if not s.mt_contract_id:
|
||||
mes += u"Не указан договор.\n"
|
||||
else:
|
||||
if not s.mt_contract_id.name:
|
||||
mes += u"Не указано наименование договора.\n"
|
||||
if not s.mt_contract_id.date_start:
|
||||
mes += u"Не указана дата договора.\n"
|
||||
if not s.kladov:
|
||||
mes += u"Не указано лицо, ответственное за передачу товаров/услуг.\n"
|
||||
else:
|
||||
if not s.kladov.partner_id.function:
|
||||
mes += u"Не указана должность лица, ответственного за передачу товаров/услуг.\n"
|
||||
return str(mes)
|
||||
7
l10n_ru_upd_xml/models/account_move_line.py
Normal file
7
l10n_ru_upd_xml/models/account_move_line.py
Normal file
@ -0,0 +1,7 @@
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = "account.move.line"
|
||||
|
||||
uom_okei = fields.Char(string=_('Код ОКЕИ'), related='product_uom_id.okei')
|
||||
62
l10n_ru_upd_xml/models/ir_actions_report.py
Normal file
62
l10n_ru_upd_xml/models/ir_actions_report.py
Normal file
@ -0,0 +1,62 @@
|
||||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class IrActionsReport(models.Model):
|
||||
_inherit = "ir.actions.report"
|
||||
|
||||
report_type = fields.Selection(
|
||||
selection_add=[("qweb-xml", "XML")], ondelete={"qweb-xml": "set default"}
|
||||
)
|
||||
xsd_schema = fields.Binary(
|
||||
string="XSD Validation Schema",
|
||||
attachment=True,
|
||||
help="File with XSD Schema for checking content of result report. Can be empty "
|
||||
"if validation is not required.",
|
||||
)
|
||||
xml_encoding = fields.Selection(
|
||||
selection=[
|
||||
("WINDOWS-1251", "WINDOWS-1251") # will be used as default even if nothing is selected
|
||||
],
|
||||
string="XML Encoding",
|
||||
help=(
|
||||
"Encoding for XML reports. If nothing is selected, "
|
||||
"then UTF-8 will be applied."
|
||||
),
|
||||
)
|
||||
xml_declaration = fields.Boolean(
|
||||
string="XML Declaration",
|
||||
help=(
|
||||
"""Add `<?xml encoding="..." version="..."?>` at the start """
|
||||
"""of final report file."""
|
||||
),
|
||||
default=True,
|
||||
)
|
||||
xml_extension = fields.Char(
|
||||
default="xml",
|
||||
help="Extension for XML Reports, by default is `xml`",
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _render_qweb_xml(self, report_ref, res_ids, data=None):
|
||||
"""
|
||||
Call `generate_report` method of report abstract class
|
||||
`report.<report technical name>` or of standard class for XML report
|
||||
rendering - `report.report_xml.abstract`
|
||||
|
||||
Args:
|
||||
* docids(list) - IDs of instances for those report will be generated
|
||||
* data(dict, None) - variables for report rendering
|
||||
|
||||
Returns:
|
||||
* str - result content of report
|
||||
* str - type of result content
|
||||
"""
|
||||
report = self._get_report(report_ref)
|
||||
report_model = self.env.get(
|
||||
f"report.{report.report_name}", self.env["report.upd_xml.abstract"]
|
||||
)
|
||||
return report_model.generate_report(
|
||||
ir_report=report, # will be used to get settings of report
|
||||
docids=res_ids,
|
||||
data=data or {},
|
||||
)
|
||||
11
l10n_ru_upd_xml/models/res_company.py
Normal file
11
l10n_ru_upd_xml/models/res_company.py
Normal file
@ -0,0 +1,11 @@
|
||||
from odoo import fields, models, _
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = 'res.company'
|
||||
|
||||
inn = fields.Char(related='partner_id.inn', readonly=False, string=_('ИНН'))
|
||||
kpp = fields.Char(related='partner_id.kpp', readonly=False, string=_('КПП'))
|
||||
okpo = fields.Char(related='partner_id.okpo', readonly=False, string=_('ОКПО'))
|
||||
edi = fields.Char(string='ID EDI', readonly=False)
|
||||
chief_id = fields.Many2one('res.users', string=_('Управляющий'))
|
||||
36
l10n_ru_upd_xml/models/res_partner.py
Normal file
36
l10n_ru_upd_xml/models/res_partner.py
Normal file
@ -0,0 +1,36 @@
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
inn = fields.Char(_('ИНН'), size=12)
|
||||
kpp = fields.Char(_('КПП'), size=9)
|
||||
okpo = fields.Char(_('ОКПО'), size=14)
|
||||
edi = fields.Char(_('ID EDI'))
|
||||
house = fields.Char(_('Дом'))
|
||||
office = fields.Char(_('Квартира, офис'))
|
||||
fias_id = fields.Char(_('Код ФИАС'))
|
||||
last_name_IP = fields.Char(_('Фамилия ИП'), compute='_compute_get_fio', readonly=False)
|
||||
first_name_IP = fields.Char(_('Имя ИП'), compute='_compute_get_fio', readonly=False)
|
||||
middle_name_IP = fields.Char(_('Отчество ИП'), compute='_compute_get_fio', readonly=False)
|
||||
|
||||
@api.depends('name')
|
||||
def _compute_get_fio(self):
|
||||
for s in self:
|
||||
if s.name:
|
||||
name = s.name
|
||||
if name.find('ИП ') != -1:
|
||||
name = name[name.find(' ') + 1:]
|
||||
s.last_name_IP = name[:name.find(' ')]
|
||||
name = name[name.find(' ') + 1:]
|
||||
s.first_name_IP = name[:name.find(' ')]
|
||||
name = name[name.find(' ') + 1:]
|
||||
s.middle_name_IP = name
|
||||
else:
|
||||
s.last_name_IP = ""
|
||||
s.first_name_IP = ""
|
||||
s.middle_name_IP = ""
|
||||
else:
|
||||
s.last_name_IP = ""
|
||||
s.first_name_IP = ""
|
||||
s.middle_name_IP = ""
|
||||
25
l10n_ru_upd_xml/models/res_users.py
Normal file
25
l10n_ru_upd_xml/models/res_users.py
Normal file
@ -0,0 +1,25 @@
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
class ResUsers(models.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
last_name = fields.Char(string=_('Фамилия'), compute='_compute_update_name')
|
||||
first_name = fields.Char(string=_('Имя'), compute='_compute_update_name')
|
||||
second_name = fields.Char(string=_('Отчество'), compute='_compute_update_name')
|
||||
|
||||
@api.depends('name')
|
||||
def _compute_update_name(self):
|
||||
for s in self:
|
||||
s.last_name = ''
|
||||
s.first_name = ''
|
||||
s.second_name = ''
|
||||
if s.name:
|
||||
s.last_name = s.name
|
||||
s.first_name = ''
|
||||
s.second_name = ''
|
||||
if len(s.name.split(' ')) == 3:
|
||||
s.last_name, s.first_name, s.second_name = s.name.split(' ')
|
||||
if len(s.name.split(' ')) == 4:
|
||||
s.last_name, s.first_name, second_name1, second_name2 = s.name.split(' ')
|
||||
s.second_name = second_name1 + ' ' + second_name2
|
||||
|
||||
7
l10n_ru_upd_xml/models/uom_uom.py
Normal file
7
l10n_ru_upd_xml/models/uom_uom.py
Normal file
@ -0,0 +1,7 @@
|
||||
from odoo import fields, models, _
|
||||
|
||||
|
||||
class UomUom(models.Model):
|
||||
_inherit = "uom.uom"
|
||||
|
||||
okei = fields.Char(string="Код ОКЕИ")
|
||||
3
l10n_ru_upd_xml/reports/__init__.py
Normal file
3
l10n_ru_upd_xml/reports/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
# License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html).
|
||||
|
||||
from . import report_report_xml_abstract
|
||||
76
l10n_ru_upd_xml/reports/demo_report.xml
Normal file
76
l10n_ru_upd_xml/reports/demo_report.xml
Normal file
@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<template id="demo_report_xml_view">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
<!-- Multicompany -->
|
||||
<t t-if="o and 'company_id' in o">
|
||||
<t t-set="company" t-value="o.company_id"></t>
|
||||
</t>
|
||||
<t t-if="not o or not 'company_id' in o">
|
||||
<t t-set="company" t-value="res_company"></t>
|
||||
</t>
|
||||
<t t-set="context" t-value="o._context"></t>
|
||||
<Файл t-att="{'ИдФайл':o.edi or '','ВерсПрог':'[bi-cons.ru] v.1.6','ВерсФорм':'5.01'}">
|
||||
<СвУчДокОбор t-att="{'ИдОтпр':company.edi,'ИдПол':o.partner_id.edi}">
|
||||
</СвУчДокОбор>
|
||||
<Документ t-att="{'КНД':'1115125','Функция':'СЧФДОП','ПоФактХЖ':'Документ об отгрузке товаров (выполнении работ), передаче имущественных прав (документ об оказании услуг)','НаимДокОпр':'Счет-фактура и документ об отгрузке товаров (выполнении работ), передаче имущественных прав (документ об оказании услуг)','ДатаИнфПр':datetime.datetime.now().strftime('%d.%m.%Y'),'ВремИнфПр':datetime.datetime.now().strftime('%H.%M.%S'),'НаимЭконСубСост':company.name or ''}">
|
||||
<СвСчФакт t-att="{'НомерСчФ':o.name or '','ДатаСчФ':o.invoice_date.strftime('%d.%m.%Y'),'КодОКВ':'643'}">
|
||||
<СвПрод t-att="{'ОКПО':company.okpo or '','КраткНазв':company.name or ''}">
|
||||
<ИдСв>
|
||||
<СвЮЛУч t-att="{'НаимОрг':company.name or '','ИННЮЛ':company.inn or '','КПП':company.kpp or ''}"/>
|
||||
</ИдСв>
|
||||
<Адрес>
|
||||
<АдрРФ t-att="{'КодРегион':company.state_id.code or '','Город':company.city or ''}"/>
|
||||
</Адрес>
|
||||
</СвПрод>
|
||||
<ГрузОт>
|
||||
<ОнЖе>он же</ОнЖе>
|
||||
</ГрузОт>
|
||||
<ГрузПолуч t-att="{'ОКПО':o.partner_id.okpo or '','КраткНазв':o.partner_id.name or ''}">
|
||||
<ИдСв>
|
||||
<СвЮЛУч t-att="{'НаимОрг':o.partner_id.name or '','ИННЮЛ':o.partner_id.inn or '','КПП':o.partner_id.kpp or ''}"/>
|
||||
</ИдСв>
|
||||
<Адрес>
|
||||
<АдрРФ t-att="{'КодРегион':o.partner_id.state_id.code or '','Город':o.partner_id.city or ''}"/>
|
||||
</Адрес>
|
||||
</ГрузПолуч>
|
||||
|
||||
<СвПокуп t-att="{'ОКПО':o.partner_id.okpo or '','КраткНазв':o.partner_id.name or ''}">
|
||||
<ИдСв>
|
||||
<СвЮЛУч t-att="{'НаимОрг':o.partner_id.name or '','ИННЮЛ':o.partner_id.inn or '','КПП':o.partner_id.kpp or ''}"/>
|
||||
</ИдСв>
|
||||
<Адрес>
|
||||
<АдрРФ t-att="{'КодРегион':o.partner_id.state_id.code or '','Город':o.partner_id.city or ''}"/>
|
||||
</Адрес>
|
||||
</СвПокуп>
|
||||
</СвСчФакт>
|
||||
<ТаблСчФакт>
|
||||
<t t-set="total_price_tax_subtotal" t-value="sum([line.price_unit*line.quantity for line in o.invoice_line_ids])"/>
|
||||
<t t-set="total_tax_subtotal" t-value="sum([line.price_unit*line.quantity*(line.tax_ids.amount/100) for line in o.invoice_line_ids])"/>
|
||||
<t t-foreach="o.invoice_line_ids" t-as="line">
|
||||
<СведТов t-att="{'НомСтр':line_index+1,'НаимТов':line.name or '','ОКЕИ_Тов':line.product_uom_id.okei or '','КолТов':line.quantity or '','ЦенаТов':(('%.2f')%(line.price_subtotal/line.quantity)).replace('.',',') or '','СтТовБезНДС':(('%.2f')%(line.price_subtotal)).replace('.',',') or '','НалСт':(('%.0f')%(line.tax_ids.amount)).replace('.',',')+'%' or 'без НДС','СтТовУчНал':(('%.2f')%(line.price_subtotal*(1+(line.tax_ids.amount/100)))).replace('.',',') or ''}">
|
||||
<Акциз>
|
||||
<БезАкциз>без акциза</БезАкциз>
|
||||
</Акциз>
|
||||
<СумНал>
|
||||
<СумНал><t t-esc="(('%.2f')%(line.price_subtotal*(line.tax_ids.amount/100))).replace('.',',') or ''"/></СумНал>
|
||||
</СумНал>
|
||||
</СведТов>
|
||||
</t>
|
||||
<ВсегоОпл t-att="{'СтТовБезНДСВсего':(('%.2f')%(total_price_tax_subtotal)).replace('.',',') or '','СтТовУчНалВсего':(('%.2f')%(total_price_tax_subtotal+total_tax_subtotal)).replace('.',',') or ''}">
|
||||
<СумНалВсего>
|
||||
<СумНал><t t-esc="(('%.2f')%(total_tax_subtotal)).replace('.',',') or ''"/></СумНал>
|
||||
</СумНалВсего>
|
||||
</ВсегоОпл>
|
||||
</ТаблСчФакт>
|
||||
<Подписант t-att="{'ОблПолн':'0','Статус':'1','ОснПолн':'Должностные обязанности'}">
|
||||
<ЮЛ t-att="{'ИННЮЛ':company.inn or '','НаимОрг':company.name or '','Должн':company.chief_id.function or ''}">
|
||||
<ФИО t-att="{'Фамилия':company.chief_id.last_name or '','Имя':company.chief_id.first_name or '','Отчество':company.chief_id.second_name or ''}"/>
|
||||
</ЮЛ>
|
||||
</Подписант>
|
||||
</Документ>
|
||||
</Файл>
|
||||
|
||||
</t>
|
||||
</template>
|
||||
</odoo>
|
||||
14
l10n_ru_upd_xml/reports/report.xml
Normal file
14
l10n_ru_upd_xml/reports/report.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
|
||||
<record id="upd_xml_report" model="ir.actions.report">
|
||||
<field name="name">УПД xml</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="report_type">qweb-xml</field>
|
||||
<field name="report_name">l10n_ru_upd_xml.demo_report_xml_view</field>
|
||||
<field name="report_file">l10n_ru_upd_xml.demo_report_xml_view</field>
|
||||
<field name="print_report_name">'%s' % (object.edi)</field>
|
||||
<field name="binding_model_id" ref="account.model_account_move"/>
|
||||
<field name="binding_type">report</field>
|
||||
</record>
|
||||
</odoo>
|
||||
177
l10n_ru_upd_xml/reports/report_report_xml_abstract.py
Normal file
177
l10n_ru_upd_xml/reports/report_report_xml_abstract.py
Normal file
@ -0,0 +1,177 @@
|
||||
# License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html).
|
||||
|
||||
from xml.dom import minidom
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools.translate import _
|
||||
from lxml import etree
|
||||
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class ReportXmlAbstract(models.AbstractModel):
|
||||
_name = "report.upd_xml.abstract"
|
||||
_description = "Abstract XML Report"
|
||||
|
||||
@api.model
|
||||
def generate_report(self, ir_report, docids, data=None):
|
||||
data = data or {}
|
||||
data.setdefault("report_type", "text")
|
||||
|
||||
records = self.env[ir_report.model].browse(docids)
|
||||
|
||||
for s in records:
|
||||
mes = ""
|
||||
company = s.company_id or self.env.company
|
||||
|
||||
# --- базовые проверки ---
|
||||
if s.name == '/':
|
||||
mes += "Отсутствует наименование документа. Проведите документ.\n"
|
||||
|
||||
if not s.only_service:
|
||||
get_delivery = getattr(s, "get_delivery_doc_name", lambda: '0')
|
||||
if get_delivery() == '0':
|
||||
mes += "Отсутствуют связанные отгрузки.\n"
|
||||
|
||||
# --- компания ---
|
||||
if not company:
|
||||
mes += "Не указана компания.\n"
|
||||
else:
|
||||
if not company.edi:
|
||||
mes += "Не указан идентификатор компании для Diadoc.\n"
|
||||
if not company.name:
|
||||
mes += "Не указано наименование компании.\n"
|
||||
if not company.okpo:
|
||||
mes += "Не указано ОКПО компании.\n"
|
||||
|
||||
if not company.inn:
|
||||
mes += "Не указан ИНН компании.\n"
|
||||
else:
|
||||
if len(company.inn) == 12:
|
||||
if not company.partner_id.last_name_IP:
|
||||
mes += "Не указана фамилия ИП компании.\n"
|
||||
if not company.partner_id.first_name_IP:
|
||||
mes += "Не указано имя ИП компании.\n"
|
||||
if not company.partner_id.middle_name_IP:
|
||||
mes += "Не указано отчество ИП компании.\n"
|
||||
elif len(company.inn) == 10:
|
||||
if not company.kpp:
|
||||
mes += "Не указан КПП компании.\n"
|
||||
else:
|
||||
mes += "Некорректный ИНН компании.\n"
|
||||
|
||||
if not company.city:
|
||||
mes += "Не указан город компании.\n"
|
||||
if not company.street:
|
||||
mes += "Не указан адрес компании.\n"
|
||||
|
||||
if not company.chief_id:
|
||||
mes += "Не указан руководитель компании.\n"
|
||||
else:
|
||||
if not company.chief_id.function:
|
||||
mes += "Не указана должность руководителя.\n"
|
||||
if not company.chief_id.last_name:
|
||||
mes += "Не указана фамилия руководителя.\n"
|
||||
if not company.chief_id.first_name:
|
||||
mes += "Не указано имя руководителя.\n"
|
||||
if not company.chief_id.second_name:
|
||||
mes += "Не указано отчество руководителя.\n"
|
||||
|
||||
# --- контрагент ---
|
||||
pid = s.partner_id.parent_id or s.partner_id
|
||||
|
||||
if not pid:
|
||||
mes += "Не указан контрагент.\n"
|
||||
else:
|
||||
if not pid.edi:
|
||||
mes += "Не указан идентификатор контрагента.\n"
|
||||
if not pid.name:
|
||||
mes += "Не указано наименование контрагента.\n"
|
||||
if not pid.okpo:
|
||||
mes += "Не указано ОКПО контрагента.\n"
|
||||
|
||||
if not pid.inn:
|
||||
mes += "Не указан ИНН контрагента.\n"
|
||||
else:
|
||||
if len(pid.inn) == 12:
|
||||
if not pid.last_name_IP:
|
||||
mes += "Не указана фамилия ИП контрагента.\n"
|
||||
if not pid.first_name_IP:
|
||||
mes += "Не указано имя ИП контрагента.\n"
|
||||
if not pid.middle_name_IP:
|
||||
mes += "Не указано отчество ИП контрагента.\n"
|
||||
elif len(pid.inn) == 10:
|
||||
if not pid.kpp:
|
||||
mes += "Не указан КПП контрагента.\n"
|
||||
else:
|
||||
mes += "Некорректный ИНН контрагента.\n"
|
||||
|
||||
if not pid.city:
|
||||
mes += "Не указан город контрагента.\n"
|
||||
if not pid.street:
|
||||
mes += "Не указан адрес контрагента.\n"
|
||||
|
||||
# --- документ ---
|
||||
if not s.edi:
|
||||
mes += "Не указан идентификатор документа.\n"
|
||||
if not s.name:
|
||||
mes += "Не указано наименование документа.\n"
|
||||
if not s.invoice_date:
|
||||
mes += "Не указана дата документа.\n"
|
||||
|
||||
# --- строки ---
|
||||
if not s.invoice_line_ids:
|
||||
mes += "Отсутствуют строки.\n"
|
||||
else:
|
||||
for line in s.invoice_line_ids:
|
||||
if not line.price_unit:
|
||||
mes += f"Нет цены: {line.name}\n"
|
||||
if not line.quantity:
|
||||
mes += f"Нет количества: {line.name}\n"
|
||||
if not line.product_uom_id.okei:
|
||||
mes += f"Нет ОКЕИ: {line.product_uom_id.name}\n"
|
||||
|
||||
# --- договор ---
|
||||
# if not s.mt_contract_id:
|
||||
# mes += "Не указан договор.\n"
|
||||
# else:
|
||||
# if not s.mt_contract_id.name:
|
||||
# mes += "Нет названия договора.\n"
|
||||
# if not s.mt_contract_id.date_start:
|
||||
# mes += "Нет даты договора.\n"
|
||||
|
||||
# --- ответственный ---
|
||||
if not s.kladov:
|
||||
mes += "Не указано ответственное лицо.\n"
|
||||
else:
|
||||
if not s.kladov.partner_id.function:
|
||||
mes += "Не указана должность ответственного.\n"
|
||||
|
||||
# ❗ если есть ошибки → стоп
|
||||
if mes:
|
||||
raise UserError(_(
|
||||
"Не удалось сформировать УПД. Выявлены следующие ошибки:\n%s"
|
||||
) % mes)
|
||||
|
||||
data = ir_report._get_rendering_context(ir_report, docids, data)
|
||||
|
||||
result_bin = ir_report._render_template(ir_report.report_name, data)
|
||||
|
||||
parsed_result_bin = minidom.parseString(result_bin)
|
||||
result = parsed_result_bin.toprettyxml(indent=" ")
|
||||
|
||||
result = "\n".join(
|
||||
line for line in result.splitlines() if line and not line.isspace()
|
||||
).encode("utf8")
|
||||
|
||||
content = etree.tostring(
|
||||
etree.fromstring(result),
|
||||
encoding=ir_report.xml_encoding or "WINDOWS-1251",
|
||||
xml_declaration=True,
|
||||
pretty_print=True,
|
||||
)
|
||||
|
||||
return content, "xml"
|
||||
|
||||
@api.model
|
||||
def _get_report_values(self, docids, data=None):
|
||||
return data or {}
|
||||
344
l10n_ru_upd_xml/reports/upd_report.xml
Normal file
344
l10n_ru_upd_xml/reports/upd_report.xml
Normal file
@ -0,0 +1,344 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<template id="demo_report_xml_view">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
<!-- Multicompany -->
|
||||
<t t-if="o and o.company_id">
|
||||
<t t-set="company" t-value="o.company_id"/>
|
||||
</t>
|
||||
<t t-elif="not o or not o.company_id">
|
||||
<t t-set="company" t-value="res_company"/>
|
||||
</t>
|
||||
|
||||
<t t-set="pid" t-value="o and o.partner_id and (o.partner_id.parent_id or o.partner_id) or False"/>
|
||||
|
||||
<Файл t-att="{
|
||||
'ИдФайл': o and o.edi and (o.edi + '_0_0_0_0_0_00') or '',
|
||||
'ВерсФорм': '5.03',
|
||||
'ВерсПрог': 'Odoo19'
|
||||
}">
|
||||
<Документ t-att="{
|
||||
'КНД': '1115131',
|
||||
'Функция': 'СЧФДОП',
|
||||
'ПоФактХЖ': 'Документ об отгрузке товаров (выполнении работ), передаче имущественных прав (документ об оказании услуг)',
|
||||
'НаимДокОпр': 'Счет-фактура и документ об отгрузке товаров (выполнении работ), передаче имущественных прав (документ об оказании услуг)',
|
||||
'ДатаИнфПр': time.strftime('%d.%m.%Y'),
|
||||
'ВремИнфПр': time.strftime('%H.%M.%S'),
|
||||
'НаимЭконСубСост': company and company.name or ''
|
||||
}">
|
||||
<СвСчФакт t-att="{
|
||||
'НомерДок': o and (o.invoice_origin or o.name) or '',
|
||||
'ДатаДок': o and o.invoice_date and o.invoice_date.strftime('%d.%m.%Y') or ''
|
||||
}">
|
||||
<СвПрод t-att="{'ОКПО': company and company.okpo or ''}">
|
||||
<ИдСв>
|
||||
<t t-if="company and company.inn and len(company.inn) == 10">
|
||||
<СвЮЛУч t-att="{
|
||||
'НаимОрг': company.name or '',
|
||||
'ИННЮЛ': company.inn or '',
|
||||
'КПП': company.kpp or ''
|
||||
}"/>
|
||||
</t>
|
||||
<t t-elif="company and company.inn and len(company.inn) == 12">
|
||||
<СвИП t-att="{
|
||||
'ИННФЛ': company.inn or '',
|
||||
'ИныеСвед': company.name or ''
|
||||
}">
|
||||
<ФИО t-att="{
|
||||
'Фамилия': company.partner_id and company.partner_id.last_name_IP or '',
|
||||
'Имя': company.partner_id and company.partner_id.first_name_IP or '',
|
||||
'Отчество': company.partner_id and company.partner_id.middle_name_IP or ''
|
||||
}"/>
|
||||
</СвИП>
|
||||
</t>
|
||||
</ИдСв>
|
||||
<Адрес>
|
||||
<АдрИнф t-att="{
|
||||
'КодСтр': '643',
|
||||
'НаимСтран': 'Российская Федерация',
|
||||
'АдрТекст': (company.city or '') + ', ' + (company.street or '')
|
||||
}"/>
|
||||
</Адрес>
|
||||
</СвПрод>
|
||||
|
||||
<t t-if="o and o.only_service == False">
|
||||
<ГрузОт>
|
||||
<ГрузОтпр>
|
||||
<t t-set="gruzootpr" t-value="o.gruzootpr or company.partner_id"/>
|
||||
<t t-set="gruzootpr" t-value="gruzootpr and (gruzootpr.parent_id or gruzootpr)"/>
|
||||
<ИдСв>
|
||||
<t t-if="gruzootpr and gruzootpr.inn and len(gruzootpr.inn) == 10">
|
||||
<СвЮЛУч t-att="{
|
||||
'НаимОрг': gruzootpr.name or '',
|
||||
'ИННЮЛ': gruzootpr.inn or '',
|
||||
'КПП': gruzootpr.kpp or ''
|
||||
}"/>
|
||||
</t>
|
||||
<t t-elif="gruzootpr and gruzootpr.inn and len(gruzootpr.inn) == 12">
|
||||
<СвИП t-att="{
|
||||
'ИННФЛ': gruzootpr.inn or '',
|
||||
'ИныеСвед': gruzootpr.name or ''
|
||||
}">
|
||||
<ФИО t-att="{
|
||||
'Фамилия': gruzootpr.last_name_IP or '',
|
||||
'Имя': gruzootpr.first_name_IP or '',
|
||||
'Отчество': gruzootpr.middle_name_IP or ''
|
||||
}"/>
|
||||
</СвИП>
|
||||
</t>
|
||||
</ИдСв>
|
||||
<Адрес>
|
||||
<АдрИнф t-att="{
|
||||
'КодСтр': '643',
|
||||
'НаимСтран': 'Российская Федерация',
|
||||
'АдрТекст': (gruzootpr.city or '') + ', ' + (gruzootpr.street or '')
|
||||
}"/>
|
||||
</Адрес>
|
||||
</ГрузОтпр>
|
||||
</ГрузОт>
|
||||
|
||||
<t t-set="gruzopol" t-value="o.gruzopol or pid"/>
|
||||
<t t-set="gruzopol" t-value="gruzopol and (gruzopol.parent_id or gruzopol)"/>
|
||||
|
||||
<ГрузПолуч t-att="{'ОКПО': gruzopol and gruzopol.okpo or ''}">
|
||||
<ИдСв>
|
||||
<t t-if="gruzopol and gruzopol.inn and len(gruzopol.inn) == 10">
|
||||
<СвЮЛУч t-att="{
|
||||
'НаимОрг': gruzopol.name or '',
|
||||
'ИННЮЛ': gruzopol.inn or '',
|
||||
'КПП': gruzopol.kpp or ''
|
||||
}"/>
|
||||
</t>
|
||||
<t t-elif="gruzopol and gruzopol.inn and len(gruzopol.inn) == 12">
|
||||
<СвИП t-att="{
|
||||
'ИННФЛ': gruzopol.inn or '',
|
||||
'ИныеСвед': gruzopol.name or ''
|
||||
}">
|
||||
<ФИО t-att="{
|
||||
'Фамилия': gruzopol.last_name_IP or '',
|
||||
'Имя': gruzopol.first_name_IP or '',
|
||||
'Отчество': gruzopol.middle_name_IP or ''
|
||||
}"/>
|
||||
</СвИП>
|
||||
</t>
|
||||
</ИдСв>
|
||||
<Адрес>
|
||||
<АдрИнф t-att="{
|
||||
'КодСтр': '643',
|
||||
'НаимСтран': 'Российская Федерация',
|
||||
'АдрТекст': (gruzopol.city or '') + ', ' + (gruzopol.street or '')
|
||||
}"/>
|
||||
</Адрес>
|
||||
</ГрузПолуч>
|
||||
</t>
|
||||
|
||||
<t t-if="o and o.payment_num and o.payment_num != ''">
|
||||
<СвПРД t-att="{
|
||||
'НомерПРД': o.payment_num,
|
||||
'ДатаПРД': o.payment_date or ''
|
||||
}"/>
|
||||
</t>
|
||||
|
||||
<t t-if="o and o.only_service == False">
|
||||
<ДокПодтвОтгрНом
|
||||
РеквНаимДок="Счет-фактура и документ об отгрузке товаров (выполнении работ), передаче имущественных прав (документ об оказании услуг)"
|
||||
t-att="{
|
||||
'РеквНомерДок': o.invoice_origin or o.name or '',
|
||||
'РеквДатаДок': o.invoice_date and o.invoice_date.strftime('%d.%m.%Y') or ''
|
||||
}"/>
|
||||
</t>
|
||||
|
||||
<СвПокуп t-att="{'ОКПО': pid and pid.okpo or ''}">
|
||||
<ИдСв>
|
||||
<t t-if="pid and pid.inn and len(pid.inn) == 10">
|
||||
<СвЮЛУч t-att="{
|
||||
'НаимОрг': pid.name or '',
|
||||
'ИННЮЛ': pid.inn or '',
|
||||
'КПП': pid.kpp or ''
|
||||
}"/>
|
||||
</t>
|
||||
<t t-elif="pid and pid.inn and len(pid.inn) == 12">
|
||||
<СвИП t-att="{
|
||||
'ИННФЛ': pid.inn or '',
|
||||
'ИныеСвед': pid.name or ''
|
||||
}">
|
||||
<ФИО t-att="{
|
||||
'Фамилия': pid.last_name_IP or '',
|
||||
'Имя': pid.first_name_IP or '',
|
||||
'Отчество': pid.middle_name_IP or ''
|
||||
}"/>
|
||||
</СвИП>
|
||||
</t>
|
||||
</ИдСв>
|
||||
<Адрес>
|
||||
<АдрИнф t-att="{
|
||||
'КодСтр': '643',
|
||||
'НаимСтран': 'Российская Федерация',
|
||||
'АдрТекст': (pid.city or '') + ', ' + (pid.street or '')
|
||||
}"/>
|
||||
</Адрес>
|
||||
</СвПокуп>
|
||||
|
||||
<ДенИзм КодОКВ="643" НаимОКВ="Российский рубль" />
|
||||
</СвСчФакт>
|
||||
|
||||
<ТаблСчФакт>
|
||||
<t t-set="total_price_subtotal" t-value="sum(line.price_subtotal for line in o.invoice_line_ids)"/>
|
||||
<t t-set="total_price_total" t-value="sum(line.price_total for line in o.invoice_line_ids)"/>
|
||||
<t t-set="total_tax_amount" t-value="total_price_total - total_price_subtotal"/>
|
||||
|
||||
<t t-foreach="o.invoice_line_ids" t-as="line">
|
||||
<t t-set="tax_rate_raw">
|
||||
<t t-if="line.tax_ids">
|
||||
<t t-set="first_tax" t-value="line.tax_ids[0]"/>
|
||||
<t t-if="first_tax.amount != 0">
|
||||
<t t-esc="str(int(first_tax.amount)) + '%'"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-esc="'без НДС'"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-esc="'без НДС'"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-set="tax_rate" t-value="tax_rate_raw.replace(' ', '').replace('\n', '').replace('\t', '')"/>
|
||||
|
||||
<t t-set="line_tax_amount" t-value="line.price_total - line.price_subtotal"/>
|
||||
|
||||
<СведТов t-att="{
|
||||
'НомСтр': line_index+1,
|
||||
'НаимТов': line.name or '',
|
||||
'НаимЕдИзм': line.product_uom_id.name or '',
|
||||
'КолТов': line.quantity or '',
|
||||
'ЦенаТов': ('%.2f' % (line.price_subtotal / line.quantity if line.quantity else 0)) if line.quantity else '0.00',
|
||||
'СтТовБезНДС': ('%.2f' % (line.price_subtotal or 0)),
|
||||
'НалСт': tax_rate,
|
||||
'СтТовУчНал': ('%.2f' % (line.price_total or 0))
|
||||
}">
|
||||
<ДопСведТов t-att="{
|
||||
'КодТов': line.product_id and line.product_id.default_code or '',
|
||||
'ГТИН': '0' + (line.product_id and line.product_id.barcode or '') if line.product_id and line.product_id.barcode else '',
|
||||
'КодВидТов': line.product_id and line.product_id.hs_code or ''
|
||||
}">
|
||||
<t t-if="line.product_id and line.product_id.tracking == 'serial'">
|
||||
<КрНаимСтрПр>Китай</КрНаимСтрПр>
|
||||
<НомСредИдентТов>
|
||||
<t t-foreach="line.sale_line_ids" t-as="line_order">
|
||||
<t t-foreach="line_order.move_ids" t-as="line_move">
|
||||
<t t-foreach="line_move.move_line_ids.filtered(lambda km: km.state=='done')" t-as="lot">
|
||||
<t t-if="lot.lot_id and lot.lot_id.cz_code_id">
|
||||
<КИЗ><t t-esc="lot.lot_id.cz_code_id.code_without_characters"/></КИЗ>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</НомСредИдентТов>
|
||||
</t>
|
||||
</ДопСведТов>
|
||||
|
||||
<Акциз>
|
||||
<БезАкциз>без акциза</БезАкциз>
|
||||
</Акциз>
|
||||
|
||||
<СумНал>
|
||||
<t t-if="line_tax_amount != 0">
|
||||
<СумНал><t t-esc="('%.2f' % line_tax_amount)"/></СумНал>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<БезНДС>без НДС</БезНДС>
|
||||
</t>
|
||||
</СумНал>
|
||||
</СведТов>
|
||||
</t>
|
||||
|
||||
<ВсегоОпл t-att="{
|
||||
'СтТовБезНДСВсего': ('%.2f' % total_price_subtotal),
|
||||
'СтТовУчНалВсего': ('%.2f' % total_price_total)
|
||||
}">
|
||||
<СумНалВсего>
|
||||
<t t-if="total_tax_amount != 0">
|
||||
<СумНал><t t-esc="('%.2f' % total_tax_amount)"/></СумНал>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<БезНДС>без НДС</БезНДС>
|
||||
</t>
|
||||
</СумНалВсего>
|
||||
</ВсегоОпл>
|
||||
</ТаблСчФакт>
|
||||
|
||||
<СвПродПер>
|
||||
<СвПер t-att="{
|
||||
'СодОпер': 'Товары или услуги по документу',
|
||||
'ДатаПер': o.invoice_date and o.invoice_date.strftime('%d.%m.%Y') or ''
|
||||
}">
|
||||
<ОснПер t-att="{
|
||||
'РеквНаимДок': 'Счет-фактура',
|
||||
'РеквНомерДок': o.invoice_origin or o.name or '',
|
||||
'РеквДатаДок': o.invoice_date and o.invoice_date.strftime('%d.%m.%Y') or ''
|
||||
}"/>
|
||||
|
||||
<СвЛицПер>
|
||||
<РабОргПрод t-att="{'Должность': o.kladov and o.kladov.partner_id and o.kladov.partner_id.function or ''}">
|
||||
<t t-if="o.kladov and o.kladov.partner_id and o.kladov.partner_id.name">
|
||||
<t t-set="name_parts" t-value="o.kladov.partner_id.name.split()"/>
|
||||
<t t-if="len(name_parts) == 3">
|
||||
<ФИО t-att="{
|
||||
'Фамилия': name_parts[0],
|
||||
'Имя': name_parts[1],
|
||||
'Отчество': name_parts[2]
|
||||
}"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<ФИО t-att="{
|
||||
'Фамилия': name_parts[0] if name_parts else '',
|
||||
'Имя': name_parts[1] if len(name_parts) > 1 else ''
|
||||
}"/>
|
||||
</t>
|
||||
</t>
|
||||
</РабОргПрод>
|
||||
</СвЛицПер>
|
||||
<Тран t-att="{'СвТран': o.transport or 'Без квитанции'}"/>
|
||||
</СвПер>
|
||||
</СвПродПер>
|
||||
|
||||
<Подписант t-att="{
|
||||
'СпосПодтПолном': '1',
|
||||
'Должн': company.chief_id and company.chief_id.function or ''
|
||||
}">
|
||||
<t t-if="company.inn and len(company.inn) == 10">
|
||||
<t t-if="company.chief_id and company.chief_id.second_name">
|
||||
<ФИО t-att="{
|
||||
'Фамилия': company.chief_id.last_name or '',
|
||||
'Имя': company.chief_id.first_name or '',
|
||||
'Отчество': company.chief_id.second_name or ''
|
||||
}"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<ФИО t-att="{
|
||||
'Фамилия': company.chief_id and company.chief_id.last_name or '',
|
||||
'Имя': company.chief_id and company.chief_id.first_name or ''
|
||||
}"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-elif="company.inn and len(company.inn) == 12">
|
||||
<t t-if="company.partner_id and company.partner_id.middle_name_IP">
|
||||
<ФИО t-att="{
|
||||
'Фамилия': company.partner_id.last_name_IP or '',
|
||||
'Имя': company.partner_id.first_name_IP or '',
|
||||
'Отчество': company.partner_id.middle_name_IP or ''
|
||||
}"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<ФИО t-att="{
|
||||
'Фамилия': company.partner_id and company.partner_id.last_name_IP or '',
|
||||
'Имя': company.partner_id and company.partner_id.first_name_IP or ''
|
||||
}"/>
|
||||
</t>
|
||||
</t>
|
||||
</Подписант>
|
||||
</Документ>
|
||||
</Файл>
|
||||
</t>
|
||||
</template>
|
||||
</odoo>
|
||||
2
l10n_ru_upd_xml/security/ir.model.access.csv
Normal file
2
l10n_ru_upd_xml/security/ir.model.access.csv
Normal file
@ -0,0 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_upd_xml_upd_xml,upd_xml.upd_xml,model_upd_xml_upd_xml,base.group_user,1,1,1,1
|
||||
|
@ -0,0 +1,56 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import {download} from "@web/core/network/download";
|
||||
import {registry} from "@web/core/registry";
|
||||
|
||||
function getReportUrl(action) {
|
||||
|
||||
let url = `/report/xml/${action.report_name}`;
|
||||
|
||||
const actionContext = action.context || {};
|
||||
|
||||
if (action.data && JSON.stringify(action.data) !== "{}") {
|
||||
|
||||
const encodedOptions = encodeURIComponent(
|
||||
JSON.stringify(action.data)
|
||||
);
|
||||
|
||||
const encodedContext = encodeURIComponent(
|
||||
JSON.stringify(actionContext)
|
||||
);
|
||||
|
||||
return `${url}?options=${encodedOptions}&context=${encodedContext}`;
|
||||
}
|
||||
|
||||
if (actionContext.active_ids) {
|
||||
url += `/${actionContext.active_ids.join(",")}`;
|
||||
}
|
||||
|
||||
return `${url}?context=${encodeURIComponent(JSON.stringify(actionContext))}`;
|
||||
}
|
||||
async function triggerDownload(action) {
|
||||
|
||||
const data = JSON.stringify([
|
||||
getReportUrl(action),
|
||||
action.report_type,
|
||||
]);
|
||||
|
||||
const context = JSON.stringify(action.context || {});
|
||||
|
||||
await download({
|
||||
url: "/report/download",
|
||||
data: { data, context },
|
||||
});
|
||||
}
|
||||
registry
|
||||
.category("ir.actions.report handlers")
|
||||
.add("xml_handler", async function (action, options) {
|
||||
|
||||
if (action.report_type !== "qweb-xml") {
|
||||
return false;
|
||||
}
|
||||
|
||||
await triggerDownload(action);
|
||||
|
||||
return true;
|
||||
});
|
||||
2
l10n_ru_upd_xml/tests/__init__.py
Normal file
2
l10n_ru_upd_xml/tests/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import test_upd_xml
|
||||
173
l10n_ru_upd_xml/tests/test_upd_xml.py
Normal file
173
l10n_ru_upd_xml/tests/test_upd_xml.py
Normal file
@ -0,0 +1,173 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Tests for l10n_ru_upd_xml — УПД XML generation.
|
||||
|
||||
Validates: Requirements 12.2, 12.4
|
||||
"""
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tests import tagged
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
def _make_company_partner(env):
|
||||
"""Return or create a company partner with INN (10 digits) and KPP."""
|
||||
partner = env['res.partner'].search(
|
||||
[('is_company', '=', True), ('inn', '!=', False), ('kpp', '!=', False)],
|
||||
limit=1,
|
||||
)
|
||||
if not partner:
|
||||
partner = env['res.partner'].create({
|
||||
'name': 'ООО Тест УПД',
|
||||
'is_company': True,
|
||||
'inn': '7700000001',
|
||||
'kpp': '770001001',
|
||||
'okpo': '12345678',
|
||||
'city': 'Москва',
|
||||
'street': 'ул. Тестовая, 1',
|
||||
})
|
||||
return partner
|
||||
|
||||
|
||||
def _make_out_invoice(env):
|
||||
"""Create a minimal out_invoice with all fields required for УПД."""
|
||||
partner = _make_company_partner(env)
|
||||
|
||||
journal = env['account.journal'].search(
|
||||
[('type', '=', 'sale'), ('company_id', '=', env.company.id)], limit=1
|
||||
)
|
||||
if not journal:
|
||||
journal = env['account.journal'].create({
|
||||
'name': 'Test Sales Journal UPD',
|
||||
'type': 'sale',
|
||||
'code': 'TUPD',
|
||||
})
|
||||
|
||||
move = env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': partner.id,
|
||||
'journal_id': journal.id,
|
||||
'invoice_date': '2024-06-01',
|
||||
})
|
||||
return move
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestUpdXml(TransactionCase):
|
||||
"""
|
||||
Tests for УПД XML generation.
|
||||
|
||||
Validates: Requirements 12.2, 12.4
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.invoice = _make_out_invoice(self.env)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Requirement 12.2 — XML generation returns a valid XML document
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def test_upd_xml_render_returns_valid_xml(self):
|
||||
"""
|
||||
Req 12.2 — rendering the УПД XML QWeb template for an account.move
|
||||
returns non-empty bytes that parse as valid XML.
|
||||
"""
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
xml_content, content_type = self.env['ir.actions.report']._render_qweb_xml(
|
||||
'l10n_ru_upd_xml.demo_report_xml_view', [self.invoice.id]
|
||||
)
|
||||
self.assertTrue(xml_content, "УПД XML render should return non-empty content")
|
||||
self.assertIsInstance(xml_content, bytes, "Rendered content should be bytes")
|
||||
|
||||
# Must parse without exception — proves it is valid XML
|
||||
try:
|
||||
root = ET.fromstring(xml_content)
|
||||
except ET.ParseError as exc:
|
||||
self.fail(f"Rendered УПД is not valid XML: {exc}")
|
||||
|
||||
self.assertIsNotNone(root, "Parsed XML root element should not be None")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Requirement 12.4 — missing INN/KPP raises UserError
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def test_check_correct_upd_missing_inn_returns_error_message(self):
|
||||
"""
|
||||
Req 12.4 — check_correct_upd on an account.move whose partner has no INN
|
||||
returns a non-empty error string mentioning the missing field.
|
||||
"""
|
||||
partner_no_inn = self.env['res.partner'].create({
|
||||
'name': 'Партнёр без ИНН',
|
||||
'is_company': True,
|
||||
# inn intentionally omitted
|
||||
})
|
||||
journal = self.env['account.journal'].search(
|
||||
[('type', '=', 'sale'), ('company_id', '=', self.env.company.id)], limit=1
|
||||
)
|
||||
move = self.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': partner_no_inn.id,
|
||||
'journal_id': journal.id,
|
||||
'invoice_date': '2024-06-01',
|
||||
})
|
||||
|
||||
error_msg = move.check_correct_upd(manually=False)
|
||||
self.assertTrue(
|
||||
error_msg.strip(),
|
||||
"check_correct_upd should return a non-empty error string when INN is missing",
|
||||
)
|
||||
self.assertIn('ИНН', error_msg, "Error message should mention ИНН")
|
||||
|
||||
def test_print_upd_missing_inn_raises_user_error(self):
|
||||
"""
|
||||
Req 12.4 — print_upd on an account.move whose partner has no INN
|
||||
raises UserError describing the missing fields.
|
||||
"""
|
||||
partner_no_inn = self.env['res.partner'].create({
|
||||
'name': 'Партнёр без ИНН для print_upd',
|
||||
'is_company': True,
|
||||
# inn intentionally omitted
|
||||
})
|
||||
journal = self.env['account.journal'].search(
|
||||
[('type', '=', 'sale'), ('company_id', '=', self.env.company.id)], limit=1
|
||||
)
|
||||
move = self.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': partner_no_inn.id,
|
||||
'journal_id': journal.id,
|
||||
'invoice_date': '2024-06-01',
|
||||
})
|
||||
|
||||
with self.assertRaises(UserError) as ctx:
|
||||
move.print_upd()
|
||||
|
||||
self.assertIn('ИНН', str(ctx.exception),
|
||||
"UserError message should mention ИНН")
|
||||
|
||||
def test_print_upd_missing_kpp_raises_user_error(self):
|
||||
"""
|
||||
Req 12.4 — print_upd on an account.move whose partner has INN (10 digits)
|
||||
but no KPP raises UserError describing the missing КПП.
|
||||
"""
|
||||
partner_no_kpp = self.env['res.partner'].create({
|
||||
'name': 'Партнёр без КПП',
|
||||
'is_company': True,
|
||||
'inn': '7700000099',
|
||||
# kpp intentionally omitted
|
||||
})
|
||||
journal = self.env['account.journal'].search(
|
||||
[('type', '=', 'sale'), ('company_id', '=', self.env.company.id)], limit=1
|
||||
)
|
||||
move = self.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': partner_no_kpp.id,
|
||||
'journal_id': journal.id,
|
||||
'invoice_date': '2024-06-01',
|
||||
})
|
||||
|
||||
with self.assertRaises(UserError) as ctx:
|
||||
move.print_upd()
|
||||
|
||||
self.assertIn('КПП', str(ctx.exception),
|
||||
"UserError message should mention КПП")
|
||||
24
l10n_ru_upd_xml/views/ir_actions_report_view.xml
Normal file
24
l10n_ru_upd_xml/views/ir_actions_report_view.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="ir_actions_report_view_form_report_xml" model="ir.ui.view">
|
||||
<field name="name">ir.actions.report.view.form.report.xml</field>
|
||||
<field name="model">ir.actions.report</field>
|
||||
<field name="inherit_id" ref="base.act_report_xml_view" />
|
||||
<field name="arch" type="xml">
|
||||
<button name="associated_view" position="attributes">
|
||||
<attribute name="invisible">report_type not in ('qweb-pdf', 'qweb-html', 'qweb-text', 'qweb-xml')</attribute>
|
||||
</button>
|
||||
<xpath expr="//page[@name='advanced']/group" position="after">
|
||||
<group
|
||||
name="xml_reports"
|
||||
string="XML Report Settings"
|
||||
invisible = 'report_type!="qweb-xml"'
|
||||
>
|
||||
<field name="xsd_schema" />
|
||||
<field name="xml_encoding" />
|
||||
<field name="xml_declaration" />
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
14
l10n_ru_upd_xml/views/res_company_view.xml
Normal file
14
l10n_ru_upd_xml/views/res_company_view.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_company_ru_form" model="ir.ui.view">
|
||||
<field name="name">res.company.ru.form</field>
|
||||
<field name="model">res.company</field>
|
||||
<field name="inherit_id" ref="base.view_company_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='vat']" position="after">
|
||||
<field name="edi"/>
|
||||
<field name="chief_id"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
21
l10n_ru_upd_xml/views/res_partner_view.xml
Normal file
21
l10n_ru_upd_xml/views/res_partner_view.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_partner_ru_form" model="ir.ui.view">
|
||||
<field name="name">res.partner.ru.form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group[2]" position="inside">
|
||||
<field name="edi" invisible="is_company==False"/>
|
||||
<field name="last_name_IP"/>
|
||||
<field name="first_name_IP"/>
|
||||
<field name="middle_name_IP"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='street']" position="after">
|
||||
<field name="house" placeholder="Дом..."/>
|
||||
<field name="office" placeholder="Офис\кв..."/>
|
||||
<field name="fias_id" placeholder="Код ФИАС..."/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
17
l10n_ru_upd_xml/views/res_users_view.xml
Normal file
17
l10n_ru_upd_xml/views/res_users_view.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_users_facsimile_form" model="ir.ui.view">
|
||||
<field name="name">res.users.signature.form</field>
|
||||
<field name="model">res.users</field>
|
||||
<field name="inherit_id" ref="base.view_users_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='name']" position="after">
|
||||
<group>
|
||||
<field name="last_name" invisible="1"/>
|
||||
<field name="first_name" invisible="1"/>
|
||||
<field name="second_name" invisible="1"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
19
l10n_ru_upd_xml/views/view_account_move.xml
Normal file
19
l10n_ru_upd_xml/views/view_account_move.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="account_move_form_upd_button_header" model="ir.ui.view">
|
||||
<field name="name">account.move.form.upd.button.header</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_move_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//form/header" position="inside">
|
||||
<button name="print_upd"
|
||||
type="object"
|
||||
string="Печать УПД в xml-формате"
|
||||
class="btn-primary"
|
||||
invisible="state != 'posted' or move_type not in ('out_invoice','out_refund')"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
29
l10n_ru_upd_xml/views/views_uom_okei.xml
Normal file
29
l10n_ru_upd_xml/views/views_uom_okei.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<odoo>
|
||||
<record id="uom_rep" model="ir.ui.view">
|
||||
<field name="name">OKEI code for uom</field>
|
||||
<field name="model">uom.uom</field>
|
||||
<field name="inherit_id" ref="uom.product_uom_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//field[@name='name']" position="after">
|
||||
<field name="okei" string="Код единицы товара по ОКЕИ"/>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- <record id="uom_categories_rep" model="ir.ui.view">-->
|
||||
<!-- <field name="name">OKEI code for uom categories</field>-->
|
||||
<!-- <field name="model">uom.category</field>-->
|
||||
<!-- <field name="inherit_id" ref="l10n_ru_doc.view_uomiher_tree"/>-->
|
||||
<!-- <field name="arch" type="xml">-->
|
||||
<!-- <list>-->
|
||||
<!-- <xpath expr="//field[@name='kod']" position="after">-->
|
||||
<!-- <field name="okei" string="Код по ОКЕИ"/>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- </list>-->
|
||||
<!-- </field>-->
|
||||
<!-- </record>-->
|
||||
|
||||
</odoo>
|
||||
|
||||
11
l10n_ru_upd_xml/views/webclient_templates.xml
Normal file
11
l10n_ru_upd_xml/views/webclient_templates.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<template id="report_xml.assets_backend" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/report_xml/static/src/js/report/action_manager_report.js"
|
||||
/>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user