[NEW] Addons creation - product_rental_bookings

This commit is contained in:
Stéphan Sainléger
2022-04-19 11:54:03 +02:00
parent 73749bcfb6
commit 8e2973526a
55 changed files with 10183 additions and 0 deletions

BIN
product_rental_bookings/.DS_Store vendored Normal file

Binary file not shown.

View File

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

View File

@@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
{
"name": "Product Rental Bookings",
"category": "Product",
"version": "14.0.1.0",
"summary": "Book products on several rental periods ",
"author": "Elabore",
"website": "https://elabore.coop/",
"installable": True,
"application": True,
"auto_install": False,
"description": """
===========================
Product Rental Web Bookings
===========================
This module allows Odoo Internal users to manage product rental stocks and booking calendar
Installation
============
Just install product_rental_web_bookings, all dependencies will be installed by default.
Known issues / Roadmap
======================
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/elabore-coop/.../issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Images
------
* Elabore: `Icon <https://elabore.coop/web/image/res.company/1/logo?unique=f3db262>`_.
Contributors
------------
* Stéphan Sainléger <https://github.com/stephansainleger>
Funders
-------
The development of this module has been financially supported by:
* Elabore (https://elabore.coop)
Maintainer
----------
This module is maintained by ELABORE.
""",
"depends": ["base", "account", "website_sale", "hr", "stock"],
"data": [
"security/ir.model.access.csv",
"security/security.xml",
"views/assets.xml",
"views/sequence.xml",
"views/account_invoice_view.xml",
"views/product_book.xml",
"views/product_view.xml",
"views/product_contract.xml",
"wizard/advance_payment_invoice.xml",
"views/product_operation.xml",
"views/product_order.xml",
"views/product_move.xml",
"views/res_config_settings_view.xml",
"views/stock_picking.xml",
"report/rental_order.xml",
"report/rental_order_report.xml",
"report/rental_contract_report.xml",
"report/rental_contract_recurring.xml",
"data/data.xml",
"views/session_config_settings_view.xml",
"views/menus.xml",
],
"qweb": [
"static/src/xml/delivery_sign.xml",
"static/src/xml/product_booking_calender.xml",
],
}

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="extra_charge_product_id" model="product.product">
<field name="name">Extra charge</field>
<field name="type">service</field>
</record>
<!-- <record id="rental_account_id" model="account.account">-->
<!-- <field name="code">708000</field>-->
<!-- <field name="name">Rental Order Account</field>-->
<!-- <field name="user_type_id" ref="account.data_account_type_direct_costs"/>-->
<!-- </record>-->
<record forcecreate="True" id="ir_cron_rental_contract_costs" model="ir.cron">
<field name="name">Generate Rental contracts costs</field>
<field name="model_id" ref="model_rental_product_contract" />
<field name="state">code</field>
<field name="code">model.run_scheduler()</field>
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field eval="False" name="doall" />
</record>
<record id="email_template_product_rental" model="mail.template">
<field name="name">Product Order- Send by Email</field>
<field name="email_from">
${(object.create_uid.email and '%s &lt;%s&gt;' % (object.create_uid.name, object.create_uid.email)or
'')|safe}>
</field>
<field name="subject">Order (Ref ${object.res_number or 'n/a' })</field>
<field name="partner_to">${object.customer_name.id}</field>
<field name="model_id" ref="product_rental_bookings.model_rental_product_order" />
<field name="report_name">${(object.res_number)}</field>
<field name="lang">${object.customer_name.lang}</field>
<field name="body_html">
<![CDATA[ <p>Dear ${object.customer_name.name},<br/>
<br/><br/>
<p>You can reply to this email if you have any questions.</p>
<p>Thank you,</p>
<p style="color:#888888;">
% if object.user_id and object.user_id.signature:
${object.user_id.signature | safe}
% endif
</p>
]]>
</field>
</record>
<record id="email_template_product_contract" model="mail.template">
<field name="name">Product Coontract- Send by Email</field>
<field name="email_from">
${(object.create_uid.email and '%s &lt;%s&gt;' % (object.create_uid.name, object.create_uid.email)or
'')|safe}>
</field>
<field name="subject">Order (Ref ${object.name or 'n/a' })</field>
<field name="partner_to">${object.partner_id.id}</field>
<field name="model_id" ref="product_rental_bookings.model_rental_product_contract" />
<field name="report_template" ref="product_rental_bookings.product_rental_contract_report" />
<field name="report_name">${(object.name)}</field>
<field name="lang">${object.partner_id.lang}</field>
<field name="body_html">
<![CDATA[ <p>Dear ${object.partner_id.name},<br/><br/>
<p>Your Rental contract has been created,Here we attached contract detail</p>
<br/><br/>
<p>You can reply to this email if you have any questions.</p>
<p>Thank you,</p>
<p style="color:#888888;">
% if object.user_id and object.user_id.signature:
${object.user_id.signature | safe}
% endif
</p>
]]>
</field>
</record>
</data>
</odoo>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
from . import account_move
from . import product
from . import product_book
from . import product_contract
from . import product_operation
from . import product_order
from . import product_logs
from . import res_config_settings
from . import stock_move
from . import stock_picking
from . import session_config_settings

View File

@@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
from datetime import datetime
from odoo import models, fields
from odoo.exceptions import UserError
class AccountInvoice(models.Model):
_inherit = "account.move"
rental_order_id = fields.Many2one("rental.product.order", string="Rental ref ")
interval_type = fields.Selection(
[("days", "Day"), ("weeks", "Week"), ("months", "Month")],
string="Interval Type",
)
interval_number = fields.Integer(string="Interval Number", readonly=1)
is_recurring = fields.Boolean(string="Recurring Invoice", default=False)
contract_id = fields.Many2one("rental.product.contract", string="Contract")
is_hours = fields.Boolean(string="Hours")
is_days = fields.Boolean(string="Days")
def action_invoice_open(self):
# lots of duplicate calls to action_invoice_open, so we remove those already open
to_open_invoices = self.filtered(lambda inv: inv.state != "open")
if to_open_invoices.filtered(lambda inv: inv.state != "draft"):
raise UserError(
_("Invoice must be in draft state in order to validate it.")
)
if to_open_invoices.filtered(lambda inv: inv.amount_total < 0):
raise UserError(
_(
"You cannot validate an invoice with a negative total amount. You should create a credit note instead."
)
)
to_open_invoices.action_date_assign()
to_open_invoices.action_move_create()
if (
self.interval_number > 0
and self.is_recuuring == False
and self.date_due >= datetime.now().strftime("%y-%m-%d-%H-%M")
):
self.is_recuuring = True
sub_name = (
str(self.number)
+ str("-" + self.interval_type if self.interval_type else "")
+ "-"
+ datetime.now().strftime("%Y-%m-%d")
)
model_id = self.env["ir.model"].search([("model", "=", self._name)])
subscription_document_id = self.env["subscription.document"].search(
[("name", "=", "Account Invoice"), ("model", "=", model_id.id)], limit=1
)
if not subscription_document_id:
subscription_document_id = self.env["subscription.document"].create(
{"name": "Account Invoice", "model": model_id.id}
)
subscription_doc_source = (
str(subscription_document_id.model.model) + "," + str(self.id)
)
subscription_id = self.env["subscription.subscription"].create(
{
"name": sub_name,
"partner_id": self.partner_id.id,
"interval_number": self.interval_number,
"interval_type": self.interval_type,
"doc_source": subscription_doc_source,
}
)
subscription_id.set_process()
return to_open_invoices.action_invoice_open()
class AccountInvoiceLine(models.Model):
_inherit = "account.move.line"
product_id = fields.Many2one("product.product", string="Product ID")
enter_hour = fields.Float(string="Hour")
enter_days = fields.Float(string="Days")
def _get_computed_price_unit(self):
self.ensure_one()
if not self.product_id:
return self.price_unit
elif self.move_id.is_sale_document(include_receipts=True):
# Out invoice.
price_unit = self.product_id.lst_price
elif self.move_id.is_purchase_document(include_receipts=True):
# In invoice.
price_unit = self.product_id.standard_price
else:
return self.price_unit
if self.product_uom_id != self.product_id.uom_id:
price_unit = self.product_id.uom_id._compute_price(
price_unit, self.product_uom_id
)
if self.enter_days:
price_unit = price_unit * self.enter_days
elif self.enter_hour:
price_unit = price_unit * self.enter_hour
return price_unit

View File

@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from odoo import models, fields
class ProductProduct(models.Model):
_inherit = "product.product"
is_rental = fields.Boolean(string="Rental", default=False)
selected_product = fields.Boolean(string="Select", default=False)
rental_amount = fields.Float(string="Rent Per Day Rate")
rental_amount_per_hour = fields.Float(string="Rent Per Hour Rate")
rental_amount_per_session = fields.Float(string="Rent Per Session Rate")
product_registration_id = fields.Many2one("rental.product.order", string="Order")
rental_qyt = fields.Float("Quantity", default=1.0)

View File

@@ -0,0 +1,368 @@
# -*- coding: utf-8 -*-
from datetime import datetime, date
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError, AccessError
class ProductBooking(models.TransientModel):
_name = "product.booking"
_rec_name = "book_number"
_description = "Book"
@api.depends(
"product_line_ids",
"total_days",
"extra_charges",
"price_based",
"from_date",
"to_date",
)
def compute_rate(self):
for record in self:
record.total = 0
record.sub_total = 0
record.total_days = 0
record.total_hours = 0
if record.from_date and record.to_date:
date_from = datetime.strptime(
str(record.from_date), "%Y-%m-%d %H:%M:%S"
)
date_to = datetime.strptime(str(record.to_date), "%Y-%m-%d %H:%M:%S")
delta = date_from - date_to
record.total_days = abs(delta.days)
record.total_hours = abs(delta.total_seconds() / 3600.0)
sub_total = 0.0
with_tax_total = 0.0
for product in record.product_line_ids.filtered(
lambda fv: fv.selected_product
):
if record.price_based == "per_day":
taxes = product.taxes_id.compute_all(
product.rental_qyt,
product.currency_id,
product.rental_amount * record.total_days,
)
sub_total += taxes["total_excluded"]
with_tax_total += taxes["total_included"]
elif record.price_based == "per_hour":
taxes = product.taxes_id.compute_all(
product.rental_qyt,
product.currency_id,
product.rental_amount_per_hour * record.total_hours,
)
sub_total += taxes["total_excluded"]
with_tax_total += taxes["total_included"]
else:
taxes = product.taxes_id.compute_all(
product.rental_qyt,
product.currency_id,
product.rental_amount_per_session,
)
sub_total += taxes["total_excluded"]
with_tax_total += taxes["total_included"]
record.sub_total = sub_total
record.total = with_tax_total + record.extra_charges
@api.model
def _get_based_on_selections(self):
enabled_day_rent = (
self.env["ir.config_parameter"].sudo().get_param("enabled_day_rent")
)
enabled_hour_rent = (
self.env["ir.config_parameter"].sudo().get_param("enabled_hour_rent")
)
enabled_session_rent = (
self.env["ir.config_parameter"].sudo().get_param("enabled_session_rent")
)
selection = []
if enabled_day_rent:
selection.append(("per_day", "Day"))
if enabled_hour_rent:
selection.append(("per_hour", "Hour"))
if enabled_session_rent:
selection.append(("per_session", "Session"))
return selection
book_number = fields.Char(string="Book Number")
from_date = fields.Datetime(string="From Date")
to_date = fields.Datetime(string="To Date")
product_line_ids = fields.Many2many(
"product.product",
"product_search_table",
"product_search_id",
"product_book_id",
string="Available Product",
)
is_search = fields.Boolean(string="Is Search", default=False)
total_days = fields.Float(string="Total Days", compute="compute_rate")
extra_charges = fields.Monetary(string="Extra Charges")
sub_total = fields.Float(string="Sub Total(Exclusive Tax)", compute="compute_rate")
total = fields.Float(string="Total", compute="compute_rate")
categ_id = fields.Many2one(
"product.category", required=True, string="Product Category"
)
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")
location_id = fields.Many2one("stock.location", string="Location")
price_based = fields.Selection(
selection=_get_based_on_selections, default="per_day", string="Based On"
)
total_hours = fields.Float(string="Total Hours", compute="compute_rate")
session_id = fields.Many2one("session.config", string="Session")
@api.constrains("from_date", "to_date")
def check_date(self):
for record in self:
if (
date.strftime(
datetime.strptime(str(record.from_date), "%Y-%m-%d %H:%M:%S"),
"%Y-%m-%d",
)
< str(date.today())
):
raise ValidationError(_("You cannot enter past date"))
if date.strftime(
datetime.strptime(str(record.to_date), "%Y-%m-%d %H:%M:%S"), "%Y-%m-%d"
) < str(date.today()):
raise ValidationError(_("You cannot enter past date"))
@api.model
def convert_float_to_hh_mm(self, session_id, from_date):
start_date = "{0:02.0f}:{1:02.0f}".format(
*divmod(session_id.start_time * 60, 60)
)
end_date = "{0:02.0f}:{1:02.0f}".format(*divmod(session_id.end_time * 60, 60))
start_date_fmt = (
"%Y-%m-%d "
+ start_date.split(":")[0]
+ ":"
+ start_date.split(":")[1]
+ ":"
+ "00"
)
end_date_fmt = (
"%Y-%m-%d "
+ end_date.split(":")[0]
+ ":"
+ end_date.split(":")[1]
+ ":"
+ "00"
)
start_str_datetime = from_date.strftime(start_date_fmt)
end_str_datetime = from_date.strftime(end_date_fmt)
start_date = self.env["rental.product.order"].convert_TZ_UTC(start_str_datetime)
end_date = self.env["rental.product.order"].convert_TZ_UTC(end_str_datetime)
return start_date, end_date
@api.onchange("price_based", "from_date", "session_id")
def onchange_date_from_price_based(self):
if self.price_based == "per_session" and self.from_date and self.session_id:
start_time, end_time = self.convert_float_to_hh_mm(
self.session_id, self.from_date
)
self.to_date = end_time
self.from_date = start_time
@api.model
def create(self, vals):
vals.update(
{
"book_number": self.env["ir.sequence"].next_by_code("product_booking")
or _("Product Booking")
}
)
return super(ProductBooking, self).create(vals)
def search_product(self):
product_id = []
if self.from_date and self.to_date:
if self.from_date > self.to_date:
raise ValidationError("To Date must be greater than From date")
elif not self.env.user.has_group("stock.group_stock_multi_locations"):
raise AccessError(
_("Manage Multiple Stock Locations need to be assign.")
)
else:
categ_ids = self.env["product.category"].search(
[("parent_id", "child_of", self.categ_id.id)]
)
self.env.cr.execute(
"""
SELECT
propro.id
FROM product_product propro
JOIN product_template protmp ON protmp.id = propro.product_tmpl_id
WHERE
protmp.categ_id IN %s AND
propro.is_rental='t' AND
propro.id IN (
Select product_id from stock_quant where location_id = %s);
""",
(
tuple(categ_ids.ids),
self.location_id.id,
),
)
product_data = self.env.cr.fetchall()
if product_data:
for products in product_data:
product = (
self.env["product.product"]
.sudo()
.search([("id", "in", products)])
)
available_product_stock = self.env["stock.quant"].search(
[
("product_id", "=", product.id),
("quantity", ">", 0),
("location_id", "=", self.location_id.id),
]
)
available_product_stock.product_id.rental_qyt = 1.0
available_product_stock.product_id.selected_product = False
product_id.extend(available_product_stock.product_id.ids)
self.update(
{
"product_line_ids": [(6, 0, product_id)],
"total": self.total,
"is_search": True,
}
)
else:
raise ValidationError("Sorry!!! No any Product Available!!!")
def book_product(self):
product = False
for product_line in self.product_line_ids:
if product_line.selected_product:
product = True
if product:
product_order_id = []
for product_id in self.product_line_ids.filtered(
lambda fv: fv.selected_product
):
# available_product_stock = self.env['stock.quant'].search([('product_id', '=', product_id.id),
# ('location_id', '=',
# self.location_id.id)])
# if product_id.rental_qyt > available_product_stock.quantity:
# raise ValidationError(_('Available quantity for %s is %d') % (product_id.name,
# available_product_stock.quantity))
order_line_obj = self.env["product.order.line"]
start_date = self.from_date
end_date = self.to_date
order_line_ids = order_line_obj.sudo().search(
[
("product_order_id.state", "=", "confirm"),
("product_id", "=", product_id.id),
]
)
total_in_order_qty = 0
for order_line in order_line_ids:
if (
(
start_date
<= order_line.product_order_id.from_date
<= end_date
)
or (
start_date
<= order_line.product_order_id.to_date
<= end_date
)
and order_line.product_order_id.picking_count == 1
):
total_in_order_qty += (
order_line.qty_needed
if order_line.product_order_id.picking_count == 1
else 0
)
elif (
(
order_line.product_order_id.from_date
<= start_date
<= order_line.product_order_id.to_date
)
or (
order_line.product_order_id.from_date
<= end_date
<= order_line.product_order_id.to_date
)
and order_line.product_order_id.picking_count == 1
):
total_in_order_qty += (
order_line.qty_needed
if order_line.product_order_id.picking_count == 1
else 0
)
if product_id.sudo().qty_available and total_in_order_qty:
qty_available = product_id.sudo().qty_available - total_in_order_qty
else:
total_in_order_qty = order_line_ids.filtered(
lambda x: x.qty_needed
if x.product_order_id.picking_count > 1
else 0
)
qty_available = product_id.sudo().qty_available + sum(
total_in_order_qty.mapped("qty_needed")
)
if (
not product_id.rental_qyt
or not qty_available >= product_id.rental_qyt
):
raise ValidationError(
_("Available quantity for %s is %d")
% (product_id.name, qty_available)
)
else:
if self.price_based == "per_day":
rate_total = product_id.rental_amount
else:
if self.price_based == "per_hour":
rate_total = product_id.rental_amount_per_hour
else:
rate_total = product_id.rental_amount_per_session
product_order_id.append(
(
0,
0,
{
"product_id": product_id.id,
"name": product_id.name,
"price_based": self.price_based,
"enter_days": self.total_days
if self.price_based == "per_day"
else 0,
"enter_hour": self.total_hours
if self.price_based in ["per_hour", "per_session"]
else 0,
"price": rate_total,
"qty_needed": product_id.rental_qyt,
"tax_id": [(6, 0, product_id.taxes_id.ids)],
},
)
)
return {
"name": "product order",
"view_type": "form",
"view_mode": "form",
"res_model": "rental.product.order",
"type": "ir.actions.act_window",
"context": {
"default_from_date": self.from_date,
"default_to_date": self.to_date,
"default_extra_charges": self.extra_charges,
"default_is_days": True if self.price_based == "per_day" else False,
"default_is_hours": True
if self.price_based in ["per_hour", "per_session"]
else False,
"default_product_order_lines_ids": product_order_id,
"default_location_id": self.location_id.id,
"default_price_based": self.price_based,
},
}
else:
raise ValidationError(_("First Please Select the Product"))

View File

