@ -0,0 +1,48 @@ |
|||||
|
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg |
||||
|
:target: https://www.gnu.org/licenses/agpl-3.0-standalone.html |
||||
|
:alt: License: AGPL-3 |
||||
|
|
||||
|
Tender Management Sales |
||||
|
======================= |
||||
|
This module allows to manage different tenders in odoo along with option for comparing different sale order lines and |
||||
|
choose best one. |
||||
|
|
||||
|
License |
||||
|
------- |
||||
|
General Public License, Version 3 (AGPL v3). |
||||
|
(https://www.odoo.com/documentation/user/16.0/legal/licenses/licenses.html) |
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
No additional configuration required |
||||
|
|
||||
|
Company |
||||
|
------- |
||||
|
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__ |
||||
|
|
||||
|
Credits |
||||
|
------- |
||||
|
Developers: Nikhil M @cybrosys, |
||||
|
|
||||
|
Contacts |
||||
|
-------- |
||||
|
* Mail Contact : odoo@cybrosys.com |
||||
|
* Website : https://cybrosys.com |
||||
|
|
||||
|
Bug Tracker |
||||
|
----------- |
||||
|
Bugs are tracked on GitHub Issues. In case of trouble, please check there if |
||||
|
your issue has already been reported. |
||||
|
|
||||
|
Maintainer |
||||
|
========== |
||||
|
.. image:: https://cybrosys.com/images/logo.png |
||||
|
:target: https://cybrosys.com |
||||
|
|
||||
|
This module is maintained by Cybrosys Technologies. |
||||
|
|
||||
|
For support and more information, please visit `Our Website <https://cybrosys.com/>`__ |
||||
|
|
||||
|
Further information |
||||
|
=================== |
||||
|
HTML Description: `<static/description/index.html>`__ |
@ -0,0 +1,23 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Nikhil M (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
|
# (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from . import models |
||||
|
from . import wizard |
@ -0,0 +1,54 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Nikhil M (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
|
# (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
{ |
||||
|
'name': 'Tender Management Sales', |
||||
|
'version': "16.0.1.0.0", |
||||
|
'summary': 'Tender Management in Sales and Option to Compare Orders', |
||||
|
'description': """Tender Management in Sales and Option to Compare Orders""", |
||||
|
'category': 'Sale', |
||||
|
'author': 'Cybrosys Techno solutions', |
||||
|
'company': 'Cybrosys Techno Solutions', |
||||
|
'maintainer': 'Cybrosys Techno Solutions', |
||||
|
'website': 'https://www.cybrosys.com', |
||||
|
'depends': ['sale_management'], |
||||
|
'data': [ |
||||
|
'security/ir.model.access.csv', |
||||
|
'data/sale_tender_data.xml', |
||||
|
'views/tender_sales_views.xml', |
||||
|
'views/sale_order_views.xml', |
||||
|
'wizard/tender_sales_create_alternative.xml', |
||||
|
], |
||||
|
'assets': { |
||||
|
'web.assets_backend': [ |
||||
|
'tender_management_sales/static/src/widgets/sale_order_alternatives_widget.js', |
||||
|
'tender_management_sales/static/src/widgets/sale_order_alternatives_widget.scss', |
||||
|
'tender_management_sales/static/src/widgets/sale_order_alternatives_widget.xml', |
||||
|
'tender_management_sales/static/src/views/list/sale_order_line_compare_list_renderer.js', |
||||
|
'tender_management_sales/static/src/views/list/sale_order_line_compare_list_view.js', |
||||
|
], |
||||
|
}, |
||||
|
'license': 'AGPL-3', |
||||
|
'images': ['static/description/banner.png'], |
||||
|
'installable': True, |
||||
|
'auto_install': False, |
||||
|
'application': False, |
||||
|
} |
@ -0,0 +1,13 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
<!-- Agreement sample data --> |
||||
|
<data noupdate="1"> |
||||
|
<record id="seq_sale_tender" model="ir.sequence"> |
||||
|
<field name="name">Sale Tender</field> |
||||
|
<field name="code">sale.tender.order</field> |
||||
|
<field name="prefix">ST</field> |
||||
|
<field name="padding">5</field> |
||||
|
<field name="company_id" eval="False"/> |
||||
|
</record> |
||||
|
</data> |
||||
|
</odoo> |
@ -0,0 +1,6 @@ |
|||||
|
## Module <tender_management_sales> |
||||
|
|
||||
|
#### 02.09.2024 |
||||
|
#### Version 16.0.1.0.0 |
||||
|
#### ADD |
||||
|
- Initial Commit for Tender Management Sales |
@ -0,0 +1,23 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Nikhil M (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
|
# (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from . import sale_order |
||||
|
from . import tender_sales |
@ -0,0 +1,296 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Nikhil M (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
|
# (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from odoo import api, fields, models, _, Command |
||||
|
from collections import defaultdict |
||||
|
|
||||
|
|
||||
|
class SaleOrderGroup(models.Model): |
||||
|
"""Creating a model to group all the orders for tender management.""" |
||||
|
_name = 'sale.order.group' |
||||
|
_description = "Technical model to group Orders for tender management." |
||||
|
|
||||
|
order_ids = fields.One2many('sale.order', 'sale_group_id',help='Sale orders') |
||||
|
|
||||
|
def write(self, vals): |
||||
|
"""Remove records where the length of the order_ids is less than |
||||
|
or equal to 1""" |
||||
|
res = super().write(vals) |
||||
|
self.filtered(lambda g: len(g.order_ids) <= 1).unlink() |
||||
|
return res |
||||
|
|
||||
|
|
||||
|
class SaleOrder(models.Model): |
||||
|
_inherit = 'sale.order' |
||||
|
|
||||
|
tender_id = fields.Many2one('sale.tender', |
||||
|
string='Sale Agreement', |
||||
|
copy=False, help='Tender being used in order') |
||||
|
sale_group_id = fields.Many2one('sale.order.group', |
||||
|
help='Sale Group') |
||||
|
alternative_so_ids = fields.One2many( |
||||
|
'sale.order', related='sale_group_id.order_ids', readonly=False, |
||||
|
domain="[('id', '!=', id), ('state', 'in', ['draft', 'sent'])]", |
||||
|
string="Alternative SOs", check_company=True, |
||||
|
help="Other potential sale orders for selling products") |
||||
|
|
||||
|
@api.onchange('sale_order_template_id') |
||||
|
def _onchange_sale_order_template_id(self): |
||||
|
""" |
||||
|
This method is triggered when the 'sale_order_template_id' field is changed. |
||||
|
It overrides the default behavior to check for the presence of a 'tender_id' field: |
||||
|
""" |
||||
|
# Call the parent class method using super() |
||||
|
if self.tender_id: |
||||
|
if self.sale_order_template_id: |
||||
|
super(SaleOrder, self)._onchange_sale_order_template_id() |
||||
|
else: |
||||
|
super(SaleOrder, self)._onchange_sale_order_template_id() |
||||
|
|
||||
|
def write(self, vals): |
||||
|
"""Overriding the write function to add the cases in case of tender |
||||
|
is added.""" |
||||
|
if vals.get('sale_group_id', False): |
||||
|
orig_sale_group = self.sale_group_id |
||||
|
result = super(SaleOrder,self).write(vals) |
||||
|
if vals.get('alternative_so_ids', False): |
||||
|
if not self.sale_group_id and len(self.alternative_so_ids + self) > len(self): |
||||
|
# create/merge a new group |
||||
|
self.env['sale.order.group'].create({'order_ids': [Command.set(self.ids + self.alternative_so_ids.ids)]}) |
||||
|
elif self.sale_group_id and len(self.alternative_so_ids + self) <= 1: |
||||
|
# write in purchase group isn't called so we have to manually |
||||
|
# unlink obsolete groups here |
||||
|
self.sale_group_id.unlink() |
||||
|
if vals.get('sale_group_id', False): |
||||
|
additional_groups = orig_sale_group - self.sale_group_id |
||||
|
if additional_groups: |
||||
|
additional_sos = (additional_groups.order_ids - self.saled_group_id.order_ids) |
||||
|
additional_groups.unlink() |
||||
|
if additional_sos: |
||||
|
self.sale_group_id.order_ids |= additional_sos |
||||
|
|
||||
|
return result |
||||
|
|
||||
|
@api.model_create_multi |
||||
|
def create(self, vals_list): |
||||
|
"""Overriding the Create function to update vals in case of tender id.""" |
||||
|
orders = super().create(vals_list) |
||||
|
if self.env.context.get('origin_so_id'): |
||||
|
origin_so_id = self.env['sale.order'].browse( |
||||
|
self.env.context.get('origin_so_id')) |
||||
|
if origin_so_id.sale_group_id: |
||||
|
origin_so_id.sale_group_id.order_ids |= orders |
||||
|
else: |
||||
|
self.env['sale.order.group'].create({'order_ids': [Command.set(origin_so_id.ids + orders.ids)]}) |
||||
|
return orders |
||||
|
|
||||
|
def action_confirm(self): |
||||
|
"""Updated the state of other related orders and the agreement state on confirming the order.""" |
||||
|
res = super(SaleOrder, self).action_confirm() |
||||
|
for so in self: |
||||
|
if not so.tender_id: |
||||
|
continue |
||||
|
if so.tender_id.type_id.exclusive == 'exclusive': |
||||
|
others_so = so.tender_id.mapped('sale_order_ids').filtered(lambda r: r.id != so.id) |
||||
|
others_so.action_cancel() |
||||
|
if so.state not in ['draft', 'sent']: |
||||
|
so.tender_id.action_done() |
||||
|
return res |
||||
|
|
||||
|
@api.onchange('tender_id') |
||||
|
def _onchange_tender_id(self): |
||||
|
"""Function to update all the values from the tender from agreement to the sale order.""" |
||||
|
if not self.tender_id: |
||||
|
return |
||||
|
if self.sale_order_template_id: |
||||
|
self.sale_order_template_id = False |
||||
|
data = [fields.Command.clear()] |
||||
|
self.sale_order_option_ids = data |
||||
|
self.order_line = data |
||||
|
|
||||
|
self = self.with_company(self.company_id) |
||||
|
tender = self.tender_id |
||||
|
if self.partner_id: |
||||
|
partner = self.partner_id |
||||
|
else: |
||||
|
partner = tender.customer_id |
||||
|
|
||||
|
FiscalPosition = self.env['account.fiscal.position'] |
||||
|
fpos = FiscalPosition.with_company( |
||||
|
self.company_id)._get_fiscal_position(partner) |
||||
|
self.partner_id = partner.id |
||||
|
self.company_id = tender.company_id.id |
||||
|
self.currency_id = tender.currency_id.id |
||||
|
if not self.origin or tender.name not in self.origin.split(', '): |
||||
|
if self.origin: |
||||
|
if tender.name: |
||||
|
self.origin = self.origin + ', ' + tender.name |
||||
|
else: |
||||
|
self.origin = tender.name |
||||
|
self.note = tender.description |
||||
|
self.date_order = fields.Datetime.now() |
||||
|
|
||||
|
if tender.type_id.line_copy != 'copy': |
||||
|
return |
||||
|
|
||||
|
# Create SO lines if necessary |
||||
|
order_lines = [] |
||||
|
for line in tender.line_ids: |
||||
|
# Compute name |
||||
|
product_lang = line.product_id.with_context( |
||||
|
lang=partner.lang or self.env.user.lang, |
||||
|
partner_id=partner.id |
||||
|
) |
||||
|
name = product_lang.display_name |
||||
|
if product_lang.description_purchase: |
||||
|
name += '\n' + product_lang.description_purchase |
||||
|
|
||||
|
# Compute taxes |
||||
|
taxes_ids = fpos.map_tax(line.product_id.supplier_taxes_id.filtered(lambda tax: tax.company_id == tender.company_id)).ids |
||||
|
# Compute quantity and price_unit |
||||
|
if line.product_uom_id != line.product_id.uom_id: |
||||
|
product_qty = line.product_uom_id._compute_quantity(line.product_qty, line.product_id.uom_id) |
||||
|
price_unit = line.product_uom_id._compute_price(line.price_unit, line.product_id.uom_id) |
||||
|
else: |
||||
|
product_qty = line.product_qty |
||||
|
price_unit = line.price_unit |
||||
|
|
||||
|
if tender.type_id.quantity_copy != 'copy': |
||||
|
product_qty = 0 |
||||
|
|
||||
|
# Create SO line |
||||
|
order_line_values = line._prepare_sale_order_line( |
||||
|
name=name, product_qty=product_qty, price_unit=price_unit, |
||||
|
taxes_ids=taxes_ids) |
||||
|
order_lines.append((0, 0, order_line_values)) |
||||
|
self.order_line = order_lines |
||||
|
|
||||
|
def action_compare_alternative_lines(self): |
||||
|
"""" Function to return to the view of line compare tree.""" |
||||
|
ctx = dict( |
||||
|
self.env.context, |
||||
|
search_default_product=True, |
||||
|
sale_order_id=self.id, |
||||
|
) |
||||
|
view_id = self.env.ref('tender_management_sales.sale_order_line_compare_tree').id |
||||
|
return { |
||||
|
'name': _('Compare Order Lines'), |
||||
|
'type': 'ir.actions.act_window', |
||||
|
'view_mode': 'list', |
||||
|
'res_model': 'sale.order.line', |
||||
|
'views': [(view_id, "list")], |
||||
|
'domain': [('order_id', 'in', (self | self.alternative_so_ids).ids), ('display_type', '=', False)], |
||||
|
'context': ctx, |
||||
|
} |
||||
|
|
||||
|
def action_create_alternative(self): |
||||
|
"""Function to create alternative orders.""" |
||||
|
ctx = dict(**self.env.context, default_origin_so_id=self.id) |
||||
|
return { |
||||
|
'name': _('Create alternative'), |
||||
|
'type': 'ir.actions.act_window', |
||||
|
'view_mode': 'form', |
||||
|
'res_model': 'sale.tender.create.alternative', |
||||
|
'view_id': self.env.ref('tender_management_sales.sale_tender_create_alternative_form').id, |
||||
|
'target': 'new', |
||||
|
'context': ctx, |
||||
|
} |
||||
|
|
||||
|
def get_tender_best_lines(self): |
||||
|
"""" Function to get the best order lines from grouped orders.""" |
||||
|
product_to_best_price_line = defaultdict(lambda: self.env['sale.order.line']) |
||||
|
product_to_best_price_unit = defaultdict(lambda: self.env['sale.order.line']) |
||||
|
so_alternatives = self | self.alternative_so_ids |
||||
|
|
||||
|
multiple_currencies = False |
||||
|
if len(so_alternatives.currency_id) > 1: |
||||
|
multiple_currencies = True |
||||
|
|
||||
|
for line in so_alternatives.order_line: |
||||
|
if not line.product_uom_qty or not line.price_subtotal or line.state in ['cancel', 'purchase', 'done']: |
||||
|
continue |
||||
|
|
||||
|
if not product_to_best_price_line[line.product_id]: |
||||
|
product_to_best_price_line[line.product_id] = line |
||||
|
product_to_best_price_unit[line.product_id] = line |
||||
|
else: |
||||
|
price_subtotal = line.price_subtotal |
||||
|
price_unit = line.price_unit |
||||
|
current_price_subtotal = product_to_best_price_line[line.product_id][0].price_subtotal |
||||
|
current_price_unit = product_to_best_price_unit[line.product_id][0].price_unit |
||||
|
if multiple_currencies: |
||||
|
price_subtotal /= line.order_id.currency_rate |
||||
|
price_unit /= line.order_id.currency_rate |
||||
|
current_price_subtotal /= product_to_best_price_line[line.product_id][0].order_id.currency_rate |
||||
|
current_price_unit /= product_to_best_price_unit[line.product_id][0].order_id.currency_rate |
||||
|
if current_price_subtotal < price_subtotal: |
||||
|
product_to_best_price_line[line.product_id] = line |
||||
|
elif current_price_subtotal == price_subtotal: |
||||
|
product_to_best_price_line[line.product_id] |= line |
||||
|
|
||||
|
if current_price_unit < price_unit: |
||||
|
product_to_best_price_unit[line.product_id] = line |
||||
|
elif current_price_unit == price_unit: |
||||
|
product_to_best_price_unit[line.product_id] |= line |
||||
|
|
||||
|
best_price_ids = set() |
||||
|
best_price_unit_ids = set() |
||||
|
for lines in product_to_best_price_line.values(): |
||||
|
best_price_ids.update(lines.ids) |
||||
|
for lines in product_to_best_price_unit.values(): |
||||
|
best_price_unit_ids.update(lines.ids) |
||||
|
return list(best_price_ids), list(best_price_unit_ids) |
||||
|
|
||||
|
|
||||
|
class SaleOrderLine(models.Model): |
||||
|
_inherit = 'sale.order.line' |
||||
|
|
||||
|
def action_clear_quantities(self): |
||||
|
"""" Function to clear quantities from the current line.""" |
||||
|
zeroed_lines = self.filtered(lambda l: l.state not in ['cancel', 'sale', 'done']) |
||||
|
zeroed_lines.write({'product_uom_qty': 0}) |
||||
|
if len(self) > len(zeroed_lines): |
||||
|
return { |
||||
|
'type': 'ir.actions.client', |
||||
|
'tag': 'display_notification', |
||||
|
'params': { |
||||
|
'title': _("Some not cleared"), |
||||
|
'message': _("Some quantities were not cleared because their status is not a Draft."), |
||||
|
'sticky': False, |
||||
|
} |
||||
|
} |
||||
|
return False |
||||
|
|
||||
|
def action_choose(self): |
||||
|
"""" Function to choose a line among different order lines so that other lines quantities will update to null.""" |
||||
|
order_lines = (self.order_id | self.order_id.alternative_so_ids).mapped('order_line') |
||||
|
order_lines = order_lines.filtered(lambda l: l.product_uom_qty and l.product_id.id in self.product_id.ids and l.id not in self.ids) |
||||
|
if order_lines: |
||||
|
return order_lines.action_clear_quantities() |
||||
|
return { |
||||
|
'type': 'ir.actions.client', |
||||
|
'tag': 'display_notification', |
||||
|
'params': { |
||||
|
'title': _("Nothing to clear"), |
||||
|
'message': _("There are no quantities to clear."), |
||||
|
'sticky': False, |
||||
|
} |
||||
|
} |
@ -0,0 +1,182 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Nikhil M (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
|
# (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from odoo import api, fields, models, _ |
||||
|
from odoo.exceptions import UserError |
||||
|
|
||||
|
SALE_TENDER_STATES = [ |
||||
|
('draft', 'Draft'), |
||||
|
('ongoing', 'Ongoing'), |
||||
|
('in_progress', 'Confirmed'), |
||||
|
('open', 'Bid Selection'), |
||||
|
('done', 'Closed'), |
||||
|
('cancel', 'Cancelled') |
||||
|
] |
||||
|
|
||||
|
|
||||
|
class SaleTenderType(models.Model): |
||||
|
"""Creating a model to store the tender type.""" |
||||
|
|
||||
|
_name = "sale.tender.type" |
||||
|
_description = "Sale Tender Type" |
||||
|
|
||||
|
name = fields.Char(string='Agreement Type', required=True, translate=True,help="Name") |
||||
|
exclusive = fields.Selection([ |
||||
|
('exclusive', 'Select only one Quotation (exclusive)'), ('multiple', 'Select multiple Quotation (non-exclusive)')], |
||||
|
string='Agreement Selection Type', required=True, default='multiple', |
||||
|
help="""Select only one Quotation (exclusive): when a sale order is confirmed, cancel the remaining sale order.\n |
||||
|
Select multiple Quotation (non-exclusive): allows multiple sale orders. On confirmation of a sale order it does not cancel the remaining orders""") |
||||
|
quantity_copy = fields.Selection([ |
||||
|
('copy', 'Use quantities of agreement'), ('none', 'Set quantities manually')], |
||||
|
string='Quantities', required=True, default='none',help="To copy quantity") |
||||
|
line_copy = fields.Selection([ |
||||
|
('copy', 'Use lines of agreement'), ('none', 'Do not create Quotations lines automatically')], |
||||
|
string='Lines', required=True, default='copy',help="To copy line") |
||||
|
|
||||
|
|
||||
|
class SaleTender(models.Model): |
||||
|
"""Creating a model to record all the sale tender agreements.""" |
||||
|
_name = "sale.tender" |
||||
|
_description = "Sale Tender" |
||||
|
_order = 'id desc' |
||||
|
_inherit = ['mail.thread', 'mail.activity.mixin'] |
||||
|
|
||||
|
@api.depends('sale_order_ids') |
||||
|
def _compute_orders_number(self): |
||||
|
"""Function to compute the number of associated orders for the agreement.""" |
||||
|
for tender in self: |
||||
|
tender.order_count = len(tender.sale_order_ids) |
||||
|
|
||||
|
def action_in_progress(self): |
||||
|
"""Function to activate the current agreement.""" |
||||
|
self.ensure_one() |
||||
|
if not self.line_ids: |
||||
|
raise UserError(_("You cannot confirm agreement '%s' because there is no product line.", self.name)) |
||||
|
for tender_line in self.line_ids: |
||||
|
if tender_line.price_unit <= 0.0: |
||||
|
raise UserError(_('You cannot confirm the Tender without price.')) |
||||
|
if tender_line.product_qty <= 0.0: |
||||
|
raise UserError(_('You cannot confirm the Tender order without quantity.')) |
||||
|
self.write({'state': 'ongoing'}) |
||||
|
# Set the sequence number regarding the tender type |
||||
|
if self.name == 'New': |
||||
|
self.name = self.env['ir.sequence'].next_by_code('sale.tender.order') |
||||
|
|
||||
|
def action_cancel(self): |
||||
|
"""Function to cancel the current agreement.""" |
||||
|
self.write({'state': 'cancel'}) |
||||
|
|
||||
|
def action_done(self): |
||||
|
""" |
||||
|
Generate all sale order based on selected lines, should only be called on one agreement at a time |
||||
|
""" |
||||
|
if any(sale_order.state in ['draft', 'sent'] for sale_order in self.mapped('sale_order_ids')): |
||||
|
raise UserError(_('You have to cancel or validate every Order before closing the sale tender.')) |
||||
|
self.write({'state': 'done'}) |
||||
|
|
||||
|
name = fields.Char('Reference', required=True, copy=False, default='New',readonly=True,help="Name") |
||||
|
|
||||
|
order_count = fields.Integer(compute='_compute_orders_number', |
||||
|
string='Number of Orders',help="Count of orders") |
||||
|
customer_id = fields.Many2one('res.partner', string="Customer", |
||||
|
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]",help="Customer Choosen") |
||||
|
type_id = fields.Many2one('sale.tender.type', |
||||
|
string="Agreement Type", required=True,help="Type") |
||||
|
ordering_date = fields.Date(string="Ordering Date", tracking=True,help="Order Date") |
||||
|
date_end = fields.Datetime(string='Agreement Deadline', tracking=True,help="End Date") |
||||
|
schedule_date = fields.Date(string='Delivery Date', index=True, |
||||
|
help="The expected and scheduled delivery date where all the products are received", |
||||
|
tracking=True) |
||||
|
user_id = fields.Many2one( |
||||
|
'res.users', string='Sales Representative', |
||||
|
default=lambda self: self.env.user, check_company=True,help="Username") |
||||
|
description = fields.Html(help="Description") |
||||
|
company_id = fields.Many2one('res.company', string='Company', required=True, |
||||
|
default=lambda self: self.env.company,help="Company") |
||||
|
sale_order_ids = fields.One2many('sale.order', 'tender_id', |
||||
|
string='Sale Orders', |
||||
|
states={'done': [('readonly', True)]},help="Sale orders") |
||||
|
line_ids = fields.One2many('sale.tender.line', 'tender_id', |
||||
|
string='Products to Sell', |
||||
|
states={'done': [('readonly', True)]}, copy=True,help="Tender Lines") |
||||
|
|
||||
|
state = fields.Selection(SALE_TENDER_STATES, |
||||
|
'Status', tracking=True, required=True, |
||||
|
copy=False, default='draft',help="Status") |
||||
|
|
||||
|
currency_id = fields.Many2one('res.currency', 'Currency', required=True, |
||||
|
default=lambda |
||||
|
self: self.env.company.currency_id.id,help="Currency") |
||||
|
|
||||
|
|
||||
|
class SaleTender_idLine(models.Model): |
||||
|
"""Creating the model to store the lines in the tender.""" |
||||
|
_name = "sale.tender.line" |
||||
|
_inherit = 'analytic.mixin' |
||||
|
_description = "Sale Tender Line" |
||||
|
_rec_name = 'product_id' |
||||
|
|
||||
|
@api.onchange('product_id') |
||||
|
def _onchange_product_id(self): |
||||
|
"""Function to update the line price.""" |
||||
|
self.price_unit = self.product_id.list_price |
||||
|
self.product_qty = 1 |
||||
|
|
||||
|
@api.model_create_multi |
||||
|
def create(self, vals_list): |
||||
|
"""Overriding the create function to update the uom value and also add restrictions.""" |
||||
|
for vals in vals_list: |
||||
|
if not vals.get('product_uom_id'): |
||||
|
vals['product_uom_id'] = self.env["product.product"].browse( |
||||
|
vals.get('product_id')).uom_id.id |
||||
|
lines = super().create(vals_list) |
||||
|
for line, vals in zip(lines, vals_list): |
||||
|
if line.tender_id.state not in ['draft', 'cancel','done'] and line.tender_id.type_id.is_quantity_copy == 'none': |
||||
|
if vals['price_unit'] <= 0.0: |
||||
|
raise UserError( |
||||
|
_('You cannot confirm the blanket order without price.')) |
||||
|
return lines |
||||
|
|
||||
|
product_id = fields.Many2one('product.product', string='Product', domain=[('sale_ok', '=', True)], required=True,help="Product") |
||||
|
product_uom_id = fields.Many2one('uom.uom', string='Product Unit of Measure', domain="[('category_id', '=', product_uom_category_id)]",help="Uom") |
||||
|
product_uom_category_id = fields.Many2one(related='product_id.uom_id.category_id',help="Uom Category") |
||||
|
product_qty = fields.Float(string='Quantity', digits='Product Unit of Measure',help="Quantity") |
||||
|
product_description_variants = fields.Char('Custom Description',help="Variants Description") |
||||
|
price_unit = fields.Float(string='Unit Price', digits='Product Price',help="Price Unit") |
||||
|
tender_id = fields.Many2one('sale.tender', required=True, string='Sale Agreement', ondelete='cascade',help="Tender") |
||||
|
company_id = fields.Many2one('res.company', related='tender_id.company_id', string='Company', store=True, readonly=True,help="Company") |
||||
|
schedule_date = fields.Date(string='Scheduled Date',help="Scheduled Date") |
||||
|
|
||||
|
def _prepare_sale_order_line(self, name, product_qty=0.0, price_unit=0.0, taxes_ids=False): |
||||
|
"""Function to prepare the required values for sale order lines.""" |
||||
|
self.ensure_one() |
||||
|
if self.product_description_variants: |
||||
|
name += '\n' + self.product_description_variants |
||||
|
res = { |
||||
|
'name': name, |
||||
|
'product_id': self.product_id.id, |
||||
|
'product_uom': self.product_uom_id.id, |
||||
|
'product_uom_qty': product_qty, |
||||
|
'price_unit': price_unit, |
||||
|
'tax_id': [(6, 0, taxes_ids)], |
||||
|
'analytic_distribution': self.analytic_distribution, |
||||
|
} |
||||
|
return res |
|
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 310 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 576 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 911 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 673 B |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 653 B |
After Width: | Height: | Size: 905 B |
After Width: | Height: | Size: 839 B |
After Width: | Height: | Size: 427 B |
After Width: | Height: | Size: 627 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 988 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 589 B |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 967 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 81 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 174 KiB |
After Width: | Height: | Size: 411 KiB |
After Width: | Height: | Size: 98 KiB |
After Width: | Height: | Size: 142 KiB |
After Width: | Height: | Size: 159 KiB |
After Width: | Height: | Size: 140 KiB |
After Width: | Height: | Size: 183 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 307 KiB |
After Width: | Height: | Size: 75 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 22 KiB |
@ -0,0 +1,683 @@ |
|||||
|
<div style="background-color: #714B67; height: 810px; width: 100%; padding: 15px; position: relative;"> |
||||
|
<!-- TITLE BAR --> |
||||
|
<div class="d-flex align-items-center justify-content-between" |
||||
|
style="border-bottom: 1px solid #875A7B; padding: 15px; display: flex; justify-content: space-between; align-items: center;"> |
||||
|
<img src="assets/misc/cybrosys-logo.png" width="42" height="42" |
||||
|
style="width: 42px; height: 42px;"/> |
||||
|
<div> |
||||
|
<div |
||||
|
style="color: #7C7BAD; font-size: 14px; font-family: 'Montserrat', sans-serif; font-weight: bold; background-color: white; display: inline-block; padding: 3px 10px; border-radius: 50px;" |
||||
|
class="mr-2"> |
||||
|
<i class="fa fa-check mr-1"></i>Community |
||||
|
</div> |
||||
|
<div |
||||
|
style="color: #875A7B; font-size: 14px; font-family: 'Montserrat', sans-serif; font-weight: bold; background-color: white; display: inline-block; padding: 3px 10px; border-radius: 50px;" |
||||
|
class="mr-2"> |
||||
|
<i class="fa fa-check mr-1"></i>Enterprise |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- END OF TITLE BAR --> |
||||
|
<div class="container"> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12 col-md-12 col-lg-12"> |
||||
|
<!-- APP HERO --> |
||||
|
<h1 style="color: #FFFFFF; font-weight: bolder; font-size: 50px; text-align: center; margin-top: 50px;"> |
||||
|
Tender Management Sales</h1> |
||||
|
<p style="color:#FFFFFF; padding: 8px 15px; text-align: center; font-size: 24px;"> |
||||
|
Tender Management Sales for managing agreements in sale orders </p> |
||||
|
<!-- END OF APP HERO --> |
||||
|
<img src="assets/screenshots/hero.gif" class="img-responsive" |
||||
|
style="width: 100%; margin-left: auto; margin-right: auto;"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<!-- NAVIGATION SECTION --> |
||||
|
<div class="d-flex align-items-center" |
||||
|
style="border-bottom: 2px solid #714B67; padding: 15px 0px; margin-top: 300px;"> |
||||
|
<div class="d-flex justify-content-center align-items-center mr-2" |
||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
||||
|
<img src="assets/misc/compass.png"/> |
||||
|
</div> |
||||
|
<h2 class="mt-2" |
||||
|
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> |
||||
|
Explore This |
||||
|
Module</h2> |
||||
|
</div> |
||||
|
<div class="row my-4" style="font-family: 'Montserrat', sans-serif;"> |
||||
|
<div class="col-sm-12 col-md-6 my-3"> |
||||
|
<a href="#overview"> |
||||
|
<div class="d-flex justify-content-between align-items-center" |
||||
|
style="background-color: #f5f5f5; padding: 30px; width: 100%;"> |
||||
|
<div> |
||||
|
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Overview</span> |
||||
|
<span style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">Learn |
||||
|
more about this |
||||
|
module</span> |
||||
|
</div> |
||||
|
<img src="assets/misc/right-arrow.png" width="36" height="36"/> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
<div class="col-sm-12 col-md-6 my-3"> |
||||
|
<a href="#features"> |
||||
|
<div class="d-flex justify-content-between align-items-center" |
||||
|
style="background-color: #f5f5f5; padding: 30px; width: 100%;"> |
||||
|
<div> |
||||
|
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Features</span> |
||||
|
<span style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">View |
||||
|
features of this |
||||
|
module</span> |
||||
|
</div> |
||||
|
<img src="assets/misc/right-arrow.png" width="36" height="36"/> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
<div class="col-sm-12 col-md-6 my-3"> |
||||
|
<a href="#screenshots"> |
||||
|
<div class="d-flex justify-content-between align-items-center" |
||||
|
style="background-color: #f5f5f5; padding: 30px; width: 100%;"> |
||||
|
<div> |
||||
|
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Screenshots</span> |
||||
|
<span style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">View |
||||
|
screenshots for this |
||||
|
module</span> |
||||
|
</div> |
||||
|
<img src="assets/misc/right-arrow.png" width="36" height="36"/> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- END OF NAVIGATION SECTION --> |
||||
|
|
||||
|
<!-- OVERVIEW SECTION --> |
||||
|
<div class="d-flex align-items-center" |
||||
|
style="border-bottom: 2px solid #714B67; padding: 15px 0px;" id="overview"> |
||||
|
<div class="d-flex justify-content-center align-items-center mr-2" |
||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
||||
|
<img src="assets/misc/pie-chart.png"/> |
||||
|
</div> |
||||
|
<h2 class="mt-2" |
||||
|
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> |
||||
|
Overview |
||||
|
</h2> |
||||
|
</div> |
||||
|
<div class="row" |
||||
|
style="font-family: 'Montserrat', sans-serif; font-weight: 400; font-size: 14px; line-height: 200%;"> |
||||
|
<div class="col-sm-12 py-4"> |
||||
|
This module allows to manage different tenders in odoo along with |
||||
|
option for comparing different sale order lines and choose best one. |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- END OF OVERVIEW SECTION --> |
||||
|
|
||||
|
<!-- FEATURES SECTION --> |
||||
|
<div class="d-flex align-items-center" |
||||
|
style="border-bottom: 2px solid #714B67; padding: 15px 0px;" id="features"> |
||||
|
<div class="d-flex justify-content-center align-items-center mr-2" |
||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
||||
|
<img src="assets/misc/features.png"/> |
||||
|
</div> |
||||
|
<h2 class="mt-2" |
||||
|
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> |
||||
|
Features |
||||
|
</h2> |
||||
|
</div> |
||||
|
<div class="row" |
||||
|
style="font-family: 'Montserrat', sans-serif; font-weight: 400; font-size: 14px; line-height: 200%;"> |
||||
|
<div class="col-sm-12 col-md-6"> |
||||
|
<div class="d-flex align-items-center" |
||||
|
style="margin-top: 30px; margin-bottom: 30px"> |
||||
|
<img src="assets/misc/check-box.png" class="mr-2"/> |
||||
|
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Add different agreements for sale order. |
||||
|
</span> |
||||
|
</div> |
||||
|
<div class="d-flex align-items-center" |
||||
|
style="margin-top: 30px; margin-bottom: 30px"> |
||||
|
<img src="assets/misc/check-box.png" class="mr-2"/> |
||||
|
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Compare different sale order lines. |
||||
|
</span> |
||||
|
</div> |
||||
|
<div class="d-flex align-items-center" |
||||
|
style="margin-top: 30px; margin-bottom: 30px"> |
||||
|
<img src="assets/misc/check-box.png" class="mr-2"/> |
||||
|
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Choose the best among the order lines.</span> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- END OF FEATURES SECTION --> |
||||
|
|
||||
|
<!-- SCREENSHOTS SECTION --> |
||||
|
<div class="d-flex align-items-center" |
||||
|
style="border-bottom: 2px solid #714B67; padding: 15px 0px;" |
||||
|
id="screenshots"> |
||||
|
<div class="d-flex justify-content-center align-items-center mr-2" |
||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
||||
|
<img src="assets/misc/pictures.png"/> |
||||
|
</div> |
||||
|
<h2 class="mt-2" |
||||
|
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> |
||||
|
Screenshots |
||||
|
</h2> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12"> |
||||
|
|
||||
|
<div style="display: block; margin: 30px auto;"> |
||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;"> |
||||
|
The menu to create sale agreements will be available from here. |
||||
|
</h3> |
||||
|
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;"> |
||||
|
The menu to create sale agreements will be available from here.</p> |
||||
|
<img src="assets/screenshots/2.png" class="img-thumbnail"> |
||||
|
</div> |
||||
|
|
||||
|
<div style="display: block; margin: 30px auto;"> |
||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;"> |
||||
|
Agreement Form. |
||||
|
</h3> |
||||
|
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;"> |
||||
|
In the agreements form we can create the agreement |
||||
|
types |
||||
|
and choose other options as we needed.</p> |
||||
|
<img src="assets/screenshots/3.png" class="img-thumbnail"> |
||||
|
</div> |
||||
|
|
||||
|
<div style="display: block; margin: 30px auto;"> |
||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;"> |
||||
|
Agreement Form |
||||
|
</h3> |
||||
|
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;"> |
||||
|
Once the agreement type is selected we can add lines and other details from here. |
||||
|
After that we can create different sale orders from the form and all the created |
||||
|
orders |
||||
|
can be accessed from the smart tab.</p> |
||||
|
<img src="assets/screenshots/4.png" class="img-thumbnail"> |
||||
|
</div> |
||||
|
|
||||
|
<div style="display: block; margin: 30px auto;"> |
||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;"> |
||||
|
Sale order. |
||||
|
</h3> |
||||
|
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;"> |
||||
|
For the sale orders created from the agreement form the agreement will be |
||||
|
automatically |
||||
|
updated in the sale order form.</p> |
||||
|
<img src="assets/screenshots/5.png" class="img-thumbnail"> |
||||
|
</div> |
||||
|
|
||||
|
<div style="display: block; margin: 30px auto;"> |
||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;"> |
||||
|
Create alternatives. |
||||
|
</h3> |
||||
|
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;"> |
||||
|
This module allows to create alternative sale orders from the alternatives tab in |
||||
|
the sale order tab. The option to use the copy of the current sale order will also |
||||
|
be available.</p> |
||||
|
<img src="assets/screenshots/6.png" class="img-thumbnail"> |
||||
|
</div> |
||||
|
|
||||
|
<div style="display: block; margin: 30px auto;"> |
||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;"> |
||||
|
Compare Lines |
||||
|
</h3> |
||||
|
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;"> |
||||
|
Once the alternative orders are created the option to compare the order |
||||
|
lines from the sale order will be available.</p> |
||||
|
<img src="assets/screenshots/7.png" class="img-thumbnail"> |
||||
|
</div> |
||||
|
<div style="display: block; margin: 30px auto;"> |
||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;"> |
||||
|
Compare Lines View |
||||
|
</h3> |
||||
|
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;"> |
||||
|
On clicking the compare lines the view to compare the lines will be available. |
||||
|
Here the lines with the best unit price and total will be highlighted and |
||||
|
can choose/ clear the lines.</p> |
||||
|
<img src="assets/screenshots/8.png" class="img-thumbnail"> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- END OF SCREENSHOTS SECTION --> |
||||
|
|
||||
|
<!-- RELATED PRODUCTS --> |
||||
|
<div class="d-flex align-items-center" |
||||
|
style="border-bottom: 2px solid #714B67; padding: 15px 0px;"> |
||||
|
<div class="d-flex justify-content-center align-items-center mr-2" |
||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
||||
|
<img src="assets/misc/categories.png"/> |
||||
|
</div> |
||||
|
<h2 class="mt-2" |
||||
|
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> |
||||
|
Related |
||||
|
Products |
||||
|
</h2> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12"> |
||||
|
<div id="demo1" class="row carousel slide" data-ride="carousel"> |
||||
|
<!-- The slideshow --> |
||||
|
<div class="carousel-inner" style="padding: 30px;"> |
||||
|
<div class="carousel-item" style="min-height: 198.656px;"> |
||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" |
||||
|
style="float:left"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/16.0/dynamic_accounts_report/" |
||||
|
target="_blank"> |
||||
|
<div style="border-radius:10px"> |
||||
|
<img class="img img-responsive center-block" |
||||
|
style="border-radius: 0px;" |
||||
|
src="assets/modules/1.png"> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" |
||||
|
style="float:left"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/15.0/custom_gantt_view/" |
||||
|
target="_blank"> |
||||
|
<div style="border-radius:10px"> |
||||
|
<img class="img img-responsive center-block" |
||||
|
style="border-radius: 0px;" |
||||
|
src="assets/modules/2.png"> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" |
||||
|
style="float:left"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/15.0/project_custom_gantt/" |
||||
|
target="_blank"> |
||||
|
<div style="border-radius:10px"> |
||||
|
<img class="img img-responsive center-block" |
||||
|
style="border-radius: 0px;" |
||||
|
src="assets/modules/3.png"> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="carousel-item active" |
||||
|
style="min-height: 198.656px;"> |
||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" |
||||
|
style="float:left"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/15.0/account_reports_xlsx/" |
||||
|
target="_blank"> |
||||
|
<div style="border-radius:10px"> |
||||
|
<img class="img img-responsive center-block" |
||||
|
style="border-radius: 0px;" |
||||
|
src="assets/modules/4.png"> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" |
||||
|
style="float:left"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/16.0/base_accounting_kit/" |
||||
|
target="_blank"> |
||||
|
<div style="border-radius:10px"> |
||||
|
<img class="img img-responsive center-block" |
||||
|
style="border-radius: 0px;" |
||||
|
src="assets/modules/5.gif"> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" |
||||
|
style="float:left"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/16.0/hr_payroll_community/" |
||||
|
target="_blank"> |
||||
|
<div style="border-radius:10px"> |
||||
|
<img class="img img-responsive center-block" |
||||
|
style="border-radius: 0px;" |
||||
|
src="assets/modules/6.png"> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- Left and right controls --> |
||||
|
<a class="carousel-control-prev" href="#demo1" data-slide="prev" |
||||
|
style="width:35px; color:#000"> <span |
||||
|
class="carousel-control-prev-icon"><i |
||||
|
class="fa fa-chevron-left" |
||||
|
style="font-size:24px"></i></span> |
||||
|
</a> <a class="carousel-control-next" href="#demo1" |
||||
|
data-slide="next" style="width:35px; color:#000"> |
||||
|
<span class="carousel-control-next-icon"><i |
||||
|
class="fa fa-chevron-right" |
||||
|
style="font-size:24px"></i></span> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- END OF RELATED PRODUCTS --> |
||||
|
|
||||
|
<!-- OUR SERVICES --> |
||||
|
|
||||
|
<div class="d-flex align-items-center" |
||||
|
style="border-bottom: 2px solid #714B67; padding: 15px 0px;"> |
||||
|
<div class="d-flex justify-content-center align-items-center mr-2" |
||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
||||
|
<img src="assets/misc/star.png"/> |
||||
|
</div> |
||||
|
<h2 class="mt-2" |
||||
|
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> |
||||
|
Our Services |
||||
|
</h2> |
||||
|
</div> |
||||
|
|
||||
|
<div class="container my-5"> |
||||
|
<div class="row"> |
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #1dd1a1 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/cogs.png" class="img-responsive" |
||||
|
height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" |
||||
|
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Customization</h6> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #ff6b6b !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/wrench.png" class="img-responsive" |
||||
|
height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" |
||||
|
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Implementation</h6> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #6462CD !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/lifebuoy.png" class="img-responsive" |
||||
|
height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" |
||||
|
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Support</h6> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #ffa801 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/user.png" class="img-responsive" |
||||
|
height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" |
||||
|
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Hire |
||||
|
Odoo |
||||
|
Developer</h6> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #54a0ff !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/puzzle.png" class="img-responsive" |
||||
|
height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" |
||||
|
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Integration</h6> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #6d7680 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/update.png" class="img-responsive" |
||||
|
height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" |
||||
|
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Migration</h6> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #786fa6 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/consultation.png" class="img-responsive" |
||||
|
height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" |
||||
|
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Consultancy</h6> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #f8a5c2 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/training.png" class="img-responsive" |
||||
|
height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" |
||||
|
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Implementation</h6> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
||||
|
style="background-color: #e6be26 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
||||
|
<img src="assets/icons/license.png" class="img-responsive" |
||||
|
height="48px" width="48px"> |
||||
|
</div> |
||||
|
<h6 class="text-center" |
||||
|
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
||||
|
Odoo |
||||
|
Licensing Consultancy</h6> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<!-- END OF END OF OUR SERVICES --> |
||||
|
|
||||
|
<!-- OUR INDUSTRIES --> |
||||
|
|
||||
|
<div class="d-flex align-items-center" |
||||
|
style="border-bottom: 2px solid #714B67; padding: 15px 0px;"> |
||||
|
<div class="d-flex justify-content-center align-items-center mr-2" |
||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
||||
|
<img src="assets/misc/corporate.png"/> |
||||
|
</div> |
||||
|
<h2 class="mt-2" |
||||
|
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> |
||||
|
Our |
||||
|
Industries |
||||
|
</h2> |
||||
|
</div> |
||||
|
|
||||
|
<div class="container my-5"> |
||||
|
<div class="row"> |
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="assets/icons/trading-black.png" |
||||
|
class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
Trading |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
Easily procure |
||||
|
and |
||||
|
sell your products</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="assets/icons/pos-black.png" |
||||
|
class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
POS |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
Easy |
||||
|
configuration |
||||
|
and convivial experience</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="assets/icons/education-black.png" |
||||
|
class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
Education |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
A platform for |
||||
|
educational management</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="assets/icons/manufacturing-black.png" |
||||
|
class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
Manufacturing |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
Plan, track and |
||||
|
schedule your operations</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="assets/icons/ecom-black.png" |
||||
|
class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
E-commerce & Website |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
Mobile |
||||
|
friendly, |
||||
|
awe-inspiring product pages</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="assets/icons/service-black.png" |
||||
|
class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
Service Management |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
Keep track of |
||||
|
services and invoice</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="assets/icons/restaurant-black.png" |
||||
|
class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
Restaurant |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
Run your bar or |
||||
|
restaurant methodically</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-3"> |
||||
|
<div class="my-4 d-flex flex-column justify-content-center" |
||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
||||
|
<img src="assets/icons/hotel-black.png" |
||||
|
class="img-responsive mb-3" height="48px" width="48px"> |
||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
||||
|
Hotel Management |
||||
|
</h5> |
||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
||||
|
An |
||||
|
all-inclusive |
||||
|
hotel management application</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- END OF END OF OUR INDUSTRIES --> |
||||
|
|
||||
|
<!-- SUPPORT --> |
||||
|
<div class="d-flex align-items-center" |
||||
|
style="border-bottom: 2px solid #714B67; padding: 15px 0px;"> |
||||
|
<div class="d-flex justify-content-center align-items-center mr-2" |
||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
||||
|
<img src="assets/misc/customer-support.png"/> |
||||
|
</div> |
||||
|
<h2 class="mt-2" |
||||
|
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> |
||||
|
Support |
||||
|
</h2> |
||||
|
</div> |
||||
|
<div class="container mt-5"> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12 col-md-6"> |
||||
|
<div style="background-color: #F6F8F9; padding: 30px; display: flex; align-items: center;"> |
||||
|
<div class="mr-4 d-flex justify-content-center align-items-center" |
||||
|
style="background-color: #714B67; display: inline-block; height: 70px; width: 70px; display: flex; align-items: center; justify-content: center;"> |
||||
|
<img src="assets/misc/support.png" height="48" width="48" |
||||
|
style="width: 42px; height: 42px;"/> |
||||
|
</div> |
||||
|
<div> |
||||
|
<h4>Need Help?</h4> |
||||
|
<p style="line-height: 100%;">Got questions or need help? |
||||
|
Get in touch.</p> |
||||
|
<a href="mailto:odoo@cybrosys.com"> |
||||
|
<p style="font-weight: 400; font-size: 28px; line-height: 80%; color: #714B67;"> |
||||
|
odoo@cybrosys.com</p> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-sm-12 col-md-6"> |
||||
|
<div style="background-color: #F6F8F9; padding: 30px; display: flex; align-items: center;"> |
||||
|
<div class="mr-4 d-flex justify-content-center align-items-center" |
||||
|
style="background-color: #2AC44D; display: inline-block; height: 70px; width: 70px; display: flex; align-items: center; justify-content: center;"> |
||||
|
<img src="assets/misc/whatsapp.png" height="52" width="52" |
||||
|
style="width: 52px; height: 52px;"/> |
||||
|
</div> |
||||
|
<div> |
||||
|
<h4>WhatsApp</h4> |
||||
|
<p style="line-height: 100%;">Say hi to us on WhatsApp!</p> |
||||
|
<a href="https://api.whatsapp.com/send?phone=918606827707"> |
||||
|
<p style="font-weight: 400; font-size: 28px; line-height: 80%; color: #714B67;"> |
||||
|
+91 86068 |
||||
|
27707</p> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12 my-5 d-flex justify-content-center align-items-center"> |
||||
|
<img src="assets/misc/logo.png" width="144" height="31" |
||||
|
style="width:144px; height: 31px; margin-top: 40px;"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- END OF SUPPORT --> |
@ -0,0 +1,50 @@ |
|||||
|
/** @odoo-module */ |
||||
|
|
||||
|
import { ListRenderer } from "@web/views/list/list_renderer"; |
||||
|
|
||||
|
const { onWillStart, useState, useSubEnv } = owl; |
||||
|
|
||||
|
export class SaleOrderLineCompareListRenderer extends ListRenderer { |
||||
|
// extending the list renderer
|
||||
|
setup() { |
||||
|
super.setup(); |
||||
|
this.bestFields = useState({ |
||||
|
best_price_ids: [], |
||||
|
best_price_unit_ids: [], |
||||
|
}); |
||||
|
onWillStart(async () => { |
||||
|
await this.updateBestFields(); |
||||
|
}); |
||||
|
const defaultOnClickViewButton = this.env.onClickViewButton; |
||||
|
useSubEnv({ |
||||
|
onClickViewButton: async (params) => { |
||||
|
await defaultOnClickViewButton(params); |
||||
|
await this.updateBestFields(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
async updateBestFields() { |
||||
|
//to update the lines having best price and unit price
|
||||
|
[this.bestFields.best_price_ids, |
||||
|
this.bestFields.best_price_unit_ids] = await this.props.list.model.orm.call( |
||||
|
"sale.order", |
||||
|
"get_tender_best_lines", |
||||
|
[this.props.list.context.sale_order_id || this.props.list.context.active_id], |
||||
|
{ context: this.props.list.context } |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
getCellClass(column, record) { |
||||
|
//to highlight the lines having best price and unit price
|
||||
|
let classNames = super.getCellClass(...arguments); |
||||
|
const customClassNames = []; |
||||
|
if (column.name === "price_subtotal" && this.bestFields.best_price_ids.includes(record.resId)) { |
||||
|
customClassNames.push("text-success"); |
||||
|
} |
||||
|
if (column.name === "price_unit" && this.bestFields.best_price_unit_ids.includes(record.resId)) { |
||||
|
customClassNames.push("text-success"); |
||||
|
} |
||||
|
return classNames.concat(" ", customClassNames.join(" ")); |
||||
|
} |
||||
|
} |
@ -0,0 +1,11 @@ |
|||||
|
/** @odoo-module **/ |
||||
|
|
||||
|
import { listView } from '@web/views/list/list_view'; |
||||
|
import { registry } from "@web/core/registry"; |
||||
|
import { SaleOrderLineCompareListRenderer } from "./sale_order_line_compare_list_renderer"; |
||||
|
export const saleOrderLineCompareListView = { |
||||
|
...listView, |
||||
|
Renderer: SaleOrderLineCompareListRenderer, |
||||
|
}; |
||||
|
// extending the list renderer
|
||||
|
registry.category("views").add("sale_order_line_compare", saleOrderLineCompareListView); |
@ -0,0 +1,46 @@ |
|||||
|
/** @odoo-module */ |
||||
|
|
||||
|
import { registry } from "@web/core/registry"; |
||||
|
import { useService } from "@web/core/utils/hooks"; |
||||
|
import { X2ManyField } from "@web/views/fields/x2many/x2many_field"; |
||||
|
import { ListRenderer } from "@web/views/list/list_renderer"; |
||||
|
|
||||
|
|
||||
|
export class FieldMany2ManyAltSosRenderer extends ListRenderer { |
||||
|
isCurrentRecord(record) { |
||||
|
return record.data.id === this.env.model.root.data.id; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
FieldMany2ManyAltSosRenderer.recordRowTemplate = "tender_sales.AltSOsListRenderer.RecordRow"; |
||||
|
|
||||
|
export class FieldMany2ManyAltSOs extends X2ManyField { |
||||
|
setup() { |
||||
|
this.orm = useService("orm"); |
||||
|
this.action = useService("action"); |
||||
|
// TODO: this is a terrible hack, make this a proper extension of many2many if/when possible
|
||||
|
this.props.record.activeFields[this.props.name].widget = "many2many"; |
||||
|
super.setup(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Override to: avoid reopening currently open record |
||||
|
* open record in same window w/breadcrumb extended |
||||
|
* @override |
||||
|
*/ |
||||
|
async openRecord(record) { |
||||
|
if (record.data.id !== this.props.record.data.id) { |
||||
|
const action = await this.orm.call(record.resModel, "get_formview_action", [[record.data.id]], { |
||||
|
context: this.props.context, |
||||
|
}); |
||||
|
await this.action.doAction(action); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
FieldMany2ManyAltSOs.components = { |
||||
|
...X2ManyField.components, |
||||
|
ListRenderer: FieldMany2ManyAltSosRenderer, |
||||
|
}; |
||||
|
|
||||
|
registry.category("fields").add("many2many_alt_sos", FieldMany2ManyAltSOs); |
@ -0,0 +1,3 @@ |
|||||
|
.o_field_many2many_alt_sos { |
||||
|
width: 100%; |
||||
|
} |
@ -0,0 +1,11 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<templates xml:space="preserve"> |
||||
|
|
||||
|
<!-- To not unlink a so from itself --> |
||||
|
<t t-name="tender_sales.AltSOsListRenderer.RecordRow" t-inherit="web.ListRenderer.RecordRow" t-inherit-mode="primary" owl="1"> |
||||
|
<xpath expr="//t[@t-if='displayOptionalFields or hasX2ManyAction']" position="attributes"> |
||||
|
<attribute name="t-if">(displayOptionalFields or hasX2ManyAction) and !isCurrentRecord(record)</attribute> |
||||
|
</xpath> |
||||
|
</t> |
||||
|
|
||||
|
</templates> |
@ -0,0 +1,96 @@ |
|||||
|
<?xml version="1.0"?> |
||||
|
<odoo> |
||||
|
<!-- view for sale order Form Inherit--> |
||||
|
<record id="sale_order_form_inherit" model="ir.ui.view"> |
||||
|
<field name="name">sale.order.form.view.inherit</field> |
||||
|
<field name="model">sale.order</field> |
||||
|
<field name="inherit_id" ref="sale.view_order_form"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<data> |
||||
|
<xpath expr="//field[@name='partner_id']" position='after'> |
||||
|
<field name="tender_id" |
||||
|
attrs="{'readonly': [('state','!=','draft')]}" |
||||
|
domain="[('state', 'in', ('in_progress', 'open', 'ongoing')), ('customer_id', 'in', (partner_id, |
||||
|
False)), ('company_id', '=', company_id)]" |
||||
|
options="{'no_create': True}"/> |
||||
|
</xpath> |
||||
|
<xpath expr="//page[@name='customer_signature']" |
||||
|
position="after"> |
||||
|
<page string="Alternatives" name="alternative_so"> |
||||
|
<group> |
||||
|
<group> |
||||
|
<p colspan="2">Create alternatives. |
||||
|
</p> |
||||
|
</group> |
||||
|
<group> |
||||
|
<p colspan="2"> |
||||
|
<button name="action_create_alternative" |
||||
|
type="object" |
||||
|
class="btn-link d-block" |
||||
|
string="Create Alternative" |
||||
|
icon="fa-copy"/> |
||||
|
<button name="action_compare_alternative_lines" |
||||
|
type="object" |
||||
|
class="btn-link d-block" |
||||
|
string="Compare Product Lines" |
||||
|
icon="fa-bar-chart" |
||||
|
attrs="{'invisible': [('alternative_so_ids', '=', [])]}"/> |
||||
|
</p> |
||||
|
</group> |
||||
|
</group> |
||||
|
<field name="alternative_so_ids" |
||||
|
attrs="{'readonly': [('id', '=', False)]}" |
||||
|
widget="many2many_alt_sos" |
||||
|
context="{}" |
||||
|
domain="[('state', 'in', ('draft', 'sent'))]"> |
||||
|
<tree string="Alternative Quotation Order" |
||||
|
decoration-muted="state in ['cancel', 'sale', 'done']" |
||||
|
decoration-bf="id == parent.id"> |
||||
|
<control> |
||||
|
<create string="Link to Existing Quotations"/> |
||||
|
</control> |
||||
|
<field name="partner_id"/> |
||||
|
<field name="name" string="Reference"/> |
||||
|
<field name="amount_total"/> |
||||
|
<field name="state"/> |
||||
|
</tree> |
||||
|
</field> |
||||
|
</page> |
||||
|
</xpath> |
||||
|
</data> |
||||
|
</field> |
||||
|
</record> |
||||
|
<!-- view for sale order line compare--> |
||||
|
<record id="sale_order_line_compare_tree" model="ir.ui.view"> |
||||
|
<field name="name">sale.order.line.compare.tree</field> |
||||
|
<field name="model">sale.order.line</field> |
||||
|
<field name="priority">1000</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<tree string="Sale Order Lines" |
||||
|
decoration-muted="state in ['cancel', 'purchase', 'done']" |
||||
|
create="0" delete="0" edit="0" expand="1" js_class="sale_order_line_compare"> |
||||
|
<header> |
||||
|
<button name="action_clear_quantities" |
||||
|
string="Clear Selected" type="object" |
||||
|
class="o_clear_qty_buttons"/> |
||||
|
</header> |
||||
|
<field name="product_id" readonly="1"/> |
||||
|
<field name="order_id" string="Reference" readonly="1"/> |
||||
|
<field name="state"/> |
||||
|
<field name="name" readonly="1"/> |
||||
|
<field name="product_uom_qty"/> |
||||
|
<field name="product_uom" groups="uom.group_uom"/> |
||||
|
<field name="price_unit"/> |
||||
|
<field name="price_subtotal" string="Total"/> |
||||
|
<field name="currency_id"/> |
||||
|
<button name="action_choose" string="Choose" type="object" |
||||
|
class="o_clear_qty_buttons" icon="fa-bullseye" |
||||
|
attrs="{'invisible': [('product_uom_qty', '<=', 0.0)]}"/> |
||||
|
<button name="action_clear_quantities" string="Clear" |
||||
|
type="object" class="o_clear_qty_buttons" |
||||
|
icon="fa-times" |
||||
|
attrs="{'invisible': ['|', ('product_uom_qty', '<=', 0.0), ('state', 'in', ['cancel', 'sale', 'done'])]}"/> |
||||
|
</tree> |
||||
|
</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,186 @@ |
|||||
|
<?xml version="1.0"?> |
||||
|
<odoo> |
||||
|
<data> |
||||
|
<!-- action to view sale order list-from tender form--> |
||||
|
<record id="action_sale_tender_list" model="ir.actions.act_window"> |
||||
|
<field name="name">Quotations</field> |
||||
|
<field name="type">ir.actions.act_window</field> |
||||
|
<field name="res_model">sale.order</field> |
||||
|
<field name="view_mode">tree,form</field> |
||||
|
<field name="domain">[('tender_id','=',active_id)]</field> |
||||
|
<field name="context">{ |
||||
|
"default_tender_id":active_id, |
||||
|
} |
||||
|
</field> |
||||
|
</record> |
||||
|
<!-- action to view sale order from tender form--> |
||||
|
<record id="sale_tender_action_so" model="ir.actions.act_window"> |
||||
|
<field name="name">Quotation</field> |
||||
|
<field name="type">ir.actions.act_window</field> |
||||
|
<field name="res_model">sale.order</field> |
||||
|
<field name="view_mode">form,tree</field> |
||||
|
<field name="domain">[('tender_id','=',active_id)]</field> |
||||
|
<field name="context">{ |
||||
|
"default_tender_id":active_id, |
||||
|
} |
||||
|
</field> |
||||
|
</record> |
||||
|
<!-- sale tender form--> |
||||
|
<record id="view_sale_tender_form" model="ir.ui.view"> |
||||
|
<field name="name">sale.tender.form</field> |
||||
|
<field name="model">sale.tender</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form string="Sale Agreements"> |
||||
|
<field name="company_id" invisible="1"/> |
||||
|
<field name="currency_id" invisible="1"/> |
||||
|
<header> |
||||
|
<button name="%(sale_tender_action_so)d" |
||||
|
type="action" |
||||
|
string="New Quotation" |
||||
|
context="{'default_currency_id': currency_id, 'default_user_id': user_id}" |
||||
|
attrs="{'invisible': [('state', '!=', 'open')]}"/> |
||||
|
<button name="%(sale_tender_action_so)d" |
||||
|
type="action" |
||||
|
string="New Quotation" class="btn-primary" |
||||
|
context="{'default_currency_id': currency_id, 'default_user_id': user_id}" |
||||
|
attrs="{'invisible': [('state', 'not in', ('in_progress', 'ongoing'))]}"/> |
||||
|
<button name="action_in_progress" states="draft" |
||||
|
string="Confirm" type="object" |
||||
|
class="btn-primary"/> |
||||
|
<button name="action_cancel" |
||||
|
states="draft,in_progress,ongoing" |
||||
|
string="Cancel" type="object"/> |
||||
|
|
||||
|
<field name="state" widget="statusbar" |
||||
|
statusbar_visible="draft,in_progress,open,done"/> |
||||
|
</header> |
||||
|
<sheet> |
||||
|
<div class="oe_button_box" name="button_box"> |
||||
|
<button name="%(action_sale_tender_list)d" |
||||
|
type="action" class="oe_stat_button" |
||||
|
icon="fa-list-alt" |
||||
|
attrs="{'invisible': [('state', '=', 'draft')]}" |
||||
|
context="{'default_currency_id': currency_id}"> |
||||
|
<field name="order_count" widget="statinfo" |
||||
|
string="Quotations/Orders"/> |
||||
|
</button> |
||||
|
</div> |
||||
|
<div class="oe_title"> |
||||
|
<label for="name" class="oe_inline"/> |
||||
|
<h1> |
||||
|
<field name="name"/> |
||||
|
</h1> |
||||
|
</div> |
||||
|
<group> |
||||
|
<group> |
||||
|
<field name="user_id" |
||||
|
attrs="{'readonly': [('state','not in',('draft','in_progress','open'))]}" |
||||
|
domain="[('share', '=', False)]"/> |
||||
|
<field name="type_id" |
||||
|
attrs="{'readonly': [('state','!=','draft')]}"/> |
||||
|
<field name="customer_id" |
||||
|
context="{'res_partner_search_mode': 'customer'}" |
||||
|
attrs="{'readonly': [('state', 'in', ['ongoing','done'])]}"/> |
||||
|
<field name="currency_id"/> |
||||
|
</group> |
||||
|
<group> |
||||
|
<field name="date_end" |
||||
|
attrs="{'readonly': [('state','not in',('draft','in_progress','open','ongoing'))]}"/> |
||||
|
<field name="ordering_date" |
||||
|
attrs="{'readonly': [('state','not in',('draft','in_progress','open','ongoing'))]}"/> |
||||
|
<field name="schedule_date" |
||||
|
attrs="{'readonly': [('state','not in',('draft','in_progress','open','ongoing'))]}"/> |
||||
|
<field name="company_id" |
||||
|
options="{'no_create': True}" |
||||
|
attrs="{'readonly': [('state','not in',('draft'))]}"/> |
||||
|
</group> |
||||
|
</group> |
||||
|
<notebook> |
||||
|
<page string="Products" name="products"> |
||||
|
<field name="line_ids"> |
||||
|
<tree string="Products" editable="bottom"> |
||||
|
<field name="product_id" |
||||
|
domain="[('purchase_ok', '=', True), '|', ('company_id', '=', False), ('company_id', '=', parent.company_id)]"/> |
||||
|
<field name="product_description_variants" |
||||
|
attrs="{'invisible': [('product_description_variants', '=', '')], 'readonly': [('parent.state', '!=', 'draft')]}"/> |
||||
|
<field name="product_qty"/> |
||||
|
<field name="product_uom_category_id" |
||||
|
invisible="1"/> |
||||
|
<field name="product_uom_id" |
||||
|
string="UoM" |
||||
|
groups="uom.group_uom" |
||||
|
optional="show" |
||||
|
attrs="{'required': [('product_id', '!=', False)]}"/> |
||||
|
<field name="schedule_date" |
||||
|
optional="hide"/> |
||||
|
<field name="analytic_distribution" |
||||
|
widget="analytic_distribution" |
||||
|
optional="hide" |
||||
|
groups="analytic.group_analytic_accounting" |
||||
|
options="{'product_field': 'product_id', 'business_domain': 'purchase_order'}"/> |
||||
|
<field name="price_unit"/> |
||||
|
</tree> |
||||
|
<form string="Products"> |
||||
|
<group> |
||||
|
<field name="product_id" |
||||
|
domain="[('sale_ok', '=', True), '|', ('company_id', '=', False), ('company_id', '=', parent.company_id)]"/> |
||||
|
<field name="product_qty"/> |
||||
|
<field name="product_uom_category_id" |
||||
|
invisible="1"/> |
||||
|
<field name="product_uom_id" |
||||
|
groups="uom.group_uom"/> |
||||
|
<field name="schedule_date"/> |
||||
|
<field name="analytic_distribution" |
||||
|
widget="analytic_distribution" |
||||
|
groups="analytic.group_analytic_accounting" |
||||
|
options="{'product_field': 'product_id', 'business_domain': 'sale_order'}"/> |
||||
|
<field name="company_id" |
||||
|
groups="base.group_multi_company" |
||||
|
options="{'no_create': True}"/> |
||||
|
</group> |
||||
|
</form> |
||||
|
</field> |
||||
|
<separator string="Terms and Conditions"/> |
||||
|
<field name="description" |
||||
|
class="oe-bordered-editor" |
||||
|
attrs="{'readonly': [('state','not in',('draft','in_progress','open'))]}"/> |
||||
|
</page> |
||||
|
</notebook> |
||||
|
</sheet> |
||||
|
<div class="oe_chatter"> |
||||
|
<field name="message_follower_ids"/> |
||||
|
<field name="activity_ids"/> |
||||
|
<field name="message_ids"/> |
||||
|
</div> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
<!-- tree view--> |
||||
|
<record id="view_sale_tender_tree" model="ir.ui.view"> |
||||
|
<field name="name">sale.tender.tree</field> |
||||
|
<field name="model">sale.tender</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<tree string="Sale Agreements" sample="1"> |
||||
|
<field name="name" decoration-bf="1"/> |
||||
|
</tree> |
||||
|
</field> |
||||
|
</record> |
||||
|
<!-- action for the tender--> |
||||
|
<record id="sale_tender_action" model="ir.actions.act_window"> |
||||
|
<field name="name">Sale Tenders</field> |
||||
|
<field name="type">ir.actions.act_window</field> |
||||
|
<field name="res_model">sale.tender</field> |
||||
|
<field name="view_mode">tree,form</field> |
||||
|
<field name="context">{}</field> |
||||
|
<field name="help" type="html"> |
||||
|
<p class="o_view_nocontent_smiling_face"> |
||||
|
Start a new Sale agreement |
||||
|
</p> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<!-- menu item for the tender --> |
||||
|
<menuitem id="sale_tender_menu" sequence="10" parent="sale.sale_order_menu" |
||||
|
action="sale_tender_action"/> |
||||
|
</data> |
||||
|
</odoo> |
@ -0,0 +1,22 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Nikhil M (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
|
# (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from . import tender_sales_create_alternative |
@ -0,0 +1,71 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Nikhil M (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
|
# (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from odoo import fields, models, Command |
||||
|
|
||||
|
|
||||
|
class SaleTenderCreateAlternative(models.TransientModel): |
||||
|
""""Creating module for wizard from alternative create confirmation""" |
||||
|
_name = 'sale.tender.create.alternative' |
||||
|
_description = 'Wizard to preset values for alternative PO' |
||||
|
|
||||
|
origin_so_id = fields.Many2one( |
||||
|
'sale.order', help="The original PO that this alternative PO is being created for." |
||||
|
) |
||||
|
partner_id = fields.Many2one( |
||||
|
'res.partner', string='Customer', required=True, |
||||
|
help="Choose a customer for alternative sO") |
||||
|
|
||||
|
copy_products = fields.Boolean( |
||||
|
"Copy Products", default=True, |
||||
|
help="If this is checked, the product quantities of the original sO will be copied") |
||||
|
|
||||
|
def action_create_alternative(self): |
||||
|
""""Function to create alternative orders.""" |
||||
|
vals = self._get_alternative_values() |
||||
|
alt_so = self.env['sale.order'].with_context(origin_so_id=self.origin_so_id.id, default_tender_id=False).create(vals) |
||||
|
return { |
||||
|
'type': 'ir.actions.act_window', |
||||
|
'view_mode': 'form', |
||||
|
'res_model': 'sale.order', |
||||
|
'res_id': alt_so.id, |
||||
|
'context': { |
||||
|
'active_id': alt_so.id, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
def _get_alternative_values(self): |
||||
|
""""function to return alternative values.""" |
||||
|
vals = { |
||||
|
'date_order': self.origin_so_id.date_order, |
||||
|
'partner_id': self.partner_id.id, |
||||
|
'user_id': self.origin_so_id.user_id.id, |
||||
|
'origin': self.origin_so_id.origin, |
||||
|
} |
||||
|
if self.copy_products and self.origin_so_id: |
||||
|
vals['order_line'] = [Command.create({ |
||||
|
'product_id': line.product_id.id, |
||||
|
'product_uom_qty': line.product_uom_qty, |
||||
|
'product_uom': line.product_uom.id, |
||||
|
'display_type': line.display_type, |
||||
|
'name': line.name, |
||||
|
}) for line in self.origin_so_id.order_line] |
||||
|
return vals |
@ -0,0 +1,27 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
<data> |
||||
|
<!-- Alternative create Form--> |
||||
|
<record id="sale_tender_create_alternative_form" model="ir.ui.view"> |
||||
|
<field name="name">Create Alternative</field> |
||||
|
<field name="model">sale.tender.create.alternative</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form string="Create Alternative"> |
||||
|
<group> |
||||
|
<field name="origin_so_id" invisible="1"/> |
||||
|
<group> |
||||
|
<field name="partner_id"/> |
||||
|
</group> |
||||
|
<group> |
||||
|
<field name="copy_products"/> |
||||
|
</group> |
||||
|
</group> |
||||
|
<footer> |
||||
|
<button name="action_create_alternative" string="Create Alternative" data-hotkey="q" type="object" colspan="1" class="btn-primary"/> |
||||
|
<button string="Cancel" data-hotkey="x" special="cancel" colspan="1" class="btn-secondary"/> |
||||
|
</footer> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
</data> |
||||
|
</odoo> |