# -*- coding: utf-8 -*-
from datetime import datetime, date, timedelta
from dateutil.relativedelta import relativedelta
from odoo import fields, api, models, _
from odoo.exceptions import UserError, ValidationError
class RentalProductContract(models.Model):
_name = "rental.product.contract"
_description = "Product Rental Contract"
name = fields.Char("Name")
partner_id = fields.Many2one("res.partner", string="Customer", required=True)
rental_id = fields.Many2one("rental.product.order", string="Rental Order Id")
contract_date = fields.Date(
string="Contract Date",
)
contractor_id = fields.Many2one("res.users", string="Contractor Name")
from_date = fields.Date(string="From Date")
to_date = fields.Date(string="To Date")
account_payment_term = fields.Many2one(
"account.payment.term", string="Payment Term", required=True
)
damage_charge = fields.Monetary(string="Damage Charge")
additional_charges = fields.Monetary(string="Additional Charges")
subtotal = fields.Monetary(string="Sub Total", readonly=True)
taxes = fields.Float(string="Taxes", compute="_compute_amount", readonly=True)
untaxed_amount = fields.Monetary(
string="Untaxed Amount",
compute="_compute_amount",
)
extra_charges = fields.Monetary(string="Extra Charges")
invoice_ids = fields.Many2one("account.move", string="Invoice Id")
signature = fields.Binary(string="Contractor Signature ")
signature_contractor = fields.Binary(string="Contractor Signature")
signature_customer = fields.Binary(string="Customer Signature")
button_name = fields.Char(string="Button Name")
terms_condition = fields.Text(string="Terms and Condition")
product_contract_lines_ids = fields.One2many(
"product.contract.lines", "product_contract_id", string="Order Line"
)
pricelist_id = fields.Many2one("product.pricelist", string="Pricelist")
total_amount = fields.Monetary(string="Total Amount", compute="_compute_amount")
total = fields.Monetary(string="Total", compute="_compute_total")
cost_generated = fields.Monetary(
string="Recurring Cost",
help="Costs paid at regular intervals, depending on the cost frequency",
)
cost_frequency = fields.Selection(
[
("no", "No"),
("daily", "Daily"),
("weekly", "Weekly"),
("monthly", "Monthly"),
("yearly", "Yearly"),
],
string="Recurring Cost Frequency",
required=True,
)
state = fields.Selection(
[
("futur", "Incoming"),
("open", "In Progress"),
("expired", "Expired"),
("diesoon", "Expiring Soon"),
("closed", "Closed"),
],
"Status",
default="open",
readonly=True,
)
cost = fields.Monetary(
string="Rent Cost",
help="This fields is to determine the cost of rent",
required=True,
)
account_type = fields.Many2one(
"account.account",
"Account",
default=lambda self: self.env["account.account"].search([("id", "=", 17)]),
)
recurring_line = fields.One2many(
"product.rental.line", "rental_number", readonly=True
)
attachment_ids = fields.Many2many(
"ir.attachment",
"product_rent_ir_attachments_rel",
"rental_id",
"attachment_id",
string="Attachments",
)
sum_cost = fields.Float(
compute="_compute_sum_cost", string="Indicative Costs Total"
)
auto_generated = fields.Boolean("Automatically Generated", readonly=True)
generated_cost_ids = fields.One2many(
"product.rental.line", "rental_number", string="Generated Costs"
)
invoice_count = fields.Integer(
compute="_invoice_count", string="# Invoice", copy=False
)
first_payment = fields.Float(string="First Payment", compute="_compute_amount")
first_invoice_created = fields.Boolean(
string="First Invoice Created", default=False
)
origin = fields.Char(string="Order Reference")
picking_id = fields.Many2one("stock.picking", string="Picking")
document_ids = fields.One2many(
"customer.document", "contract_id", string="Contract"
)
company_id = fields.Many2one(
"res.company", string="Company", default=lambda self: self.env.user.company_id
)
currency_id = fields.Many2one("res.currency", related="company_id.currency_id")
cancel_policy_ids = fields.One2many(
"rental.policy", "contract_id", string="Cancel Policy"
)
number_of_slot = fields.Integer(string="Number of Slot")
is_hours = fields.Boolean(string="Hours")
is_days = fields.Boolean(string="Days")
def generate_policy(self):
if not self.cancel_policy_ids and self.number_of_slot != 0:
number_of_days = self.from_date - self.contract_date
cancel_policy_list = []
if number_of_days.days >= (self.number_of_slot * 2):
day_per_slot = int(number_of_days.days / self.number_of_slot - 1)
day = 0
for i in range(self.number_of_slot - 1):
cancel_policy_list.append(
(
0,
0,
{
"from_date": self.contract_date + timedelta(day),
"to_date": self.contract_date
+ timedelta(day_per_slot + day),
},
)
)
day += day_per_slot + 1
cancel_policy_list.append(
(
0,
0,
{
"from_date": self.contract_date + timedelta(day),
"to_date": self.from_date - timedelta(days=2),
},
)
)
cancel_policy_list.append(
(
0,
0,
{
"from_date": self.from_date - timedelta(days=1),
"policy_charged": 100,
},
)
)
self.cancel_policy_ids = cancel_policy_list
else:
raise ValidationError(_("Please enter the sufficient Number of Slot"))
def write(self, vals):
if "button_name" in vals.keys():
if vals["button_name"] == "signature_contractor":
vals["signature_contractor"] = vals["signature"]
elif vals["button_name"] == "signature_customer":
vals["signature_customer"] = vals["signature"]
return super(RentalProductContract, self).write(vals)
@api.depends("product_contract_lines_ids")
def _compute_amount(self):
"""
Compute the total amounts of the SO.
"""
for order in self:
untaxed_amount = 0.0
taxes = 0.0
total_amount = 0.0
for line in order.product_contract_lines_ids:
untaxed_amount += line.sub_total
taxes += line.price_tax
total_amount += line.sub_total + line.price_tax
order.update(
{
"untaxed_amount": untaxed_amount,
"taxes": taxes,
"total_amount": untaxed_amount + taxes + order.extra_charges,
"first_payment": untaxed_amount + taxes + order.extra_charges,
}
)
@api.depends("recurring_line.recurring_amount")
def _compute_sum_cost(self):
for contract in self:
contract.sum_cost = sum(contract.recurring_line.mapped("recurring_amount"))
def _invoice_count(self):
invoice_ids = self.env["account.move"].search(
[("invoice_origin", "=", self.name)]
)
self.invoice_count = len(invoice_ids)
@api.model
def create(self, vals):
sequence_no = self.env["ir.sequence"].next_by_code("product_contract") or _(
"Product Contract"
)
vals.update({"name": sequence_no})
return super(RentalProductContract, self).create(vals)
@api.depends("product_contract_lines_ids", "damage_charge")
def _compute_total(self):
self.total = self.total_amount + self.damage_charge
def contract_close(self):
invoice_ids = self.env["account.move"].search(
[("invoice_origin", "=", self.name)]
)
order_ids = self.env["rental.product.order"].search(
[("res_number", "=", self.origin)]
)
is_paid = 0
for each in invoice_ids:
if each.state != "posted":
is_paid = 1
break
if is_paid == 0:
self.state = "closed"
order_ids.state = "close"
else:
raise UserError("Please Check invoices There are Some Invoices are pending")
def contract_open(self):
for record in self:
order_ids = self.env["rental.product.order"].search(
[("res_number", "=", record.origin)]
)
record.state = "open"
order_ids.state = "draft"
def act_renew_contract(self):
product_list = []
for product_line in self.product_contract_lines_ids:
product_list.append(
(
0,
0,
{
"product_id": product_line.product_id.id,
"price_based": product_line.price_based,
"enter_days": product_line.enter_days,
"price": product_line.price,
"enter_hour": product_line.enter_hour,
},
)
)
assert (
len(self.ids) == 1
), "This operation should only be done for 1 single contract at a time, as it it suppose to open a window as result"
for element in self:
# compute end date
startdate = fields.Date.from_string(element.from_date)
enddate = fields.Date.from_string(element.to_date)
diffdate = enddate - startdate
default = {
"contract_date": fields.Date.context_today(self),
"from_date": fields.Date.to_string(
fields.Date.from_string(element.to_date) + relativedelta(days=1)
),
"to_date": fields.Date.to_string(enddate + diffdate),
"cost_generated": self.cost_generated,
"product_contract_lines_ids": product_list,
}
newid = element.copy(default).id
return {
"name": _("Renew Contract"),
"view_mode": "form",
"view_id": self.env.ref(
"product_rental_bookings.rental_product_contract_form"
).id,
"view_type": "tree,form",
"res_model": "rental.product.contract",
"type": "ir.actions.act_window",
"res_id": newid,
}
def send_product_contract(self):
"""
This is Email for send contract Detail
"""
self.ensure_one()
ir_model_data = self.env["ir.model.data"]
try:
template_id = ir_model_data.get_object_reference(
"product_rental_bookings", "email_template_product_contract"
)[1]
except ValueError:
template_id = False
try:
compose_form_id = ir_model_data.get_object_reference(
"mail", "email_compose_message_wizard_form"
)[1]
except ValueError:
compose_form_id = False
ctx = {
"default_model": "rental.product.contract",
"default_res_id": self.ids[0],
"default_use_template": bool(template_id),
"default_template_id": template_id,
"mark_so_as_sent": True,
}
return {
"type": "ir.actions.act_window",
"view_type": "form",
"view_mode": "form",
"res_model": "mail.compose.message",
"views": [(compose_form_id, "form")],
"view_id": compose_form_id,
"target": "new",
"context": ctx,
}
def send_email_for_firstpayment(self, inv_id, contracts):
"""
Send email for payment.
"""
mail_content = _(
"
First Payment!
Hi %s,
This is to notify that You have to pay amount as per mention below.
"
"Please find the details below:
"
"Reference Number | | %s | |
"
"Date | | %s | |
|
Amount | | %s | |
"
) % (
contracts.partner_id.name,
inv_id.invoice_origin,
date.today(),
inv_id.amount_total,
)
main_content = {
"subject": _("You First Payment For: %s") % inv_id.invoice_origin,
"author_id": contracts.env.user.partner_id.id,
"body_html": mail_content,
"email_to": contracts.partner_id.email,
}
self.env["mail.mail"].create(main_content).send()
def notification_email_for_expire_contract(self, contracts):
mail_content = _(
"Expiration Of Rental Contract
"
"
Dear %s,
"
"Our record indicate that your rental contract %s, expire soon,
"
"If you want to renew this contract Then contact to our agency before last date of contract."
"
"
"
"
"
"
"
"
""
""
"Contract Ref | | "
"%s | | "
"
"
""
"Responsible Person | | "
" %s - %s | | "
"
"
""
) % (
contracts.partner_id.name,
contracts.name,
contracts.name,
contracts.contractor_id.name,
contracts.contractor_id.mobile,
)
main_content = {
"subject": "Expiration Of Rental Contract!",
"author_id": contracts.env.user.partner_id.id,
"body_html": mail_content,
"email_to": contracts.partner_id.email,
}
self.env["mail.mail"].create(main_content).send()
def send_email_for_recurring_invoice(self, inv_id, contracts):
mail_content = _(
"Reminder Recurrent Payment!
"
"
Hi %s,
This is to remind you that the "
"recurrent payment for the "
"rental contract has to be done."
"Please make the payment at the earliest."
"
"
"
"
"Please find the details below:"
"
"
"
"
""
""
"Amount | | "
" %s | | "
"
"
""
"Due Date | | "
" %s | | "
"
"
""
"Responsible Person | | "
" %s, %s | | "
"
"
""
) % (
contracts.partner_id.name,
inv_id.amount_total,
date.today(),
inv_id.user_id.name,
inv_id.user_id.mobile,
)
main_content = {
"subject": "Reminder Recurrent Payment!",
"author_id": contracts.env.user.partner_id.id,
"body_html": mail_content,
"email_to": contracts.partner_id.email,
}
self.env["mail.mail"].create(main_content).send()
def create_invoice(self):
inv_obj = self.env["account.move"]
recurring_obj = self.env["product.rental.line"]
inv_line = []
today = date.today()
journal_id = (
self.env["account.journal"]
.sudo()
.search(
[("type", "=", "sale"), ("company_id", "=", self.company_id.id)],
limit=1,
)
)
for contracts in self.search([]):
if not contracts.first_invoice_created:
contracts.first_invoice_created = True
supplier = contracts.partner_id
account_id = self.env["account.account"].search(
[
("code", "like", "708000"),
("company_id", "=", self.company_id.id),
]
)
if not account_id:
user_type_id = self.env.ref("account.data_account_type_revenue")
account_id = self.env["account.account"].create(
{
"code": "708000",
"name": "Location",
"company_id": self.company_id.id,
"user_type_id": user_type_id.id,
}
)
for each_product in contracts.product_contract_lines_ids:
if each_product.price_based == "per_hour":
total_qty = each_product.enter_hour
else:
total_qty = each_product.enter_days
inv_line_data = (
0,
0,
{
"name": each_product.product_id.name or "Deposit",
"product_id": each_product.product_id.id or False,
"product_id": each_product.product_id.id or False,
"account_id": account_id.id,
"price_unit": each_product.price * total_qty or 0.0,
"quantity": each_product.qty_needed,
"enter_hour": each_product.enter_hour,
"enter_days": each_product.enter_days,
"tax_ids": [(6, 0, each_product.tax_id.ids)],
},
)
inv_line.append(inv_line_data)
if self.extra_charges:
extra_charge_p_id = self.env.ref(
"product_rental_bookings.extra_charge_product_id"
)
extra_charge_inv_line = (
0,
0,
{
"name": extra_charge_p_id.name,
"product_id": extra_charge_p_id.id or False,
"price_unit": self.extra_charges,
"account_id": account_id.id,
"quantity": 1.0,
},
)
inv_line.append(extra_charge_inv_line)
inv_data = {
"move_type": "out_invoice",
"amount_residual": self.total_amount,
"currency_id": self.env.company.currency_id.id,
"journal_id": journal_id.id,
"company_id": self.env.company.id,
"partner_id": supplier.id,
"invoice_date_due": self.to_date,
"invoice_origin": contracts.name,
"contract_id": self.id,
"is_hours": self.is_hours,
"is_days": self.is_days,
"rental_order_id": self.rental_id.id,
"invoice_line_ids": inv_line,
}
bokeh = self.env["ir.module.module"].search(
[("name", "in", ["l10n_in", "l10n_in_purchase", "l10n_in_sale"])],
limit=1,
)
if bokeh and bokeh.state == "installed":
inv_data.update(
{
"l10n_in_gst_treatment": supplier.l10n_in_gst_treatment
or "unregistered",
}
)
inv_id = inv_obj.create(inv_data)
inv_id.action_post()
recurring_data = {
"name": "demo",
"date_today": today,
"rental_number": contracts.id,
"recurring_amount": contracts.first_payment,
"invoice_number": inv_id.id,
"invoice_ref": inv_id.id,
}
recurring_obj.create(recurring_data)
# self.send_email_for_firstpayment(inv_id, contracts)
if inv_id:
return {
"name": _("Account Move"),
"view_mode": "form",
"view_id": self.env.ref("account.view_move_form").id,
"view_type": "tree,form",
"res_model": "account.move",
"type": "ir.actions.act_window",
"res_id": inv_id.id,
}
@api.model
def scheduler_manage_invoice(self):
journal_id = self.env["account.move"].default_get(["journal_id"])["journal_id"]
inv_obj = self.env["account.move"]
recurring_obj = self.env["product.rental.line"]
_inv_line_data = {}
today = date.today()
for contracts in self.search([]):
account_id = self.env["account.account"].search(
[
("code", "like", "708000"),
("company_id", "=", contracts.company_id.id),
]
)
if not account_id:
user_type_id = self.env.ref("account.data_account_type_revenue")
account_id = self.env["account.account"].create(
{
"code": "708000",
"name": "Location",
"company_id": contracts.company_id.id,
"user_type_id": user_type_id.id,
}
)
start_date = datetime.strptime(str(contracts.from_date), "%Y-%m-%d").date()
end_date = datetime.strptime(str(contracts.to_date), "%Y-%m-%d").date()
if end_date >= date.today():
is_recurring = 0
if contracts.cost_frequency == "daily":
is_recurring = 1
elif contracts.cost_frequency == "weekly":
week_days = (date.today() - start_date).days
if week_days % 7 == 0 and week_days != 0:
is_recurring = 1
elif contracts.cost_frequency == "monthly":
if (
start_date.day == date.today().day
and start_date != date.today()
):
is_recurring = 1
elif contracts.cost_frequency == "yearly":
if (
start_date.day == date.today().day
and start_date.month == date.today().month
and start_date != date.today()
):
is_recurring = 1
if (
is_recurring == 1
and contracts.cost_frequency != "no"
and contracts.state != "expire"
and contracts.state != "close"
and contracts.state != "futur"
and contracts.first_invoice_created == True
):
inv_line = []
supplier = contracts.partner_id
line_len = len(contracts.product_contract_lines_ids)
for each_product in contracts.product_contract_lines_ids:
unit_price = contracts.cost_generated / line_len
inv_line_data = (
0,
0,
{
"product_id": each_product.product_id.id,
"name": each_product.product_id.name,
"product_id": each_product.product_id.id,
"account_id": account_id.id,
"price_unit": float(unit_price),
"quantity": 1,
"exclude_from_invoice_tab": False,
},
)
inv_line.append(inv_line_data)
inv_data = {
"type": "out_invoice",
"currency_id": contracts.account_type.company_id.currency_id.id,
"journal_id": journal_id,
"company_id": contracts.account_type.company_id.id,
"name": supplier.name,
"partner_id": supplier.id,
"invoice_date_due": contracts.to_date,
"invoice_origin": contracts.name,
"contract_id": contracts.id,
"invoice_line_ids": inv_line,
"is_hours": contracts.is_hours,
"is_days": contracts.is_days,
}
inv_id = inv_obj.create(inv_data)
payment_id = self.env["account.payment"].create(
{
"payment_type": "inbound",
"partner_type": "supplier",
"partner_id": supplier.id,
"amount": inv_id.amount_total,
"journal_id": journal_id,
"payment_date": date.today(),
"payment_method_id": "1",
"communication": inv_id.name,
}
)
recurring_data = {
"name": "demo",
"date_today": today,
"rental_number": contracts.id,
"recurring_amount": contracts.cost_generated,
"invoice_number": inv_id.id,
"invoice_ref": inv_id.id,
}
recurring_obj.create(recurring_data)
# self.send_email_for_recurring_invoice(inv_id, contracts)
else:
inv_line = []
if (
not contracts.first_invoice_created
and contracts.state != "futur"
and contracts.state != "expired"
):
contracts.first_invoice_created = True
supplier = contracts.partner_id
for each_product in contracts.product_contract_lines_ids:
if each_product.price_based == "per_day":
total_qty = each_product.enter_days
else:
total_qty = each_product.enter_hour
inv_line_data = (
0,
0,
{
"product_id": each_product.product_id.id,
"name": each_product.product_id.name,
"product_id": each_product.product_id.id,
"account_id": supplier.property_account_payable_id.id,
"price_unit": each_product.price,
"quantity": total_qty,
"tax_ids": [(6, 0, each_product.tax_id.ids)],
"exclude_from_invoice_tab": False,
},
)
inv_line.append(inv_line_data)
inv_data = {
"name": supplier.name,
"partner_id": supplier.id,
"currency_id": contracts.account_type.company_id.currency_id.id,
"journal_id": journal_id,
"invoice_origin": contracts.name,
"company_id": contracts.account_type.company_id.id,
"invoice_date_due": self.to_date,
"invoice_line_ids": inv_line,
}
inv_id = inv_obj.create(inv_data)
recurring_data = {
"name": "demo",
"date_today": today,
"rental_number": contracts.id,
"recurring_amount": contracts.first_payment,
"invoice_number": inv_id.id,
"invoice_ref": inv_id.id,
}
recurring_obj.create(recurring_data)
# self.send_email_for_firstpayment(inv_id, contracts)
@api.model
def shedule_manage_contract(self):
date_today = fields.Date.from_string(fields.Date.today())
in_fifteen_days = fields.Date.to_string(date_today + relativedelta(days=+15))
nearly_expired_contracts = self.search(
[("state", "=", "open"), ("to_date", "<", in_fifteen_days)]
)
res = {}
for contract in nearly_expired_contracts:
if contract.partner_id.id in res:
res[contract.partner_id.id] += 1
else:
res[contract.partner_id.id] = 1
# contract.notification_email_for_expire_contract(contract)
nearly_expired_contracts.write({"state": "diesoon"})
expired_contracts = self.search(
[("state", "!=", "expired"), ("to_date", "<", fields.Date.today())]
)
expired_contracts.write({"state": "expired"})
futur_contracts = self.search(
[
("state", "not in", ["futur", "closed"]),
("from_date", ">", fields.Date.today()),
]
)
futur_contracts.write({"state": "futur"})
now_running_contracts = self.search(
[("state", "=", "futur"), ("from_date", "<=", fields.Date.today())]
)
now_running_contracts.write({"state": "open"})
@api.model
def run_scheduler(self):
self.shedule_manage_contract()
self.scheduler_manage_invoice()
class productConLines(models.Model):
_name = "product.contract.lines"
_description = "Product Rental Contract Lines"
product_id = fields.Many2one("product.product", string="product Name")
price_based = fields.Selection(
[("per_day", "Day"), ("per_hour", "Hour"), ("per_session", "Session")],
default="per_day",
string="Based On",
)
enter_hour = fields.Float(string="Hour")
enter_days = fields.Float(string="Days")
price = fields.Monetary(string="Price")
total = fields.Monetary(string="Total")
product_contract_id = fields.Many2one("rental.product.contract", string="Contract")
tax_id = fields.Many2many("account.tax", "product_contract_tax_rel", string="Tax")
sub_total = fields.Monetary(string="Sub Total", compute="_get_subtotal", store=True)
price_tax = fields.Float(
compute="_get_subtotal", string="Taxes", readonly=True, store=True
)
price_total = fields.Monetary(
compute="_get_subtotal", string="Total ", readonly=True, store=True
)
description = fields.Char(string="Description")
currency_id = fields.Many2one(
"res.currency",
related="product_contract_id.currency_id",
store=True,
readonly=True,
)
qty_needed = fields.Integer(string="Quantity", default=1)
@api.depends("product_id", "enter_hour", "enter_days", "price", "tax_id")
def _get_subtotal(self):
for line in self:
if line.price_based == "per_day":
qty = line.enter_days * line.qty_needed
elif line.price_based == "per_session":
qty = line.qty_needed
else:
qty = line.enter_hour * line.qty_needed
print("\n\n\n\n\n\n------>", qty)
taxes = line.tax_id.compute_all(
qty, line.product_contract_id.currency_id, line.price
)
line.update(
{
"price_tax": sum(
t.get("amount", 0.0) for t in taxes.get("taxes", [])
),
"price_total": taxes["total_included"],
"sub_total": taxes["total_excluded"],
}
)
class ProductRentalLine(models.Model):
_name = "product.rental.line"
_description = "Rental Lines"
name = fields.Char(string="Name")
date_today = fields.Date("Date")
recurring_amount = fields.Float("Amount")
rental_number = fields.Many2one("rental.product.contract", string="Rental Number")
payment_info = fields.Char(
compute="paid_info", string="Payment Stage", default="draft"
)
auto_generated = fields.Boolean("Automatically Generated", readonly=True)
invoice_number = fields.Integer(string="Invoice ID")
invoice_ref = fields.Many2one("account.move", string="Invoice Ref")
@api.depends("payment_info")
def paid_info(self):
for record in self:
if self.env["account.move"].browse(record.invoice_number):
record.payment_info = (
self.env["account.move"].browse(record.invoice_number).state
)
else:
record.payment_info = "Record Deleted"
class CustomerDocument(models.Model):
_name = "customer.document"
_description = "Customer Document"
name = fields.Binary(string="Document")
id_number = fields.Char(string="ID Number")
contract_id = fields.Many2one("rental.product.contract", string="Conrtract")
class RentalPolicy(models.Model):
_name = "rental.policy"
_description = "Rental Policy"
contract_id = fields.Many2one("rental.product.contract", string="Contract")
from_date = fields.Date(string="From Date")
to_date = fields.Date(string="To Date")
policy_charged = fields.Float(string="Charge(In Percentage)")