@@ -0,0 +1,851 @@
# -*- 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 = _(
"<h3>First Payment!</h3><br/>Hi %s, <br/> This is to notify that You have to pay amount as per mention below.<br/><br/>"
"Please find the details below:<br/><br/>"
"<table><tr><td>Reference Number<td/><td> %s<td/><tr/>"
"<tr><td>Date<td/><td> %s <td/><tr/><tr><td>Amount <td/><td> %s<td/><tr/><table/>"
) % (
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 = _(
"<h3>Expiration Of Rental Contract</h3>"
"<br/>Dear %s, <br/>"
"Our record indicate that your rental contract <b>%s,</b> expire soon,<br/>"
"If you want to renew this contract Then contact to our agency before last date of contract."
"<br/>"
"<br/>"
"<br/>"
"<br/>"
"<table>"
"<tr>"
"<td>Contract Ref<td/>"
"<td>%s<td/>"
"<tr/>"
"<tr>"
"<td>Responsible Person <td/>"
"<td> %s - %s<td/>"
"<tr/>"
"<table/>"
) % (
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 = _(
"<h3>Reminder Recurrent Payment!</h3>"
"<br/>Hi %s, <br/> This is to remind you that the "
"recurrent payment for the "
"rental contract has to be done."
"Please make the payment at the earliest."
"<br/>"
"<br/>"
"Please find the details below:"
"<br/>"
"<br/>"
"<table>"
"<tr>"
"<td>Amount <td/>"
"<td> %s<td/>"
"<tr/>"
"<tr>"
"<td>Due Date <td/>"
"<td> %s<td/>"
"<tr/>"
"<tr>"
"<td>Responsible Person <td/>"
"<td> %s, %s<td/>"
"<tr/>"
"<table/>"
) % (
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)")

View File

@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
from odoo import models, fields
class RentalProductLogs(models.Model):
_name = "rental.product.logs"
_description = "product Products Logs"
_rec_name = "product_id"
product_id = fields.Many2one("product.product", string="Product")
customer_id = fields.Many2one("res.partner", string="Customer")
from_date = fields.Date(string="From Date")
to_date = fields.Date(string="To Date")
company_id = fields.Many2one(
"res.company", string="Company", default=lambda self: self.env.user.company_id
)

View File

@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
from odoo import models, fields
class RentalProductOperation(models.Model):
_name = "rental.product.operation"
_description = "product Products Operation"
name = fields.Char(string="Operation Types Name", required=True, translate=True)
color = fields.Integer(string="Color")
rental_move_type = fields.Selection(
[("outgoing", "Customers"), ("incoming", "Return"), ("internal", "Internal")],
string="Types of Operation",
required=True,
default="outgoing",
)
source_location = fields.Many2one("stock.location", string="Source Location")
destination_location = fields.Many2one(
"stock.location", string="Destination Location"
)
location_id = fields.Many2one("stock.location", string="Location")
state = fields.Selection(
[
("ready", "Ready"),
("on_rent", "On Rent"),
("service", "Service"),
("done", "Done"),
]
)
count_operation_ready = fields.Integer(compute="_compute_operation_count")
count_operation_on_rent = fields.Integer(compute="_compute_operation_count")
count_operation_service = fields.Integer(compute="_compute_operation_count")
operation_type_id = fields.Many2one(
"rental.product.operation", string="Operation Type"
)
company_id = fields.Many2one(
"res.company", string="Company", default=lambda self: self.env.user.company_id
)
rental_move_type = fields.Selection(
[("outgoing", "Customers"), ("incoming", "Return"), ("internal", "Internal")],
string="Types of Operation",
required=True,
default="internal",
)
def _compute_operation_count(self):
for each in self:
ready_state = self.env["stock.picking"].search_count(
[
("state", "in", ["draft"]),
("location_id", "=", each.location_id.id),
("is_rental", "=", True),
]
)
each.count_operation_ready = ready_state
on_rent_state = self.env["stock.picking"].search_count(
[
("location_id", "=", each.location_id.id),
("state", "=", "confirmed"),
("is_rental", "=", True),
]
)
each.count_operation_on_rent = on_rent_state
service_state = self.env["stock.picking"].search_count(
[
("state", "=", "assigned"),
("location_id", "=", each.location_id.id),
("is_rental", "=", True),
]
)
each.count_operation_service = service_state
def get_action_operation(self):
if self.rental_move_type == "outgoing":
state = ["draft"]
elif self.rental_move_type == "incoming":
state = ["confirmed"]
elif self.rental_move_type == "internal":
state = ["assigned"]
action_id = self.env.ref(
"product_rental_bookings.action_rental_product_move"
).read()[0]
action_id["context"] = {"default_rental_move_type": self.rental_move_type}
action_id["domain"] = [
("state", "in", state),
("rental_move_type", "=", self.rental_move_type),
("location_id", "in", self.location_id.ids),
]
return action_id

View File

@@ -0,0 +1,866 @@
# -*- coding: utf-8 -*-
from datetime import timedelta, date, datetime
import pytz
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
class PaymentTransaction(models.Model):
_inherit = "payment.transaction"
@api.model
def _compute_reference_prefix(self, values):
prefix = super(PaymentTransaction, self)._compute_reference_prefix(values)
if not prefix and values:
prefix = "Rental Order"
return prefix
def render_rental_button(self, order, submit_txt=None, render_values=None):
values = {
"partner_id": order.partner_shipping_id.id or order.partner_invoice_id.id,
"billing_partner_id": order.partner_invoice_id.id,
"type": "form_save",
}
if render_values:
values.update(render_values)
self._log_payment_transaction_sent()
return (
self.acquirer_id.with_context(
submit_class="btn btn-primary", submit_txt=submit_txt or _("Pay Now")
)
.sudo()
.render(
self.reference,
order.total_amount,
order.currency_id.id,
values=values,
)
)
class RentalProductOrder(models.Model):
_name = "rental.product.order"
_description = "product Product Order"
_rec_name = "res_number"
def _default_price_list(self):
return (
self.env["product.pricelist"]
.search(
[
("company_id", "in", (False, self.env.company.id)),
("currency_id", "=", self.env.company.currency_id.id),
],
limit=1,
)
.id
)
res_number = fields.Char(string="Order number", readonly=True, default="New")
product_name = fields.Many2one("product.product", string="Product Name")
from_date = fields.Datetime(string="From Date")
date_order = fields.Datetime(string="Date From")
to_date = fields.Datetime(string="To Date")
start_date = fields.Char(string="start Date")
end_date = fields.Char(string="end Date")
customer_name = fields.Many2one("res.partner", string="Customer Name")
book_date = fields.Datetime(string="Booking Date", default=datetime.now())
account_payment_term = fields.Many2one(
"account.payment.term", string="Payment Term", required=True, default=1
)
state = fields.Selection(
[
("draft", "Draft"),
("confirm", "Confirm"),
("cancel", "Cancel"),
("close", "Close"),
],
default="draft",
)
is_invoice = fields.Boolean(string="invoice")
rental_product_ids = fields.One2many(
"product.product", "product_registration_id", string="Product"
)
is_agreement = fields.Boolean(string="Contracts Require?", default=True)
product_order_lines_ids = fields.One2many(
"product.order.line", "product_order_id", string="Order Line "
)
invoice_count = fields.Integer(compute="_invoice_total", string="Total Invoiced")
contract_count = fields.Integer(compute="_contract_total", string="Total Contract")
picking_count = fields.Integer(compute="_picking_total", string="Total Invoiced ")
stock_picking_ids = fields.One2many(
"stock.picking", "product_order_rel_id", string="Picking Id"
)
invoice_ids = fields.One2many(
"account.move", "rental_order_id", string="Invoice Id"
)
contract_ids = fields.One2many(
"rental.product.contract", "rental_id", string="Contract Id"
)
pricelist_id = fields.Many2one(
"product.pricelist", string="Pricelist", default=_default_price_list
) # _default_pricelist
extra_charges = fields.Monetary(string="Extra Charges", readonly=True)
total_amount = fields.Monetary(
string="Total Amount", compute="_compute_amount", store=True
)
taxes = fields.Float(string="Taxes", compute="_compute_amount", store=True)
untaxed_amount = fields.Monetary(
string="Untaxed Amount", compute="_compute_amount", store=True
)
amount_untaxed = fields.Monetary(string="Untaxed Amount 1")
user_id = fields.Many2one(
"res.users", string="Dealer", default=lambda self: self.env.user
)
terms_condition = fields.Text(string="Terms And Condition")
invoice_status = fields.Char(compute="get_invoice_status", string="Invoice Status")
company_id = fields.Many2one(
"res.company",
string="Company",
readonly=True,
default=lambda self: self.env.user.company_id,
)
currency_id = fields.Many2one("res.currency", related="company_id.currency_id")
count = fields.Integer(
string="Count", compute="_compute_count", store=True, invisible=True
)
is_true = fields.Boolean(string="True")
partner_shipping_id = fields.Many2one(
"res.partner", related="customer_name", string="Delivery Address"
)
partner_invoice_id = fields.Many2one(
"res.partner", "Invoicing Address", related="customer_name"
)
return_date = fields.Datetime("Return Date")
location_id = fields.Many2one("stock.location", string="Location")
is_hours = fields.Boolean(string="Hours")
is_days = fields.Boolean(string="Days")
def _create_payment_transaction(self, vals):
"""Similar to self.env['payment.transaction'].create(vals) but the values are filled with the
current sales orders fields (e.g. the partner or the currency).
:param vals: The values to create a new payment.transaction.
:return: The newly created payment.transaction record.
"""
# Try to retrieve the acquirer. However, fallback to the token's acquirer.
acquirer_id = int(vals.get("acquirer_id"))
acquirer = False
payment_token_id = vals.get("payment_token_id")
acquirer = self.env["payment.acquirer"].browse(acquirer_id)
partner = self.env["res.partner"].browse(vals.get("partner_id"))
if payment_token_id and acquirer_id:
payment_token = (
self.env["payment.token"].sudo().browse(int(payment_token_id))
)
if payment_token and payment_token.acquirer_id != acquirer:
raise ValidationError(
_("Invalid token found! Token acquirer %s != %s")
% (payment_token.acquirer_id.name, acquirer.name)
)
if payment_token and payment_token.partner_id != partner:
raise ValidationError(
_("Invalid token found! Token partner %s != %s")
% (payment_token.partner_id.name, partner.name)
)
else:
acquirer = payment_token.acquirer_id
# Check an acquirer is there.
if not acquirer_id and not acquirer:
raise ValidationError(
_("A payment acquirer is required to create a transaction.")
)
if not acquirer:
acquirer = self.env["payment.acquirer"].browse(acquirer_id)
# Check a journal is set on acquirer.
if not acquirer.journal_id:
raise ValidationError(
_("A journal must be specified of the acquirer %s." % acquirer.name)
)
if not acquirer_id and acquirer:
vals["acquirer_id"] = acquirer.id
vals.update(
{
"date": datetime.now(),
"amount": vals.get("amount"),
"currency_id": vals.get("currency_id"),
"partner_id": vals.get("partner_id"),
}
)
transaction = self.env["payment.transaction"].create(vals)
# Process directly if payment_token
if transaction.payment_token_id:
transaction.s2s_do_transaction()
return transaction
@api.depends("customer_name")
def _compute_count(self):
self.ensure_one()
self.count = len(self.search([("customer_name", "=", self.customer_name.id)]))
@api.depends("invoice_status")
def get_invoice_status(self):
for record in self:
record.invoice_status = ""
for invoice in record.invoice_ids:
record.invoice_status = invoice.state
@api.onchange("customer_name")
def onchange_customer(self):
self.account_payment_term = self.customer_name.property_payment_term_id.id
@api.depends("stock_picking_ids")
def _picking_total(self):
for order in self:
order.picking_count = len(order.stock_picking_ids)
@api.depends("invoice_ids")
def _invoice_total(self):
for order in self:
order.invoice_count = len(order.invoice_ids)
def convert_TZ_UTC(self, TZ_datetime):
fmt = "%Y-%m-%d %H:%M:%S"
# Current time in UTC
now_utc = datetime.now(pytz.timezone("UTC"))
# Convert to current user time zone
now_timezone = now_utc.astimezone(pytz.timezone(self.env.user.tz))
UTC_OFFSET_TIMEDELTA = datetime.strptime(
now_utc.strftime(fmt), fmt
) - datetime.strptime(now_timezone.strftime(fmt), fmt)
local_datetime = datetime.strptime(TZ_datetime, fmt)
result_utc_datetime = local_datetime + UTC_OFFSET_TIMEDELTA
return result_utc_datetime.strftime(fmt)
def action_view_order_invoices(self):
action = self.env.ref("account.action_move_out_invoice_type").read()[0]
invoices = self.mapped("invoice_ids")
if len(invoices) > 1:
action["domain"] = [("id", "in", invoices.ids)]
elif invoices:
action["views"] = [(self.env.ref("account.view_move_form").id, "form")]
action["res_id"] = invoices.id
return action
@api.depends("contract_ids")
def _contract_total(self):
for contract in self:
contract.contract_count = len(contract.contract_ids)
def action_view_order_contract(self):
action = self.env.ref(
"product_rental_bookings.action_rental_contract_view_tree"
).read()[0]
contracts = self.mapped("contract_ids")
if len(contracts) > 1:
action["domain"] = [("id", "in", contracts.ids)]
elif contracts:
action["views"] = [
(
self.env.ref(
"product_rental_bookings.rental_product_contract_form"
).id,
"form",
)
]
action["res_id"] = contracts.id
return action
@api.onchange("customer_name")
def customer_pricelist(self):
values = {
"pricelist_id": self.customer_name.property_product_pricelist
and self.customer_name.property_product_pricelist.id
or False,
}
self.update(values)
@api.model
def create(self, vals):
product_order_line = []
if vals.get("is_true"):
for order_line in vals.get("product_order_lines_ids"):
if order_line[2] and order_line[0] != 0:
product_order_line.append([0, False, order_line[2]])
elif order_line[2] and order_line[0] == 0:
product_order_line.append(order_line)
vals.update({"product_order_lines_ids": product_order_line})
sequence = self.env["ir.sequence"].next_by_code("product_registration") or _(
"Product Register"
)
vals.update({"res_number": sequence})
res = super(RentalProductOrder, self).create(vals)
from_date, to_date = self.start_end_date_global(res.from_date, res.to_date)
res.start_date = from_date
res.end_date = to_date
return res
@api.depends("product_order_lines_ids", "customer_name")
def _compute_amount(self):
"""
Compute the total amounts of the RO.
"""
for order in self:
untaxed_amount = 0.0
taxes = 0.0
for line in order.product_order_lines_ids:
untaxed_amount += line.sub_total
taxes += line.price_tax
if order.pricelist_id:
order.update(
{
"untaxed_amount": order.pricelist_id.currency_id.round(
untaxed_amount
),
"taxes": order.pricelist_id.currency_id.round(taxes),
"total_amount": untaxed_amount + taxes + order.extra_charges,
}
)
else:
order.update(
{
"untaxed_amount": untaxed_amount,
"taxes": taxes,
"total_amount": untaxed_amount + taxes + order.extra_charges,
}
)
def book(self):
self.state = "book"
@api.model
def _get_picking_type(self, company_id):
picking_type = self.env["stock.picking.type"].search(
[("code", "=", "incoming"), ("warehouse_id.company_id", "=", company_id)]
)
if not picking_type:
picking_type = self.env["stock.picking.type"].search(
[("code", "=", "incoming"), ("warehouse_id", "=", False)]
)
return picking_type[:1].id
def confirm(self):
product_order_id = []
move_ids_without_package = []
for each in self.product_order_lines_ids:
product_order_id.append((0, 0, {"product_id": each.product_id.id}))
move_ids_without_package.append(
(
0,
0,
{
"product_id": each.product_id.id,
"product_uom_qty": each.qty_needed,
"product_uom": each.product_id.uom_id.id,
"location_id": self.location_id.id
or each.product_id.location_id.id,
"location_dest_id": self.env.ref(
"stock.stock_location_customers"
).id,
"name": each.product_id.name,
"company_id": self.company_id.id,
},
)
)
stock_picking_id = self.env["stock.picking"].create(
{
"partner_id": self.customer_name.id,
"location_id": self.location_id.id,
"location_dest_id": self.env.ref("stock.stock_location_customers").id,
"rental_move_type": "outgoing",
"picking_type_id": self._get_picking_type(self.company_id.id),
"product_order_rel_id": self.id,
"is_rental": True,
"origin": self.res_number,
"move_ids_without_package": move_ids_without_package,
}
)
self.state = "confirm"
if self.is_agreement:
rental_product_contract_obj = self.env["rental.product.contract"]
product_order_id = []
for each in self.product_order_lines_ids:
product_order_id.append(
(
0,
0,
{
"product_id": each.product_id.id or "",
"price_based": each.price_based or "",
"enter_days": each.enter_days or "",
"enter_hour": each.enter_hour or "",
"price": each.price or "",
"qty_needed": each.qty_needed,
"sub_total": each.sub_total or "",
"tax_id": [(6, 0, each.tax_id.ids)],
},
)
)
self.state = "confirm"
view_id = self.env.ref(
"product_rental_bookings.rental_product_contract_form"
)
contract = rental_product_contract_obj.create(
{
"partner_id": self.customer_name.id,
"from_date": self.from_date,
"to_date": self.to_date,
"total_amount": self.total_amount,
"rental_id": self.id,
"product_contract_lines_ids": product_order_id,
"cost_frequency": "no",
"contract_date": self.book_date,
"account_payment_term": self.account_payment_term.id,
"contractor_id": self.user_id.id,
"origin": self.res_number,
"cost": 12,
"is_hours": self.is_hours,
"is_days": self.is_days,
"picking_id": stock_picking_id.id,
"company_id": self.company_id.id,
"name": "New",
}
)
return {
"name": _("product Contract"),
"type": "ir.actions.act_window",
"view_type": "form",
"view_mode": "form",
"res_model": "rental.product.contract",
"res_id": contract.id,
"view_id": view_id.id,
}
self.state = "confirm"
def action_view_invoice(self):
invoices = self.mapped("invoice_ids")
action = self.env.ref("account.action_invoice_tree1").read()[0]
if len(invoices) > 1:
action["domain"] = [("id", "in", invoices.ids)]
elif len(invoices) == 1:
action["views"] = [(self.env.ref("account.move_form").id, "form")]
action["res_id"] = invoices.ids[0]
else:
action = {"type": "ir.actions.act_window_close"}
return action
def action_view_stock_pickings(self):
pickings = self.mapped("stock_picking_ids")
return {
"name": "Pickings",
"view_mode": "tree,form",
"res_model": "stock.picking",
"domain": [("id", "in", pickings.ids)],
"res_id": self.id,
"type": "ir.actions.act_window",
}
def cancel(self):
inv_obj = self.env["account.move"]
order_id = self.env["product.order.line"].browse(int(self.id)).product_order_id
self = order_id
for contract in order_id.contract_ids:
for cancel_policy in contract.cancel_policy_ids:
if cancel_policy.from_date and cancel_policy.to_date:
if (
date.today() >= cancel_policy.from_date
and date.today() <= cancel_policy.to_date
):
invoice_browse = (
self.env["account.move"]
.sudo()
.search(
[
("contract_id", "=", contract.id),
("type", "=", "out_invoice"),
]
)
)
for each_invoice in invoice_browse:
if each_invoice.state == "draft":
invoice_line_data = []
invoice_line_data.append(
(
0,
0,
{
"product_id": self.product_name.id,
"name": "Cancel Policy " + self.res_number,
"account_id": self.customer_name.property_account_receivable_id.id,
"price_unit": (
contract.total_amount
* cancel_policy.policy_charged
)
/ 100,
"quantity": 1,
},
)
)
invoice = inv_obj.create(
{
"name": self.res_number,
"origin": self.res_number,
"partner_id": self.customer_name.id,
"type": "out_invoice",
"date_invoice": date.today(),
"reference": False,
"account_id": self.customer_name.property_account_receivable_id.id,
"invoice_line_ids": invoice_line_data,
}
)
elif each_invoice.state == "paid":
invoice_line_data = []
invoice_line_data.append(
(
0,
0,
{
"product_id": self.product_name.id,
"name": "Cancel Policy " + self.res_number,
"account_id": self.customer_name.property_account_receivable_id.id,
"price_unit": each_invoice.total_amount
- (
(
contract.total_amount
* cancel_policy.policy_charged
)
/ 100
),
"quantity": 1,
},
)
)
invoice = inv_obj.create(
{
"name": self.res_number,
"origin": self.res_number,
"partner_id": self.customer_name.id,
"type": "in_refund",
"date_invoice": date.today(),
"reference": False,
"account_id": self.customer_name.property_account_receivable_id.id,
"invoice_line_ids": invoice_line_data,
}
)
if not cancel_policy.to_date:
if date.today() >= cancel_policy.from_date:
invoice_browse = self.env["account.move"].search(
[
("contract_id", "=", contract.id),
("type", "=", "out_invoice"),
]
)
for each_invoice in invoice_browse:
if each_invoice.state == "draft":
each_invoice.state = "paid"
self.state = "cancel"
def send_product_quote(self):
"""
This is Email for send quotation product order inquiry
"""
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_rental"
)[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.order",
"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 close(self):
view_id = self.env.ref("rental_product_rental")
return {
"name": "product Service",
"type": "ir.actions.act_window",
"view_type": "form",
"view_mode": "form",
"res_model": "wizard.product.service",
"view_id": view_id.id,
"target": "new",
}
@api.model
def start_end_date_global(self, start, end):
tz = pytz.utc
current_time = datetime.now(tz)
hour_tz = int(str(current_time)[-5:][:2])
min_tz = int(str(current_time)[-5:][3:])
sign = str(current_time)[-6][:1]
sdate = str(start)
edate = str(end)
if sign == "+":
start_date = (
datetime.strptime(sdate, "%Y-%m-%d %H:%M:%S")
+ timedelta(hours=hour_tz, minutes=min_tz)
).strftime("%Y-%m-%d %H:%M:%S")
end_date = (
datetime.strptime(edate, "%Y-%m-%d %H:%M:%S")
+ timedelta(hours=hour_tz, minutes=min_tz)
).strftime("%Y-%m-%d %H:%M:%S")
if sign == "-":
start_date = (
datetime.strptime(sdate, "%Y-%m-%d %H:%M:%S")
- timedelta(hours=hour_tz, minutes=min_tz)
).strftime("%Y-%m-%d %H:%M:%S")
end_date = (
datetime.strptime(edate, "%Y-%m-%d %H:%M:%S")
- timedelta(hours=hour_tz, minutes=min_tz)
).strftime("%Y-%m-%d %H:%M:%S")
return start_date, end_date
@api.model
def start_and_end_date_global(self, start, end):
tz = pytz.timezone(self.env.user.tz) or "UTC"
current_time = datetime.now(tz)
hour_tz = int(str(current_time)[-5:][:2])
min_tz = int(str(current_time)[-5:][3:])
sign = str(current_time)[-6][:1]
sdate = str(start)
edate = str(end)
if sign == "-":
start_date = (
datetime.strptime(sdate.split(".")[0], "%Y-%m-%d %H:%M:%S")
+ timedelta(hours=hour_tz, minutes=min_tz)
).strftime("%Y-%m-%d %H:%M:%S")
end_date = (
datetime.strptime(edate.split(".")[0], "%Y-%m-%d %H:%M:%S")
+ timedelta(hours=hour_tz, minutes=min_tz)
).strftime("%Y-%m-%d %H:%M:%S")
if sign == "+":
start_date = (
datetime.strptime(sdate.split(".")[0], "%Y-%m-%d %H:%M:%S")
- timedelta(hours=hour_tz, minutes=min_tz)
).strftime("%Y-%m-%d %H:%M:%S")
end_date = (
datetime.strptime(edate.split(".")[0], "%Y-%m-%d %H:%M:%S")
- timedelta(hours=hour_tz, minutes=min_tz)
).strftime("%Y-%m-%d %H:%M:%S")
return start_date, end_date
@api.model
def utc_to_tz(self, start, end):
start_date = pytz.utc.localize(
datetime.strptime(start, "%Y-%m-%d %H:%M:%S")
).astimezone(pytz.timezone(self.env.user.tz))
end_date = pytz.utc.localize(
datetime.strptime(end, "%Y-%m-%d %H:%M:%S")
).astimezone(pytz.timezone(self.env.user.tz))
return start_date, end_date
@api.model
def get_booking_data(self, model_id):
resourcelist = []
eventlist = []
id_list = []
product_booking = self.env["rental.product.order"].search(
[("state", "in", ["confirm"])]
)
categ_ids = self.env["product.category"].search(
[("parent_id", "child_of", int(model_id))]
)
for data in product_booking:
if data.product_order_lines_ids and data.from_date.date() >= date.today():
for line in data.product_order_lines_ids:
if (
line.product_id.categ_id.id in categ_ids.ids
and line.product_id.id not in id_list
):
resourcelist.append(
{
"id": line.product_id.id,
"building": line.product_id.categ_id.name,
"title": line.product_id.name,
"type": line.product_id.categ_id.id or False,
"product_id": line.product_id.id,
}
)
id_list.append(line.product_id.id)
if line.product_id.categ_id.id in categ_ids.ids:
if data.start_date and data.end_date:
start_date, end_date = self.utc_to_tz(
data.start_date, data.end_date
)
start = (
str(start_date.date()) + "T" + str(start_date.time())
)
end = str(end_date.date()) + "T" + str(end_date.time())
else:
start_date, end_date = self.utc_to_tz(
data.start_date, data.end_date
)
start = (
str(start_date.date()) + "T" + str(start_date.time())
)
end = str(end_date.date()) + "T" + str(end_date.time())
eventlist.append(
{
"id": line.id,
"line_id": line.id,
"resourceId": line.product_id.id,
"start": start,
"end": end,
"title": data.res_number,
"type": model_id or False,
"product_id": line.product_id.id,
}
)
product_model = self.env["product.product"].search([("is_rental", "=", True)])
for product in product_model:
if product.categ_id.id in categ_ids.ids and product.id not in id_list:
id_list.append(product.id)
resourcelist.append(
{
"id": product.id,
"building": product.categ_id.name,
"title": product.name,
"type": product.categ_id.id or False,
"product_id": product.id,
}
)
if not resourcelist:
eventlist = []
return [resourcelist, eventlist]
@api.model
def remove_event(self, line_id):
record_line_id = self.env["product.order.line"].browse(int(line_id))
if len(record_line_id.product_order_id.product_order_lines_ids.ids) == 1:
record_line_id.product_order_id.state = "cancel"
elif len(record_line_id.product_order_id.product_order_lines_ids.ids) > 1:
record_line_id.unlink()
class productOrderLine(models.Model):
_name = "product.order.line"
_description = "product Order Line"
product_order_id = fields.Many2one("rental.product.order", string="product order")
product_id = fields.Many2one("product.product", string="product")
price_based = fields.Selection(
[("per_day", "Day"), ("per_hour", "Hour"), ("per_session", "Session")],
default="per_day",
string="Based On",
)
tax_id = fields.Many2many("account.tax", "product_order_tax_rel", string="Tax")
enter_kms = fields.Float(string="KM")
enter_hour = fields.Float(string="Hour")
enter_days = fields.Float(string="Days")
price = fields.Monetary(string="Price")
total = fields.Monetary(string="Total")
sub_total = fields.Monetary(string="Sub Total", compute="_get_subtotal", store=True)
price_tax = fields.Float(compute="_get_subtotal", string="Taxes", store=True)
price_total = fields.Monetary(
compute="`_get_subtotal`", string="Total Price", store=True
)
name = fields.Char(string="Description")
currency_id = fields.Many2one(
"res.currency", related="product_order_id.currency_id"
)
qty_needed = fields.Integer(string="Quantity", default=1)
@api.onchange("price_based")
def get_price_value(self):
self.sub_total = 0.0
if self.price_based == "per_day":
self.price = self.product_id.rental_amount
self.sub_total = (
self.enter_days * self.qty_needed * self.product_id.rental_amount
)
elif self.price_based == "per_hour":
self.price = self.product_id.rental_amount_per_hour
self.sub_total = (
self.enter_hour
* self.qty_needed
* self.product_id.rental_amount_per_hour
)
else:
self.price = self.product_id.rental_amount_per_session
self.sub_total = (
self.enter_hour
* self.qty_needed
* self.product_id.rental_amount_per_session
)
@api.onchange("product_id")
def get_line_value(self):
if self.product_order_id.from_date and self.product_order_id.to_date:
if self.price_based == "per_hour":
self.price = self.product_id.rental_amount_per_hour
elif self.price_based == "per_day":
self.price = self.product_id.rental_amount
else:
self.price = self.product_id.rental_amount_per_session
else:
raise ValidationError(_("Please Select From date Or to date!!!"))
@api.depends("product_id", "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
taxes = line.tax_id.compute_all(
qty, line.product_order_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"],
}
)
line.get_line_value()
class WizardProductServiceModel(models.TransientModel):
_name = "wizard.product.service"
_description = "product Service"
_inherit = "rental.product.order"
is_damaged = fields.Boolean(string="Is Damaged")
service_location_id = fields.Many2one("stock.location", string="Service Location")
product_location_id = fields.Many2one("stock.location", string="Product Location")
def confirm_service(self):
self.state = "confirm"

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"
enabled_day_rent = fields.Boolean(string="Day Rent")
enabled_hour_rent = fields.Boolean(string="Hour Rent")
enabled_session_rent = fields.Boolean(string="Session Rent")
@api.model
def get_values(self):
res = super(ResConfigSettings, self).get_values()
get_param = self.env["ir.config_parameter"].sudo().get_param
res.update(
enabled_day_rent=get_param("enabled_day_rent"),
enabled_hour_rent=get_param("enabled_hour_rent"),
enabled_session_rent=get_param("enabled_session_rent"),
)
return res
def set_values(self):
self.env["ir.config_parameter"].sudo().set_param(
"enabled_day_rent", self.enabled_day_rent
)
self.env["ir.config_parameter"].sudo().set_param(
"enabled_hour_rent", self.enabled_hour_rent
)
self.env["ir.config_parameter"].sudo().set_param(
"enabled_session_rent", self.enabled_session_rent
)
res = super(ResConfigSettings, self).set_values()
ICPSudo = self.env["ir.config_parameter"].sudo()
ICPSudo.set_param(
"product_rental_bookings.enabled_day_rent", self.enabled_day_rent
)
ICPSudo.set_param(
"product_rental_bookings.enabled_day_rent", self.enabled_hour_rent
)
ICPSudo.set_param(
"product_rental_bookings.enabled_day_rent", self.enabled_session_rent
)
return res

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
class SessionConfigSettings(models.Model):
_name = "session.config"
_description = "Session configuration settings"
name = fields.Char("Name")
start_time = fields.Float("Start Time")
end_time = fields.Float("End Time")
@api.constrains("start_time", "end_time")
def _check_time_constraint(self):
for record in self:
if (
record.start_time < 0.0
or 24.0 < record.start_time
or record.end_time < 0.0
or 24.0 < record.end_time
):
raise ValidationError(_("Start time or End time is wrong"))
elif record.start_time > record.end_time:
raise ValidationError(_("Start time must be smaller than End time"))

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
from odoo import models, fields
class StockMove(models.Model):
_inherit = "stock.move"
product_move_id = fields.Many2one("stock.picking", string="Product Move")
products_checked = fields.Boolean(string="Select Products")

View File

@@ -0,0 +1,163 @@
# -*- coding: utf-8 -*-
from datetime import datetime
from odoo import models, fields, api, _
from odoo.exceptions import UserError
from odoo.tests import Form
class StockPicking(models.Model):
_inherit = "stock.picking"
product_order_rel_id = fields.Many2one(
"rental.product.order", string="product Order"
)
contract_ids = fields.One2many(
"rental.product.contract", "picking_id", string="Contract Id"
)
is_rental = fields.Boolean(string="Rental Move")
product_move_line_id = fields.One2many(
"stock.move", "product_move_id", string="Move Lines"
)
rental_move_type = fields.Selection(
[("outgoing", "Customers"), ("incoming", "Return"), ("internal", "Internal")],
string="Types of Operation",
required=True,
default="outgoing",
)
def delivery(self):
move_ids_without_package = []
if all([each.products_checked for each in self.move_ids_without_package]):
self.action_confirm()
self.action_assign()
res = self.button_validate()
if isinstance(res, bool):
pass
else:
Form(
self.env["stock.immediate.transfer"].with_context(res["context"])
).save().process()
for each in self.move_ids_without_package.filtered(
lambda l: l.products_checked
):
move_ids_without_package.append(
(
0,
0,
{
"product_id": each.product_id.id,
"products_checked": each.products_checked,
"name": each.product_id.name,
"product_uom": each.product_uom.id,
"product_uom_qty": each.product_uom_qty,
"location_id": self.env.ref(
"stock.stock_location_customers"
).id,
"location_dest_id": each.picking_id.location_id.id,
},
)
)
stock_picking_receipt = self.env["stock.picking"].create(
{
"partner_id": self.partner_id.id,
"location_id": self.env.ref("stock.stock_location_customers").id,
"rental_move_type": "incoming",
"location_dest_id": self.location_id.id,
"product_order_rel_id": self.product_order_rel_id.id,
"picking_type_id": self.picking_type_id.id,
"is_rental": True,
"origin": self.origin,
"move_ids_without_package": move_ids_without_package,
}
)
stock_picking_receipt.state = "confirmed"
return stock_picking_receipt
elif any([each.products_checked for each in self.move_ids_without_package]):
deliver_move_id = self.copy()
for each in self.move_ids_without_package:
if not each.product_id:
self.unlink()
for each in self.product_move_line_id.filtered(
lambda l: l.products_checked
):
move_ids_without_package.append(
(
0,
0,
{
"product_id": each.product_id.id,
"products_checked": each.products_checked,
"name": each.product_id.name,
"product_uom_qty": each.product_uom_qty,
"product_uom": each.product_uom.id,
"location_id": each.picking_id.location_id.id,
"location_dest_id": self.env.ref(
"stock.stock_location_customers"
).id,
},
)
)
each.unlink()
deliver_move_id.write(
{
"rental_move_type": "incoming",
"state": "confirmed",
"move_ids_without_package": move_ids_without_package,
}
)
return deliver_move_id
else:
raise UserError(_("Please Select Some Product to Move"))
def incoming(self):
self.write({"rental_move_type": "incoming"})
product_order_id = []
for each in self.move_ids_without_package:
product_order_id.append(
(
0,
0,
{
"product_id": each.product_id.id,
"products_checked": each.products_checked,
"name": each.product_id.name,
"product_uom": each.product_uom.id,
"location_id": each.picking_id.location_id.id,
"location_dest_id": self.env.ref(
"stock.stock_location_customers"
).id,
},
)
)
self.env["rental.product.logs"].create(
{
"customer_id": self.partner_id.id,
"product_id": each.product_id.id,
"from_date": self.scheduled_date,
"to_date": self.scheduled_date,
}
)
order_id = self.env["rental.product.order"].search(
[("res_number", "=", self.origin)]
)
for each_order in order_id:
each_order.state = "close"
each_order.return_date = datetime.now()
self.state = "assigned"
def move(self):
self.state = "done"
self.action_confirm()
self.action_assign()
res = self.button_validate()
if isinstance(res, bool):
pass
else:
Form(
self.env["stock.immediate.transfer"].with_context(res["context"])
).save().process()

View File

@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="recurring_report">
<t t-call="web.external_layout">
<t t-set="doc" t-value="doc.with_context({'lang':doc.partner_id.lang})" />
<div class="page">
<div class="oe_structure" />
<div class="row">
<div class="col-xs-6"></div>
<div class="col-xs-5 col-xs-offset-1">
<strong>Customer Details:</strong>
<div t-field="doc.partner_id" t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}' />
<p t-if="doc.partner_id.vat">
<t t-esc="doc.company_id.country_id.vat_label or 'TIN'" />
:
<span t-field="doc.partner_id.vat" />
</p>
</div>
</div>
<h2>
<center>
<span>Recurring Invoice Details :</span>
</center>
</h2>
<h4>
<center>
<span>Contract -</span>
<span t-field="doc.name" />
</center>
</h4>
<div class="row mt32 mb32" id="informations">
<div t-if="doc.contract_date" class="col-xs-3" style="margin-right: 60px;">
<strong>Date Ordered:</strong>
<p t-field="doc.contract_date" />
</div>
<div t-if="doc.contractor_id" class="col-xs-3" style="margin-right: 60px;">
<strong>Contractor:</strong>
<p t-field="doc.contractor_id" />
</div>
<div name="account_payment_term" t-if="doc.account_payment_term" class="col-xs-3" style="margin-right: 60px;">
<strong>Payment Terms:</strong>
<p t-field="doc.account_payment_term" />
</div>
<div name="from_date" t-if="doc.from_date" class="col-xs-3" style="margin-right: 60px;">
<strong>From Date:</strong>
<p t-field="doc.from_date" />
</div>
<div name="to_date" t-if="doc.to_date" class="col-xs-3" style="margin-right: 60px;">
<strong>To Date:</strong>
<p t-field="doc.to_date" />
</div>
</div>
<div class="row mt32 mb32" id="informations">
<div t-if="doc.cost_frequency" class="col-xs-3" style="margin-right: 60px;">
<strong>Interval Type:</strong>
<p t-field="doc.cost_frequency" />
</div>
<div t-if="doc.cost_generated" class="col-xs-3" style="margin-right: 60px;">
<strong>Recurring Amount:</strong>
<p t-field="doc.cost_generated" />
</div>
<div t-if="doc.security_deposit" class="col-xs-3" style="margin-right: 60px;">
<strong>Security Deposite:</strong>
<p t-field="doc.security_deposit" />
</div>
<div t-if="doc.first_payment" class="col-xs-3" style="margin-right: 60px;">
<strong>First Payment Amount:</strong>
<p t-field="doc.first_payment" />
</div>
</div>
<table class="table table-condensed">
<thead>
<tr>
<th>
<strong>Date</strong>
</th>
<th class="text-center">
<strong>Payment State</strong>
</th>
<th class="text-center">
<strong>Amount</strong>
</th>
</tr>
</thead>
<tbody>
<tr t-foreach="doc.recurring_line" t-as="line">
<td>
<t t-if="line.date_today">
<span t-field="line.date_today" />
</t>
</td>
<td class="text-center">
<t t-if="line.payment_info">
<span t-field="line.payment_info" />
</t>
</td>
<td class="text-right">
<t t-if="line.recurring_amount">
<span t-field="line.recurring_amount" />
</t>
</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-xs-2 pull-right">
<table class="table table-condensed">
<tr class="border-black">
<td>
<strong>Total</strong>
</td>
<td class="text-right">
<span t-field="doc.sum_cost" />
</td>
</tr>
</table>
</div>
</div>
</div>
</t>
</template>
<template id="rental_recurring_report_template">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="product_rental_bookings.recurring_report" t-lang="doc.partner_id.lang" />
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="rental_contract">
<t t-call="web.external_layout">
<t t-set="doc" t-value="doc.with_context({'lang':doc.partner_id.lang})" />
<div class="page">
<div class="oe_structure" />
<div class="row">
<div class="col-xs-6"></div>
<div class="col-xs-5 col-xs-offset-1">
<strong>Customer Details:</strong>
<div t-field="doc.partner_id" t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}' />
<p t-if="doc.partner_id.vat">
<t t-esc="doc.company_id.country_id.vat_label or 'TIN'" />
:
<span t-field="doc.partner_id.vat" />
</p>
</div>
</div>
<h2>
<center>
<span>Contract -</span>
<span t-field="doc.name" />
</center>
</h2>
<div class="row mt32 mb32" id="informations">
<div t-if="doc.contract_date" class="col-xs-3" style="margin-right: 60px">
<strong>Date Ordered:</strong>
<p t-field="doc.contract_date" />
</div>
<div t-if="doc.contractor_id" class="col-xs-3" style="margin-right: 60px">
<strong>Contractor:</strong>
<p t-field="doc.contractor_id" />
</div>
<div name="account_payment_term" t-if="doc.account_payment_term" class="col-xs-3" style="margin-right: 60px">
<strong>Payment Terms:</strong>
<p t-field="doc.account_payment_term" />
</div>
<div name="from_date" t-if="doc.from_date" class="col-xs-3" style="margin-right: 60px">
<strong>From Date:</strong>
<p t-field="doc.from_date" />
</div>
<div name="to_date" t-if="doc.to_date" class="col-xs-3" style="margin-right: 60px">
<strong>To Date:</strong>
<p t-field="doc.to_date" />
</div>
</div>
<table class="table table-condensed">
<thead>
<tr>
<th>
<strong>Description</strong>
</th>
<th>
<strong>Based On</strong>
</th>
<th class="text-center">
<strong>Days</strong>
</th>
<th class="text-center">
<strong>Tax</strong>
</th>
<th class="text-center">
<strong>Price</strong>
</th>
<th class="text-center">
<strong>subtotal</strong>
</th>
</tr>
</thead>
<tbody>
<tr t-foreach="doc.product_contract_lines_ids" t-as="line">
<td>
<span t-field="line.product_id.name" />
</td>
<td class="text-center">
<span t-field="line.price_based" />
</td>
<td class="text-center">
<span t-field="line.enter_days" />
</td>
<td class="text-center">
<span t-esc="','.join(map(lambda x: x.name, line.tax_id))" />
</td>
<td class="text-center">
<span t-field="line.price" />
</td>
<td class="text-center">
<span t-field="line.sub_total" />
</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-xs-4 pull-right">
<table class="table table-condensed">
<tr class="border-black">
<td>
<strong>Subtotal</strong>
</td>
<td class="text-right">
<span t-field="doc.untaxed_amount" />
</td>
</tr>
<tr>
<td>Taxes</td>
<td class="text-right">
<span t-field="doc.taxes" />
</td>
</tr>
<tr>
<td>Extra Charges</td>
<td class="text-right">
<span t-field="doc.extra_charges" />
</td>
</tr>
<tr class="border-black">
<td>
<strong>Total</strong>
</td>
<td class="text-right">
<span t-field="doc.total_amount" />
</td>
</tr>
</table>
</div>
</div>
<p t-field="doc.terms_condition" />
<div class="oe_structure" />
</div>
<div class="row" style="padding-top: 280px">
<div class="row">
<div style="margin-left: 40px;">
<strong>Contractor Signature</strong>
<br />
<img t-att-src="'data:image/png;base64,%s' % to_text(doc.signature_contractor)" height="70" width="100" />
</div>
<div style="margin-left: 480px;">
<strong>Customer Signature</strong>
<br />
<img t-att-src="'data:image/png;base64,%s' % to_text(doc.signature_customer)" height="70" width="100" />
</div>
</div>
</div>
</t>
</template>
<template id="rental_contract_report_template">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="product_rental_bookings.rental_contract" t-lang="doc.partner_id.lang" />
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="product_rental_order_report" model="ir.actions.report">
<field name="name">Rental/Order</field>
<field name="model">rental.product.order</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">product_rental_bookings.rental_order_template</field>
<field name="report_file">product_rental_bookings.rental_order_report</field>
<field name="print_report_name">'Rental/Order'</field>
<field name="binding_model_id" ref="model_rental_product_order" />
<field name="binding_type">report</field>
</record>
<record id="product_rental_contract_report" model="ir.actions.report">
<field name="name">Rental Contract</field>
<field name="model">rental.product.contract</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">product_rental_bookings.rental_contract_report_template</field>
<field name="report_file">product_rental_bookings.rental_contract_report</field>
<field name="print_report_name">'Rental Contract'</field>
<field name="binding_model_id" ref="model_rental_product_contract" />
<field name="binding_type">report</field>
</record>
<record id="product_contract_recurring_report" model="ir.actions.report">
<field name="name">Recurring Invoice</field>
<field name="model">rental.product.contract</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">product_rental_bookings.rental_recurring_report_template</field>
<field name="report_file">product_rental_bookings.rental_contract_recurring</field>
<field name="print_report_name">'Recurring Invoice'</field>
<field name="binding_model_id" ref="model_rental_product_contract" />
<field name="binding_type">report</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_product_rental_order">
<t t-call="web.external_layout">
<t t-set="doc" t-value="doc.with_context({'lang':doc.customer_name.lang})" />
<div class="page">
<div class="oe_structure" />
<div class="row">
<div class="col-xs-6"></div>
<div class="col-xs-5 col-xs-offset-1">
<strong>Customer Details:</strong>
<div t-field="doc.customer_name" t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}' />
<p t-if="doc.customer_name.vat">
<t t-esc="doc.company_id.country_id.vat_label or 'TIN'" />
:
<span t-field="doc.customer_name.vat" />
</p>
</div>
</div>
<h2>
<center>
<span>Rental Order -</span>
<span t-field="doc.res_number" />
</center>
</h2>
<div class="row mt32 mb32" id="informations">
<div t-if="doc.book_date" class="col-xs-6" style="margin-right: 60px">
<strong>Date Ordered:</strong>
<p t-field="doc.book_date" />
</div>
<div t-if="doc.user_id" class="col-xs-6" style="margin-right: 60px">
<strong>Salesperson:</strong>
<p t-field="doc.user_id" />
</div>
<div name="account_payment_term" t-if="doc.account_payment_term" class="col-xs-6" style="margin-right: 60px">
<strong>Payment Terms:</strong>
<p t-field="doc.account_payment_term" />
</div>
<div name="from_date" t-if="doc.from_date" class="col-xs-6" style="margin-right: 60px">
<strong>From Date:</strong>
<p t-field="doc.from_date" />
</div>
<div name="to_date" t-if="doc.to_date" class="col-xs-6" style="margin-right: 60px">
<strong>To Date:</strong>
<p t-field="doc.to_date" />
</div>
</div>
<table class="table table-condensed">
<thead>
<tr>
<th>
<strong>Description</strong>
</th>
<th>
<strong>Based On</strong>
</th>
<th class="text-center">
<strong>Days</strong>
</th>
<th class="text-center">
<strong>Tax</strong>
</th>
<th class="text-center">
<strong>Price</strong>
</th>
<th class="text-center">
<strong>subtotal</strong>
</th>
</tr>
</thead>
<tbody>
<tr t-foreach="doc.product_order_lines_ids" t-as="line">
<td>
<span t-field="line.product_id.name" />
</td>
<td class="text-center">
<span t-field="line.price_based" />
</td>
<td class="text-center">
<span t-field="line.enter_days" />
</td>
<td class="text-center">
<span t-esc="','.join(map(lambda x: x.name, line.tax_id))" />
</td>
<td class="text-center">
<span t-field="line.price" />
</td>
<td class="text-center">
<span t-field="line.sub_total" />
</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-xs-4 pull-right">
<table class="table table-condensed">
<tr class="border-black">
<td>
<strong>Subtotal</strong>
</td>
<td class="text-right">
<span t-field="doc.untaxed_amount" />
</td>
</tr>
<tr>
<td>Taxes</td>
<td class="text-right">
<span t-field="doc.taxes" />
</td>
</tr>
<tr>
<td>Extra Charges</td>
<td class="text-right">
<span t-field="doc.extra_charges" />
</td>
</tr>
<tr class="border-black">
<td>
<strong>Total</strong>
</td>
<td class="text-right">
<span t-field="doc.total_amount" />
</td>
</tr>
</table>
</div>
</div>
<p t-field="doc.terms_condition" />
<div class="oe_structure" />
</div>
</t>
</template>
<template id="rental_order_template">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="product_rental_bookings.report_product_rental_order" t-lang="doc.customer_name
.lang" />
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,24 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_rental_product_order,access_rental_product_order,model_rental_product_order,,1,1,1,1
access_product_book,access_product_book,model_product_booking,,1,1,1,1
access_product_order_line,access_product_order_line,model_product_order_line,,1,1,1,1
access_rental_product_contract,access_rental_product_contract,model_rental_product_contract,,1,1,1,1
access_product_contract_lines,access_product_contract_lines,model_product_contract_lines,base.group_user,1,1,1,1
access_rental_product_operation,access_rental_product_operation,model_rental_product_operation,,1,1,1,1
access_product_rental_line,access_product_rental_line,model_product_rental_line,,1,1,1,1
access_rental_product_logs,access_rental_product_logs,model_rental_product_logs,,1,1,1,1
access_customer_document,access_customer_document,model_customer_document,base.group_user,1,1,1,1
access_rental_policy,access_rental_policy,model_rental_policy,base.group_user,1,1,1,1
access_wizard_product_service,access_wizard_product_service,model_wizard_product_service,base.group_user,1,1,1,1
access_product_advance_payment_invoice,access_product_advance_payment_invoice,model_product_advance_payment_invoice,base.group_user,1,1,1,1
access_product_category_portal_user,access_product_category_portal_user,product.model_product_category,base.group_portal,1,1,1,1
access_product_category_public,access_product_category_public,product.model_product_category,base.group_public,1,0,1,0
access_product_product_portal,access_product_product_portal,product.model_product_product,base.group_portal,1,0,1,0
access_product_product_public,access_product_product_public,product.model_product_product,base.group_public,1,0,1,0
access_product_template_portal,access_product_template_portal,product.model_product_template,base.group_portal,1,0,1,0
access_product_template_public,access_product_template_public,product.model_product_template,base.group_public,1,0,1,0
access_ir_sequence_group_public,access_ir_sequence group_public,base.model_ir_sequence,base.group_public,1,0,1,0
access_ir_sequence_group_portal,access_ir_sequence_group_portal,base.model_ir_sequence,,base.group_portal,1,0,1,0
access_stock_quant_group_public,access_stock_quant group_public,stock.model_stock_quant,base.group_public,1,0,1,0
access_stock_quant_group_portal,access_stock_quant_group_portal,stock.model_stock_quant,,base.group_portal,1,0,1,0
access_session_config,access_session_config,model_session_config,base.group_user,1,1,1,1
Can't render this file because it has a wrong number of fields in line 21.

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<record id="product_contract_rule" model="ir.rule">
<field name="name">product Contract Rule</field>
<field name="model_id" ref="model_rental_product_contract" />
<field eval="True" name="global" />
<field name="domain_force">['|',('company_id','=',user.company_id.id),('company_id','=',False)]</field>
</record>
<record id="rental_product_rule" model="ir.rule">
<field name="name">product product</field>
<field name="model_id" ref="model_product_product" />
<field eval="True" name="global" />
<field name="domain_force">['|',('company_id','=',user.company_id.id),('company_id','=',False)]</field>
</record>
<record id="product_order_rule" model="ir.rule">
<field name="name">product Order Rule</field>
<field name="model_id" ref="model_rental_product_order" />
<field eval="True" name="global" />
<field name="domain_force">['|',('company_id','=',user.company_id.id),('company_id','=',False)]</field>
</record>
<record id="product_log_rule" model="ir.rule">
<field name="name">product Log Rule</field>
<field name="model_id" ref="model_rental_product_logs" />
<field eval="True" name="global" />
<field name="domain_force">['|',('company_id','=',user.company_id.id),('company_id','=',False)]</field>
</record>
</data>
</odoo>

