[IMP] extract function for docx report printed from docx template
This commit is contained in:
parent
20e7dc02de
commit
38d49cac61
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
idea/
|
idea/
|
||||||
|
.DS_store
|
||||||
|
10
README.md
10
README.md
@ -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
|
|
||||||
|
11
TODOLIST.md
11
TODOLIST.md
@ -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
|
|
@ -1,2 +1 @@
|
|||||||
from . import models
|
from . import models
|
||||||
from . import wizard
|
|
||||||
|
@ -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
@ -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
1140
i18n/ru.po
File diff suppressed because it is too large
Load Diff
@ -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
|
|
||||||
|
@ -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
183
models/ir_actions_report.py
Normal 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()
|
@ -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",
|
|
||||||
)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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,
|
|
||||||
)
|
|
@ -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="",
|
|
||||||
)
|
|
@ -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()
|
|
@ -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}"
|
|
@ -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,3 +1 @@
|
|||||||
from . import misc
|
|
||||||
|
|
||||||
MODULE_NAME = __package__.split(".")[-2]
|
MODULE_NAME = __package__.split(".")[-2]
|
||||||
|
@ -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
|
|
@ -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}"
|
|
18
views/ir_actions_report_views.xml
Executable file
18
views/ir_actions_report_views.xml
Executable 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>
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -1 +0,0 @@
|
|||||||
from . import res_partner_contract_wizard
|
|
@ -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")
|
|
@ -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>
|
|
BIN
Шаблон_КП.docx
BIN
Шаблон_КП.docx
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user