[IMP] extract function for docx report printed from docx template

This commit is contained in:
Ivan shirokikh 2021-05-21 04:25:00 +04:00
parent 20e7dc02de
commit 38d49cac61
35 changed files with 211 additions and 5425 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
*.pyc *.pyc
idea/ idea/
.DS_store

View File

@ -1,9 +1 @@
# Account Contracts # DOCS report
## Summary
Create and storage form for contracts. Generate a print form of document of contract.
## Features
- Contract management: create, sign, close, renew
- Create .docx form of contract using any document template
- Fields in document fill from Odoo models

View File

@ -1,11 +0,0 @@
# TODO LIST
## Features
## Fixes
- Change all `parents` to `genitive`
- Merge `document_type` and `template_type` in `res.partner.document.template`
- Change `annex_number` to `annex_counter`
## Big feature
- Separate XML actions that generates transient fields for all types of documents

View File

@ -1,2 +1 @@
from . import models from . import models
from . import wizard

View File

@ -1,28 +1,17 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
{ {
"name": "Client Contracts", "name": "DOCX report",
"summary": """ "summary": """
Print forms for contracts with clients""", Print docx report from docx template""",
"description": """ "description": """""",
Module for storing and creating print forms for contracts.
""",
"author": "RYDLAB", "author": "RYDLAB",
"website": "http://rydlab.ru", "website": "http://rydlab.ru",
"category": "Invoicing & Payments", "category": "Technical",
"version": "14.0.1.0.0", "version": "0.0.1",
"depends": ["base", "contacts", "hr", "l10n_ru", "sale", "sale_margin"], "depends": ["base"],
"external_dependencies": {"python": ["docxtpl", "num2words"]}, "external_dependencies": {"python": ["docxtpl", "num2words"]},
"data": [ "data": [
"data/assets_extension.xml", "data/assets_extension.xml",
"data/fields_default.xml", "views/ir_actions_report_views.xml",
"data/payment_terms.xml",
"security/ir.model.access.csv",
"views/res_partner_contract.xml",
"views/res_partner_contract_annex.xml",
"views/res_partner_contract_field.xml",
"views/res_partner_document_template.xml",
"views/res_partner.xml",
"views/sale_order.xml",
"wizard/res_partner_contract_wizard.xml",
], ],
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data noupdate="0">
<record id="payment_term_prepaid" model="account.payment.term">
<field name="name">100% Prepaid</field>
<field name="note">Payment terms: 100% Prepaid</field>
<field name="sequence">1</field>
</record>
<record id="payment_term_postpayment" model="account.payment.term">
<field name="name">100% Postpaid</field>
<field name="note">Payment terms: 100% Postpaid</field>
<field name="sequence">2</field>
</record>
<record id="payment_term_partial_2" model="account.payment.term">
<field name="name">2 stages</field>
<field name="note">Payment terms: Partial 2 Stages</field>
<field name="sequence">3</field>
</record>
<record id="payment_term_partial_3" model="account.payment.term">
<field name="name">3 stages</field>
<field name="note">Payment terms: Partial 3 Stages</field>
<field name="sequence">4</field>
</record>
</data>
</odoo>

File diff suppressed because it is too large Load Diff

1140
i18n/ru.po

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1 @@
from . import account_invoice from . import ir_actions_report
from . import product_product
from . import res_partner
from . import res_partner_contract
from . import res_partner_contract_annex
from . import res_partner_contract_field
from . import res_partner_contract_field_transient
from . import res_partner_document_template
from . import sale_order

View File

@ -1,67 +0,0 @@
from odoo import models, _
from odoo.exceptions import UserError
from ..utils import MODULE_NAME
class AccountInvoice(models.Model):
_inherit = "account.move"
@staticmethod
def check_contract_presence(sale_order_ids):
error_message = ""
if any(not so.contract_annex_id.contract_id for so in sale_order_ids):
error_message = _("There is a Sale order without binding contract.")
if any(not so.contract_annex_id for so in sale_order_ids):
error_message = _("There is a Sale order without annex.")
if error_message:
raise UserError(error_message)
def action_invoice_print(self):
"""
for so in self.env["sale.order"].search([]):
if self.id in so.invoice_ids.ids:
order = so
break
else:
return super().action_invoice_print()
if not order.contract_annex_id or not order.contract_annex_id.contract_id:
raise UserError(
_(
"There is no binding contract. It is necessary to link the order with the annex to the contract."
)
)
self.sent = True
"""
sale_orders_ids = self.env["sale.order"].search(
[("invoice_ids", "in", self.ids)]
)
if not sale_orders_ids:
return super().action_invoice_print()
self.check_contract_presence(sale_orders_ids)
self.filtered(lambda inv: not inv.is_move_sent).write({"is_move_sent": True})
view = self.env.ref(
"{}.res_partner_wizard_print_document_view".format(MODULE_NAME)
)
# annex = order.contract_annex_id
return {
"name": _("Print Form of Contract Annex"),
"type": "ir.actions.act_window",
"res_model": "res.partner.contract.wizard",
"view_mode": "form",
"view_id": view.id,
"target": "new",
"context": {
# "self_id": annex.id,
"active_ids": self.ids,
"active_model": "res.partner.contract.annex",
# "company_form": annex.partner_id.company_form
# if annex.partner_id.is_company
# else "person",
"attachment_model": self._name,
"attachment_res_id": self.id,
},
}

183
models/ir_actions_report.py Normal file
View File

@ -0,0 +1,183 @@
import io
from collections import OrderedDict
from jinja2 import Environment as Jinja2Environment
from logging import getLogger
from docxcompose.composer import Composer
from docx import Document
from docxtpl import DocxTemplate
from odoo import _, api, fields, models, SUPERUSER_ID
from odoo.exceptions import AccessError, UserError
from odoo.sql_db import TestCursor
from odoo.tools.safe_eval import safe_eval, time
from ..utils.num2words import num2words_, num2words_currency
_logger = getLogger(__name__)
class IrActionsReport(models.Model):
_inherit = "ir.actions.actions"
report_name = fields.Char(required=False)
report_type = fields.Selection(
selection_add=[("docx-docx", "DOCX")], ondelete="cascade"
)
report_docx_template = fields.Binary(
string="Report docx template",
)
def _render_docx_docx(self, res_ids=None, data=None):
if not data:
data = {}
data.setdefault("report_type", "docx")
# access the report details with sudo() but evaluation context as current user
self_sudo = self.sudo()
save_in_attachment = OrderedDict()
# Maps the streams in `save_in_attachment` back to the records they came from
stream_record = dict()
if res_ids:
Model = self.env[self_sudo.model]
record_ids = Model.browse(res_ids)
docx_record_ids = Model
if self_sudo.attachment:
for record_id in record_ids:
attachment = self_sudo.retrieve_attachment(record_id)
if attachment:
stream = self_sudo._retrieve_stream_from_attachment(attachment)
save_in_attachment[record_id.id] = stream
stream_record[stream] = record_id
if not self_sudo.attachment_use or not attachment:
docx_record_ids += record_id
else:
docx_record_ids = record_ids
res_ids = docx_record_ids.ids
if save_in_attachment and not res_ids:
_logger.info("The DOCS report has been generated from attachments.")
return self_sudo._post_docx(save_in_attachment), "docx"
template = self.report_docx_template
template_path = template._full_path(template.store_fname)
doc = DocxTemplate(template_path)
jinja_env = Jinja2Environment()
functions = {
"number2words": num2words_,
"currency2words": num2words_currency,
}
jinja_env.globals.update(**functions)
doc.render(data, jinja_env)
docx_content = io.BytesIO()
doc.save(docx_content)
docx_content.seek(0)
if res_ids:
_logger.info(
"The DOCS report has been generated for model: %s, records %s."
% (self_sudo.model, str(res_ids))
)
return (
self_sudo._post_docx(
save_in_attachment, docx_content=docx_content, res_ids=res_ids
),
"docx",
)
return docx_content, "docx"
def _post_docx(self, save_in_attachment, docx_content=None, res_ids=None):
def close_streams(streams):
for stream in streams:
try:
stream.close()
except Exception:
pass
if len(save_in_attachment) == 1 and not docx_content:
return list(save_in_attachment.values())[0].getvalue()
streams = []
if docx_content:
# Build a record_map mapping id -> record
record_map = {
r.id: r
for r in self.env[self.model].browse(
[res_id for res_id in res_ids if res_id]
)
}
# If no value in attachment or no record specified, only append the whole docx.
if not record_map or not self.attachment:
streams.append(docx_content)
else:
if len(res_ids) == 1:
# Only one record, so postprocess directly and append the whole docx.
if (
res_ids[0] in record_map
and not res_ids[0] in save_in_attachment
):
new_stream = self._postprocess_docx_report(
record_map[res_ids[0]], docx_content
)
# If the buffer has been modified, mark the old buffer to be closed as well.
if new_stream and new_stream != docx_content:
close_streams([docx_content])
docx_content = new_stream
streams.append(docx_content)
else:
streams.append(docx_content)
if self.attachment_use:
for stream in save_in_attachment.values():
streams.append(stream)
if len(streams) == 1:
result = streams[0].getvalue()
else:
try:
result = self._merge_docx(streams)
except Exception:
raise UserError(_("One of the documents, you try to merge is fallback"))
close_streams(streams)
return result
def _postprocess_docx_report(self, record, buffer):
attachment_name = safe_eval(self.attachment, {"object": record, "time": time})
if not attachment_name:
return None
attachment_vals = {
"name": attachment_name,
"raw": buffer.getvalue(),
"res_model": self.model,
"res_id": record.id,
"type": "binary",
}
try:
self.env["ir.attachment"].create(attachment_vals)
except AccessError:
_logger.info(
"Cannot save DOCX report %r as attachment", attachment_vals["name"]
)
else:
_logger.info(
"The DOCX document %s is now saved in the database",
attachment_vals["name"],
)
return buffer
def _merge_docx(self, streams):
writer = Document()
composer = Composer(writer)
for stream in streams:
reader = Document(stream)
composer.append(reader)
return composer.getvalue()

View File

@ -1,12 +0,0 @@
from odoo import fields, models
class ProductProduct(models.Model):
_inherit = "product.product"
description_sale = fields.Text(
"Sale Description",
translate=True,
help="A product's description you want to tell to your customers.\n"
"This description will be copied to every Sales Order, Delivery Order and Customer Invoice/Credit Note",
)

View File

@ -1,73 +0,0 @@
from odoo import api, fields, models
class ResPartner(models.Model):
_inherit = "res.partner"
name_write = fields.Char(
string="Name in contracts",
help="This name used in contracts",
)
name_genitive = fields.Char(
string="Name Genitive",
)
name_initials = fields.Char(
string="Name Initials",
)
function_genitive = fields.Char(
string="Job position genitive",
)
client_contract_ids = fields.One2many(
"res.partner.contract",
"partner_id",
string="Contracts",
)
contract_count = fields.Integer(
compute="_compute_contract_count", string="# of contracts"
)
full_address = fields.Char(
compute="_compute_full_address"
) # Check for res.partner.contact_address in base/res
street_actual = fields.Many2one(
"res.partner",
string="Actual Address",
)
representative_id = fields.Many2one(
"res.partner", string="Representative", help="Person representing company"
)
representative_document = fields.Char(
string="Representative acts on the basis of",
help="Parent Case",
)
signature = fields.Binary(string="Client signature")
whatsapp = fields.Char(
string="WhatsApp",
help="If a contact have a WhatsApp number",
)
telegram = fields.Char(
string="Telegram",
help="If a contact have a Telegram number or identifier",
)
@api.depends("street", "street2", "city", "state_id", "zip", "country_id")
def _compute_full_address(self):
for record in self:
address_data = filter(
None,
map(
lambda s: s and s.strip(),
[
record.zip,
record.street,
record.street2,
record.city,
record.country_id.l10n_ru_short_name or record.country_id.name,
],
),
)
record.full_address = ", ".join(address_data)
@api.depends("self.client_contract_ids")
def _compute_contract_count(self):
self.ensure_one()
self.contract_count = len(self.client_contract_ids)

View File

@ -1,157 +0,0 @@
import datetime
from odoo import _, fields, models
from ..utils import MODULE_NAME
# from ..utils.misc import Extension, IDocument
class PartnerContract(models.Model): # , IDocument, Extension):
_name = "res.partner.contract"
_description = "Contract"
_inherit = [
"mail.thread",
"mail.activity.mixin",
"mail.followers",
"client_contracts.utils",
]
def _get_default_name(self):
"""Returns name format `№YYMM-D-N`,
where N is a sequence number of contracts which are created this day
"""
current_day_ts = (
datetime.datetime.now()
.replace(minute=0, hour=0, second=0, microsecond=0)
.timestamp()
)
contracts_today = self.search([("create_date_ts", ">=", current_day_ts)])
contract_date = "{format_date}-{number}".format(
format_date=datetime.date.strftime(datetime.date.today(), "%y%m-%d"),
number=len(contracts_today) + 1,
)
return contract_date
@staticmethod
def _get_default_create_date_ts():
"""Returns timestamp of now by local datetime"""
return datetime.datetime.now().timestamp()
partner_id = fields.Many2one(
"res.partner",
string="Partner",
default=lambda self: self.env.context.get("active_id"),
required=True,
)
company_id = fields.Many2one(
"res.company",
string="Company",
default=lambda self: self.env.company,
)
create_date_ts = fields.Char(default=_get_default_create_date_ts)
res_model = fields.Char(default=lambda self: self._name)
name = fields.Char(
string="Contract number",
default=_get_default_name,
)
create_date = fields.Datetime(string="Created on")
date_conclusion = fields.Date(
string="Signing date in system",
)
date_conclusion_fix = fields.Date(
string="Actual signing date",
help="Field for pointing out manually when contract is signed or closed",
default=lambda self: self.date_conclusion,
)
contract_annex_ids = fields.One2many(
comodel_name="res.partner.contract.annex",
inverse_name="contract_id",
string="Annexes",
help="Annexes to this contract",
)
contract_annex_number = fields.Integer(
default=1, help="Counter for generate Annex name"
)
state = fields.Selection(
[
("draft", "New"),
("sign", "Signed"),
("close", "Closed"),
],
string="Status",
readonly=True,
copy=False,
index=True,
tracking=True,
track_sequence=3,
default="draft",
)
def action_sign(self):
self.write({"state": "sign", "date_conclusion": fields.Date.today()})
def action_close(self):
self.write({"state": "close"})
def action_renew(self):
self.write({"state": "draft"})
def action_print_form(self):
self.ensure_one()
view = self.env.ref(
"{}.res_partner_wizard_print_document_view".format(MODULE_NAME)
)
return {
"name": _("Print Form of Contract"),
"type": "ir.actions.act_window",
"res_model": "res.partner.contract.wizard",
"view_mode": "form",
"view_id": view.id,
"target": "new",
"context": {
"self_id": self.id,
"active_model": self._name,
"company_form": self.partner_id.company_form
if self.partner_id.is_company
else "person",
},
}
def get_name_by_document_template(self, document_template_id):
self.ensure_one()
return self.name
def get_filename_by_document_template(self, document_template_id):
self.ensure_one()
return _("{type} {number} from {date}").format(
type=_(
dict(document_template_id._fields["document_type"].selection).get(
document_template_id.document_type
)
),
number=self.name,
date=self.get_date().strftime("%d.%m.%Y"),
)
def get_date(self):
"""Uses in xml action (data/fields_default)
Returns:
datetime.datetime -- date_conclusion_fix or date_conclusion or create_date
"""
self.ensure_one()
date = self.date_conclusion_fix or self.date_conclusion
return date or self.create_date
@staticmethod
def _(arg):
"""Uses in xml action (data/fields_default)
Arguments:
arg {str} -- String to translate
"""
return _(arg)

View File

@ -1,236 +0,0 @@
import math
import logging
from odoo import _, api, fields, models
from ..utils import MODULE_NAME
# from ..utils.misc import Extension, IDocument
_logger = logging.getLogger(__name__)
class ContractOrderAnnex(models.Model): # , IDocument, Extension):
_name = "res.partner.contract.annex"
_inherit = ["client_contracts.utils"]
_description = "Contract Annex"
name = fields.Char(
string="Name",
)
display_name = fields.Char(
compute="_compute_display_name",
)
specification_name = fields.Char(
compute="_compute_specification_name",
)
contract_id = fields.Many2one(
"res.partner.contract",
string="Contract",
readonly=True,
)
company_id = fields.Many2one(
comodel_name="res.company",
related="contract_id.company_id",
)
partner_id = fields.Many2one(
comodel_name="res.partner",
related="contract_id.partner_id",
)
order_id = fields.Many2one(
comodel_name="sale.order",
string="Sale order",
help="Sale order for this annex.",
required=True,
)
date_conclusion = fields.Date(
string="Signing Date",
default=fields.Date.today(),
)
counter = fields.Integer(
string="",
help="Contract Annexes counter",
)
currency_id = fields.Many2one(
comodel_name="res.currency",
string="Currency",
default=lambda self: self.env.company.currency_id.id,
)
design_period = fields.Integer(
string="Design Period",
)
design_cost = fields.Monetary(
string="Design Cost",
currency_field="currency_id",
)
design_doc_period = fields.Integer(
string="Documentation Design Period (days)",
)
design_doc_cost = fields.Monetary(
string="Documentation Design Cost",
currency_field="currency_id",
)
delivery_address = fields.Char(
string="Delivery Address",
)
delivery_period = fields.Integer(string="Delivery Period (days)")
installation_address = fields.Char(
string="Installation Address",
)
installation_period = fields.Integer(
string="Installation Period (days)",
)
installation_cost = fields.Integer(
string="Installation Cost",
)
total_cost = fields.Monetary(
string="Total Cost",
currency_field="currency_id",
)
payment_part_one = fields.Float(
string="Payment 1 Part (%)",
default=100,
digits="Account",
)
payment_part_two = fields.Float(
string="Payment 2 Part (%)",
digits="Account",
)
payment_part_three = fields.Float(
string="Payment 3 Part (%)",
digits="Account",
)
@api.depends("name")
def _compute_display_name(self):
for record in self:
record.display_name = "{} {}".format(
record.counter or record.contract_id.contract_annex_number, record.name
)
@api.depends("contract_id", "order_id")
def _compute_specification_name(self):
self.specification_name = _("{name} from {date}").format(
name="{}-{}".format(self.contract_id.name, self.order_id.name),
date=self.contract_id.get_date().strftime("%d.%m.%Y"),
)
@api.onchange("order_id")
def _domain_order_id(self):
"""Using domain function because of
simple domain does not working properly because of
contract_id is still False"""
return {
"domain": {
"order_id": [
("partner_id", "=", self.contract_id.partner_id.id),
("contract_annex_id", "=", False),
]
}
}
@api.onchange("order_id")
def _onchange_order_id(self):
contract_number = self.contract_id.name
order_number = self.order_id.name or "SO###"
self.name = "{contract}-{order}".format(
contract=contract_number,
order=order_number,
)
def create(self, values_list):
_logger.debug("\n\n Values: %s\n\n", values_list)
if isinstance(values_list, dict):
values_list = [values_list]
_logger.debug("\n\n Values fixed: %s\n\n", values_list)
records = super(ContractOrderAnnex, self).create(values_list)
for record in records:
# Fill annex_id to domain it in future
# record.order_id.contract_annex_id = record.id
# Counter
record.counter = record.contract_id.contract_annex_number
record.contract_id.contract_annex_number += (
1 # TODO: should I use a sequence?
)
return records
def action_print_form(self):
view = self.env.ref(
"{}.res_partner_wizard_print_document_view".format(MODULE_NAME)
)
return {
"name": _("Print Form of Contract Annex"),
"type": "ir.actions.act_window",
"res_model": "res.partner.contract.wizard",
"view_mode": "form",
"view_id": view.id,
"target": "new",
"context": {
"self_id": self.id,
"active_model": self._name,
"company_form": self.partner_id.company_form
if self.partner_id.is_company
else "person",
},
}
def get_name_by_document_template(self, document_template_id):
active_invoices = self.order_id.invoice_ids.filtered(
lambda r: r.state not in ("draft", "cancel")
)
bill_name = active_invoices and active_invoices[-1].number
return (
{
"bill": "{bill_name}",
"specification": "{counter} {name}",
"approval_list": "{counter}.1 {name}-1",
"act_at": "{counter}.2 {name}-2",
"act_ad": "{counter}.3 {name}-3",
}
.get(document_template_id.document_type_name, "Unknown")
.format(
counter=self.counter,
name=self.name,
bill_name=(bill_name or "Счёт отсутствует"),
)
)
def get_filename_by_document_template(self, document_template_id):
return "{type}{name}".format(
type=_(
dict(document_template_id._fields["document_type"].selection).get(
document_template_id.document_type
)
),
name={
"bill": "{counter} {type} {name}",
"specification": "{counter} {type} {name}",
"approval_list": "{counter}.1 {type} {name}-1",
"act_at": "{counter}.2 {type} {name}-2",
"act_ad": "{counter}.3 {type} {name}-3",
}
.get(document_template_id.document_type_name, "Unknown")
.format(
counter=self.counter,
type=_(
dict(
document_template_id._fields["document_type_name"].selection
).get(document_template_id.document_type_name)
),
name=self.name,
),
)
def modf(self, arg):
"""Math.modf function for using in XML ir.action.server code
Uses in data/fields_default.xml
"""
return math.modf(arg)

View File

@ -1,33 +0,0 @@
from odoo import fields, models
class ContractField(models.Model):
_name = "res.partner.contract.field"
_description = "Contract Field"
_order = "sequence"
name = fields.Char(
string="Name",
required=True,
translate=True,
)
technical_name = fields.Char(
string="Technical Name",
help="Name for using in templates",
required=True,
)
description = fields.Char(
string="Description",
help="Description for this field to be showed in fields list in print form creation wizard.",
translate=True,
default="",
)
sequence = fields.Integer(
string="Sequence",
)
visible = fields.Boolean(
string="Visible",
help="To show this field in fields list in print form creation wizard\n"
"User can change showed field's values in wizard.",
default=True,
)

View File

@ -1,38 +0,0 @@
from odoo import fields, models
class ContractFieldTransient(models.TransientModel):
_name = "res.partner.contract.field.transient"
_description = "Contract Field Transient"
_contract_wizard_id = fields.Many2one(
"res.partner.contract.wizard",
string="Contract",
readonly=True,
)
contract_field_id = fields.Many2one(
"res.partner.contract.field",
string="Field",
)
name = fields.Char(
related="contract_field_id.name",
string="Name",
readonly=True,
)
technical_name = fields.Char(
related="contract_field_id.technical_name",
string="Technical Name",
readonly=True,
)
description = fields.Char(
related="contract_field_id.description",
string="Description",
readonly=True,
)
visible = fields.Boolean(
related="contract_field_id.visible",
)
value = fields.Char(
string="Value",
default="",
)

View File

@ -1,50 +0,0 @@
from odoo import _, fields, models
class DocumentTemplate(models.Model):
_name = "res.partner.document.template"
_description = "Document Template"
_order = "template_type desc,company_type,sequence"
name = fields.Char()
attachment_id = fields.Many2one(
"ir.attachment",
string="Template Attachment",
ondelete="cascade",
required=True,
)
document_type = fields.Selection(
string="Type of document",
selection=[
("contract", _("Contract")),
("annex", _("Annex")),
("addition", _("Addition")),
("offer", _("Offer")),
],
)
document_type_name = fields.Selection(
string="Document",
selection=[
("offer", _("Offer")),
("bill", _("Bill")),
("specification", _("Specification")),
("approval_list", _("Approval List")),
("act_at", _("Act of Acceptance and Transfer")),
("act_ad", _("Act of Acceptance and Delivery")),
],
)
company_type = fields.Selection(
selection=[
("person", "Individual"),
("sp", "Sole Proprietor"),
("plc", "Private Limited Company"),
]
)
template_type = fields.Selection(
selection=[
("contract", "Contract"),
("annex", "Annex"),
("offer", _("Offer")),
]
)
sequence = fields.Integer()

View File

@ -1,95 +0,0 @@
from odoo import api, fields, models, _
from ..utils import MODULE_NAME
class SaleOrder(models.Model):
_inherit = "sale.order"
# TODO: exists original field "commitment_date".
delivery_time = fields.Integer(
string="Delivery Time",
default=45,
)
contract_annex_id = fields.Many2one(
comodel_name="res.partner.contract.annex",
string="Contract Annex",
compute="get_contract_annex_id",
)
contract_annex_ids = fields.One2many(
comodel_name="res.partner.contract.annex",
inverse_name="order_id",
string="Annex for this Sale order",
help="Technical field for binding with contract annex\n"
"In form this link showed in 'contract_annex_id' field.",
)
# Extend default field for showing payment terms created by this module only.
payment_term_id = fields.Many2one(
comodel_name="account.payment.term",
domain=lambda self: [("id", "in", self._get_payment_terms())],
)
def _get_payment_terms(self):
terms = [
self.env.ref("{}.{}".format(MODULE_NAME, external_id)).id
for external_id in (
"payment_term_prepaid",
"payment_term_postpayment",
"payment_term_partial_2",
"payment_term_partial_3",
)
]
return terms
@api.onchange("contract_annex_ids")
def get_contract_annex_id(self):
if self.contract_annex_ids:
self.contract_annex_id = self.contract_annex_ids[0].id
else:
self.contract_annex_id = False
def action_print_form(self):
self.ensure_one()
view = self.env.ref(
"{}.res_partner_wizard_print_document_view".format(MODULE_NAME)
)
return {
"name": _("Print Form of Contract"),
"type": "ir.actions.act_window",
"res_model": "res.partner.contract.wizard",
"view_mode": "form",
"view_id": view.id,
"target": "new",
"context": {
"self_id": self.id,
"active_model": self._name,
"company_form": self.partner_id.company_form
if self.partner_id.is_company
else "person",
"attachment_model": "sale.order",
},
}
def get_filename_by_document_template(self, document_template_id):
self.ensure_one()
return "{doc_type} {number} {from_} {date}".format(
doc_type=_("Offer"),
number=self.name,
from_=_("from"),
date=self.date_order.strftime("%d.%m.%Y"),
)
@staticmethod
def _t(arg):
"""Uses in xml action (data/fields_default)
Arguments:
arg {str} -- String to translate
"""
return _(arg)
@staticmethod
def to_fixed(number, digit=2):
if isinstance(number, str) and number.isdigit():
number = float(number)
return f"{number:.{digit}f}"

View File

@ -1,12 +0,0 @@
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_contracts_contracts,access_contracts_contracts,model_res_partner_contract,base.group_user,1,1,1,1
access_contracts_field,access_contracts_field,model_res_partner_contract_field,base.group_user,1,0,0,0
access_contracts_field_manager,access_contracts_field,model_res_partner_contract_field,base.group_no_one,1,1,1,1
access_contracts_field_transient,access_contracts_field_transient,model_res_partner_contract_field_transient,base.group_user,1,1,1,1
access_contracts_field_contract_annex,access_contracts_field_contract_annex,model_res_partner_contract_annex,base.group_user,1,1,1,1
access_contract_document_template,access_contract_document_template,model_res_partner_document_template,base.group_user,1,0,0,0
access_contract_document_template_manager,access_contract_document_template,model_res_partner_document_template,base.group_no_one,1,1,1,1
access_contracts_wizard,access_contracts_wizard,model_res_partner_contract_wizard,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_contracts_contracts access_contracts_contracts model_res_partner_contract base.group_user 1 1 1 1
3 access_contracts_field access_contracts_field model_res_partner_contract_field base.group_user 1 0 0 0
4 access_contracts_field_manager access_contracts_field model_res_partner_contract_field base.group_no_one 1 1 1 1
5 access_contracts_field_transient access_contracts_field_transient model_res_partner_contract_field_transient base.group_user 1 1 1 1
6 access_contracts_field_contract_annex access_contracts_field_contract_annex model_res_partner_contract_annex base.group_user 1 1 1 1
7 access_contract_document_template access_contract_document_template model_res_partner_document_template base.group_user 1 0 0 0
8 access_contract_document_template_manager access_contract_document_template model_res_partner_document_template base.group_no_one 1 1 1 1
9 access_contracts_wizard access_contracts_wizard model_res_partner_contract_wizard base.group_user 1 1 1 1

View File

@ -1,3 +1 @@
from . import misc
MODULE_NAME = __package__.split(".")[-2] MODULE_NAME = __package__.split(".")[-2]

View File

@ -1,26 +0,0 @@
import io
from docxtpl import DocxTemplate
from jinja2 import Environment as Jinja2Environment
from .num2words import num2words_, num2words_currency
def get_document_from_values_stream(path_to_template: str, vals: dict):
doc = DocxTemplate(path_to_template)
jinja_env = Jinja2Environment()
functions = {
"number2words": num2words_,
"currency2words": num2words_currency,
}
jinja_env.globals.update(**functions)
doc.render(vals, jinja_env)
file_stream = io.BytesIO()
doc.save(file_stream)
file_stream.seek(0)
return file_stream

View File

@ -1,11 +0,0 @@
from odoo import models
class Utils(models.AbstractModel):
_name = "client_contracts.utils"
@staticmethod
def to_fixed(number, digit=2):
if isinstance(number, str) and number.isdigit():
number = float(number)
return f"{number:.{digit}f}"

View File

@ -0,0 +1,18 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<record id="act_report_xml_inherit_view_form" model="ir.ui.view">
<field name="name">ir.actions.report.inherit.view.form</field>
<field name="model">ir.actions.report</field>
<field name="inherit_id" ref="base.act_report_xml_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='report_name']" position="attributes">
<attribute name="attrs">{'required': [('report_type', 'not in', ['docx-docx'])]}</attribute>
</xpath>
<xpath expr="//field[@name='report_name']" position="after">
<field name="report_docx_template" attrs="{'required': [('report_type', 'in', ['docx-docx'])]}"/>
</xpath>
</field>
</record>
</odoo>

View File

@ -1,92 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- res.partner.contract.field action window -->
<record id="res_partner_contract_field_action" model="ir.actions.act_window">
<field name="name">Contract Fields</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.partner.contract.field</field>
<field name="view_mode">tree,form</field>
</record>
<!-- res.partner.document.template action window -->
<record id="res_partner_document_template_action" model="ir.actions.act_window">
<field name="name">Document Templates</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.partner.document.template</field>
<field name="view_mode">tree,form</field>
</record>
<record id="res_partner_contract_info_inherit_view" model="ir.ui.view">
<field name="name">res.partner.contract.info</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="priority" eval="25"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='name']" position="attributes">
<attribute name="placeholder">Name in ERP</attribute>
</xpath>
<xpath expr="//field[@name='type']" position="before">
<field name="name_write" placeholder="i.e. Ural Bank for Reconstruction and Development" attrs="{'invisible': [('is_company', '=', False)]}"/>
<field name="name_genitive" attrs="{'invisible': [('is_company', '=', True)]}"/>
<field name="name_initials" attrs="{'invisible': [('is_company', '=', True)]}"/>
</xpath>
<xpath expr="//field[@name='function']" position="after">
<field name="function_genitive" attrs="{'invisible': [('is_company', '=', True)]}"/>
</xpath>
<xpath expr="//field[@name='category_id']" position="after">
<field name="representative_id" domain="[('is_company', '=', False)]" attrs="{'invisible': [('is_company', '=', False)]}"/>
<field name="representative_document" attrs="{'invisible': [('is_company', '=', False)]}"/>
<field name="signature" widget="image"/>
</xpath>
<xpath expr="//field[@name='function']" position="after">
<field name="function_genitive" attrs="{'invisible': [('is_company','=', True)]}" invisible="1"/>
</xpath>
<xpath expr="//field[@name='email']" position="before">
<field name="whatsapp" attrs="{'invisible': [('is_company', '=', True)]}"/>
<field name="telegram" attrs="{'invisible': [('is_company', '=', True)]}"/>
</xpath>
</field>
</record>
<record id="res_partner_contract_view_buttons" model="ir.ui.view">
<field name="name">res.partner.contract.view.buttons</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form" />
<field name="priority" eval="25"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<button class="oe_inline oe_stat_button" type="action" name="%(res_partner_contract_partner_action)d" attrs="{'invisible': [('parent_id', '!=', False)]}" icon="fa-pencil-square-o">
<field string="Contracts" name="contract_count" widget="statinfo"/>
</button>
</xpath>
</field>
</record>
<menuitem id="res_partner_menu_contracts"
name="Contract"
parent="contacts.res_partner_menu_config"
sequence="7"/>
<menuitem id="res_partner_menu_contracts_fields"
name="Fields"
action="res_partner_contract_field_action"
parent="res_partner_menu_contracts"
sequence="1"/>
<menuitem id="res_partner_menu_contracts_templates"
name="Templates"
parent="res_partner_menu_contracts"
action="res_partner_document_template_action"
sequence="2"/>
</odoo>

View File

@ -1,106 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<!-- res.partner.contract tree -->
<record id="res_partner_contract_tree" model="ir.ui.view">
<field name="name">Contracts</field>
<field name="model">res.partner.contract</field>
<field name="arch" type="xml">
<tree string="Contracts">
<field name="name"/>
<field name="partner_id"/>
<field name="date_conclusion"/>
<field name="state"/>
</tree>
</field>
</record>
<!-- res.partner.contract form -->
<record id="res_partner_contract_form" model="ir.ui.view">
<field name="name">Contract Form</field>
<field name="model">res.partner.contract</field>
<field name="arch" type="xml">
<form string="Contract" create="0">
<header attrs="{'invisible': [('id', '=', False)]}">
<button name="action_sign" type="object" string="Sign" states="draft" class="oe_highlight"/>
<button name="action_close" type="object" string="Close" states="sign" class="oe_highlight"/>
<button name="action_renew" type="object" string="Renew" states="close"/>
<button name="action_print_form" type="object" string="Print"/>
<field name="state" widget="statusbar" statusbar_visible="draft,sign,close"/>
</header>
<sheet>
<group string="Contract parameters" name="single_params">
<field name="name" readonly="1"/>
<field name="partner_id" readonly="1"/>
<field name="create_date" readonly="1" attrs="{'invisible': [('id', '=', False)]}"/>
<field name="date_conclusion" readonly="1" attrs="{'invisible': ['|', ('id', '=', False), ('date_conclusion', '=', False)]}"/>
<field name="date_conclusion_fix" attrs="{'invisible': [('id', '=', False)]}"/>
<!-- Uses to generate number of Annex -->
<field name="contract_annex_number" invisible="1"/>
</group>
<group string="Annexed Specifications" name="annexes"> <!--attrs="{'invisible': ['|', ('id', '=', False), ('state', 'in', 'draft')]}"-->
<field name="contract_annex_ids"
nolabel="1"
mode="tree"
attrs="{'readonly': ['|', ('id', '=', False), ('state', 'in', 'draft')]}"
>
<tree>
<field name="contract_id" invisible="1"/>
<field name="display_name" string="Name"/>
<button name="action_print_form" type="object" string="Print" attrs="{'invisible': [('id', '=', False)]}"/>
</tree>
</field>
</group>
</sheet>
</form>
</field>
</record>
<!-- Mail Thread -->
<!-- res.partner.contract inherit form -->
<record id="view_id" model="ir.ui.view">
<field name="name">res.partner.contract.inherit.view.form</field>
<field name="model">res.partner.contract</field>
<field name="inherit_id" ref="res_partner_contract_form"/>
<field name="arch" type="xml">
<xpath expr="//sheet" position="after">
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</xpath>
</field>
</record>
<!-- Need it? -->
<record id="search_res_partner_contract_filter" model="ir.ui.view">
<field name="name">res_partner_contract_search</field>
<field name="model">res.partner.contract</field>
<field name="arch" type="xml">
<search string="Search Contract">
<field name="partner_id" operator="child_of"/>
</search>
</field>
</record>
<record id="res_partner_contract_action" model="ir.actions.act_window">
<field name="name">Contracts</field>
<field name="res_model">res.partner.contract</field>
<field name="view_mode">tree,form</field>
<field name="context">{}</field>
</record>
<record id="res_partner_contract_partner_action" model="ir.actions.act_window">
<field name="name">Contracts</field>
<field name="res_model">res.partner.contract</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_partner_id': active_id}</field>
</record>
</odoo>

View File

@ -1,51 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data noupdate="0">
<!-- res.partner.contract.annex form view -->
<record id="res_partner_contract_annex_view_form" model="ir.ui.view">
<field name="name">res.partner.contract.annex.view.form</field>
<field name="model">res.partner.contract.annex</field>
<field name="arch" type="xml">
<form string="Contract Annex">
<sheet>
<group name="options" invisible="1">
<field name="display_name" invisible="1"/>
<field name="currency_id" invisible="1"/>
</group>
<group name="info" string="Info">
<field name="name" placeholder="Leave empty for compute"/>
<field name="contract_id" attrs="{'invisible': [('contract_id', '=', False)]}"/>
<field name="order_id" options="{'no_create': True}"/>
<field name="date_conclusion"/>
</group>
<group name="design" string="Design">
<field name="design_period"/>
<field name="design_cost"/>
<field name="design_doc_period"/>
<field name="design_doc_cost"/>
</group>
<group name="delivery" string="Delivery">
<field name="delivery_address"/>
<field name="delivery_period"/>
</group>
<group name="installation" string="Installation">
<field name="installation_address"/>
<field name="installation_period"/>
<field name="installation_cost"/>
</group>
<group name="payment" string="Payment">
<field name="total_cost"/>
<field name="payment_part_one" widget="integer"/>
<field name="payment_part_two" widget="integer"/>
<field name="payment_part_three" widget="integer"/>
</group>
</sheet>
</form>
</field>
</record>
</data>
</odoo>

View File

@ -1,20 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data>
<!-- res.partner.contract.field tree view -->
<record id="res_partner_contract_field_view_tree" model="ir.ui.view">
<field name="name">res.partner.contract.field.view.tree</field>
<field name="model">res.partner.contract.field</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="description"/>
<field name="technical_name"/>
<field name="visible"/>
</tree>
</field>
</record>
</data>
</odoo>

View File

@ -1,17 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<!-- res.partner.document.template tree view -->
<record id="res_partner_document_template_view_tree" model="ir.ui.view">
<field name="name">res.partner.document.template.view.tree</field>
<field name="model">res.partner.document.template</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="company_type"/>
<field name="document_type"/>
</tree>
</field>
</record>
</odoo>

View File

@ -1,25 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<!-- sale.order inherit form view -->
<record id="view_order_form" model="ir.ui.view">
<field name="name">sale.order.inherit.view.form</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<xpath expr="//header" position="inside">
<button name="action_print_form" type="object" string="Create offer document"/>
</xpath>
<xpath expr="//field[@name='payment_term_id']" position="after">
<field name="delivery_time"/>
<field name="contract_annex_id" readonly="1"/>
</xpath>
</field>
</record>
<menuitem id="res_partner_contract_menu_act" name="Contracts" parent="sale.sale_order_menu" action="res_partner_contract_action" sequence="4"/>
</odoo>

View File

@ -1 +0,0 @@
from . import res_partner_contract_wizard

View File

@ -1,369 +0,0 @@
import base64
import logging
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError, UserError
from ..utils import MODULE_NAME
from ..utils.docxtpl import get_document_from_values_stream
_logger = logging.getLogger(__name__)
class ContractWizard(models.TransientModel):
_name = "res.partner.contract.wizard"
_inherit = ["client_contracts.utils"]
def _default_target(self):
return "{model},{target_id}".format(
model=self.env.context.get("active_model"),
target_id=int(self.env.context.get("self_id")),
)
def _default_document_template(self):
return self.env["res.partner.document.template"].search(
self._get_template_domain(), limit=1
)
def _get_template_domain(self):
template_type = {
"res.partner.contract": "contract",
"res.partner.contract.annex": "annex",
"sale.order": "offer",
}.get(self.active_model, False)
company_type = self.env.context.get("company_form", False)
document_template_domain = [
("template_type", "=", template_type),
("company_type", "=", company_type),
]
return document_template_domain
target = fields.Reference(
selection=[
("res.partner.contract", "Contract"),
("res.partner.contract.annex", "Contract Annex"),
("sale.order", "Offer"),
],
string="Target",
default=_default_target,
help="Record of contract or annex entity, from where wizard has been called",
)
company_id = fields.Many2one(
comodel_name="res.partner",
string="Company",
compute="_compute_company_id",
)
partner_id = fields.Many2one(
comodel_name="res.partner",
string="Partner",
compute="_compute_partner_id",
)
document_name = fields.Char(
string="Document Name", compute="_compute_document_name"
)
document_template = fields.Many2one(
comodel_name="res.partner.document.template",
string="Document Template",
default=_default_document_template,
domain=lambda self: self._get_template_domain(),
readonly=False,
)
transient_field_ids = fields.One2many(
comodel_name="res.partner.contract.field.transient",
inverse_name="_contract_wizard_id",
string="Contract Fields",
)
transient_field_ids_hidden = fields.One2many(
comodel_name="res.partner.contract.field.transient",
inverse_name="_contract_wizard_id",
)
@api.depends("target")
def _compute_company_id(self):
if self.target and self.target.company_id:
self.company_id = self.target.company_id.id
else:
self.company_id = self.env.company.id
@api.depends("target")
def _compute_partner_id(self):
if self.target:
self.partner_id = self.target.partner_id
@api.depends("document_template", "target")
def _compute_document_name(self):
self.document_name = self.target.get_name_by_document_template(
self.document_template
)
@api.constrains("document_template")
def _check_document_template(self):
if not self.document_template:
raise ValidationError("You did not set up the template...")
@api.onchange("document_template")
def _domain_document_template(self):
return {
"domain": {
"document_template": self._get_template_domain(),
}
}
@api.onchange("document_template")
def _onchange_document_template(self):
"""Creates transient fields for generate contract template
Looks as a tree view of *_contract_field_transient model in xml
"""
def get_contract_field_data(field_name, field_value):
rec = self.env["res.partner.contract.field"].search(
[("technical_name", "=", field_name)]
)
if not rec:
raise UserError(
_(
'Field "%s" specified in template, not found in model "res.partner.contract.field"'
)
% field_name
)
return {
"contract_field_id": rec.id,
"visible": rec.visible,
"value": field_value,
}
model_to_action = {
"res.partner.contract": "action_get_contract_context",
"res.partner.contract.annex": "action_get_annex_context",
"sale.order": "action_get_so_context",
}
action_external_id = "{}.{}".format(
MODULE_NAME, model_to_action[self.active_model]
)
action_rec = self.env.ref(action_external_id)
action_rec.model_id = (
self.env["ir.model"].search([("model", "=", self.active_model)]).id
)
# Get dictionary for `transient_fields_ids` with editable fields
# With data from Odoo database
contract_context_values = action_rec.with_context(
{"onchange_self": self.target}
).run()
transient_fields_data = [
get_contract_field_data(field_name, field_value)
for field_name, field_value in contract_context_values.items()
]
transient_fields_hidden_data = list(
filter(lambda item: not item["visible"], transient_fields_data)
)
transient_fields_data = list(
filter(lambda item: item["visible"], transient_fields_data)
)
self.transient_field_ids = [
(
6,
False,
self.env["res.partner.contract.field.transient"]
.create(transient_fields_data)
.ids,
)
]
self.transient_field_ids_hidden = [
(
6,
False,
self.env["res.partner.contract.field.transient"]
.create(transient_fields_hidden_data)
.ids,
)
]
# Other
def get_docx_contract(self):
template = self.document_template.attachment_id
template_path = template._full_path(template.store_fname)
payload = self.payload()
binary_data = get_document_from_values_stream(template_path, payload).read()
encoded_data = base64.b64encode(binary_data)
get_fn = self.target.get_filename_by_document_template
attachment_name = "{}.docx".format(get_fn(self.document_template or "Unknown"))
document_as_attachment = (
self.env["ir.attachment"]
.sudo()
.create(
{
"name": attachment_name,
"store_fname": attachment_name,
"type": "binary",
"datas": encoded_data,
}
)
)
return self.afterload(document_as_attachment)
def get_so_lines(self):
"""
Generates lines for printing from Sale order lines, including folding groups
ended with text "--fold".
:return: 2 values: lines data with folded groups and total amount of SO.
"""
def number_generator(n=1):
while True:
yield n
n += 1
sale_order_rec = (
self.target if self.target._name == "sale.order" else self.target.order_id
)
counter = number_generator()
lines_data = []
folded_group = False
group_description = ""
group_amount = 0.0
for item in sale_order_rec.order_line:
# Folded group ends #
if item.display_type == "line_section" and folded_group:
folded_group = False
lines_data.append(
{
"number": next(counter),
"vendor_code": "",
"label": group_description,
"count": 1.0,
"unit": self.env.ref("uom.product_uom_unit").name,
"cost_wo_vat": self.to_fixed(group_amount),
"subtotal": self.to_fixed(group_amount),
"display_type": False,
}
)
# Folded group starts #
if item.display_type == "line_section" and item.name.find("--fold") >= 0:
folded_group = True
group_amount = 0.0
index_for_cut = item.name.find("--fold")
group_description = item.name[:index_for_cut].strip()
# Regular, unfolded group or comment or regular line with product #
if (
item.display_type == "line_note"
or item.display_type == "line_section"
and item.name.find("--fold") == -1
or not item.display_type
) and not folded_group:
lines_data.append(
{
"number": next(counter) if not item.display_type else "",
"vendor_code": item.product_id.default_code
if (item.product_id and item.product_id.default_code)
else "",
"label": item.product_id.display_name
if item.product_id
else "",
"description": item.name,
"count": item.product_uom_qty,
"unit": item.product_uom.name if item.product_uom else "",
"cost": self.to_fixed(item.price_unit),
"cost_wo_vat": self.to_fixed(item.price_reduce_taxexcl),
"discount": item.discount,
"subtotal": self.to_fixed(item.price_subtotal),
"display_type": item.display_type,
}
)
# Line with product or comment inside folded group #
if folded_group and not item.display_type:
group_amount += item.price_subtotal
# Last folded group handling #
if folded_group and group_description:
lines_data.append(
{
"number": next(counter),
"vendor_code": "",
"label": group_description,
"count": 1.0,
"unit": self.env.ref("uom.product_uom_unit").name,
"cost_wo_vat": self.to_fixed(group_amount),
"subtotal": self.to_fixed(group_amount),
"display_type": False,
}
)
return lines_data, sale_order_rec.amount_total
def payload(self):
# Collect fields into a key-value structure
fields = {
transient_field.technical_name: transient_field.value
for transient_field in (
self.transient_field_ids + self.transient_field_ids_hidden
)
}
# Extend with special case
if self.target._name == "res.partner.contract.annex":
fields.update(
{
"annex_name": self.document_name,
"specification_name": self.target.specification_name,
}
)
# Extend with order product lines
if (
self.target._name == "sale.order"
or hasattr(self.target, "order_id")
and self.target.order_id.order_line
):
so_lines, total_amount = self.get_so_lines()
fields.update(
{
"products": so_lines,
"total_amount": total_amount,
"products_amount": len(
list(filter(lambda rec: not rec["display_type"], so_lines))
),
}
)
return self.middleware_fields(fields)
def afterload(self, result):
res_id = self.target.id
if hasattr(self.target, "contract_id"):
res_id = self.target.contract_id.id
target_model = (
self.target._name
if self.target._name
not in ("res.partner.contract", "res.partner.contract.annex")
else "res.partner.contract"
)
self.env["mail.message"].create(
{
"model": self.env.context.get("attachment_model") or target_model,
"res_id": self.env.context.get("attachment_res_id", res_id),
"message_type": "comment",
"attachment_ids": [(4, result.id, False)],
}
)
return result
@staticmethod
def middleware_fields(kv):
"""
Removes items without values from dictionary.
:kv: dict.
"""
# Debug False values
empty = []
for k, v in list(kv.items()):
if not v:
empty.append(k)
kv.pop(k)
_logger.debug("Empty fields: {}".format(empty))
return kv
@property
def active_model(self):
return self.env.context.get("active_model")

View File

@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="res_partner_wizard_print_document_view" model="ir.ui.view">
<field name="name">Generate Document</field>
<field name="model">res.partner.contract.wizard</field>
<field name="arch" type="xml">
<form>
<group string="Template">
<field name="document_template"></field>
</group>
<group invisible="1">
<!-- Need for generate a document -->
<field name="target"/>
<field name="partner_id"/>
<field name="company_id"/>
</group>
<button string="Create a document" type="object" name="get_docx_contract" />
<group string="Values">
<field name="transient_field_ids" nolabel="1" colspan="4">
<tree editable="1" create="0" delete="0">
<field name="name"/>
<field name="value"/>
<field name="description"/>
<field name="technical_name" invisible="1"/>
</tree>
</field>
<field name="transient_field_ids_hidden" invisible="1"/>
</group>
<footer/>
</form>
</field>
</record>
</odoo>

Binary file not shown.