BIN
product_rental_bookings/static/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -0,0 +1,4 @@
.model-body-sign .canvas-container{
border: 2px dotted #999999;
margin: 0 auto;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,463 @@
/*!
* FullCalendar Scheduler v1.9.4
* Docs & License: https://fullcalendar.io/scheduler/
* (c) 2018 Adam Shaw
*/
/* TODO: break this file up */
/* Scroller
--------------------------------------------------------------------------------------------------*/
.fc-scroller-clip {
overflow: hidden;
/* for clipping scrollbars */
position: relative;
/* so things like scrollfollowers can attach to this */ }
/* supresses rendering of native scrollbars */
/* on .fc-scroller */
.fc-no-scrollbars {
background: rgba(255, 255, 255, 0);
/* hack for dynamic DOM nodes (in Chrome OSX at least) */ }
.fc-no-scrollbars::-webkit-scrollbar {
width: 0;
height: 0; }
.fc-scroller-canvas {
position: relative;
/* origin for bg */
box-sizing: border-box;
/* so that padding (for gutter) will be part of height */
min-height: 100%; }
.fc-scroller-canvas > .fc-bg {
z-index: 1;
/* make default? */ }
.fc-scroller-canvas > .fc-content {
z-index: 2;
/* make default? */
position: relative;
/* origin for inner content */
border-style: solid;
border-width: 0; }
/* for themed, hard to get the border-color, so just forget it (REVISIT) */
.ui-widget .fc-scroller-canvas > .fc-content {
border-color: transparent; }
.fc-scroller-canvas.fc-gutter-left > .fc-content {
border-left-width: 1px;
margin-left: -1px; }
.fc-scroller-canvas.fc-gutter-right > .fc-content {
border-right-width: 1px;
margin-right: -1px; }
.fc-scroller-canvas.fc-gutter-top > .fc-content {
border-top-width: 1px;
margin-top: -1px; }
/* content is responsible for bottom border */
/* View Structure
--------------------------------------------------------------------------------------------------*/
.fc-rtl .fc-timeline {
direction: rtl; }
.fc-timeline .fc-divider {
width: 3px;
border-style: double;
/* overcome neighboring borders */ }
.fc-timeline .fc-head > tr > .fc-divider {
border-bottom: 0; }
.fc-timeline .fc-body > tr > .fc-divider {
border-top: 0; }
.fc-timeline .fc-body .fc-divider.ui-widget-header {
background-image: none; }
.fc-scrolled .fc-head .fc-scroller {
z-index: 2;
/* so drop shadow will go above body panes */ }
.fc-timeline.fc-scrolled .fc-head .fc-scroller {
box-shadow: 0 3px 4px rgba(0, 0, 0, 0.075); }
.fc-timeline .fc-body .fc-scroller {
z-index: 1; }
/*
on most tables that expand to the edges, kill the outer border,
because the container elements take care of it.
example tables:
.fc-scroller-canvas .fc-content table
.fc-scroller-canvas .fc-bg .fc-slats table
*/
.fc-timeline .fc-scroller-canvas > div > table,
.fc-timeline .fc-scroller-canvas > div > div > table {
border-style: hidden; }
/*
for resource rows (in both the spreadsheet and timeline areas),
undo previous rule in order to always show last border.
*/
.fc-timeline .fc-scroller-canvas > .fc-content > .fc-rows > table {
border-bottom-style: none; }
/* Table Cell Common
--------------------------------------------------------------------------------------------------*/
.fc-timeline th,
.fc-timeline td {
white-space: nowrap; }
.fc-timeline .fc-cell-content {
overflow: hidden; }
.fc-timeline .fc-cell-text {
padding-left: 4px;
padding-right: 4px; }
.fc-timeline .fc-col-resizer {
cursor: col-resize; }
/*
Cells at the start of a week
TODO: figure out better styling
.fc-ltr .fc-timeline .fc-em-cell div {
border-left: 3px solid #eee;
height: 100%;
}
.fc-rtl .fc-timeline .fc-em-cell {
border-right-width: 3px;
}
*/
/* head */
.fc-timeline th {
vertical-align: middle; }
.fc-timeline .fc-head .fc-cell-content {
padding-top: 3px;
padding-bottom: 3px; }
/* body */
.fc-timeline .fc-body .ui-widget-content {
background-image: none; }
/* Resource Area
--------------------------------------------------------------------------------------------------*/
.fc-resource-area {
width: 30%; }
.fc-resource-area col {
width: 40%;
min-width: 70px;
/* will be read by JS */ }
.fc-resource-area col.fc-main-col {
width: 60%;
/* make the first column in a nested setup bigger */ }
.fc-flat .fc-expander-space {
/* fc-flat is opposite of fc-nested */
display: none; }
.fc-ltr .fc-resource-area tr > * {
text-align: left; }
.fc-rtl .fc-resource-area tr > * {
text-align: right; }
.fc-resource-area .fc-cell-content {
padding-left: 4px;
padding-right: 4px; }
/* head */
.fc-resource-area .fc-super th {
text-align: center; }
.fc-resource-area th > div {
position: relative; }
.fc-resource-area th .fc-cell-content {
position: relative;
z-index: 1; }
.fc-resource-area th .fc-col-resizer {
position: absolute;
z-index: 2;
top: 0;
bottom: 0;
width: 5px; }
.fc-ltr .fc-resource-area th .fc-col-resizer {
right: -3px; }
.fc-rtl .fc-resource-area th .fc-col-resizer {
left: -3px; }
/* body */
tr.fc-collapsed > td,
tr.fc-transitioning > td {
/* during the transition */
overflow: hidden;
/* prevents absolutely-positioned events from bleeding out */ }
tr.fc-transitioning > td > div {
transition: margin-top 0.2s; }
tr.fc-collapsed > td > div {
margin-top: -10px; }
.fc-body .fc-resource-area .fc-cell-content {
/* might BE the cell */
position: relative;
/* optimization for ScrollFollower */
padding-top: 8px;
padding-bottom: 8px; }
.fc-no-overlap .fc-body .fc-resource-area .fc-cell-content {
/* might BE the cell */
padding-top: 5px;
padding-bottom: 5px; }
.fc-resource-area .fc-icon {
/* the expander and spacers before the expander */
width: 1em;
/* ensure constant width, esp for empty icons */
font-size: .9em;
vertical-align: middle;
margin-top: -1%; }
.fc-resource-area .fc-expander {
cursor: pointer;
color: #666;
/* for the icon within */ }
/* Time Area
--------------------------------------------------------------------------------------------------*/
.fc-time-area col {
min-width: 2.2em;
/* detected by JS */ }
/* head */
.fc-ltr .fc-time-area .fc-chrono th {
text-align: left; }
.fc-rtl .fc-time-area .fc-chrono th {
text-align: right; }
/* body slats (vertical lines) */
.fc-time-area .fc-slats {
/* fc-bg is responsible for a lot of this now! */
position: absolute;
z-index: 1;
top: 0;
left: 0;
right: 0;
bottom: 0; }
.fc-time-area .fc-slats table {
height: 100%; }
.fc-time-area .fc-slats .fc-minor {
border-style: dotted; }
.fc-time-area .fc-slats td {
border-width: 0 1px;
/* need to do this. sometimes -1 margin wouldn't hide the dotted */ }
.fc-ltr .fc-time-area .fc-slats td {
border-right-width: 0; }
.fc-rtl .fc-time-area .fc-slats td {
border-left-width: 0; }
/* body content containers
can be within rows or directly within the pane's content
*/
.fc-time-area .fc-bgevent-container,
.fc-time-area .fc-highlight-container {
position: absolute;
z-index: 2;
/* only for directly within pane. not for row. overridden later */
top: 0;
bottom: 0;
width: 0; }
.fc-ltr .fc-time-area .fc-helper-container,
.fc-ltr .fc-time-area .fc-bgevent-container,
.fc-ltr .fc-time-area .fc-highlight-container {
left: 0; }
.fc-rtl .fc-time-area .fc-helper-container,
.fc-rtl .fc-time-area .fc-bgevent-container,
.fc-rtl .fc-time-area .fc-highlight-container {
right: 0; }
.fc-time-area .fc-bgevent,
.fc-time-area .fc-highlight {
position: absolute;
top: 0;
bottom: 0; }
/* body resource rows */
.fc-time-area .fc-rows {
position: relative;
z-index: 3; }
.fc-time-area .fc-rows .ui-widget-content {
background: none; }
.fc-time-area .fc-rows td > div {
position: relative; }
.fc-time-area .fc-rows .fc-bgevent-container,
.fc-time-area .fc-rows .fc-highlight-container {
z-index: 1; }
.fc-time-area .fc-event-container {
position: relative;
z-index: 2;
/* above bgevent and highlight */
width: 0;
/* for event positioning. will end up on correct side based on dir */ }
.fc-time-area .fc-helper-container {
/* also an fc-event-container */
position: absolute;
z-index: 3;
top: 0; }
.fc-time-area .fc-event-container {
padding-bottom: 8px;
top: -1px; }
.fc-time-area tr:first-child .fc-event-container {
top: 0; }
.fc-no-overlap .fc-time-area .fc-event-container {
padding-bottom: 0;
top: 0; }
/* Now Indicator
--------------------------------------------------------------------------------------------------*/
.fc-timeline .fc-now-indicator {
/* both the arrow and the line */
z-index: 3;
/* one above scroller's fc-content */
top: 0; }
.fc-time-area .fc-now-indicator-arrow {
margin: 0 -6px;
/* 5, then one more to counteract scroller's negative margins */
/* triangle pointing down... */
border-width: 6px 5px 0 5px;
border-left-color: transparent;
border-right-color: transparent; }
.fc-time-area .fc-now-indicator-line {
margin: 0 -1px;
/* counteract scroller's negative margins */
bottom: 0;
border-left-width: 1px; }
/* Time Grid Events
--------------------------------------------------------------------------------------------------*/
.fc-timeline-event {
position: absolute;
border-radius: 0;
padding: 2px 0;
margin-bottom: 1px; }
.fc-no-overlap .fc-timeline-event {
padding: 5px 0;
margin-bottom: 0; }
/* don't overlap grid lines at the event's end */
.fc-ltr .fc-timeline-event {
margin-right: 1px; }
.fc-rtl .fc-timeline-event {
margin-left: 1px; }
.fc-timeline-event .fc-content {
padding: 0 1px;
white-space: nowrap;
overflow: hidden; }
.fc-timeline-event .fc-time {
font-weight: bold;
padding: 0 1px; }
.fc-rtl .fc-timeline-event .fc-time {
display: inline-block;
/* will force it on the other side */ }
.fc-timeline-event .fc-title {
position: relative;
/* optimization for ScrollFollower */
padding: 0 1px; }
.fc-timeline-event.fc-selected .fc-bg {
display: none;
/* hide semi-white background, to appear darker */ }
/* follower logic */
.fc-ltr .fc-timeline-event .fc-title {
padding-left: 10px;
margin-left: -8px; }
.fc-rtl .fc-timeline-event .fc-title {
padding-right: 10px;
margin-right: -8px; }
.fc-ltr .fc-timeline-event.fc-not-start .fc-title {
margin-left: -2px; }
.fc-rtl .fc-timeline-event.fc-not-start .fc-title {
margin-right: -2px; }
.fc-timeline-event.fc-not-start .fc-title,
.fc-body .fc-time-area .fc-following {
position: relative; }
.fc-timeline-event.fc-not-start .fc-title:before,
.fc-body .fc-time-area .fc-following:before {
/* generic arrow */
content: "";
position: absolute;
top: 50%;
margin-top: -5px;
border: 5px solid #000;
border-top-color: transparent;
border-bottom-color: transparent;
opacity: .5; }
.fc-ltr .fc-timeline-event.fc-not-start .fc-title:before,
.fc-ltr .fc-body .fc-time-area .fc-following:before {
/* LTR. left pointing arrow */
border-left: 0;
left: 2px; }
.fc-rtl .fc-timeline-event.fc-not-start .fc-title:before,
.fc-rtl .fc-body .fc-time-area .fc-following:before {
/* RTL. right pointing arrow */
border-right: 0;
right: 2px; }
/* License Message
--------------------------------------------------------------------------------------------------*/
.fc-license-message {
position: absolute;
z-index: 99999;
bottom: 1px;
left: 1px;
background: #eee;
border-color: #ddd;
border-style: solid;
border-width: 1px 1px 0 0;
padding: 2px 4px;
font-size: 12px;
border-top-right-radius: 3px; }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,184 @@
function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.Popper = factory());
}(this, (function () { 'use strict';
Object.assign||Object.defineProperty(Object,'assign',{enumerable:!1,configurable:!0,writable:!0,value:function value(a){if(a===void 0||null===a)throw new TypeError('Cannot convert first argument to object');var b=Object(a);for(var c=1;c<arguments.length;c++){var d=arguments[c];if(void 0!==d&&null!==d){d=Object(d);var e=Object.keys(d);for(var f=0,g=e.length;f<g;f++){var h=e[f],j=Object.getOwnPropertyDescriptor(d,h);void 0!==j&&j.enumerable&&(b[h]=d[h]);}}}return b}});
if(!window.requestAnimationFrame){var lastTime=0,vendors=['ms','moz','webkit','o'];for(var x=0;x<vendors.length&&!window.requestAnimationFrame;++x)window.requestAnimationFrame=window[vendors[x]+'RequestAnimationFrame'],window.cancelAnimationFrame=window[vendors[x]+'CancelAnimationFrame']||window[vendors[x]+'CancelRequestAnimationFrame'];window.requestAnimationFrame||(window.requestAnimationFrame=function(a){var b=new Date().getTime(),c=Math.max(0,16-(b-lastTime)),d=window.setTimeout(function(){a(b+c);},c);return lastTime=b+c,d}),window.cancelAnimationFrame||(window.cancelAnimationFrame=function(a){clearTimeout(a);});}
function findIndex(a,b,c){var d=a.filter(function(e){return e[b]===c})[0];return a.indexOf(d)}
function getOffsetParent(a){var b=a.offsetParent;return b&&'BODY'!==b.nodeName?b:window.document.documentElement}
function getStyleComputedProperty(a,b){if(1!==a.nodeType)return[];var c=window.getComputedStyle(a,null);return c[b]}
function getParentNode(a){return a.parentNode||a.host}
function getScrollParent(a){return a===window.document?window.document.body.scrollTop?window.document.body:window.document.documentElement:-1!==['scroll','auto'].indexOf(getStyleComputedProperty(a,'overflow'))||-1!==['scroll','auto'].indexOf(getStyleComputedProperty(a,'overflow-x'))||-1!==['scroll','auto'].indexOf(getStyleComputedProperty(a,'overflow-y'))?a===window.document.body?getScrollParent(getParentNode(a)):a:getParentNode(a)?getScrollParent(getParentNode(a)):a}
function getOffsetRect(a){var b=window.document.documentElement,c=void 0;return c=a===b?{width:Math.max(b.clientWidth,window.innerWidth||0),height:Math.max(b.clientHeight,window.innerHeight||0),left:0,top:0}:{width:a.offsetWidth,height:a.offsetHeight,left:a.offsetLeft,top:a.offsetTop},c.right=c.left+c.width,c.bottom=c.top+c.height,c}
function isFixed(a){return a!==window.document.body&&(!('fixed'!==getStyleComputedProperty(a,'position'))||(getParentNode(a)?isFixed(getParentNode(a)):a))}
function getPosition(a){var b=getOffsetParent(a),c=isFixed(b);return c?'fixed':'absolute'}
function getBoundingClientRect(a){var b=a.getBoundingClientRect();return{left:b.left,top:b.top,right:b.right,bottom:b.bottom,width:b.right-b.left,height:b.bottom-b.top}}
function getOffsetRectRelativeToCustomParent(a,b){var c=2<arguments.length&&void 0!==arguments[2]&&arguments[2],d=3<arguments.length&&void 0!==arguments[3]&&arguments[3],e=getBoundingClientRect(a),f=getBoundingClientRect(b);if(c&&!d){var j=getScrollParent(b);f.top+=j.scrollTop,f.bottom+=j.scrollTop,f.left+=j.scrollLeft,f.right+=j.scrollLeft;}var g={top:e.top-f.top,left:e.left-f.left,bottom:e.top-f.top+e.height,right:e.left-f.left+e.width,width:e.width,height:e.height},h=b.scrollTop,i=b.scrollLeft;return g.top+=h,g.bottom+=h,g.left+=i,g.right+=i,g}
function getBoundaries(a,b,c){var d={},e=getOffsetParent(a),f=getScrollParent(a);if('window'===c){var g=window.document.body,h=window.document.documentElement,i=Math.max(g.scrollHeight,g.offsetHeight,h.clientHeight,h.scrollHeight,h.offsetHeight),j=Math.max(g.scrollWidth,g.offsetWidth,h.clientWidth,h.scrollWidth,h.offsetWidth);d={top:0,right:j,bottom:i,left:0};}else if('viewport'===c){var _g=getOffsetRect(e),_h=getPosition(a);d='fixed'===_h?{top:0,right:window.document.documentElement.clientWidth,bottom:window.document.documentElement.clientHeight,left:0}:{top:0-_g.top,right:window.document.documentElement.clientWidth-_g.left,bottom:window.document.documentElement.clientHeight-_g.top,left:0-_g.left};}else d=f===c||'scrollParent'===c?getOffsetRectRelativeToCustomParent(f,e):getOffsetRectRelativeToCustomParent(c,e);if(e.contains(f)){var _g2=f.scrollLeft,_h2=f.scrollTop;d.right+=_g2,d.bottom+=_h2;}return d.left+=b,d.top+=b,d.right-=b,d.bottom-=b,d}
function getOuterSizes(a){var b=a.style.display,c=a.style.visibility;a.style.display='block',a.style.visibility='hidden';var d=window.getComputedStyle(a),e=parseFloat(d.marginTop)+parseFloat(d.marginBottom),f=parseFloat(d.marginLeft)+parseFloat(d.marginRight),g={width:a.offsetWidth+f,height:a.offsetHeight+e};return a.style.display=b,a.style.visibility=c,g}
function getPopperClientRect(a){return Object.assign({},a,{right:a.left+a.width,bottom:a.top+a.height})}
function getSupportedPropertyName(a){var b=['','ms','webkit','moz','o'];for(var c=0;c<b.length;c++){var d=b[c]?b[c]+a.charAt(0).toUpperCase()+a.slice(1):a;if('undefined'!=typeof window.document.body.style[d])return d}return null}
function isFunction(a){return a&&'[object Function]'==={}.toString.call(a)}
function isModifierRequired(a,b,c){return!!a.filter(function(d){if(d.name===c)return!0;return d.name!==b&&!1}).length}
function isNumeric(a){return''!==a&&!isNaN(parseFloat(a))&&isFinite(a)}
function isTransformed(a){return a!==window.document.body&&('none'!==getStyleComputedProperty(a,'transform')||(getParentNode(a)?isTransformed(getParentNode(a)):a))}
function runModifiers(a,b,c){var d=void 0===c?a:a.slice(0,findIndex(a,'name',c));return d.forEach(function(e){e.enabled&&isFunction(e.function)&&(b=e.function(b,e));}),b}
function setStyle(a,b){Object.keys(b).forEach(function(c){var d='';-1!==['width','height','top','right','bottom','left'].indexOf(c)&&isNumeric(b[c])&&(d='px'),a.style[c]=b[c]+d;});}
var Utils = {findIndex:findIndex,getBoundaries:getBoundaries,getBoundingClientRect:getBoundingClientRect,getOffsetParent:getOffsetParent,getOffsetRectRelativeToCustomParent:getOffsetRectRelativeToCustomParent,getOuterSizes:getOuterSizes,getPopperClientRect:getPopperClientRect,getPosition:getPosition,getScrollParent:getScrollParent,getStyleComputedProperty:getStyleComputedProperty,getSupportedPropertyName:getSupportedPropertyName,isFixed:isFixed,isFunction:isFunction,isModifierRequired:isModifierRequired,isNumeric:isNumeric,isTransformed:isTransformed,runModifiers:runModifiers,setStyle:setStyle};
var nativeHints=['native code','[object MutationObserverConstructor]'];var isNative = (function(a){return nativeHints.some(function(b){return-1<(a||'').toString().indexOf(b)})});
var longerTimeoutBrowsers=['Edge','Trident','Firefox']; var timeoutDuration=0;for(var a=0;a<longerTimeoutBrowsers.length;a+=1)if(0<=navigator.userAgent.indexOf(longerTimeoutBrowsers[a])){timeoutDuration=1;break}function microtaskDebounce(a){var b=!1,c=0,d=document.createElement('span'),e=new MutationObserver(function(){a(),b=!1;});return e.observe(d,{childList:!0}),function(){b||(b=!0,d.textContent=''+c,c+=1);}}function taskDebounce(a){var b=!1;return function(){b||(b=!0,setTimeout(function(){b=!1,a();},timeoutDuration));}}var supportsNativeMutationObserver=isNative(window.MutationObserver);var debounce = supportsNativeMutationObserver?microtaskDebounce:taskDebounce;
function getOffsets(a,b,c,d){d=d.split('-')[0];var e={};e.position=a.position;var f='fixed'===e.position,g=a.isParentTransformed,h=getOffsetParent(f&&g?c:b),i=getOffsetRectRelativeToCustomParent(c,h,f,g),j=getOuterSizes(b);return-1===['right','left'].indexOf(d)?(e.left=i.left+i.width/2-j.width/2,e.top='top'===d?i.top-j.height:i.bottom):(e.top=i.top+i.height/2-j.height/2,e.left='left'===d?i.left-j.width:i.right),e.width=j.width,e.height=j.height,{popper:e,reference:i}}
function setupEventListeners(a,b,c,d){if(c.updateBound=d,window.addEventListener('resize',c.updateBound,{passive:!0}),'window'!==b.boundariesElement){var e=getScrollParent(a);(e===window.document.body||e===window.document.documentElement)&&(e=window),e.addEventListener('scroll',c.updateBound,{passive:!0}),c.scrollElement=e;}}
function removeEventListeners(a,b){return window.removeEventListener('resize',b.updateBound),b.scrollElement&&b.scrollElement.removeEventListener('scroll',b.updateBound),b.updateBound=null,b.scrollElement=null,b}
function sortModifiers(c,d){if(c.order<d.order)return-1;return c.order>d.order?1:0}
function applyStyle(a){var b={position:a.offsets.popper.position},c=Math.round(a.offsets.popper.left),d=Math.round(a.offsets.popper.top),e=getSupportedPropertyName('transform');return a.instance.options.gpuAcceleration&&e?(b[e]='translate3d('+c+'px, '+d+'px, 0)',b.top=0,b.left=0):(b.left=c,b.top=d),Object.assign(b,a.styles),setStyle(a.instance.popper,b),a.instance.popper.setAttribute('x-placement',a.placement),a.offsets.arrow&&setStyle(a.arrowElement,a.offsets.arrow),a}function applyStyleOnLoad(a,b,c){return b.setAttribute('x-placement',c.placement),c}
function arrow(a,b){var c=b.element;if('string'==typeof c&&(c=a.instance.popper.querySelector(c)),!c)return a;if(!a.instance.popper.contains(c))return console.warn('WARNING: `arrowElement` must be child of its popper element!'),a;if(!isModifierRequired(a.instance.modifiers,'arrow','keepTogether'))return console.warn('WARNING: keepTogether modifier is required by arrow modifier in order to work, be sure to include it before arrow!'),a;var d={},e=a.placement.split('-')[0],f=getPopperClientRect(a.offsets.popper),g=a.offsets.reference,h=-1!==['left','right'].indexOf(e),i=h?'height':'width',j=h?'top':'left',k=h?'left':'top',l=h?'bottom':'right',m=getOuterSizes(c)[i];g[l]-m<f[j]&&(a.offsets.popper[j]-=f[j]-(g[l]-m)),g[j]+m>f[l]&&(a.offsets.popper[j]+=g[j]+m-f[l]);var n=g[j]+g[i]/2-m/2,o=n-getPopperClientRect(a.offsets.popper)[j];return o=Math.max(Math.min(f[i]-m,o),0),d[j]=o,d[k]='',a.offsets.arrow=d,a.arrowElement=c,a}
function getOppositePlacement(a){var b={left:'right',right:'left',bottom:'top',top:'bottom'};return a.replace(/left|right|bottom|top/g,function(c){return b[c]})}
function getOppositeVariation(a){if('end'===a)return'start';return'start'===a?'end':a}
function flip(a,b){if(a.flipped&&a.placement===a.originalPlacement)return a;var c=getBoundaries(a.instance.popper,b.padding,b.boundariesElement),d=a.placement.split('-')[0],e=getOppositePlacement(d),f=a.placement.split('-')[1]||'',g=[];return g='flip'===b.behavior?[d,e]:b.behavior,g.forEach(function(h,i){if(d!==h||g.length===i+1)return a;d=a.placement.split('-')[0],e=getOppositePlacement(d);var j=getPopperClientRect(a.offsets.popper),k='left'===d&&Math.floor(j.left)<Math.floor(c.left)||'right'===d&&Math.floor(j.right)>Math.floor(c.right)||'top'===d&&Math.floor(j.top)<Math.floor(c.top)||'bottom'===d&&Math.floor(j.bottom)>Math.floor(c.bottom),l=-1!==['top','bottom'].indexOf(d),m=!!b.flipVariations&&(l&&'start'===f&&Math.floor(j.left)<Math.floor(c.left)||l&&'end'===f&&Math.floor(j.right)>Math.floor(c.right)||!l&&'start'===f&&Math.floor(j.top)<Math.floor(c.top)||!l&&'end'===f&&Math.floor(j.bottom)>Math.floor(c.bottom));(k||m)&&(a.flipped=!0,k&&(d=g[i+1]),m&&(f=getOppositeVariation(f)),a.placement=d+(f?'-'+f:''),a.offsets.popper=getOffsets(a.instance.state,a.instance.popper,a.instance.reference,a.placement).popper,a=runModifiers(a.instance.modifiers,a,'flip'));}),a}
function keepTogether(a){var b=getPopperClientRect(a.offsets.popper),c=a.offsets.reference,d=Math.floor,e=a.placement.split('-')[0];return-1===['top','bottom'].indexOf(e)?(b.bottom<d(c.top)&&(a.offsets.popper.top=d(c.top)-b.height),b.top>d(c.bottom)&&(a.offsets.popper.top=d(c.bottom))):(b.right<d(c.left)&&(a.offsets.popper.left=d(c.left)-b.width),b.left>d(c.right)&&(a.offsets.popper.left=d(c.right))),a}
function offset(a,b){var c=a.placement,d=a.offsets.popper,e=void 0;return isNumeric(b.offset)?e=[b.offset,0]:(e=b.offset.split(' '),e=e.map(function(f,g){var h=f.match(/(\d*\.?\d*)(.*)/),i=+h[1],j=h[2],k=-1!==c.indexOf('right')||-1!==c.indexOf('left');if(1===g&&(k=!k),'%'===j||'%r'===j){var l=getPopperClientRect(a.offsets.reference),m=void 0;return m=k?l.height:l.width,m/100*i}if('%p'===j){var _l=getPopperClientRect(a.offsets.popper),_m=void 0;return _m=k?_l.height:_l.width,_m/100*i}if('vh'===j||'vw'===j){var _l2=void 0;return _l2='vh'===j?Math.max(document.documentElement.clientHeight,window.innerHeight||0):Math.max(document.documentElement.clientWidth,window.innerWidth||0),_l2/100*i}return'px'===j?+i:+f})),-1===a.placement.indexOf('left')?-1===a.placement.indexOf('right')?-1===a.placement.indexOf('top')?-1!==a.placement.indexOf('bottom')&&(d.left+=e[0],d.top+=e[1]||0):(d.left+=e[0],d.top-=e[1]||0):(d.top+=e[0],d.left+=e[1]||0):(d.top+=e[0],d.left-=e[1]||0),a}
function preventOverflow(c,d){var e=d.boundariesElement||getOffsetParent(c.instance.popper),f=getBoundaries(c.instance.popper,d.padding,e);d.boundaries=f;var g=d.priority,h=getPopperClientRect(c.offsets.popper),i={left:function left(){var j=h.left;return h.left<f.left&&!shouldOverflowBoundary(c,d,'left')&&(j=Math.max(h.left,f.left)),{left:j}},right:function right(){var j=h.left;return h.right>f.right&&!shouldOverflowBoundary(c,d,'right')&&(j=Math.min(h.left,f.right-h.width)),{left:j}},top:function top(){var j=h.top;return h.top<f.top&&!shouldOverflowBoundary(c,d,'top')&&(j=Math.max(h.top,f.top)),{top:j}},bottom:function bottom(){var j=h.top;return h.bottom>f.bottom&&!shouldOverflowBoundary(c,d,'bottom')&&(j=Math.min(h.top,f.bottom-h.height)),{top:j}}};return g.forEach(function(j){c.offsets.popper=Object.assign(h,i[j]());}),c}function shouldOverflowBoundary(c,d,e){return!!d.escapeWithReference&&(c.flipped&&isSameAxis(c.originalPlacement,e)||!!isSameAxis(c.originalPlacement,e)||!0)}function isSameAxis(c,d){var e=c.split('-')[0],f=d.split('-')[0];return e===f||e===getOppositePlacement(d)}
function shift(a){var b=a.placement,c=b.split('-')[0],d=b.split('-')[1];if(d){var e=a.offsets.reference,f=getPopperClientRect(a.offsets.popper),g={y:{start:{top:e.top},end:{top:e.top+e.height-f.height}},x:{start:{left:e.left},end:{left:e.left+e.width-f.width}}},h=-1===['bottom','top'].indexOf(c)?'y':'x';a.offsets.popper=Object.assign(f,g[h][d]);}return a}
function hide(a){if(!isModifierRequired(a.instance.modifiers,'hide','preventOverflow'))return console.warn('WARNING: preventOverflow modifier is required by hide modifier in order to work, be sure to include it before hide!'),a;var b=a.offsets.reference,c=a.instance.modifiers.filter(function(d){return'preventOverflow'===d.name})[0].boundaries;if(b.bottom<c.top||b.left>c.right||b.top>c.bottom||b.right<c.left){if(!0===a.hide)return a;a.hide=!0,a.instance.popper.setAttribute('x-out-of-boundaries','');}else{if(!1===a.hide)return a;a.hide=!1,a.instance.popper.removeAttribute('x-out-of-boundaries');}return a}
var modifiersFunctions = {applyStyle:applyStyle,arrow:arrow,flip:flip,keepTogether:keepTogether,offset:offset,preventOverflow:preventOverflow,shift:shift,hide:hide};var modifiersOnLoad={applyStyleOnLoad:applyStyleOnLoad};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var get = function get(object, property, receiver) {
if (object === null) object = Function.prototype;
var desc = Object.getOwnPropertyDescriptor(object, property);
if (desc === undefined) {
var parent = Object.getPrototypeOf(object);
if (parent === null) {
return undefined;
} else {
return get(parent, property, receiver);
}
} else if ("value" in desc) {
return desc.value;
} else {
var getter = desc.get;
if (getter === undefined) {
return undefined;
}
return getter.call(receiver);
}
};
var set = function set(object, property, value, receiver) {
var desc = Object.getOwnPropertyDescriptor(object, property);
if (desc === undefined) {
var parent = Object.getPrototypeOf(object);
if (parent !== null) {
set(parent, property, value, receiver);
}
} else if ("value" in desc && desc.writable) {
desc.value = value;
} else {
var setter = desc.set;
if (setter !== undefined) {
setter.call(receiver, value);
}
}
return value;
};
var DEFAULTS={placement:'bottom',gpuAcceleration:!0,modifiers:{shift:{order:100,enabled:!0,function:modifiersFunctions.shift},offset:{order:200,enabled:!0,function:modifiersFunctions.offset,offset:0},preventOverflow:{order:300,enabled:!0,function:modifiersFunctions.preventOverflow,priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,function:modifiersFunctions.keepTogether},arrow:{order:500,enabled:!0,function:modifiersFunctions.arrow,element:'[x-arrow]'},flip:{order:600,enabled:!0,function:modifiersFunctions.flip,behavior:'flip',padding:5,boundariesElement:'viewport'},hide:{order:700,enabled:!0,function:modifiersFunctions.hide},applyStyle:{order:800,enabled:!0,function:modifiersFunctions.applyStyle,onLoad:modifiersOnLoad.applyStyleOnLoad}}};var Popper=function(){function Popper(a,b){var _this=this,c=2<arguments.length&&void 0!==arguments[2]?arguments[2]:{};return classCallCheck(this,Popper),this.Defaults=DEFAULTS,this.update=debounce(this.update.bind(this)),this.scheduleUpdate=function(){return requestAnimationFrame(_this.update)},this.state={isDestroyed:!1,isCreated:!1},this.reference=a.jquery?a[0]:a,this.popper=b.jquery?b[0]:b,this.options=Object.assign({},DEFAULTS,c),this.modifiers=Object.keys(DEFAULTS.modifiers).map(function(d){return Object.assign({name:d},DEFAULTS.modifiers[d])}),this.modifiers=this.modifiers.map(function(d){var e=c.modifiers&&c.modifiers[d.name]||{},f=Object.assign({},d,e);return f}),c.modifiers&&(this.options.modifiers=Object.assign({},DEFAULTS.modifiers,c.modifiers),Object.keys(c.modifiers).forEach(function(d){if(void 0===DEFAULTS.modifiers[d]){var e=c.modifiers[d];e.name=d,_this.modifiers.push(e);}})),this.modifiers=this.modifiers.sort(sortModifiers),this.modifiers.forEach(function(d){d.enabled&&isFunction(d.onLoad)&&d.onLoad(_this.reference,_this.popper,_this.options,d);}),this.state.position=getPosition(this.reference),this.state.isParentTransformed=isTransformed(this.popper.parentNode),this.update(),setupEventListeners(this.reference,this.options,this.state,this.scheduleUpdate),this}return createClass(Popper,[{key:'update',value:function update(){var a={instance:this,styles:{},flipped:!1};this.state.position=getPosition(this.reference),setStyle(this.popper,{position:this.state.position}),this.state.isDestroyed||(a.placement=this.options.placement,a.originalPlacement=this.options.placement,a.offsets=getOffsets(this.state,this.popper,this.reference,a.placement),a=runModifiers(this.modifiers,a),this.state.isCreated?isFunction(this.state.updateCallback)&&this.state.updateCallback(a):(this.state.isCreated=!0,isFunction(this.state.createCallback)&&this.state.createCallback(a)));}},{key:'onCreate',value:function onCreate(a){return this.state.createCallback=a,this}},{key:'onUpdate',value:function onUpdate(a){return this.state.updateCallback=a,this}},{key:'destroy',value:function destroy(){return this.state.isDestroyed=!0,this.popper.removeAttribute('x-placement'),this.popper.style.left='',this.popper.style.position='',this.popper.style.top='',this.popper.style[getSupportedPropertyName('transform')]='',this.state=removeEventListeners(this.reference,this.state),this.options.removeOnDestroy&&this.popper.parentNode.removeChild(this.popper),this}}]),Popper}();Popper.Utils=Utils;
return Popper;
})));

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,287 @@
/* ===========================================================
* bootstrap-tooltip.js v2.2.2
* http://twitter.github.com/bootstrap/javascript.html#tooltips
* Inspired by the original jQuery.tipsy by Jason Frame
* ===========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
!function ($) {
"use strict"; // jshint ;_;
/* TOOLTIP PUBLIC CLASS DEFINITION
* =============================== */
var Tooltip = function (element, options) {
this.init('tooltip', element, options)
}
Tooltip.prototype = {
constructor: Tooltip
, init: function (type, element, options) {
var eventIn
, eventOut
this.type = type
this.$element = $(element)
this.options = this.getOptions(options)
this.enabled = true
if (this.options.trigger == 'click') {
this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
} else if (this.options.trigger != 'manual') {
eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
}
this.options.selector ?
(this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
this.fixTitle()
}
, getOptions: function (options) {
options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())
if (options.delay && typeof options.delay == 'number') {
options.delay = {
show: options.delay
, hide: options.delay
}
}
return options
}
, enter: function (e) {
var self = $(e.currentTarget)[this.type](this._options).data(this.type)
if (!self.options.delay || !self.options.delay.show) return self.show()
clearTimeout(this.timeout)
self.hoverState = 'in'
this.timeout = setTimeout(function() {
if (self.hoverState == 'in') self.show()
}, self.options.delay.show)
}
, leave: function (e) {
var self = $(e.currentTarget)[this.type](this._options).data(this.type)
if (this.timeout) clearTimeout(this.timeout)
if (!self.options.delay || !self.options.delay.hide) return self.hide()
self.hoverState = 'out'
this.timeout = setTimeout(function() {
if (self.hoverState == 'out') self.hide()
}, self.options.delay.hide)
}
, show: function () {
var $tip
, inside
, pos
, actualWidth
, actualHeight
, placement
, tp
if (this.hasContent() && this.enabled) {
$tip = this.tip()
this.setContent()
if (this.options.animation) {
$tip.addClass('fade')
}
placement = typeof this.options.placement == 'function' ?
this.options.placement.call(this, $tip[0], this.$element[0]) :
this.options.placement
inside = /in/.test(placement)
$tip
.detach()
.css({ top: 0, left: 0, display: 'block' })
.insertAfter(this.$element)
pos = this.getPosition(inside)
actualWidth = $tip[0].offsetWidth
actualHeight = $tip[0].offsetHeight
switch (inside ? placement.split(' ')[1] : placement) {
case 'bottom':
tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
break
case 'top':
tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
break
case 'left':
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
break
case 'right':
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
break
}
$tip
.offset(tp)
.addClass(placement)
.addClass('in')
}
}
, setContent: function () {
var $tip = this.tip()
, title = this.getTitle()
$tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
$tip.removeClass('fade in top bottom left right')
}
, hide: function () {
var that = this
, $tip = this.tip()
$tip.removeClass('in')
function removeWithAnimation() {
var timeout = setTimeout(function () {
$tip.off($.support.transition.end).detach()
}, 500)
$tip.one($.support.transition.end, function () {
clearTimeout(timeout)
$tip.detach()
})
}
$.support.transition && this.$tip.hasClass('fade') ?
removeWithAnimation() :
$tip.detach()
return this
}
, fixTitle: function () {
var $e = this.$element
if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
$e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
}
}
, hasContent: function () {
return this.getTitle()
}
, getPosition: function (inside) {
return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
width: this.$element[0].offsetWidth
, height: this.$element[0].offsetHeight
})
}
, getTitle: function () {
var title
, $e = this.$element
, o = this.options
title = $e.attr('data-original-title')
|| (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
return title
}
, tip: function () {
return this.$tip = this.$tip || $(this.options.template)
}
, validate: function () {
if (!this.$element[0].parentNode) {
this.hide()
this.$element = null
this.options = null
}
}
, enable: function () {
this.enabled = true
}
, disable: function () {
this.enabled = false
}
, toggleEnabled: function () {
this.enabled = !this.enabled
}
, toggle: function (e) {
var self = $(e.currentTarget)[this.type](this._options).data(this.type)
self[self.tip().hasClass('in') ? 'hide' : 'show']()
}
, destroy: function () {
this.hide().$element.off('.' + this.type).removeData(this.type)
}
}
/* TOOLTIP PLUGIN DEFINITION
* ========================= */
var old = $.fn.tooltip
$.fn.tooltip = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('tooltip')
, options = typeof option == 'object' && option
if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
if (typeof option == 'string') data[option]()
})
}
$.fn.tooltip.Constructor = Tooltip
$.fn.tooltip.defaults = {
animation: true
, placement: 'top'
, selector: false
, template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
, trigger: 'hover'
, title: ''
, delay: 0
, html: false
}
/* TOOLTIP NO CONFLICT
* =================== */
$.fn.tooltip.noConflict = function () {
$.fn.tooltip = old
return this
}
}(window.jQuery);

View File

@@ -0,0 +1,451 @@
odoo.define('product_rental_bookings.product_booking_calender', function (require) {
"use strict";
var AbstractAction = require('web.AbstractAction');
var Widget = require('web.Widget');
var rpc = require('web.rpc');
var core = require('web.core');
var QWeb = core.qweb;
var _t = core._t;
var ajax = require('web.ajax');
var session = require('web.session');
var dialogs = require('web.view_dialogs');
var field_utils = require('web.field_utils');
var _lt = core._lt;
var datepicker = require('web.datepicker');
var config = require('web.config');
var Domain = require('web.Domain');
var DropdownMenu = require('web.DropdownMenu');
var ResourceView = AbstractAction.extend({
title: core._t('Product Rental Booking'),
template: 'ResourceViewTemplate',
init: function (parent, params) {
this._super.apply(this, arguments);
var self = this;
this.action_manager = parent;
this.params = params;
this.filtersMapping = [];
this.items = [];
this.propositions = [];
this.fields = _(fields).chain()
.map(function (val, key) { return _.extend({}, val, { 'name': key }); })
.filter(function (field) { return !field.deprecated && field.searchable; })
.sortBy(function (field) { return field.string; })
.value();
this.attrs = { _: _, fields: this.fields, selected: null };
this.value = null;
this.items = []
self.get_title = false;
self.resource_obj = false;
self.event_obj = false;
self.first_start = false;
self.first_end = false;
self.second_start = false;
self.second_end = false;
self.final_date = false,
self.cust_list = [];
self.color_dict = false;
self.calendar_cust_search_string = '';
self.highlight_dates = [];
self.beautician_lst = [];
self.event_date_list = []
var fields = {}
},
start: function () {
this._super.apply(this, arguments);
this.set("title", this.title);
var self = this;
this.items = [];
return this._super(); //.done(this.proxy('changed'))
},
custom_events: {
},
events: {
'change #model_type_ids': 'my_method',
},
my_method: function (e) {
var self = this;
setTimeout(function () {
var model_id = $('#model_type_ids').val()
var moment_date = false;
rpc.query({
model: 'rental.product.order',
method: 'get_booking_data',
args: [model_id]
}).then(function (data) {
$('#backend_resource_view').fullCalendar('destroy');
self.prepareResourceView(false, data)
})
}, 200)
},
prepareResourceView: function (moment_date, data) {
var self = this;
var resourceList = false;
var eventList = false;
var color_dict = false;
var counter = -1;
if (data) {
resourceList = data[0];
if (data[1]) {
eventList = data[1]
}
}
self.event_obj = eventList;
self.event_date_list = eventList;
self.$el.find('#backend_resource_view').fullCalendar({
defaultView: 'timelineWeek',
defaultDate: moment_date ? moment(moment_date).format('YYYY-MM-DD') : moment(),
aspectRatio: 5,
editable: true,
allDaySlot: false,
eventOverlap: false,
selectable: true,
height: 550,
resourceAreaWidth: "17%",
slotDuration: '00:00',
eventLimit: true, // allow "more" link when too many eventsfullcalendar
slotEventOverlap: true,
//
customButtons: {
},
header: {
left: 'prev, today, next',
center: 'title',
right: 'month, timelineWeek',
},
buttonText: {
month: 'Month',
today: 'Today',
},
buttonIcons: {
prev: 'left-double-arrow',
next: 'right-double-arrow',
},
resourceColumns: [{
labelText: 'Products',
field: 'title'
}],
resourceGroupField: 'building',
resources: resourceList,
eventRender: function (event, element) {
$(element).css({
"font-weight": "bold",
'font-size': '12px',
'color': 'white',
});
if (event['rendering'] === 'background') { } else {
var id = event.resourceId;
var line_id = event.line_id;
var product_type = event.type
var product_id = event.product_id
element.prepend("<div data-id=" + id + " data-type=" + product_type + " data-product-id=" + product_id + " class='ibox-tools' style='cursor:pointer;float:right;position:relative;height:20px;width: auto;z-index: 1000;'><a style='position:relative;background-color: transparent; margin-right: 12px;height: auto;' class='testing pull-left'><i class='fa fa-times remove_booking' data-id=" + id + " data-line-id=" + line_id + " style='position:absolute;height:auto;margin-left: 2px;'></i></a></div>");
}
element.find(".remove_booking").click(function (e) {
if (confirm('Are you sure you want to delete ?')) {
$('#backend_resource_view').fullCalendar('removeEvents', $(e.currentTarget).attr('data-id'));
rpc.query({
model: 'rental.product.order',
method: 'cancel',
args: [$(e.currentTarget).attr('data-line-id')]
}, {
async: false
}).then(function (res) {
$('#backend_resource_view').fullCalendar('removeEvents', event._id);
return true;
});
} else { }
});
},
events: eventList,
/*EVENT RESIZE*/
/*SELECT EVENT ON VIEW CLICK*/
select: function (start, end, jsEvent, view, resource) {
var current_time = moment().format('YYYY-MM-DD HH:mm:ss')
var start_date = moment(start).format('YYYY-MM-DD HH:mm:ss')
var end_date = moment(end).format('YYYY-MM-DD HH:mm:ss')
var context = false
context = rpc.query({
model: 'rental.product.order',
method: 'start_and_end_date_global',
args: [start_date, end_date],
}).then(function (sdate) {
if (sdate) {
context = {
'default_from_date': sdate[0],
'default_to_date': sdate[1],
'default_is_true': true,
}
}
if (resource) {
var id = resource.id
var product_type = resource.type
var product_id = resource.product_id
var list = [[0, 0, { 'product_id': product_id }]]
context['default_product_type_id'] = 1
context['default_product_order_lines_ids'] = list
context['default_is_true'] = true
}
var dialog = new dialogs.FormViewDialog(self, {
res_model: 'rental.product.order',
res_id: false,
title: _t("Rental Order"),
readonly: false,
context: context,
on_saved: function (record, changed) {
$('#backend_resource_view').fullCalendar('destroy');
self.renderElement()
$('.fc-divider').find('.fc-cell-content').addClass('fc-expander');
},
}).open();
});
},
selectAllow: function (selectInfo) {
if (selectInfo.start.isBefore(moment().subtract(1, 'days').toDate()))
return false;
return true;
},
viewRender: function (view, element) {
if (view.type && view.type == "customWeek") {
$('.fc-divider').find('.fc-cell-content').addClass('fc-expander');
}
},
});
},
next_prev_today_BtnClick: function () {
var self = this;
var date = moment($('#backend_resource_view').fullCalendar('getDate')).format('YYYY-MM-DD');
$('#backend_resource_view').fullCalendar('destroy');
self.prepareResourceView(date);
$('.fc-divider').find('.fc-cell-content').addClass('fc-expander');
},
changed: function (e) {
var nval = this.$(".o_searchview_extended_prop_field").val();
if (this.attrs.selected === null || this.attrs.selected === undefined || nval != this.attrs.selected.name) {
this.select_field(_.detect(this.fields, function (x) { return x.name == nval; }));
}
},
operator_changed: function (e) {
this.value.show_inputs($(e.target));
},
/**
* Selects the provided field object
*
* @param field a field descriptor object (as returned by fields_get, augmented by the field name)
*/
select_field: function (field) {
var self = this;
if (this.attrs.selected !== null && this.attrs.selected !== undefined) {
this.value.destroy();
this.value = null;
this.$('.o_searchview_extended_prop_op').html('');
}
this.attrs.selected = field;
if (field === null || field === undefined) {
return;
}
var type = field.type;
var Field = core.search_filters_registry.getAny([type, "char"]);
this.value = new Field(this, field);
_.each(this.value.operators, function (operator) {
$('<option>', { value: operator.value })
.text(String(operator.text))
.appendTo(self.$('.o_searchview_extended_prop_op'));
});
var $value_loc = this.$('.o_searchview_extended_prop_value').show().empty();
this.value.appendTo($value_loc);
},
renderElement: function () {
var self = this;
this._super.apply(this, arguments);
// var r = new r();
var user_ids = [];
rpc.query({
model: 'product.category',
method: 'search_read',
fields: ['id', 'name'],
}, {
async: false
}).then(function (model_name) {
var model_type_html = QWeb.render('model_template', {
model_name: model_name,
widget: self,
});
self.$el.find('#model_selection').empty();
self.$el.find('#model_selection').append(model_type_html);
});
setTimeout(function () {
this.$('#cal_cust_search').autocomplete({
source: function (request, response) {
var query = request.term;
var search_timeout = null;
self.loaded_partners = [];
if (query) {
search_timeout = setTimeout(function () {
var partners_list = [];
self.loaded_partners = self.load_partners(query);
_.each(self.loaded_partners, function (partner) {
partners_list.push({
'id': partner.id,
'value': partner.name,
'label': partner.name
});
});
response(partners_list);
}, 70);
}
},
select: function (event, partner) {
event.stopImmediatePropagation();
// event.preventDefault();
if (partner.item && partner.item.id) {
var selected_partner = _.find(self.loaded_partners, function (customer) {
return customer.id == partner.item.id;
});
var highlight_dates = [];
_.find(self.event_obj, function (customer) {
if (customer.partner_id === selected_partner.id) {
highlight_dates.push(moment(customer.start, 'YYYY-MM_DD').format('D-M-YYYY'));
}
});
self.highlight_dates = highlight_dates;
if (highlight_dates && highlight_dates.length > 0) {
$(".ui-datepicker-trigger").trigger("click");
} else {
self.highlight_dates = [];
}
}
},
focus: function (event, ui) {
event.preventDefault(); // Prevent the default focus behavior.
},
close: function (event) {
// it is necessary to prevent ESC key from propagating to field
// root, to prevent unwanted discard operations.
if (event.which === $.ui.keyCode.ESCAPE) {
event.stopPropagation();
}
},
autoFocus: true,
html: true,
minLength: 1,
delay: 200
});
self.my_method();
self.prepareResourceView();
var input = document.createElement("input");
input.type = "text";
input.name = "";
input.setAttribute("id", "select_date");
input.setAttribute("class", 'datepicker');
input.setAttribute("style", "display:none;");
var span_tag = document.createElement("SPAN");
span_tag.name = "";
span_tag.setAttribute('class', 'title-display');
$('.fc-left').find('.fc-next-button').after(input);
$('.fc-left').find('.fc-next-button').after(span_tag);
$('.title-display').html(self.get_title)
$("#select_date").datepicker({
showOn: "button",
buttonText: "<i class='fa fa-calendar'></i>",
beforeShowDay: function (date) {
if (self.highlight_dates) {
var month = date.getMonth() + 1;
var year = date.getFullYear();
var day = date.getDate();
var newdate = day + "-" + month + '-' + year;
var tooltip_text = "New event on " + newdate;
if ($.inArray(newdate, self.highlight_dates) != -1) {
return [true, "highlight", tooltip_text];
}
return [true];
}
},
onSelect: function (dateText) {
$('#backend_resource_view').fullCalendar('gotoDate', moment(dateText).format('YYYY-MM-DD'));
$('.title-display').html(self.get_title)
}
});
$('button.o_dropdown_toggler_btn').on('click', function (e) {
e.preventDefault()
e.stopPropagation();
this.generatorMenuIsOpen = !this.generatorMenuIsOpen;
var def;
if (!this.generatorMenuIsOpen) {
_.invoke(this.propositions, 'destroy');
this.propositions = [];
}
$('.o_filters_menu').toggle()
self.changed()
});
$("#room_type_ids").select2({
placeholder: "Room Type",
allowClear: true,
});
$("#select_filter").select2({
placeholder: "Filter",
allowClear: true,
});
$('#select_filter').change(function (e) {
});
$('.autocomplete_li').mouseover(function (e) {
$(e.currentTarget).addClass('o-selection-focus');
});
$('.autocomplete_li').mouseout(function (e) {
$(e.currentTarget).removeClass('o-selection-focus');
});
}, 0);
},
load_partners: function (query) {
var self = this;
var loaded_partners = [];
rpc.query({
model: 'res.partner',
method: 'search_read',
fields: ['name'],
domain: ['|', '|', ['name', 'ilike', query],
['mobile', 'ilike', query],
['email', 'ilike', query]
],
limit: 7,
}, {
async: false
}).then(function (partners) {
loaded_partners = partners
});
return loaded_partners;
},
});
core.action_registry.add('resource_view_new', ResourceView);
return {
ResourceView: ResourceView,
};
});

View File

@@ -0,0 +1,124 @@
odoo.define('product_rental_bookings.contract_sign', function (require) {
"use strict";
var BasicController = require('web.BasicController');
var rpc = require('web.rpc');
var FormRenderer = require('web.FormRenderer');
var FormController = require('web.FormController');
var core = require('web.core');
var _t = core._t;
var sign;
FormRenderer.include({
_addOnClickAction: function ($el, node) {
var self = this;
$el.click(function () {
if (node.attrs.name == 'signature_customer' || node.attrs.name == 'signature_contractor') {
self.trigger_up('sign_button_clicked', {
attrs: node.attrs,
record: self.state,
});
} else {
self.trigger_up('button_clicked', {
attrs: node.attrs,
record: self.state,
});
}
});
},
});
FormController.include({
custom_events: _.extend({}, FormController.prototype.custom_events, {
sign_button_clicked: '_onSignButtonClicked',
}),
_onSignButtonClicked: function (event) {
var self = this;
self.saveRecord(self.handle, {
stayInEdit: true,
}).then(function () {
var record = self.model.get(event.data.record.id);
self._callSignButtonAction(event.data.attrs, record)
});
},
_callSignButtonAction: function (attrs, record) {
if (attrs.name == 'signature_customer' || attrs.name == 'signature_contractor' && record.model == 'rental.product.contract') {
this.open_signature_popup(attrs, record);
}
},
open_signature_popup: function (attrs, record) {
var self = this;
$('.model-body-sign').empty();
$('.model-body-sign').html('<canvas id="canvas" width="600px" height="200px"></canvas>');
var canvas = new fabric.Canvas('canvas');
if (record.res_id) {
rpc.query({
model: 'rental.product.contract',
method: 'search_read',
args: [[['id', '=', parseInt(record.res_id)]]],
}, {
async: false
}).then(function (result) {
if (result && result[0]) {
$('#customerModal').modal('show');
}
});
}
canvas.observe('mouse:move', function (o) {
canvas.isDrawingMode = 1;
canvas.freeDrawingBrush.width = 3;
canvas.freeDrawingBrush.color = '#4c4c4c';
});
canvas.on('mouse:up', function (o) {
canvas.isDrawingMode = 0;
});
sign = attrs.name
$('.modal-footer').delegate('.save_sign', 'click', { 'canvas': canvas, 'active_id': record.res_id, 'self': self, 'attrs': sign }, self.save_sign);
$('.modal-footer').delegate('.reset_sign', 'click', { 'canvas': canvas }, self.reset_sign);
},
save_sign: function (event) {
var self = this;
var signature = {}
var signature_img = event.data.canvas.toDataURL().split('data:image/png;base64,')[1]
event.stopImmediatePropagation()
var id = event.data.active_id;
var blank = document.createElement('canvas');
blank.width = event.data.canvas.width;
blank.height = event.data.canvas.height;
if (blank.toDataURL() == event.data.canvas.toDataURL()) {
event.data.self.do_warn(_t('Please add signature and try again.'));
} else {
if (id) {
rpc.query({
model: 'rental.product.contract',
method: 'write',
args: [parseInt(id),
{ 'signature': signature_img, 'button_name': sign, }],
}, {
async: false
}).then(function (result) {
if (result) {
event.data.self.do_notify(_t('Information'), _t("Signature stored successfully."));
event.data.canvas.clear();
event.data.canvas.renderAll();
$('#customerModal').modal('hide');
setTimeout(function () {
location.reload();
}, 1000);
} else {
alert("Signature not save.");
}
});
} else {
alert("Record id not found, Please refresh page and try again.");
}
}
// render form view
event.data.self.update({});
},
});
});

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-extend="FormView.buttons">
<t t-jquery="div.o_form_buttons_edit" t-operation="after">
<div class="modal fade" id="customerModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">
Add Signature
</h4>
</div>
<div class="modal-body model-body-sign"/>
<div class="modal-footer">
<button type="dummy" name="close" class="btn btn-primary" data-dismiss="modal">Close</button>
<button type="dummy" name="close" class="btn btn-primary reset_sign" data-dismiss="modal" style="display:none;">Reset</button>
or
<button type="dummy" name="close" class="btn btn-primary save_sign">Save</button>
</div>
</div>
</div>
</div>
</t>
</t>
</templates>

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="tag_resource_view">
<div t-name="ResourceViewTemplate" style="width:100%">
<div class="container-fluid" style="margin-top:5px">
<div class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-6">
<div class="row">
<div class="col-md-8">
<div style="width: 50%;float: left;">
<div style="width: 28%;float: left;line-height: 30px;">Category:</div>
<div id="model_selection" style="width:60%;float: left;line-height: 30px;"></div>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="btn-group o_dropdown" style="display:none;">
<div class="dropdown-menu o_dropdown_menu o_filters_menu" role="menu"
x-placement="bottom-start"
style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 30px, 0px); width: 170px;">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col-md-12" style="padding:0">
<div id='backend_resource_view'></div>
</div>
</div>
</div>
</div>
<t t-name="model_template">
<select id="model_type_ids" class="form-control" placeholder="Model">
<t t-if="model_name and model_name.length > 0">
<t t-foreach="model_name" t-as="each_model">
<t t-if="each_model">
<option t-att-value="each_model.id">
<t t-esc="each_model.name"/>
</option>
</t>
</t>
</t>
</select>
</t>
<t t-name="fuel_template">
<select id="fuel_type_ids" class="form-control" placeholder="Model">
<t t-if="model_name and model_name.length > 0">
<t t-foreach="model_name" t-as="each_model">
<t t-if="each_model">
<option t-att-value="each_model.id">
<t t-esc="each_model.name"/>
</option>
</t>
</t>
</t>
</select>
</t>
</templates>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record model="ir.ui.view" id="inherit_account_move_form">
<field name="name">Account Move Inherit</field>
<field name="inherit_id" ref="account.view_move_form"/>
<field name="model">account.move</field>
<field name="arch" type="xml">
<field name="team_id" position="after">
<field name="contract_id" invisible="1"/>
<field name="is_hours" invisible="1"/>
<field name="is_days" invisible="1"/>
</field>
<xpath expr="//page/field[@name='invoice_line_ids']/tree/field[@name='quantity']"
position="before">
<field name="enter_hour" attrs="{'column_invisible':[('parent.is_hours', '=', False)]}"/>
<field name="enter_days" attrs="{'column_invisible':[('parent.is_days', '=', False)]}"/>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<template id="assets_backend" name="rental_delivery_sign assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/product_rental_bookings/static/src/js/fabric.min.js" />
<script type="text/javascript" src="/product_rental_bookings/static/src/js/product_contract_sign.js" />
<link rel="stylesheet" href="/product_rental_bookings/static/src/css/backend.css" id="pos-stylesheet" />
</xpath>
</template>
<template id="fullcalender_assests_backend" name="rental_fullcalender" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/product_rental_bookings/static/src/css/fullcalendar.css" />
<link rel="stylesheet" href="/product_rental_bookings/static/src/css/scheduler.css" />
<link rel="stylesheet" href="/product_rental_bookings/static/src/css/scheduler.min.css" />
<script type="text/javascript" src="/product_rental_bookings/static/src/js/lib/fullcalendar.js" />
<script type="text/javascript" src="/product_rental_bookings/static/src/js/lib/scheduler.js" />
<script type="text/javascript" src="/product_rental_bookings/static/src/js/product_booking_calender.js" />
</xpath>
</template>
</data>
</odoo>

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- ROOT -->
<menuitem id="rental_product_main_menu" name="Rental" web_icon="product_rental_bookings/static/description/book_product.png/" />
<!-- QUICK SEARCH -->
<menuitem id="feet_product_quick_booking_menu" name="Quick Search" parent="rental_product_main_menu" sequence="0" action="action_product_quick_product_booking" />
<!-- PRODUCTS -->
<menuitem id="product_product_product" name="Product" parent="rental_product_main_menu" sequence="1" action="product.product_normal_action_sell" />
<!-- BOOKING -->
<menuitem id="rental_product_booking_menu" name="Rental" parent="rental_product_main_menu" sequence="2" />
<menuitem id="product_order_menu" name="Rental Orders" parent="rental_product_booking_menu" sequence="0" action="action_product_order_id" />
<menuitem id="feet_product_contract_sub_menu" name="Rental Contracts" parent="rental_product_booking_menu" sequence="1" action="action_rental_product_contract" />
<menuitem id="feet_product_calender_booking_menu" name="Rental Bookings" parent="rental_product_booking_menu" sequence="2" action="action_product_calender_product_booking1" />
<menuitem id="feet_product_logs_menu" name="Rental Logs" parent="rental_product_booking_menu" sequence="3" action="action_rental_product_logs" />
<!-- INVENTORY -->
<menuitem id="rental_product_inventory_menu" name="Inventory" parent="rental_product_main_menu" sequence="3" />
<menuitem id="stock_inventory_sub_menu" name="Stock inventory" parent="rental_product_inventory_menu" sequence="1" action="stock.action_view_quants" />
<!-- ANALYSIS -->
<menuitem id="rental_product_analysis" name="Analysis" parent="rental_product_main_menu" sequence="5" />
<menuitem id="product_rental_order_analysis" name="Rental Orders" parent="rental_product_analysis" action="action_rental_order_analysis" sequence="2" />
<menuitem id="feet_product_logs_analysis" name="Rental Log" parent="rental_product_analysis" action="action_rental_product_logs_analysis" />
<!-- SETTINGS -->
<menuitem id="product_configuration_menu" name="Configuration" parent="rental_product_main_menu" sequence="10" />
<menuitem id="rental_product_branch_menu" name="Operation" parent="product_configuration_menu" />
<menuitem id="menu_operation_type" name="Operations Types" parent="rental_product_branch_menu" action="action_picking_type_list" sequence="4" />
<menuitem id="rental_menu_configuration" name="Settings" parent="product_configuration_menu" sequence="0" action="rental_config_settings_action" groups="base.group_system" />
<menuitem id="menu_session_config" name="Session Configuration" parent="product_configuration_menu" action="action_session_config" sequence="4" />
</data>
</odoo>

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="action_product_calender_product_booking1" model="ir.actions.client">
<field name="name">Product Rental Booking</field>
<field name="tag">resource_view_new</field>
</record>
<record id="product_booking_id" model="ir.ui.view">
<field name="name">product booking</field>
<field name="model">product.booking</field>
<field name="arch" type="xml">
<form string="product product Booking">
<header>
<button name="search_product" string="Search" type="object" class="oe_highlight" />
<button name="book_product" string="Create" type="object" class="oe_highlight" attrs="{'invisible':[('is_search','=',False)]}" />
</header>
<sheet>
<field name="book_number" invisible="True" />
<group col="4">
<field name="price_based" />
<field name="session_id" attrs="{'invisible':[('price_based','!=','per_session')], 'required':[('price_based','=','per_session')]}" />
</group>
<group col="4" attrs="{'invisible':[('price_based','!=','per_session')]}">
<field name="from_date" required="1" string="Date" />
<field name="to_date" invisible="1" />
</group>
<group col="4" attrs="{'invisible':[('price_based','=','per_session')]}">
<field name="from_date" required="1" />
<field name="to_date" attrs="{'required':[('price_based','!=','per_session')]}" />
</group>
<group col="4">
<field name="location_id" required="1" domain="[('usage', '=', 'internal')]" />
<field name="categ_id" required="1" />
<field name="is_search" invisible="1" />
</group>
<notebook attrs="{'invisible':[('is_search','=',False)]}">
<page name="product_details" string="Product Details">
<field name="product_line_ids">
<tree editable="bottom">
<field name="name" />
<field name="categ_id" />
<field name="currency_id" invisible="1" />
<field name="rental_qyt" />
<field name="rental_amount" widget="monetary" options="{'currency_field': 'currency_id'}" attrs="{'column_invisible':[('parent.price_based','!=','per_day')]}" />
<field name="rental_amount_per_hour" widget="monetary" options="{'currency_field': 'currency_id'}" attrs="{'column_invisible':[('parent.price_based','!=','per_hour')]}" />
<field name="rental_amount_per_session" widget="monetary" options="{'currency_field': 'currency_id'}" attrs="{'column_invisible':[('parent.price_based','!=','per_session')]}" />
<field name="taxes_id" widget="many2many_tags" />
<field name="selected_product" />
</tree>
</field>
</page>
</notebook>
<group>
<group>
<group>
<field name="total_days" attrs="{'invisible':['|',('is_search','=',False),('price_based', '!=', 'per_day')]}" />
<field name="total_hours" widget="float_time" attrs="{'invisible':['|',('is_search','=',False),('price_based', '=', 'per_day')]}" />
</group>
</group>
<group>
<field name="currency_id" invisible="1" />
<field name="extra_charges" widget="monetary" options="{'currency_field': 'currency_id'}" attrs="{'invisible':[('is_search','=',False)]}" />
<field name="sub_total" widget="monetary" options="{'currency_field': 'currency_id'}" attrs="{'invisible':[('is_search','=',False)]}" readonly="1" />
<field name="total" widget="monetary" options="{'currency_field': 'currency_id'}" attrs="{'invisible':[('is_search','=',False)]}" readonly="1" />
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_product_quick_product_booking" model="ir.actions.act_window">
<field name="name">Product booking</field>
<field name="res_model">product.booking</field>
<field name="view_mode">form</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,191 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id='rental_product_contract_form' model="ir.ui.view">
<field name="name">Rental Contracts</field>
<field name="model">rental.product.contract</field>
<field name="arch" type="xml">
<form>
<header>
<button name="contract_close" states="open,expired,future" type="object" class="oe_highlight" string="Close Contract" />
<button name="send_product_contract" string="Send Contract by Email" type="object" class="oe_highlight" />
<button name="contract_open" states="closed" type="object" class="oe_highlight" string="Set
Contract In Progress" />
<button class="oe_highlight" name="act_renew_contract" type="object" string="Renew
Contract" help="Create a new contract automatically with
all the same informations except for the date that will
start at the end of current contract" attrs="{'invisible':[('state','!=','diesoon')]}" />
<button class="oe_highlight" name="create_invoice" type="object" string="Create invoice" attrs="{'invisible':[('first_invoice_created','=',True)]}" />
<button name="generate_policy" type="object" string="Generate Policy" invisible="1" />
<field name="state" widget="statusbar" />
</header>
<sheet>
<div class="oe_title">
<h1>
<field name="name" placeholder="New" />
</h1>
</div>
<group>
<group col="2">
<field name="currency_id" invisible="1" />
<field name="partner_id" invisible="1" />
<field name="origin" string="Source Document" invisible="1" />
<field name="company_id" readonly="1" />
<field name="rental_id" invisible="1" />
<field name="picking_id" invisible="1" />
<field name="is_hours" invisible="1" />
<field name="is_days" invisible="1" />
</group>
<group>
<field name="contract_date" readonly="1" />
<field name="from_date" required="1" readonly="1" />
<field name="to_date" required="1" readonly="1" />
<field name="cost" invisible="1" />
<field name="account_type" invisible="1" />
<label for="cost_generated" invisible="1" />
<div>
<field name="cost_frequency" class="oe_inline" invisible="1" />
<field name="cost_generated" style="width:142px;" invisible="1" />
</div>
</group>
<group col="2">
<separator string="Payment Details" />
<field name="account_payment_term" readonly="1" />
<field name="first_invoice_created" invisible="1" />
</group>
</group>
<notebook>
<page string="Product Details" name="product_details">
<group>
<field name="attachment_ids" widget="many2many_binary" />
</group>
<group>
<field name="product_contract_lines_ids" nolabel="1">
<tree editable="bottom">
<field name="product_id" string="Product" domain="[('is_rental', '=', True)]" />
<field name="description" />
<field name="currency_id" invisible="1" />
<field name="price_based" />
<field name="enter_days" attrs="{'column_invisible':[('parent.is_days', '=', False)],
'invisible': [('price_based', '=', 'per_hour')]}" />
<field name="enter_hour" attrs="{'column_invisible':[('parent.is_hours', '=', False)],
'invisible': [('price_based', '=', 'per_day')]}" />
<field name="qty_needed" />
<field name="price" widget="monetary" options="{'currency_field': 'currency_id'}" />
<field name="tax_id" widget="many2many_tags" />
<field name="sub_total" widget="monetary" options="{'currency_field': 'currency_id'}" />
<field name="product_contract_id" invisible="1" />
</tree>
</field>
</group>
</page>
<page string="Recurring Invoice">
<field name="recurring_line" mode="tree" sum="recurring_amount">
<tree string="product Reccurring Lines" colors="#0b7a35:payment_info=='paid';#f20b07:payment_info!='paid'">
<field name="date_today" />
<field name="payment_info" />
<field name="recurring_amount" sum="recurring_amount" />
</tree>
</field>
<field name="sum_cost" invisible="1" />
</page>
<page string="Document">
<field name="document_ids" nolabel="1">
<tree editable="bottom">
<field name="name" />
<field name="id_number" />
<field name="contract_id" invisible="1" />
</tree>
</field>
</page>
<page string="Cancellation Policy">
<group>
<field name="number_of_slot" required="True" />
</group>
<field name="cancel_policy_ids" mode="tree">
<tree string="Cancellation Policy" editable="bottom" create="false" delete="false">
<field name="contract_id" invisible="1" />
<field name="from_date" />
<field name="to_date" />
<field name="policy_charged" />
</tree>
</field>
</page>
</notebook>
<group>
<group>
<label for="first_payment" />
<field name="first_payment" nolabel="1" widget='monetary' />
</group>
<group class="oe_subtotal_footer oe_right" colspan="2" name="sale_total">
<label for="untaxed_amount" />
<field name="untaxed_amount" nolabel="1" widget="monetary" options="{'currency_field': 'currency_id'}" />
<label for="taxes" />
<field name="taxes" nolabel="1" widget='monetary' />
<label for="extra_charges" />
<field name="extra_charges" readonly="1" nolabel="1" widget="monetary" options="{'currency_field': 'currency_id'}" />
<div class="oe_subtotal_footer_separator oe_inline o_td_label">
<label for="total_amount" />
</div>
<field name="total_amount" nolabel="1" class="oe_subtotal_footer_separator" widget="monetary" options="{'currency_field': 'currency_id'}" />
</group>
</group>
<group>
<field name="terms_condition" />
</group>
<group col="4">
<label for="signature_contractor" />
<div>
<field name="signature_contractor" widget="image" options="{'size': [90, 35]}" />
<button name="signature_contractor" string="Contractor Signature" class="oe_highlight" />
</div>
<label for="signature_customer" />
<div>
<field name="signature_customer" widget="image" options="{'size': [90, 35]}" />
<button name="signature_customer" string="Customer Signature" class="oe_highlight" />
</div>
</group>
</sheet>
</form>
</field>
</record>
<record id='rental_product_contract_tree' model='ir.ui.view'>
<field name="name">Rental Contracts</field>
<field name="model">rental.product.contract</field>
<field name="arch" type="xml">
<tree>
<field name="partner_id" />
<field name="from_date" />
<field name="to_date" />
<field name="state" />
</tree>
</field>
</record>
<record id='action_rental_product_contract' model='ir.actions.act_window'>
<field name="name">Rental Contracts</field>
<field name="res_model">rental.product.contract</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class='oe_view_nocontent_create'>Click to add Products Contract</p>
</field>
</record>
<record id='action_rental_product_contract_form' model='ir.actions.act_window'>
<field name="name">Rental Contracts</field>
<field name="res_model">rental.product.contract</field>
<field name="view_mode">form,tree</field>
<field name="help" type="html">
<p class='oe_view_nocontent_create'>Click to add Products Contract</p>
</field>
</record>
<record id="action_rental_contract_view_tree" model="ir.actions.act_window">
<field name="name">Contract</field>
<field name="res_model">rental.product.contract</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="rental_product_contract_tree" />
<field name="domain">[('state', 'not in', ['futur', 'closed'])]</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="rental_product_logs_form_id" model="ir.ui.view">
<field name="name">Product Logs</field>
<field name="model">rental.product.logs</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<group>
<field name="product_id" />
<field name="customer_id" />
<field name="from_date" />
<field name="to_date" />
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="rental_product_logs_tree_id" model="ir.ui.view">
<field name="name">Product Logs</field>
<field name="model">rental.product.logs</field>
<field name="arch" type="xml">
<tree>
<field name="product_id" />
<field name="customer_id" />
<field name="from_date" />
<field name="to_date" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_product_logs_pivot">
<field name="name">Products Logs</field>
<field name="model">rental.product.logs</field>
<field name="arch" type="xml">
<pivot string="product Logs">
<field name="product_id" type="row" />
<field name="from_date" type="col" />
</pivot>
</field>
</record>
<record model="ir.ui.view" id="view_product_logs_graph">
<field name="name">Products Logs</field>
<field name="model">rental.product.logs</field>
<field name="arch" type="xml">
<graph string="product Logs">
<field name="product_id" type="row" />
<field name="from_date" type="col" />
</graph>
</field>
</record>
<record id="action_rental_product_logs" model="ir.actions.act_window">
<field name="name">Rental Logs</field>
<field name="res_model">rental.product.logs</field>
<field name="view_mode">tree,form</field>
</record>
<record id="action_rental_product_logs_analysis" model="ir.actions.act_window">
<field name="name">product Logs</field>
<field name="res_model">rental.product.logs</field>
<field name="view_mode">graph,pivot</field>
</record>
<record id="action_rental_product_move" model="ir.actions.act_window">
<field name="name">Rental Move</field>
<field name="res_model">stock.picking</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_undone': 1}</field>
<field name="domain">[('is_rental', '=', True)]</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,134 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="rental_product_operation_form_id" model="ir.ui.view">
<field name="name">Operation Type</field>
<field name="model">rental.product.operation</field>
<field name="arch" type="xml">
<form>
<sheet>
<div class="oe_title">
<label class="oe_edit_only" for="name" string="Operation Type Name" />
<h1>
<field name="name" />
</h1>
</div>
<group>
<group>
<field name="rental_move_type" />
<field name="location_id" />
</group>
<group>
<field name="source_location" />
<field name="destination_location" />
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="rental_product_type_kanban" model="ir.ui.view">
<field name="name">rental.product.operation.kanban</field>
<field name="model">rental.product.operation</field>
<field name="arch" type="xml">
<kanban class="oe_background_grey o_kanban_dashboard o_emphasize_colors o_stock_kanban" create="0">
<field name="color" />
<field name="rental_move_type" />
<field name="state" />
<field name="count_operation_ready" />
<field name="count_operation_on_rent" />
<field name="count_operation_service" />
<templates>
<t t-name="kanban-box">
<div t-attf-class="#{kanban_color(record.color.raw_value)}">
<div>
<div t-attf-class="o_kanban_card_header">
<div class="o_kanban_card_header_title">
<div class="o_primary">
<field name="name" />
</div>
<div class="o_secondary">
<field class="o_secondary" name="location_id" />
</div>
</div>
<div class="o_kanban_manage_button_section">
<a class="o_kanban_manage_toggle_button" href="#">
<i class="fa fa-ellipsis-v" role="img" aria-label="Manage" title="Manage" />
</a>
</div>
</div>
<div class="container o_kanban_card_content">
<div class="row">
<div class="col-xs-6 o_kanban_primary_left" style="margin-left: 16px;">
<button class="btn btn-primary" name="get_action_operation" type="object">
<span t-if="record.rental_move_type.raw_value =='outgoing'">
<t t-esc="record.count_operation_ready.value" />
Deliveries of Product
</span>
<span t-if="record.rental_move_type.raw_value =='incoming'">
<t t-esc="record.count_operation_on_rent.value" />
Incoming Products
</span>
<span t-if="record.rental_move_type.raw_value =='internal'">
<t t-esc="record.count_operation_service.value" />
Internal Moves
</span>
</button>
</div>
</div>
</div>
<div class="container o_kanban_card_manage_pane dropdown-menu" role="menu">
<div t-if="widget.editable" class="o_kanban_card_manage_settings row">
<div class="col-xs-8">
<ul class="oe_kanban_colorpicker" data-field="color" />
</div>
<div class="col-xs-4 text-right">
<a type="edit">Settings</a>
</div>
</div>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="rental_product_operation_tree_id" model="ir.ui.view">
<field name="name">Operation type</field>
<field name="model">rental.product.operation</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="location_id" />
<field name="rental_move_type" />
</tree>
</field>
</record>
<record id="action_picking_type_list" model="ir.actions.act_window">
<field name="name">Operation Type</field>
<field name="res_model">rental.product.operation</field>
<field name="type">ir.actions.act_window</field>
<field name="view_mode">list,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">Click to define a new transfer.</p>
</field>
</record>
<record id="action_product_operation_type" model="ir.actions.act_window">
<field name="name">Operation Type</field>
<field name="res_model">rental.product.operation</field>
<field name="type">ir.actions.act_window</field>
<field name="view_mode">kanban,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">Click to create a new operation type.</p>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,195 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="rental_product_order_search" model="ir.ui.view">
<field name="name">Rental Order</field>
<field name="model">rental.product.order</field>
<field name="arch" type="xml">
<search>
<field name="res_number" filter_domain="[('res_number','ilike',self)]" string="Order Number" />
<field name="customer_name" filter_domain="[('customer_name','ilike',self)]" string="Customer" />
</search>
</field>
</record>
<record id="rental_product_order_form_id" model="ir.ui.view">
<field name="name">Rental Order</field>
<field name="model">rental.product.order</field>
<field name="arch" type="xml">
<form string="product Order">
<header>
<field name="state" widget="statusbar" />
<button name="send_product_quote" string="Send by Email" type="object" class="oe_highlight" />
<button name="confirm" string="Confirm" type="object" class="oe_highlight" states="book,draft" />
<button name="cancel" string="Cancel" type="object" class="oe_highlight" attrs="{'invisible': [('state', 'in', ('cancel', 'close'))]}" />
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button type="object" class="oe_stat_button" icon="fa-truck" name="action_view_stock_pickings" invisible="1">
<div class="o_form_field o_stat_info">
<span class="o_stat_value">
<field name="picking_count" />
</span>
<span class="o_stat_text">Move</span>
</div>
</button>
<button type="object" class="oe_stat_button" icon="fa-pencil-square-o" name="action_view_order_invoices" attrs="{'invisible': [('invoice_count','=',0)]}">
<div class="o_form_field o_stat_info">
<span class="o_stat_value">
<field name="invoice_count" />
</span>
<span class="o_stat_text">Invoiced</span>
</div>
</button>
<button type="object" class="oe_stat_button" icon="fa-book" name="action_view_order_contract" attrs="{'invisible': [('contract_count','=',0)]}">
<div class="o_form_field o_stat_info">
<span class="o_stat_value">
<field name="contract_count" />
</span>
<span class="o_stat_text">Contract</span>
</div>
</button>
</div>
<div class="oe_title">
<h1>
<field name="res_number" placeholder="New" />
</h1>
</div>
<group col="4">
<field name="customer_name" required="1" />
<field name="book_date" />
<field name="from_date" required="1" readonly="1" />
<field name="to_date" required="1" readonly="1" />
<field name="start_date" invisible="1" />
<field name="end_date" invisible="1" />
<field name="is_agreement" invisible="1" />
<field name="account_payment_term" />
<field name="location_id" invisible="1" />
<field name="company_id" invisible="1" />
<field name="count" invisible="1" />
<field name="is_true" invisible="1" />
<field name="pricelist_id" invisible="1" />
<field name="return_date" invisible="1" />
<field name="currency_id" invisible="1" />
<field name="is_hours" invisible="1" />
<field name="is_days" invisible="1" />
</group>
<group>
<group col="2"></group>
<group col="2"></group>
</group>
<notebook>
<page name="rental_order" string="Rental Order">
<field name="product_order_lines_ids" readonly="1">
<tree editable="bottom">
<field name="product_id" string="Product" context="{'from_date':parent.from_date, 'to_date':parent.to_date, 'from_product_order': True}" domain="[('is_rental', '=', True)]" />
<field name="name" />
<field name="product_order_id" invisible="1" />
<field name="price_based" />
<field name="enter_days" attrs="{'column_invisible':[('parent.is_days', '=', False)],'invisible': [('price_based', '=', 'per_hour')]}" />
<field name="enter_hour" attrs="{'column_invisible':[('parent.is_hours', '=', False)],'invisible': [('price_based', '=', 'per_day')]}" />
<field name="qty_needed" />
<field name="currency_id" invisible="1" />
<field name="price" widget="monetary" options="{'currency_field': 'currency_id'}" />
<field name="tax_id" widget="many2many_tags" />
<field name="sub_total" widget="monetary" options="{'currency_field': 'currency_id'}" />
</tree>
</field>
<group class="oe_subtotal_footer oe_right" colspan="2" name="sale_total">
<label for="untaxed_amount" widget="monetary" options="{'currency_field': 'currency_id'}" />
<field name="untaxed_amount" nolabel="1" widget='monetary' />
<label for="taxes" />
<field name="taxes" nolabel="1" widget='monetary' />
<label for="extra_charges" widget="monetary" options="{'currency_field': 'currency_id'}" />
<field name="extra_charges" nolabel="1" widget="monetary" options="{'currency_field': 'currency_id'}" />
<div class="oe_subtotal_footer_separator oe_inline o_td_label">
<label for="total_amount" widget="monetary" options="{'currency_field': 'currency_id'}" />
</div>
<field name="total_amount" nolabel="1" class="oe_subtotal_footer_separator" widget='monetary' />
</group>
<group>
<field name="terms_condition" />
</group>
</page>
<page name="other_info" string="Other Information">
<group>
<group>
<field name="user_id" />
<field name="invoice_status" />
</group>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="rental_product_driver_schedule" model="ir.ui.view">
<field name="name">Rental Order</field>
<field name="model">rental.product.order</field>
<field name="arch" type="xml">
<tree decoration-muted="state=='cancel'" decoration-info="state=='draft'" decoration-success="state=='confirm'">
<field name="customer_name" />
<field name="res_number" />
<field name="from_date" />
<field name="to_date" />
<field name="state" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_product_rental_order_pivot">
<field name="name">rental.order.pivot</field>
<field name="model">rental.product.order</field>
<field name="arch" type="xml">
<pivot string="Rental Orders">
<field name="customer_name" type="row" />
<field name="count" interval="month" type="col" />
<field name="total_amount" type="measure" />
</pivot>
</field>
</record>
<record model="ir.ui.view" id="view_product_rental_order_graph">
<field name="name">rental.order.graph</field>
<field name="model">rental.product.order</field>
<field name="arch" type="xml">
<graph string="Rental Orders">
<field name="customer_name" type="row" />
<field name="total_amount" type="col" />
</graph>
</field>
</record>
<record id="action_product_order_id" model="ir.actions.act_window">
<field name="name">Rental Order</field>
<field name="res_model">rental.product.order</field>
<field name="view_mode">tree,form,calendar</field>
<field name="context">{'search_default_undone': 1}</field>
</record>
<record id="action_product_order_id_new" model="ir.actions.act_window">
<field name="name">Rental Order</field>
<field name="res_model">rental.product.order</field>
<field name="view_mode">form,tree</field>
</record>
<record id="action_rental_order_analysis" model="ir.actions.act_window">
<field name="name">Rental Order</field>
<field name="res_model">rental.product.order</field>
<field name="view_mode">graph,pivot</field>
</record>
<record id="product_booking_calendar" model="ir.ui.view">
<field name="name">Rental order</field>
<field name="model">rental.product.order</field>
<field name="arch" type="xml">
<calendar string="product" date_start="from_date" date_stop="to_date" event_open_popup="true" quick_add="false" form_view_id="%(product_rental_bookings.action_product_order_id)s" event_limit="3" color="customer_name" mode="month">
<field name="customer_name" />
</calendar>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="product_normal_form_view_inherit" model="ir.ui.view">
<field name="name">Product Variant</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_normal_form_view" />
<field name="arch" type="xml">
<xpath expr="//field[@name='purchase_ok']/parent::div" position="after">
<div>
<field name="is_rental" />
<label for="is_rental" />
</div>
</xpath>
<xpath expr="//field[@name='uom_po_id']" position="after">
<field name="rental_amount" widget="monetary" options="{'currency_field': 'currency_id'}" />
<field name="rental_amount_per_hour" widget="monetary" options="{'currency_field': 'currency_id'}" />
<field name="rental_amount_per_session" widget="monetary" options="{'currency_field': 'currency_id'}" />
</xpath>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<record id="rental_res_config_settings_form_inherit" model="ir.ui.view">
<field name="name">res.config.settings.from</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="website.res_config_settings_view_form" />
<field name="arch" type="xml">
<xpath expr="//div[hasclass('settings')]" position="inside">
<div class="app_settings_block" data-string="Rental" string="Rental" data-key="product_rental_bookings">
<h2>Rental Configuration</h2>
<div class="row o_settings_container mt16">
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_right_pane">
<div class="content-group">
<div class="row mt16">
<label for="enabled_day_rent" class="col-md-3 o_light_label" style="font-weight:bold;" />
<field name="enabled_day_rent" />
</div>
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_right_pane">
<div class="content-group">
<div class="row mt16">
<label for="enabled_hour_rent" class="col-md-3 o_light_label" style="font-weight:bold;" />
<field name="enabled_hour_rent" />
</div>
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_right_pane">
<div class="content-group">
<div class="row mt16">
<label for="enabled_session_rent" class="col-md-3 o_light_label" style="font-weight:bold;" />
<field name="enabled_session_rent" />
</div>
</div>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
<record id="rental_config_settings_action" model="ir.actions.act_window">
<field name="name">Settings</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.config.settings</field>
<field name="view_mode">form</field>
<field name="target">inline</field>
<field name="context">{'module' : 'product_rental_bookings', 'bin_size': False}</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="rental_view_order_form" model="ir.ui.view">
<field name="name">sale.order.form</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form" />
<field name="arch" type="xml">
<field name="partner_id" position="after">
<field name="signature" widget="image" options="{'always_reload': True, 'size': [90, 90]}" />
</field>
</field>
</record>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.delivery</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="sale.res_config_settings_view_form" />
<field name="arch" type="xml">
<xpath expr="//div[@class='app_settings_block']" position="inside">
<h2>Signature</h2>
<div class="row mt16 o_settings_container">
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="signature" />
</div>
<div class="o_setting_right_pane">
<label for="signature" />
<div class="text-muted">
Required signature before confirm sale order.
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="sequence_product_information" model="ir.sequence">
<field name="name">product Contract</field>
<field name="code">product_contract</field>
<field name="prefix">L</field>
<field name="padding">3</field>
<field name="suffix">/%(month)s/%(day)s</field>
<field name="company_id" eval="False" />
</record>
<record id="sequence_product_registration" model="ir.sequence">
<field name="name">product Registration</field>
<field name="code">product_registration</field>
<field name="prefix">REG</field>
<field name="padding">3</field>
<field name="suffix">/%(month)s/%(day)s</field>
<field name="company_id" eval="False" />
</record>
<record id="sequence_product_move" model="ir.sequence">
<field name="name">product Move</field>
<field name="code">product_move</field>
<field name="prefix">PM</field>
<field name="padding">3</field>
<field name="suffix">/%(month)s/%(day)s</field>
<field name="company_id" eval="False" />
</record>
<record id="sequence_quick_search" model="ir.sequence">
<field name="name">product Search</field>
<field name="code">product_booking</field>
<field name="prefix">QS</field>
<field name="padding">3</field>
<field name="suffix">%(day)s</field>
<field name="company_id" eval="False" />
</record>
<record id="sequence_calender_record" model="ir.sequence">
<field name="name">Full Calender</field>
<field name="code">virtual_</field>
<field name="padding">3</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id='session_config_form' model="ir.ui.view">
<field name="name">Session Configuration Form</field>
<field name="model">session.config</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<group>
<field name="name" required="1"/>
</group>
</group>
<group col="4">
<group>
<field name="start_time" widget="float_time"/>
</group>
<group>
<field name="end_time" widget="float_time"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id='session_config_tree' model="ir.ui.view">
<field name="name">Session Configuration Tree</field>
<field name="model">session.config</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="start_time" widget="float_time"/>
<field name="end_time" widget="float_time"/>
</tree>
</field>
</record>
<record id='action_session_config' model='ir.actions.act_window'>
<field name="name">Session Configuration</field>
<field name="res_model">session.config</field>
<field name="view_mode">tree,form</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="stock_picking_form_inherit" model="ir.ui.view">
<field name="name">view.picking.form.inherit</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form" />
<field name="arch" type="xml">
<xpath expr="//button[@name='action_confirm']" position="after">
<button name="incoming" attrs="{'invisible': [('state', 'in', ['draft', 'waiting', 'assigned', 'done', 'cancel'])]}" string="Receive" type="object" class="oe_highlight" states="on_rent" />
<button name="delivery" attrs="{'invisible': [('state', 'in', ['waiting', 'confirmed', 'assigned', 'done', 'cancel'])]}" string="Delivery" type="object" class="oe_highlight" groups="base.group_user" />
<button name="move" attrs="{'invisible': [('state', 'in', ['draft','waiting', 'confirmed', 'done', 'cancel'])]}" string="Move to Store" type="object" class="oe_highlight" />
</xpath>
<xpath expr="//field[@name='partner_id']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='move_ids_without_package']/tree/field[@name='product_id']" position="after">
<field name="products_checked" />
</xpath>
<xpath expr="//field[@name='origin']" position="after">
<field name="rental_move_type" invisible="1" />
<field name="is_rental" invisible="1" />
</xpath>
<xpath expr="//field[@name='origin']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
</field>
</record>
</data>
</odoo>

View File

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

View File

@@ -0,0 +1,177 @@
# -*- coding: utf-8 -*-
import time
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class ProductAdvancePaymentInvoice(models.TransientModel):
_name = "product.advance.payment.invoice"
_description = "product Advance Payment Invoice"
@api.model
def _count(self):
return len(self._context.get("active_ids", []))
@api.model
def _get_advance_payment_method(self):
if self._count() == 1:
rental_obj = self.env["rental.product.order"]
order = rental_obj.browse(self._context.get("active_ids"))[0]
if order.invoice_count:
return "all"
return "delivered"
@api.model
def _default_product_id(self):
product_id = (
self.env["ir.config_parameter"]
.sudo()
.get_param("sale.default_deposit_product_id")
)
return self.env["product.product"].browse(int(product_id))
@api.model
def _default_deposit_account_id(self):
return self._default_product_id().property_account_income_id
@api.model
def _default_deposit_taxes_id(self):
return self._default_product_id().taxes_id
advance_payment_method = fields.Selection(
[
("delivered", "Invoiceable lines"),
("all", "Invoiceable lines (deduct down payments)"),
("percentage", "Down payment (percentage)"),
("fixed", "Down payment (fixed amount)"),
],
string="What do you want to invoice?",
default=_get_advance_payment_method,
required=True,
)
product_id = fields.Many2one(
"product.product",
string="Down Payment Product",
domain=[("type", "=", "service")],
default=_default_product_id,
)
count = fields.Integer(default=_count, string="# of Orders")
amount = fields.Float(
"Down Payment Amount",
help="The amount to be invoiced in advance, taxes excluded.",
)
deposit_account_id = fields.Many2one(
"account.account",
string="Income Account",
domain=[("deprecated", "=", False)],
help="Account used for deposits",
default=_default_deposit_account_id,
)
deposit_taxes_id = fields.Many2many(
"account.tax",
string="Customer Taxes",
help="Taxes used for deposits",
default=_default_deposit_taxes_id,
)
def _create_invoice(self, order, ro_line, amount):
inv_obj = self.env["account.move"]
account_id = False
if self.product_id.id:
account_id = self.product_id.property_account_income_id.id
if self.amount <= 0.00:
raise UserError(_("The value of the down payment amount must be positive."))
if self.advance_payment_method == "percentage":
amount = order.untaxed_amount * self.amount / 100
name = _("Down payment of %s%%") % (self.amount,)
else:
amount = self.amount
name = _("Down Payment")
taxes = self.product_id.taxes_id
tax_ids = taxes.ids
invoice = inv_obj.create(
{
"name": order.res_number,
"origin": order.res_number,
"partner_id": order.customer_name.id,
"type": "out_invoice",
"reference": False,
"account_id": order.customer_name.property_account_receivable_id.id,
"invoice_line_ids": [
(
0,
0,
{
"name": name,
"account_id": account_id,
"price_unit": amount,
"product_id": self.product_id.id,
"invoice_line_tax_ids": [(6, 0, tax_ids)],
},
)
],
"currency_id": order.pricelist_id.currency_id.id,
"user_id": order.user_id.id,
}
)
invoice.compute_taxes()
invoice.message_post_with_view(
"mail.message_origin_link",
values={"self": invoice, "origin": order},
subtype_id=self.env.ref("mail.mt_note").id,
)
return invoice
def create_invoices(self):
rental_orders = self.env["rental.product.order"].browse(
self._context.get("active_ids", [])
)
if self.advance_payment_method == "delivered":
rental_orders.action_invoice_create()
elif self.advance_payment_method == "all":
rental_orders.action_invoice_create(final=True)
else:
# Create deposit product if necessary
if not self.product_id:
vals = self._prepare_deposit_product()
self.product_id = self.env["product.product"].create(vals)
self.env["ir.config_parameter"].sudo().set_param(
"sale.default_deposit_product_id", self.product_id.id
)
rental_line_obj = self.env["product.order.line"]
for order in rental_orders:
if self.advance_payment_method == "percentage":
amount = order.untaxed_amount * self.amount / 100
else:
amount = self.amount
taxes = self.product_id.taxes_id
tax_ids = taxes.ids
ro_line = rental_line_obj.create(
{
"name": _("Advance: %s") % (time.strftime("%m %Y"),),
"price": amount,
"product_order_id": order.id,
"tax_id": [(6, 0, tax_ids)],
}
)
self._create_invoice(order, ro_line, amount)
if self._context.get("open_invoices", False):
return rental_orders.action_view_invoice()
return {"type": "ir.actions.act_window_close"}
def _prepare_deposit_product(self):
return {
"name": "Down payment",
"type": "service",
"invoice_policy": "order",
"property_account_income_id": self.deposit_account_id.id,
"taxes_id": [(6, 0, self.deposit_taxes_id.ids)],
}

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_product_advance_payment_invoice" model="ir.ui.view">
<field name="name">Invoice Rental Orders</field>
<field name="model">product.advance.payment.invoice</field>
<field name="arch" type="xml">
<form string="Invoice Rentals Order">
<p class="oe_grey">
Invoices will be created in draft so that you can review
them before validation.
</p>
<group>
<field name="count" readonly="True"/>
<field name="advance_payment_method" class="oe_inline" widget="radio"
/>
<field name="product_id"
context="{'search_default_services': 1, 'default_type': 'service', 'default_invoice_policy': 'order'}"
class="oe_inline"
invisible="1"/>
<label for="amount"
attrs="{'invisible': [('advance_payment_method', 'not in', ('fixed','percentage'))]}"/>
<div attrs="{'invisible': [('advance_payment_method', 'not in', ('fixed','percentage'))]}">
<field name="amount"
attrs="{'required': [('advance_payment_method', 'in', ('fixed','percentage'))]}"
class="oe_inline" widget="monetary"/>
</div>
<field name="deposit_account_id" class="oe_inline"
attrs="{'invisible': ['|', ('advance_payment_method', 'not in', ('fixed', 'percentage')), ('product_id', '!=', False)]}"
groups="account.group_account_manager"/>
<field name="deposit_taxes_id" class="oe_inline" widget="many2many_tags"
domain="[('type_tax_use','=','sale')]"
attrs="{'invisible': ['|', ('advance_payment_method', 'not in', ('fixed', 'percentage')), ('product_id', '!=', False)]}"/>
</group>
<footer>
<button name="create_invoices" string="Create and View Invoices" type="object"
context="{'open_invoices': True}" class="btn-primary"/>
<button name="create_invoices" string="Create Invoices" type="object"
class="btn-primary"/>
<button string="Cancel" class="btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_view_product_advance_payment_invoice" model="ir.actions.act_window">
<field name="name">Invoice Rental Orders</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">product.advance.payment.invoice</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</odoo>