diff --git a/all_in_one_sales_kit/README.rst b/all_in_one_sales_kit/README.rst new file mode 100755 index 000000000..8bc686b42 --- /dev/null +++ b/all_in_one_sales_kit/README.rst @@ -0,0 +1,46 @@ +.. 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 + +All in One Sales Kit +==================== +This module combines a variety of sales features. + +Configuration +============= +* No Additional configuration is needed. + +Company +------- +* `Cybrosys Techno Solutions `__ + +License +------- +General Public License, Version 3 (AGPL-3). +(https://www.gnu.org/licenses/agpl-3.0-standalone.html) + +Credits +------- +Developer: (V16) Swetha Anand, Contact: odoo@cybrosys.com + +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 `__ + +Further information +=================== +HTML Description: ``__ diff --git a/all_in_one_sales_kit/__init__.py b/all_in_one_sales_kit/__init__.py new file mode 100644 index 000000000..ea2bb0b1c --- /dev/null +++ b/all_in_one_sales_kit/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Swetha Anand (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 . +# +############################################################################### +from . import controllers +from . import models +from . import report +from . import wizard diff --git a/all_in_one_sales_kit/__manifest__.py b/all_in_one_sales_kit/__manifest__.py new file mode 100644 index 000000000..abeadbbfc --- /dev/null +++ b/all_in_one_sales_kit/__manifest__.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Swetha Anand (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 . +# +############################################################################### +{ + 'name': 'Odoo16 All in One Sales Kit', + 'version': '16.0.1.0.0', + 'category': 'Sales', + 'summary': 'This module combines a variety of sales features.', + 'description': 'Sale Order Line Images, Barcode Scan Support for Sales, ' + 'Advanced Sale Reports (Product Profit Report, ' + 'Sales Invoice Analysis Report, Sales Category Report, ' + 'Sales Indent Report, Sales Analysis Report, ' + 'Hourly Sales Report), Product Pack, and ' + 'Salesperson Signature for Confirm Order are some of the ' + 'features included in this module.' + 'Previous Sale Product Rate, Create Various Sale Order ' + 'Versions, Create Custom Fields for Sale Orders, ' + 'Recognise Previous Sales of Products,' + 'A separate quotation number,' + 'Multiple warehouses in sale order lines,' + 'sales order and quotation line views,' + 'approval of the sale order discount,sales restrictions ' + 'for out-of-stock items depending on forecast and ' + 'stock level,automate the sale process,' + 'Sales one-stop report generation,Add more than one ' + 'item to the quotation,pivot view for partner sales,' + 'sale order archive,Dashboard.', + 'author': 'Cybrosys Techno Solutions', + 'company': 'Cybrosys Techno Solutions', + 'maintainer': 'Cybrosys Techno Solutions', + 'website': 'https://www.cybrosys.com', + 'depends': ['sale_management', 'delivery', 'stock','sale'], + 'data': [ + 'security/all_in_one_sales_kit_groups.xml', + 'security/ir.model.access.csv', + 'data/field_widget_data.xml', + 'data/ir_sequence_data.xml', + 'wizard/product_sale_order_history_views.xml', + 'wizard/sale_order_dynamic_fields_views.xml', + 'wizard/sale_report_advance_views.xml', + 'wizard/sale_report_analysis_views.xml', + 'wizard/sale_report_category_views.xml', + 'wizard/sale_report_indent_views.xml', + 'wizard/sale_report_invoice_views.xml', + 'wizard/sale_report_weekly_views.xml', + 'wizard/select_product_pack_views.xml', + 'views/res_config_settings_views.xml', + 'views/all_in_one_sales_kit_menus.xml', + 'views/sale_order_views.xml', + 'views/sale_order_line_views.xml', + 'views/res_users_views.xml', + 'views/sale_report_views.xml', + 'views/product_template_views.xml', + 'views/ir_fields_search_views.xml', + 'views/product_product_views.xml', + 'views/dashboard_menu.xml', + 'report/invoice_analysis_templates.xml', + 'report/sale_order_document_templates.xml', + 'report/sale_order_report_templates.xml', + 'report/sale_profit_templates.xml', + 'report/sale_reports.xml', + 'report/sales_analysis_templates.xml', + 'report/sales_category_templates.xml', + 'report/sales_indent_templates.xml', + 'report/sales_weekly_templates.xml' + ], + 'assets': { + 'web.assets_backend': [ + 'all_in_one_sales_kit/static/src/css/sale_report.css', + 'all_in_one_sales_kit/static/src/scss/dashboard.scss', + 'all_in_one_sales_kit/static/src/js/action_manager.js', + 'all_in_one_sales_kit/static/src/js/sale_report.js', + 'all_in_one_sales_kit/static/src/js/dashboard.js', + 'all_in_one_sales_kit/static/src/xml/sale_report_templates.xml', + 'all_in_one_sales_kit/static/src/xml/dashboard_templates.xml', + 'https://cdn.jsdelivr.net/npm/chart.js', + ], + }, + 'images': ['static/description/banner.jpg'], + 'license': 'AGPL-3', + 'installable': True, + 'auto_install': False, + 'application': False, +} diff --git a/all_in_one_sales_kit/controllers/__init__.py b/all_in_one_sales_kit/controllers/__init__.py new file mode 100644 index 000000000..ca4c1ff9f --- /dev/null +++ b/all_in_one_sales_kit/controllers/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Swetha Anand (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 . +# +############################################################################### +from . import all_in_one_sales_kit diff --git a/all_in_one_sales_kit/controllers/all_in_one_sales_kit.py b/all_in_one_sales_kit/controllers/all_in_one_sales_kit.py new file mode 100644 index 000000000..90d11d008 --- /dev/null +++ b/all_in_one_sales_kit/controllers/all_in_one_sales_kit.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2022-TODAY Cybrosys Technologies(). +# +# 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 . +# +############################################################################# +import json +from odoo import http +from odoo.http import content_disposition, request +from odoo.tools import html_escape + + +class XLSXReportController(http.Controller): + """This class is to add sales xlsx reports.""" + + @http.route('/xlsx_reports', type='http', auth='user', + methods=['POST'], csrf=False) + def get_report_xlsx(self, model, options, output_format, + report_name, **kw): + """This is to pass data required for the xlsx report.""" + token = 'dummy-because-api-expects-one' + try: + if output_format == 'xlsx': + response = request.make_response( + None, + headers=[('Content-Type', 'application/vnd.ms-excel'), + ('Content-Disposition', + content_disposition(report_name + '.xlsx')) + ] + ) + request.env[model].sudo().browse( + request.session.uid).get_xlsx_report(json.loads(options), + response) + response.set_cookie('fileToken', token) + return response + except Exception: + error = { + 'code': 200, + 'message': 'Odoo Server Error', + } + return request.make_response(html_escape(json.dumps(error))) diff --git a/all_in_one_sales_kit/data/field_widget_data.xml b/all_in_one_sales_kit/data/field_widget_data.xml new file mode 100644 index 000000000..5b22ea740 --- /dev/null +++ b/all_in_one_sales_kit/data/field_widget_data.xml @@ -0,0 +1,77 @@ + + + + + + image + Image + + + + many2many_tags + Many2many Tags + + + + binary + Binary + + + + radio + Radio + + + + priority + Priority + + + + monetary + Monetary + + + + selection + Selection + + + + image + Image + + + + many2many_tags + Many2many Tags + + + + binary + Binary + + + + radio + Radio + + + + priority + Priority + + + + monetary + Monetary + + + + selection + Selection + + + diff --git a/all_in_one_sales_kit/data/ir_sequence_data.xml b/all_in_one_sales_kit/data/ir_sequence_data.xml new file mode 100644 index 000000000..780573bcf --- /dev/null +++ b/all_in_one_sales_kit/data/ir_sequence_data.xml @@ -0,0 +1,11 @@ + + + + + Quotation + sale.order + SQ + 5 + + + diff --git a/all_in_one_sales_kit/doc/RELEASE_NOTES.md b/all_in_one_sales_kit/doc/RELEASE_NOTES.md new file mode 100644 index 000000000..63786b412 --- /dev/null +++ b/all_in_one_sales_kit/doc/RELEASE_NOTES.md @@ -0,0 +1,7 @@ +## Module + +#### 07.12.2023 +#### Version 16.0.1.0.0 +#### ADD + +- Initial commit for All in One Sales Kit diff --git a/all_in_one_sales_kit/models/__init__.py b/all_in_one_sales_kit/models/__init__.py new file mode 100644 index 000000000..488c1d49e --- /dev/null +++ b/all_in_one_sales_kit/models/__init__.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Swetha Anand (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 . +# +############################################################################### +from . import field_widget +from . import ir_model_fields +from . import pack_product +from . import product_product +from . import product_template +from . import res_config_settings +from . import res_partner +from . import res_users +from . import sale_order +from . import sale_order_line +from . import sales_report diff --git a/all_in_one_sales_kit/models/field_widget.py b/all_in_one_sales_kit/models/field_widget.py new file mode 100644 index 000000000..665f61b2d --- /dev/null +++ b/all_in_one_sales_kit/models/field_widget.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Swetha Anand (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 . +# +############################################################################### +from odoo import fields, models + + +class FieldWidget(models.Model): + """ + Need of this model is because we can't filter a selection field dynamically + so when we select a field its widgets also need to change according to + field type, we can't do it by a 'selection' field, + need a 'Many2one' field. + """ + + _name = 'field.widget' + _description = 'Field Widgets' + _rec_name = 'description' + + name = fields.Char(string="Name", help="Name of widget.") + description = fields.Char( + string="Description", help="Description about the widget.") diff --git a/all_in_one_sales_kit/models/ir_model_fields.py b/all_in_one_sales_kit/models/ir_model_fields.py new file mode 100644 index 000000000..caa1d4bff --- /dev/null +++ b/all_in_one_sales_kit/models/ir_model_fields.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Swetha Anand (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 . +# +############################################################################### +from odoo import fields, models + + +class IrModelFields(models.Model): + """Adding a new field to understand the dynamically created fields.""" + + _inherit = 'ir.model.fields' + + is_dynamic = fields.Boolean( + string="Dynamic Field", + help="To make sure it is a dynamically created field.") diff --git a/all_in_one_sales_kit/models/pack_product.py b/all_in_one_sales_kit/models/pack_product.py new file mode 100644 index 000000000..55ec5409e --- /dev/null +++ b/all_in_one_sales_kit/models/pack_product.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Swetha Anand (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 . +# +############################################################################### +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError + + +class PackProduct(models.Model): + """ A new model is created to store products as pack.""" + + _name = 'pack.product' + _rec_name = 'product_tmpl_id' + _description = 'Select Pack Products' + + product_id = fields.Many2one( + 'product.product', string='Product', required=True, + domain=[('is_pack', '=', False)], help="Product") + product_tmpl_id = fields.Many2one( + 'product.template', string='Product', help="Product") + price = fields.Float( + string='Price', compute='_compute_price', store=True, + help="Computed price of product according to the quantity.") + quantity = fields.Integer( + string='Quantity', default=1, + help="Quantity of product that should be in the pack.") + qty_available = fields.Float( + string='Quantity Available', compute='_compute_qty_available', + store=True, readonly=False, help="Available quantity of product.") + total_available_quantity = fields.Float( + string='Total Quantity', + help="Total Quantity available of that product") + + @api.depends('product_id', + 'total_available_quantity', + 'product_id.qty_available') + def _compute_qty_available(self): + """It is to compute the available quantity.""" + for record in self: + location_id = record.product_tmpl_id.pack_location_id + if location_id: + stock_quant = self.env['stock.quant'].search( + [('product_id', '=', record.product_id.id), + ('location_id', '=', location_id.id)]) + if stock_quant: + record.qty_available = stock_quant.quantity + else: + record.qty_available = False + else: + record.qty_available = False + + @api.depends('product_id', 'quantity') + def _compute_price(self): + """It is to compute price of each product compared to quantity.""" + for record in self: + record.price = record.product_id.lst_price * record.quantity + + @api.onchange('quantity') + def _onchange_quantity(self): + """It is to set price.""" + self.price = self.product_id.lst_price * self.quantity + + @api.constrains('quantity') + def _check_quantity(self): + """This function is to ensure product quantity is positive.""" + if any([product.quantity < 0 for product in self]): + raise ValidationError(_('You can not enter negative quantities.')) diff --git a/all_in_one_sales_kit/models/product_product.py b/all_in_one_sales_kit/models/product_product.py new file mode 100644 index 000000000..007ff97fc --- /dev/null +++ b/all_in_one_sales_kit/models/product_product.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Swetha Anand (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 . +# +############################################################################### +from odoo import fields, models +from odoo.exceptions import UserError + + +class ProductProduct(models.Model): + """Inherits product.product.""" + _inherit = "product.product" + + order_partner_id = fields.Many2one( + 'res.partner', string="Partner", help="Current Partner") + + def action_sale_product_prices(self): + """It is to show to product history.""" + rel_view_id = self.env.ref( + 'all_in_one_sales_kit.last_sale_product_prices_view') + if self.order_partner_id.id: + sale_lines = self.env['sale.order.line'].search( + [('product_id', '=', self.id), + ('order_partner_id', '=', self.order_partner_id.id)], + order='create_date DESC').ids + else: + sale_lines = self.env['sale.order.line'].search( + [('product_id', '=', self.id)], + order='create_date DESC').ids + if not sale_lines: + raise UserError("No sales history found.!") + else: + return { + 'domain': [('id', 'in', sale_lines)], + 'views': [(rel_view_id.id, 'tree')], + 'name': 'Sales History', + 'res_model': 'sale.order.line', + 'view_id': False, + 'type': 'ir.actions.act_window', + } + + def action_add_quotation(self): + """It is a button function on clicking the product is entered to + the order line.""" + order_id = self.env.context.get('order_id') + list = self.env['sale.order.line'].search( + [('order_id', '=', order_id)]).mapped('product_id') + if self in list: + order_line_id = self.env['sale.order.line'].search( + [('order_id', '=', order_id), ('product_id', '=', self.id)]) + order_line_id.product_uom_qty += 1 + else: + self.env['sale.order.line'].create({ + 'product_id': self.id, + 'order_id': order_id, + 'qty_available': self.qty_available, + 'forecast_quantity': self.virtual_available, + }) diff --git a/all_in_one_sales_kit/models/product_template.py b/all_in_one_sales_kit/models/product_template.py new file mode 100644 index 000000000..d3f2b1745 --- /dev/null +++ b/all_in_one_sales_kit/models/product_template.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Swetha Anand (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 . +# +############################################################################### +from odoo import api, fields, models, _ +from odoo.exceptions import UserError + + +class ProductTemplate(models.Model): + """Inherits product.template.""" + _inherit = 'product.template' + + def default_pack_location(self): + """Sets the default value for the 'location_id' field based + on the current user's warehouse.""" + warehouse = self.env['stock.warehouse'].search( + [('company_id', '=', self.env.company.id)], limit=1) + if warehouse: + return warehouse.lot_stock_id.id + + is_pack = fields.Boolean(string='Is a Pack', help="The product is a pack.") + pack_price = fields.Integer( + string="Pack Price", compute='_compute_pack_price', store=True, + help="Total price of products inside the pack") + pack_products_ids = fields.One2many( + 'pack.product', 'product_tmpl_id', + string='Pack Products', copy=True, help="Products inside the pack") + pack_quantity = fields.Integer( + string='Pack Quantity', help="Pack quantity available") + pack_location_id = fields.Many2one( + 'stock.location', + domain=[('usage', 'in', ['internal', 'transit'])], + default=default_pack_location, help="Warehouse", string="Warehouse") + + @api.depends('pack_products_ids', 'pack_products_ids.price') + def _compute_pack_price(self): + """It is to set total price of the pack according to the + products in the pack.""" + price = 0 + for record in self: + for line in record.pack_products_ids: + price = price + line.price + record.pack_price = price + + @api.model + def create(self, values): + """Here create function is over ride to check whether the product is + a pack or not.""" + if values.get('is_pack', False): + if not values.get('pack_products_ids', []): + raise UserError(_( + 'You need to add atleast one product in the Pack...!')) + if values.get('type', False) == 'service': + raise UserError(_( + 'You cannot define a pack product as a service..!')) + return super(ProductTemplate, self).create(values) + + def write(self, values): + """Here, the write function is overridden to determine if + there is at least one product inside the package.""" + super(ProductTemplate, self).write(values) + if self.is_pack: + if not self.pack_products_ids: + raise UserError(_( + 'You need to add atleast one product in the Pack...!')) + if self.type == 'service': + raise UserError( + _('You cannot define a pack product as a service..!')) + + def action_update_price_product(self): + """Updates the 'list_price' field of the current object + based on the value of the 'pack_price' field on button click.""" + self.list_price = self.pack_price + + def action_get_quantity(self): + """It is to return the pack quantity.""" + total_quantity = 1 + flag = 1 + while flag: + for line in self.pack_products_ids: + if line.qty_available >= line.quantity * total_quantity: + continue + else: + if line.product_id.type != 'product': + continue + flag = 0 + break + if flag: + total_quantity = total_quantity + 1 + self.pack_quantity = total_quantity - 1 + + def action_update_quantity(self): + """It is to return the updated pack quantity.""" + product_id = len( + self.product_variant_ids) == 1 and self.product_variant_id.id + location_id = self.pack_location_id.id + if not location_id: + warehouse = self.env['stock.warehouse'].search( + [('company_id', '=', self.env.company.id)], limit=1) + location_id = warehouse.lot_stock_id.id + if not location_id: + raise UserError(_( + 'You need to select the location to update' + ' the pack quantity...!')) + self.env['stock.quant'].with_context(inventory_mode=True).sudo( + ).create({ + 'product_id': product_id, + 'location_id': location_id, + 'inventory_quantity': self.pack_quantity, + }) + + @api.onchange('pack_location_id') + def _onchange_pack_location_id(self): + """It is to change the available quantity based on location.""" + for line in self.pack_products_ids: + stock_quant = self.env['stock.quant'].search( + [('product_id', '=', line.product_id.id), + ('location_id', '=', self.pack_location_id.id)]) + if stock_quant: + line.total_available_quantity = stock_quant.quantity + else: + line.total_available_quantity = stock_quant.quantity diff --git a/all_in_one_sales_kit/models/res_config_settings.py b/all_in_one_sales_kit/models/res_config_settings.py new file mode 100644 index 000000000..f08c13cf2 --- /dev/null +++ b/all_in_one_sales_kit/models/res_config_settings.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Swetha Anand (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 . +# +############################################################################### +from odoo import api, fields, models + + +class ResConfigSettings(models.TransientModel): + """ This is to add new fields to the settings.res.config.settings is + inherited.""" + _inherit = 'res.config.settings' + + show_product_image_in_sale_report = fields.Boolean( + string="Show Product Image", default=False, + help="Enable Show Product Image") + sale_document_approve = fields.Boolean( + config_parameter='all_in_one_sales_kit.sale_document_approve', + string="Sale Document Approval", + help="Sale Approval") + product_restriction = fields.Boolean( + string='Out Of Stock Product Restriction', + help='Enable Out Of Stock Product Restriction') + check_stock = fields.Selection( + [('on_hand_quantity', 'On Hand Quantity'), + ('forecast_quantity', 'Forecast Quantity')], string="Based On", + help='Choose the type of restriction') + automate_invoice = fields.Boolean( + string='Create Invoice', default=False, + help="Create invoices for sales order") + automate_validate_invoice = fields.Boolean( + string='Validate Invoice', default=False, + help="Automate validation of invoice") + automate_print_invoices = fields.Boolean( + string='Print Invoices', default=False, + help="Print invoice from corresponding sales order") + + @api.model + def set_values(self): + """The function set_values() is to store the new fields values.""" + self.env['ir.config_parameter'].sudo().set_param( + 'sale_product_image.show_product_image_in_sale_report', + self.show_product_image_in_sale_report) + self.env['ir.config_parameter'].sudo().set_param( + 'sale_stock_restrict.product_restriction', + self.product_restriction) + self.env['ir.config_parameter'].sudo().set_param( + 'sale_stock_restrict.check_stock', self.check_stock) + self.env['ir.config_parameter'].sudo().set_param( + 'automate_print_invoices', self.automate_print_invoices) + self.env['ir.config_parameter'].sudo().set_param( + 'automate_invoice', self.automate_invoice) + self.env['ir.config_parameter'].sudo().set_param( + 'automate_validate_invoice', self.automate_validate_invoice) + res = super(ResConfigSettings, self).set_values() + return res + + def get_values(self): + """Show the new field values.""" + res = super(ResConfigSettings, self).get_values() + ir_config_param = self.env['ir.config_parameter'].sudo().get_param + res.update( + show_product_image_in_sale_report=ir_config_param( + 'sale_product_image.show_product_image_in_sale_report', + self.show_product_image_in_sale_report), + product_restriction=ir_config_param( + 'sale_stock_restrict.product_restriction'), + check_stock=ir_config_param('sale_stock_restrict.check_stock'), + automate_print_invoices=ir_config_param('automate_print_invoices'), + automate_invoice=ir_config_param('automate_invoice'), + automate_validate_invoice=ir_config_param( + 'automate_validate_invoice'), + ) + return res diff --git a/all_in_one_sales_kit/models/res_partner.py b/all_in_one_sales_kit/models/res_partner.py new file mode 100644 index 000000000..cbb2c2705 --- /dev/null +++ b/all_in_one_sales_kit/models/res_partner.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Swetha Anand (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 . +# +############################################################################### +from odoo import models + + +class ResPartner(models.Model): + """res.partner is inherited.""" + _inherit = 'res.partner' + + def action_view_sale_order(self): + """This is to add a new pivot view to customer to show + their sale orders.""" + action = self.env['ir.actions.act_window']._for_xml_id( + 'sale.act_res_partner_2_sale_order') + all_child = self.with_context(active_test=False).search( + [('id', 'child_of', self.ids)]) + action["domain"] = [("partner_id", "in", all_child.ids)] + action["view_mode"] = "tree,kanban,form,graph,pivot" + action["views"] = [(False, 'tree'), (False, 'kanban'), + (False, 'form'), (False, 'graph'), (False, 'pivot')] + return action diff --git a/all_in_one_sales_kit/models/res_users.py b/all_in_one_sales_kit/models/res_users.py new file mode 100644 index 000000000..76d6aac6d --- /dev/null +++ b/all_in_one_sales_kit/models/res_users.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Swetha Anand (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 . +# +############################################################################### +from odoo import fields, models + + +class ResUsers(models.Model): + """Here res.users is inherited to add two fields.""" + + _inherit = 'res.users' + + discount_control = fields.Boolean( + string='Discount Control', default=False, + help="Enable to set a discount limit value.") + allow_discount = fields.Float( + string='Allow Discount', + help="Enter the discount limit in percentage here.") diff --git a/all_in_one_sales_kit/models/sale_order.py b/all_in_one_sales_kit/models/sale_order.py new file mode 100644 index 000000000..4c001530f --- /dev/null +++ b/all_in_one_sales_kit/models/sale_order.py @@ -0,0 +1,492 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Swetha Anand (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 . +# +############################################################################### +import itertools +from odoo import api, fields, models, _ +from odoo.exceptions import UserError + + +class SaleOrder(models.Model): + """Inherits Sales""" + _inherit = "sale.order" + + is_version = fields.Boolean(string="Is Version", + help="For checking version or not") + version_count = fields.Integer(string="Sale Version Count", + compute='_compute_version_count', + help="Count of version created") + current_version_id = fields.Many2one("sale.order", + string="Current Version", + help="For creating versions") + version_ids = fields.One2many("sale.order", + string="Version", + inverse_name="current_version_id", + help="Versions created") + quotation_ref = fields.Char(string='Quotation Reference', + copy=False, readonly=True, tracking=True, + help="Quotation Reference") + state = fields.Selection( + selection_add=[('waiting_for_approval', 'Waiting For Approval'), + ('sale',)]) + approval_user_id = fields.Many2one('res.users', + string='Discount Approved By', + help="Discount approving person.") + onhand_check = fields.Boolean(string='Enable OnHand', + help='To check whether it is based on' + ' on hand quantity') + forecast_check = fields.Boolean(string='Enable Forecast', + help='To check whether it is based on' + ' Forecast quantity') + automate_print_invoices = fields.Boolean( + string='Print Invoices', + help="Print invoices for corresponding sale orders") + signature = fields.Binary(string='Signature', + help="Field for adding " + "the signature of the " + "sales person") + check_signature = fields.Boolean(compute='_compute_check_signature', + help="To check signature approval is " + "needed") + settings_approval = fields.Boolean(compute='_compute_settings_approval', + help="To check signature approval is " + "enabled in settings") + active = fields.Boolean(string='Active', help='Active', default=True) + user_salesperson = fields.Boolean(string="User Salesperson", + compute="_compute_user_salesperson", + help="Check if user is salesperson") + + @api.depends('user_salesperson') + def _compute_user_salesperson(self): + """Computes the user_salesperson field based on login user""" + for rec in self: + if rec.user_id == rec.env.user: + rec.user_salesperson = True + else: + rec.user_salesperson = False + + @api.depends('signature') + def _compute_check_signature(self): + """In this function computes the value of + the boolean field check signature + which is used to hide/unhide the validate + button in the current document""" + if self.env['ir.config_parameter'].sudo().get_param( + 'all_in_one_sales_kit.sale_document_approve'): + if self.signature: + self.check_signature = True + else: + self.check_signature = False + else: + self.check_signature = True + + def action_create_versions(self): + """For creating the versions of the sale order""" + sale_order_copy_id = self.copy() + sale_order_copy_id.is_version = True + length = len(self.version_ids) + sale_order_copy_id.name = "%s-%s" % (self.name, str(length + 1)) + + self.write({'version_ids': [(4, sale_order_copy_id.id)]}) + + @api.depends('version_ids') + def _compute_version_count(self): + """For calculating the number of versions created""" + for sale in self: + sale.version_count = len(sale.version_ids) + + @api.depends('partner_id') + def _compute_settings_approval(self): + """Computes the settings_approval field based on settings field.""" + for rec in self: + if rec.env['ir.config_parameter'].sudo().get_param( + 'all_in_one_sales_kit.sale_document_approve'): + rec.settings_approval = True + else: + rec.settings_approval = False + + def action_view_versions(self): + """Action for viewing versions""" + action = { + "type": "ir.actions.act_window", + "view_mode": "kanban,tree,form", + "name": _("Sale Order Versions"), + "res_model": self._name, + "domain": [('id', 'in', self.version_ids.ids)], + "target": "current", + } + return action + + def action_confirm(self): + """Override the confirm button of the sale order for cancelling the + other versions and making the current version main,also method for + confirming the sale order discount and sending mail for the approving + person if approval limit crossed.Super the method create to confirm + quotation, create and validate invoice""" + res = super().action_confirm() + automate_invoice = self.env[ + 'ir.config_parameter'].sudo().get_param( + 'automate_invoice') + automate_print_invoices = self.env[ + 'ir.config_parameter'].sudo().get_param( + 'automate_print_invoices') + automate_validate_invoice = self.env[ + 'ir.config_parameter'].sudo().get_param( + 'automate_validate_invoice') + if automate_print_invoices: + self.automate_print_invoices = True + if automate_invoice: + self._create_invoices() + if automate_validate_invoice: + self.invoice_ids.action_post() + if not self.version_ids: + parent_sale = self.current_version_id + versions = parent_sale.mapped('version_ids').ids + if versions: + versions.append(parent_sale.id) + for version in parent_sale.version_ids: + if version.state == 'sale': + # Updating the version name into main version name and + # other versions state into cancel + version.current_version_id.update({'is_version': True, + 'state': 'cancel'}) + version.update({'version_ids': versions, + "name": version.current_version_id.name, + 'is_version': False}) + if version.state == 'draft': + version.update({'state': 'cancel'}) + else: + if self.state == 'sale': + for sale in self.version_ids: + sale.update({'state': 'cancel'}) + low_qty = ["Can't confirm the sale order due to: \n"] + for rec in self.order_line: + product_restriction = self.env[ + 'ir.config_parameter'].sudo().get_param( + 'sale_stock_restrict.product_restriction') + check_stock = self.env[ + 'ir.config_parameter'].sudo().get_param( + 'sale_stock_restrict.check_stock') + if product_restriction: + if rec.product_id.detailed_type == 'product': + if check_stock == 'on_hand_quantity': + if rec.product_uom_qty > rec.qty_available: + self.onhand_check = True + onhand_qty_list = "You have added %s units of %s" \ + " but you only have %s units" \ + " available.\n" % ( + rec.product_uom_qty, + rec.product_id.name, + rec.qty_available) + low_qty.append(onhand_qty_list) + + if check_stock == 'forecast_quantity': + if rec.product_uom_qty > rec.forecast_quantity: + self.forecast_check = True + forecast_qty_list = "You have added %s" \ + " units of %s but " \ + "you only have" \ + " %s units available.\n" % ( + rec.product_uom_qty, + rec.product_id.name, + rec.forecast_quantity) + low_qty.append(forecast_qty_list) + listToStr = ' '.join(map(str, low_qty)) + if self.onhand_check: + raise UserError(listToStr) + if self.forecast_check: + raise UserError(listToStr) + to_approve = False + discount_vals = self.order_line.mapped('discount') + approval_users = self.env.ref( + 'all_in_one_sales_kit.group_approval_manager').users + user_discount = self.env.user.allow_discount + if self.env.user.discount_control == True: + for rec in discount_vals: + if rec > user_discount: + to_approve = True + break + if to_approve: + display_id = self.id + action_id = self.env.ref( + 'sale.action_quotations_with_onboarding').id + base_url = self.env['ir.config_parameter'].sudo().get_param( + 'web.base.url') + redirect_link = "/web#id=%s&cids=1&menu_id=178&action=%s" \ + "&model" \ + "=sale.order&view_type=form" % ( + display_id, action_id) + url = base_url + redirect_link + for user in approval_users: + mail_body = """ +

Hello,

+

New sale order '%s' + Created with Discount by '%s' + need your approval on it.

+

To Approve, Cancel Order, + Click on the Following + Link: + + Click Me +

+

Thank You.

""" % (self.name, + self.env.user.name, + url) + mail_values = { + 'subject': "'%s' Discount Approval Request" % (self.name), + 'body_html': mail_body, + 'email_to': user.partner_id.email, + 'model': 'sale.order', + } + mail_id = self.env['mail.mail'].sudo().create(mail_values) + mail_id.sudo().send() + self.state = 'waiting_for_approval' + for line in self.order_line: + if line.product_id.is_pack: + for record in line.product_id.pack_products_ids: + dest_loc = self.env.ref( + 'stock.stock_location_customers').id + self.env['stock.move'].create({ + 'name': record.product_id.name, + 'product_id': record.product_id.id, + 'product_uom_qty': + record.quantity * line.product_uom_qty, + 'product_uom': record.product_id.uom_id.id, + 'picking_id': self.picking_ids[0].id, + 'location_id': + self.picking_ids.picking_type_id.default_location_src_id.id, + 'location_dest_id': dest_loc, + }) + return res + + @api.model + def create(self, vals): + """Method for generating sequence for quotation """ + res = super(SaleOrder, self).create(vals) + seq_val = self.env.ref( + 'all_in_one_sales_kit.seq_quotation').id + res.quotation_ref = self.env['ir.sequence'].browse( + seq_val).next_by_id() + return res + + def action_waiting_approval(self): + """Method for approving the sale order discount""" + self.approval_user_id = self.env.user.id + self.state = 'sale' + + def action_print_invoice(self): + """Method to print invoice""" + data = self.invoice_ids + return self.env.ref('account.account_invoices').report_action(data) + + @api.model + def get_data(self): + """To get data to the sales dashboard.""" + domain = [('user_id', '=', self.env.user.id)] + quotation = self.env['sale.order'].search( + domain + [('state', '=', 'draft')]) + my_sale_order_templates = self.env['sale.order'].search( + domain + [('state', '=', 'sale')]) + quotation_sent = self.env['sale.order'].search( + domain + [('state', '=', 'sent')]) + quotation_cancel = self.env['sale.order'].search( + domain + [('state', '=', 'cancel')]) + customers = self.env['res.partner'].search([]) + to_invoice = self.env['sale.order'].search( + domain + [('invoice_status', '=', 'to invoice')]) + products = self.env['product.template'].search([]) + return { + 'quotation': len(quotation), + 'my_sale_order_templates': len(my_sale_order_templates), + 'quotation_sent': len(quotation_sent), + 'quotation_cancel': len(quotation_cancel), + 'customers': len(customers), + 'products': len(products), + 'to_invoice': len(to_invoice), + } + + @api.model + def get_value(self, start_date, end_date): + """It is to pass values according to start and end date to the + dashboard.""" + domain = [('user_id', '=', self.env.user.id), + ('date_order', '>=', start_date), + ('date_order', '<=', end_date)] + if start_date and end_date: + quotation = self.env['sale.order'].search( + domain + [('state', '=', 'draft')]) + my_sale_order_templates = self.env['sale.order'].search( + domain + [('state', '=', 'sale')]) + quotation_sent = self.env['sale.order'].search( + domain + [('state', '=', 'sent')]) + quotation_cancel = self.env['sale.order'].search( + domain + [('state', '=', 'cancel')]) + customers = self.env['res.partner'].search([]) + products = self.env['product.template'].search([]) + to_invoice = self.env['sale.order'].search( + domain + [('invoice_status', '=', 'to invoice')]) + elif start_date: + quotation = self.env['sale.order'].search( + domain + [('state', '=', 'draft')]) + my_sale_order_templates = self.env['sale.order'].search( + domain + [('state', '=', 'sale')]) + quotation_sent = self.env['sale.order'].search( + domain + [('state', '=', 'sent')]) + quotation_cancel = self.env['sale.order'].search( + domain + [('state', '=', 'cancel')]) + customers = self.env['res.partner'].search([]) + products = self.env['product.template'].search([]) + to_invoice = self.env['sale.order'].search( + domain + [('invoice_status', '=', 'to invoice')]) + elif end_date: + quotation = self.env['sale.order'].search( + domain + [('state', '=', 'draft')]) + my_sale_order_templates = self.env['sale.order'].search( + domain + [('state', '=', 'sale')]) + quotation_sent = self.env['sale.order'].search( + domain + [('state', '=', 'sent')]) + quotation_cancel = self.env['sale.order'].search( + domain + [('state', '=', 'cancel')]) + customers = self.env['res.partner'].search([]) + products = self.env['product.template'].search([]) + to_invoice = self.env['sale.order'].search( + domain + [('invoice_status', '=', 'to invoice')]) + return { + 'quotation': len(quotation), + 'my_sale_order_templates': len(my_sale_order_templates), + 'quotation_sent': len(quotation_sent), + 'quotation_cancel': len(quotation_cancel), + 'customers': len(customers), + 'products': len(products), + 'to_invoice': len(to_invoice), + } + + @api.model + def get_lead_customer(self): + """Returns customer data to the graph of dashboard""" + lead_template = {} + sale = {} + partner_id = self.env['res.partner'].sudo().search([]) + vals = self.env['sale.order'].sudo().search([ + ]).mapped('partner_id').ids + for record in partner_id: + if record.id in vals: + record.ref = vals.count(record.id) + sale.update({record: vals.count(record.id)}) + sort = dict( + sorted(sale.items(), key=lambda item: item[1], reverse=True)) + out = dict(itertools.islice(sort.items(), 10)) + for count in out: + lead_template[count.name] = out[count] + return { + 'lead_templates': lead_template, + } + + @api.model + def get_lead_product(self): + """Returns product data to the graph of dashboard""" + lead_template = {} + sale = {} + product_id = self.env['product.template'].search([]) + for record in product_id: + sale.update({record: record.sales_count}) + sort = dict( + sorted(sale.items(), key=lambda item: item[1], reverse=True)) + out = dict(itertools.islice(sort.items(), 10)) + for product in out: + lead_template[product.name] = out[product] + return { + 'lead_templates': lead_template, + } + + @api.model + def get_lead_order(self): + """Returns lead sale order data to the graph of dashboard""" + lead_template = {} + sale = {} + order_id = self.env['sale.order'].search([('state', '=', 'sale')]) + for record in order_id: + sale.update({record: record.amount_total}) + sort = dict( + sorted(sale.items(), key=lambda item: item[1], reverse=True)) + out = dict(itertools.islice(sort.items(), 10)) + for order in out: + lead_template[order.name] = out[order] + return { + 'lead_templates': lead_template, + } + + @api.model + def get_my_monthly_comparison(self): + """Returns my monthly sale count data to the graph of dashboard""" + lead_template = {} + sales_order = self.env['sale.order'].search( + [('user_id', '=', self.env.user.id)]) + list = [rec.date_order.month for rec in sales_order] + for i in range(1, 13): + count = list.count(i) + lead_template.update({ + i: count + }) + return { + 'lead_templates': lead_template, + } + + @api.model + def get_sales_team(self): + """Returns sales team data to the graph of dashboard""" + lead_template = {} + sale = {} + sales_team = self.env['crm.team'].search([]) + for record in sales_team: + total = sum(self.env['sale.order'].search( + [('state', '=', 'sale'), + ('team_id', '=', record.id)]).mapped('amount_total')) + sale.update({record: total}) + sort = dict( + sorted(sale.items(), key=lambda item: item[1], reverse=True)) + out = dict(itertools.islice(sort.items(), 10)) + for team in out: + lead_template[team.name] = out[team] + return { + 'lead_templates': lead_template, + } + + @api.model + def get_least_sold(self): + """Returns least sold product data to the graph of dashboard""" + lead_template = {} + sale = {} + product_id = self.env['product.template'].search([]) + for record in product_id: + if record.sales_count != 0: + sale.update({record: record.sales_count}) + sort = dict( + sorted(sale.items(), key=lambda item: item[1], reverse=False)) + out = dict(itertools.islice(sort.items(), 10)) + for product in out: + lead_template[product.name] = out[product] + return { + 'lead_templates': lead_template, + } diff --git a/all_in_one_sales_kit/models/sale_order_line.py b/all_in_one_sales_kit/models/sale_order_line.py new file mode 100644 index 000000000..f4c9c0584 --- /dev/null +++ b/all_in_one_sales_kit/models/sale_order_line.py @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Swetha Anand (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 . +# +############################################################################### +from odoo import api, fields, models, _ +from odoo.tools import float_compare + + +class SaleOrderLine(models.Model): + """Inherits sale.order.line.""" + _inherit = 'sale.order.line' + + sale_date = fields.Datetime(comodel_name='sale.order', string='Sale Date', + related='order_id.date_order', store=True, + help="Sale order date") + product_warehouse_id = fields.Many2one( + 'stock.warehouse', + string='Warehouse', help='Warehouses') + order_line_image = fields.Binary(string="Image", + related="product_id.image_1920", + help="Product image should be shown or " + "not.") + contact_email = fields.Char(string="Email", + related="order_partner_id.email", + help="Email of the customer.") + contact_phone = fields.Char(string="Phone no.", + related="order_partner_id.phone", + help="Phone no. of the customer.") + qty_available = fields.Float(string="On Hand Quantity", + help='Count of On Hand quantity') + forecast_quantity = fields.Float(string="Forecast Quantity", + help='Count of Forecast quantity') + discount = fields.Float(string='Discount (%)', + digits=(16, 2), default=0.0, + help="Discount in percentage.") + total_discount = fields.Float(string="Total Discount", + default=0.0, store=True, + help="Total Discount.") + barcode_scan = fields.Char(string='Product Barcode', + help="Here you can provide " + "the barcode for the product") + + def action_get_product_form(self): + """ + This method returns an action that opens a form view for a specific product. + It sets the order partner ID based on the order's partner ID and constructs an action + to open the product's form view with the product's details. + :return: Dictionary representing an action to open the product's form view. + :rtype: dict + """ + self.product_id.order_partner_id = self.order_id.partner_id.id + return { + 'name': self.product_id.name, + 'view_mode': 'form', + 'res_model': 'product.product', + 'type': 'ir.actions.act_window', + 'target': 'current', + 'res_id': self.product_id.id + } + + def _action_launch_stock_rule(self, previous_product_uom_qty=False): + """ + Overwriting the function for adding functionalities of + multiple warehouses in the sale order line. + param previous_product_uom_qty(str): + Uom quantity of previous product + boolean: Returns True, if the picking created. + """ + if self._context.get("skip_procurement"): + return True + precision = self.env['decimal.precision'].precision_get( + 'Product Unit of Measure') + procurements = [] + for line in self: + line = line.with_company(line.company_id) + if line.state != 'sale' or not line.product_id.type in ( + 'consu', 'product'): + continue + qty = line._get_qty_procurement(previous_product_uom_qty) + if float_compare(qty, line.product_uom_qty, + precision_digits=precision) == 0: + continue + group_id = line._get_procurement_group() + if not group_id: + group_id = self.env['procurement.group'].create( + line._prepare_procurement_group_vals()) + line.order_id.procurement_group_id = group_id + else: + updated_vals = {} + if group_id.partner_id != line.order_id.partner_shipping_id: + updated_vals.update( + {'partner_id': line.order_id.partner_shipping_id.id}) + if group_id.move_type != line.order_id.picking_policy: + updated_vals.update( + {'move_type': line.order_id.picking_policy}) + if updated_vals: + group_id.write(updated_vals) + values = line._prepare_procurement_values(group_id=group_id) + #replacing default warehouse_id into product_warehouse_id in the + #sale order line and adding it into procurement values. + if line.product_warehouse_id: + values['warehouse_id'] = line.product_warehouse_id + product_qty = line.product_uom_qty - qty + line_uom = line.product_uom + quant_uom = line.product_id.uom_id + product_qty, procurement_uom = line_uom._adjust_uom_quantities( + product_qty, quant_uom) + procurements.append(self.env['procurement.group'].Procurement( + line.product_id, product_qty, procurement_uom, + line.order_id.partner_shipping_id.property_stock_customer, + line.product_id.display_name, line.order_id.name, + line.order_id.company_id, values)) + if procurements: + self.env['procurement.group'].run(procurements) + orders = self.mapped('order_id') + for order in orders: + pickings_to_confirm = order.picking_ids.filtered( + lambda p: p.state not in ['cancel', 'done']) + if pickings_to_confirm: + pickings_to_confirm.action_confirm() + return True + + @api.onchange('product_id') + def _onchange_product_id(self): + """it is to check product stock according th the chosen product + restriction.""" + product_restriction = self.env['ir.config_parameter'].sudo().get_param( + 'sale_stock_restrict.product_restriction') + check_stock = self.env[ + 'ir.config_parameter'].sudo().get_param( + 'sale_stock_restrict.check_stock') + if product_restriction: + if check_stock == 'on_hand_quantity': + self.qty_available = self.product_id.qty_available + if check_stock == 'forecast_quantity': + self.forecast_quantity = self.product_id.virtual_available + + @api.onchange('barcode_scan') + def _onchange_barcode_scan(self): + """It is to add the scanned products to the order line.""" + product_rec = self.env['product.product'] + if self.barcode_scan: + product = product_rec.search([('barcode', '=', self.barcode_scan)]) + self.product_id = product.id + + def action_get_product_history_data(self): + """It is to pass previous history of the chosen product for that + customer.""" + values = [] + customer_id = self.order_id.partner_id + customer_order = self.env['sale.order'].search( + [('partner_id', '=', customer_id.id), ( + 'state', 'in', ('sale', 'done'))]) + for order in customer_order: + for line in order.order_line: + if line.product_id == self.product_id: + values.append((0, 0, {'sale_order_id': order.id, + 'history_price': line.price_unit, + 'history_qty': line.product_uom_qty, + 'history_total': order.amount_total + })) + history_id = self.env['product.sale.order.history'].create({ + 'product_id': self.product_id.id, + 'product_sale_history_ids': values + }) + return { + 'name': 'Customer Product Sales History', + 'view_mode': 'form', + 'res_model': 'product.sale.order.history', + 'type': 'ir.actions.act_window', + 'target': 'new', + 'res_id': history_id.id + } + + def action_add_catalog_control(self): + """It is to add function to the button catalog.""" + return { + 'type': 'ir.actions.act_window', + 'name': _('Products'), + 'context': {'order_id': self.env.context.get('id')}, + 'res_model': 'product.product', + 'view_mode': 'kanban,tree,form', + 'target': 'current', + } diff --git a/all_in_one_sales_kit/models/sales_report.py b/all_in_one_sales_kit/models/sales_report.py new file mode 100644 index 000000000..b11cac70c --- /dev/null +++ b/all_in_one_sales_kit/models/sales_report.py @@ -0,0 +1,540 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2022-TODAY Cybrosys Technologies(). +# +# 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 . +# +############################################################################# +import io +from odoo import api, fields, models + +try: + from odoo.tools.misc import xlsxwriter +except ImportError: + import xlsxwriter + + +class SalesReport(models.Model): + """A new class sales.report is created.""" + _name = "sales.report" + _description = "Sales Report" + + sale_report = fields.Char(string="Sale Report", help="Sale Report") + date_from = fields.Datetime(string="Date From", + help="Date from which the report should be " + "viewed.") + date_to = fields.Datetime(string="Date to", + help="Date to which the report should be viewed.") + report_type = fields.Selection([ + ('report_by_order', 'Report By Order'), + ('report_by_order_detail', 'Report By Order Detail'), + ('report_by_product', 'Report By Product'), + ('report_by_categories', 'Report By Categories'), + ('report_by_salesperson', 'Report By Sales Person'), + ('report_by_state', 'Report By State')], default='report_by_order', + help="Report type", string="Report type") + + @api.model + def sale_report(self, option): + """This is to pass values to the sales report.""" + report_values = self.env['sales.report'].browse(option[0]) + data = { + 'report_type': report_values.report_type, + 'model': self, + } + if report_values.date_from: + data.update({ + 'date_from': report_values.date_from, + }) + if report_values.date_to: + data.update({ + 'date_to': report_values.date_to, + }) + filters = self.get_filter(option) + lines = self._get_report_values(data).get('SALE') + main_line = self._get_report_values(data).get('sale_main') + return { + 'name': "Sale Orders", + 'type': 'ir.actions.client', + 'tag': 's_r', + 'orders': data, + 'filters': filters, + 'report_lines': lines, + 'report_main_line': main_line, + } + + def get_filter(self, option): + """It is to get value for the chosen filter.""" + data = self.get_filter_data(option) + filters = {} + if data.get('report_type') == 'report_by_order': + filters['report_type'] = 'Report By Order' + elif data.get('report_type') == 'report_by_order_detail': + filters['report_type'] = 'Report By Order Detail' + elif data.get('report_type') == 'report_by_product': + filters['report_type'] = 'Report By Product' + elif data.get('report_type') == 'report_by_categories': + filters['report_type'] = 'Report By Categories' + elif data.get('report_type') == 'report_by_salesperson': + filters['report_type'] = 'Report By Sales Person' + elif data.get('report_type') == 'report_by_state': + filters['report_type'] = 'Report By State' + else: + filters['report_type'] = 'report_by_order' + return filters + + def get_filter_data(self, option): + """It is to get data according to the filter selected.""" + filter_dict = { + 'report_type': self.env['sales.report'].browse( + option[0]).report_type, + } + return filter_dict + + @api.model + def create(self, vals): + """It super create.""" + res = super(SalesReport, self).create(vals) + return res + + def write(self, vals): + """It super write.""" + res = super(SalesReport, self).write(vals) + return res + + def _get_report_sub_lines(self, data): + """This is to get the table lines according to the filter.""" + report_sub_lines = [] + if data.get('report_type') == 'report_by_order': + query = ''' + select so.id,so.name as number,so.date_order, + so.partner_id,so.amount_total, + so.user_id,res_partner.name as customer, + res_users.partner_id as user_partner,so.id as id, + sum(sale_order_line.product_uom_qty), + (SELECT res_partner.name as sales_man + FROM res_partner + WHERE res_partner.id = res_users.partner_id) + from sale_order as so + inner join res_partner + on so.partner_id = res_partner.id + inner join res_users + on so.user_id = res_users.id + inner join sale_order_line + on so.id = sale_order_line.order_id + ''' + term = 'Where ' + if data.get('date_from'): + query += "Where so.date_order >= '%s' " % data.get('date_from') + term = 'AND ' + if data.get('date_to'): + query += term + "so.date_order <= '%s' " % data.get('date_to') + query += "group by so.user_id,res_users.partner_id," \ + "res_partner.name,so.partner_id,so.date_order," \ + "so.name,so.amount_total,so.id" + self._cr.execute(query) + report_by_order = self._cr.dictfetchall() + report_sub_lines.append(report_by_order) + elif data.get('report_type') == 'report_by_order_detail': + query = ''' + SELECT so.id,so.name as number,so.date_order, + res_partner.name as customer, + rc.name as company, + product_template.name as product, + product_product.default_code, + so_line.product_uom_qty, + so_line.price_subtotal,so.amount_total, + so.partner_id, + so.user_id,ru.id,so_line.product_id, + sum(so_line.product_uom_qty), + (SELECT res_partner.name as salesman + FROM res_partner + WHERE res_partner.id = res_users.partner_id) + from sale_order as so + inner join sale_order_line as so_line + on so.id = so_line.order_id + inner join product_product + ON so_line.product_id=product_product.id + inner join product_template + ON product_product.product_tmpl_id = + product_template.id + inner join res_partner + on so.partner_id=res_partner.id + inner join res_users on so.user_id = res_users.id + inner join res_company as rc on so.company_id=rc.id + inner join res_users as ru on so.user_id=ru.id + ''' + term = 'Where ' + if data.get('date_from'): + query += "Where so.date_order >= '%s' " % data.get('date_from') + term = 'AND ' + if data.get('date_to'): + query += term + "so.date_order <= '%s' " % data.get('date_to') + query += ''' group by so.user_id, so.name, so.id,so.date_order, + res_partner.name,rc.name,product_template.name, + product_product.default_code,so_line.product_uom_qty, + so_line.price_subtotal,so.amount_total,so.partner_id, + so.user_id,ru.id,so_line.product_id, + res_users.partner_id + ''' + self._cr.execute(query) + report_by_order_details = self._cr.dictfetchall() + report_sub_lines.append(report_by_order_details) + elif data.get('report_type') == 'report_by_product': + query = ''' + SELECT so.id,so.date_order, + product_template.name as product, + product_category.name as category, + product_product.default_code, + so_line.product_uom_qty, + so.amount_total,so.name as number + From sale_order as so + inner join sale_order_line + as so_line on so.id = so_line.order_id + inner join product_product + ON so_line.product_id=product_product.id + inner join product_template + ON product_product.product_tmpl_id = + product_template.id + inner join product_category + on product_category.id = product_template.categ_id + ''' + term = 'Where ' + if data.get('date_from'): + query += "Where so.date_order >= '%s' " % data.get('date_from') + term = 'AND ' + if data.get('date_to'): + query += term + "so.date_order <= '%s' " % data.get('date_to') + query += "group by so.id,so.date_order,product_template.name," \ + "product_category.name,product_product.default_code," \ + "so_line.product_uom_qty" + self._cr.execute(query) + report_by_product = self._cr.dictfetchall() + report_sub_lines.append(report_by_product) + elif data.get('report_type') == 'report_by_categories': + query = ''' + select product_category.name, + sum(so_line.product_uom_qty) as qty, + sum(so_line.price_subtotal) as amount_total + from sale_order_line as so_line + inner join product_template + on so_line.product_id = product_template.id + inner join product_category + on product_category.id = product_template.categ_id + inner join sale_order + on so_line.order_id = sale_order.id + ''' + term = 'Where ' + if data.get('date_from'): + query += "Where sale_order.date_order >= '%s' " % data.get( + 'date_from') + term = 'AND ' + if data.get('date_to'): + query += term + "sale_order.date_order <= '%s' " % data.get( + 'date_to') + query += "group by product_category.name" + self._cr.execute(query) + report_by_categories = self._cr.dictfetchall() + report_sub_lines.append(report_by_categories) + elif data.get('report_type') == 'report_by_salesperson': + query = ''' + select res_partner.name, + sum(sale_order_line.product_uom_qty) as qty, + sum(sale_order_line.price_subtotal) as amount, + count(so.id) as order + from sale_order as so + inner join res_users + on so.user_id = res_users.id + inner join res_partner + on res_users.partner_id = res_partner.id + inner join sale_order_line + on so.id = sale_order_line.order_id + ''' + term = 'Where ' + if data.get('date_from'): + query += "Where so.date_order >= '%s' " % data.get('date_from') + term = 'AND ' + if data.get('date_to'): + query += term + "so.date_order <= '%s' " % data.get('date_to') + query += "group by res_partner.name" + self._cr.execute(query) + report_by_salesperson = self._cr.dictfetchall() + report_sub_lines.append(report_by_salesperson) + elif data.get('report_type') == 'report_by_state': + query = ''' + select so.state,count(so.id), + sum(sale_order_line.product_uom_qty) as qty, + sum(sale_order_line.price_subtotal) + as amount from sale_order as so + inner join sale_order_line + on so.id = sale_order_line.order_id + ''' + term = 'Where ' + if data.get('date_from'): + query += "Where so.date_order >= '%s' " % data.get('date_from') + term = 'AND ' + if data.get('date_to'): + query += term + "so.date_order <= '%s' " % data.get('date_to') + query += "group by so.state" + self._cr.execute(query) + report_by_state = self._cr.dictfetchall() + report_sub_lines.append(report_by_state) + return report_sub_lines + + def _get_report_total_value(self, data, report): + """It is to pass total for each report type.""" + report_main_lines = [] + if data.get('report_type') == 'report_by_order': + self._cr.execute(''' + select count(so.id) as order,sum(so.amount_total) as amount + from sale_order as so + ''') + report_by_order = self._cr.dictfetchall() + report_main_lines.append(report_by_order) + elif data.get('report_type') == 'report_by_order_detail': + self._cr.execute(''' + select count(so_line.id) as order, + sum(so_line.price_subtotal) as total + from sale_order_line as so_line + ''') + report_by_order_detail = self._cr.dictfetchall() + report_main_lines.append(report_by_order_detail) + elif data.get('report_type') == 'report_by_product': + self._cr.execute(''' + select count(so_line.product_id) as order, + sum(so_line.price_subtotal) as amount + from sale_order_line as so_line + ''') + report_by_product = self._cr.dictfetchall() + report_main_lines.append(report_by_product) + else: + report_main_lines = False + return report_main_lines + + def _get_report_values(self, data): + """It is to pass sublines for report.""" + docs = data['model'] + if data['report_type'] == 'report_by_order_detail': + report = ['Report By Order Detail'] + elif data['report_type'] == 'report_by_product': + report = ['Report By Product'] + elif data['report_type'] == 'report_by_categories': + report = ['Report By Categories'] + elif data['report_type'] == 'report_by_salesperson': + report = ['Report By Sales Person'] + elif data['report_type'] == 'report_by_state': + report = ['Report By State'] + else: + report = ['Report By Order'] + report_res_total = self._get_report_total_value(data, report) + if data.get('report_type'): + report_res = self._get_report_sub_lines(data)[0] + else: + report_res = self._get_report_sub_lines(data) + if data.get('report_type') == 'report_by_order': + report_res_total = self._get_report_total_value(data, report)[0] + return { + 'doc_ids': self.ids, + 'docs': docs, + 'SALE': report_res, + 'sale_main': report_res_total, + } + + def get_xlsx_report(self, data, response): + """This function is to pass values to the xlsx report.""" + report_data_main = data['report_lines'] + output = io.BytesIO() + filters = data['filters'] + workbook = xlsxwriter.Workbook(output, {'in_memory': True}) + sheet = workbook.add_worksheet() + head = workbook.add_format({'align': 'center', 'bold': True, + 'font_size': '20px'}) + heading = workbook.add_format( + {'align': 'center', 'bold': True, 'font_size': '10px', + 'border': 2, + 'border_color': 'black'}) + txt_l = workbook.add_format( + {'font_size': '10px', 'border': 1, 'bold': True}) + sheet.merge_range('A2:H3', + 'Sales Report', + head) + if filters.get('report_type') == 'Report By Order': + sheet.merge_range('B5:D5', 'Report Type: ' + + filters.get('report_type'), txt_l) + sheet.write('A7', 'Sale', heading) + sheet.write('B7', 'Date Order', heading) + sheet.write('C7', 'Customer', heading) + sheet.write('D7', 'Sales Person', heading) + sheet.write('E7', 'Total Qty', heading) + sheet.write('F7', 'Amount Total', heading) + lst = [] + for rec in report_data_main[0]: + lst.append(rec) + row = 6 + col = 0 + sheet.set_column(3, 0, 15) + sheet.set_column(4, 1, 15) + sheet.set_column(5, 2, 15) + sheet.set_column(6, 3, 15) + sheet.set_column(7, 4, 15) + sheet.set_column(8, 5, 15) + for rec_data in report_data_main: + row += 1 + sheet.write(row, col, rec_data['number'], txt_l) + sheet.write(row, col + 1, rec_data['date_order'], txt_l) + sheet.write(row, col + 2, rec_data['customer'], txt_l) + sheet.write(row, col + 3, rec_data['sales_man'], txt_l) + sheet.write(row, col + 4, rec_data['sum'], txt_l) + sheet.write(row, col + 5, rec_data['amount_total'], txt_l) + if filters.get('report_type') == 'Report By Order Detail': + sheet.merge_range('B5:D5', 'Report Type: ' + + filters.get('report_type'), txt_l) + sheet.write('A7', 'Sale', heading) + sheet.write('B7', 'Date Order', heading) + sheet.write('C7', 'Customer', heading) + sheet.write('D7', 'Company', heading) + sheet.write('E7', 'Sales Person', heading) + sheet.write('F7', 'Product Name', heading) + sheet.write('G7', 'Product Code', heading) + sheet.write('H7', 'Quantity', heading) + sheet.write('I7', 'Price Subtotal', heading) + sheet.write('J7', 'Amount Total', heading) + lst = [] + for rec in report_data_main[0]: + lst.append(rec) + row = 6 + col = 0 + sheet.set_column(3, 0, 15) + sheet.set_column(4, 1, 15) + sheet.set_column(5, 2, 15) + sheet.set_column(6, 3, 15) + sheet.set_column(7, 4, 15) + sheet.set_column(8, 5, 15) + sheet.set_column(9, 6, 15) + sheet.set_column(10, 7, 15) + sheet.set_column(11, 8, 15) + sheet.set_column(12, 9, 15) + for rec_data in report_data_main: + row += 1 + product = [val for val in rec_data['product'].values()] + sheet.write(row, col, rec_data['number'], txt_l) + sheet.write(row, col + 1, rec_data['date_order'], txt_l) + sheet.write(row, col + 2, rec_data['customer'], txt_l) + sheet.write(row, col + 3, rec_data['company'], txt_l) + sheet.write(row, col + 4, rec_data['salesman'], txt_l) + sheet.write(row, col + 5, product[0], txt_l) + sheet.write(row, col + 6, rec_data['default_code'], txt_l) + sheet.write(row, col + 7, rec_data['product_uom_qty'], txt_l) + sheet.write(row, col + 8, rec_data['price_subtotal'], txt_l) + sheet.write(row, col + 9, rec_data['amount_total'], txt_l) + if filters.get('report_type') == 'Report By Product': + sheet.merge_range('B5:D5', 'Report Type: ' + + filters.get('report_type'), txt_l) + sheet.write('A7', 'Product', heading) + sheet.write('B7', 'Category', heading) + sheet.write('C7', 'Product Code', heading) + sheet.write('D7', 'Quantity', heading) + sheet.write('E7', 'Amount Total', heading) + lst = [] + for rec in report_data_main[0]: + lst.append(rec) + row = 6 + col = 0 + sheet.set_column(3, 0, 15) + sheet.set_column(4, 1, 15) + sheet.set_column(5, 2, 15) + sheet.set_column(6, 3, 15) + sheet.set_column(7, 4, 15) + for rec_data in report_data_main: + row += 1 + product = [val for val in rec_data['product'].values()] + sheet.write(row, col, product[0], txt_l) + sheet.write(row, col + 1, rec_data['category'], txt_l) + sheet.write(row, col + 2, rec_data['default_code'], txt_l) + sheet.write(row, col + 3, rec_data['product_uom_qty'], txt_l) + sheet.write(row, col + 4, rec_data['amount_total'], txt_l) + if filters.get('report_type') == 'Report By Categories': + sheet.merge_range('B5:D5', 'Report Type: ' + + filters.get('report_type'), txt_l) + sheet.write('B7', 'Category', heading) + sheet.write('C7', 'Qty', heading) + sheet.write('D7', 'Amount Total', heading) + lst = [] + for rec in report_data_main[0]: + lst.append(rec) + row = 6 + col = 1 + sheet.set_column(3, 1, 15) + sheet.set_column(4, 2, 15) + sheet.set_column(5, 3, 15) + for rec_data in report_data_main: + row += 1 + sheet.write(row, col, rec_data['name'], txt_l) + sheet.write(row, col + 1, rec_data['qty'], txt_l) + sheet.write(row, col + 2, rec_data['amount_total'], txt_l) + if filters.get('report_type') == 'Report By Sales Person': + sheet.merge_range('B5:D5', 'Report Type: ' + + filters.get('report_type'), txt_l) + sheet.write('A7', 'Sales Person', heading) + sheet.write('B7', 'Total Order', heading) + sheet.write('C7', 'Total Qty', heading) + sheet.write('D7', 'Total Amount', heading) + lst = [] + for rec in report_data_main[0]: + lst.append(rec) + row = 6 + col = 0 + sheet.set_column(3, 0, 15) + sheet.set_column(4, 1, 15) + sheet.set_column(5, 2, 15) + sheet.set_column(6, 3, 15) + for rec_data in report_data_main: + row += 1 + sheet.write(row, col, rec_data['name'], txt_l) + sheet.write(row, col + 1, rec_data['order'], txt_l) + sheet.write(row, col + 2, rec_data['qty'], txt_l) + sheet.write(row, col + 3, rec_data['amount'], txt_l) + if filters.get('report_type') == 'Report By State': + sheet.merge_range('B5:D5', 'Report Type: ' + + filters.get('report_type'), txt_l) + sheet.write('A7', 'State', heading) + sheet.write('B7', 'Total Count', heading) + sheet.write('C7', 'Quantity', heading) + sheet.write('D7', 'Amount', heading) + lst = [] + for rec in report_data_main[0]: + lst.append(rec) + row = 6 + col = 0 + sheet.set_column(3, 0, 15) + sheet.set_column(4, 1, 15) + sheet.set_column(5, 2, 15) + sheet.set_column(6, 3, 15) + for rec_data in report_data_main: + row += 1 + if rec_data['state'] == 'draft': + sheet.write(row, col, 'Quotation', txt_l) + elif rec_data['state'] == 'sent': + sheet.write(row, col, 'Quotation Sent', txt_l) + elif rec_data['state'] == 'sale': + sheet.write(row, col, 'Sale Order', txt_l) + sheet.write(row, col + 1, rec_data['count'], txt_l) + sheet.write(row, col + 2, rec_data['qty'], txt_l) + sheet.write(row, col + 3, rec_data['amount'], txt_l) + workbook.close() + output.seek(0) + response.stream.write(output.read()) + output.close() diff --git a/all_in_one_sales_kit/report/__init__.py b/all_in_one_sales_kit/report/__init__.py new file mode 100644 index 000000000..985490f9a --- /dev/null +++ b/all_in_one_sales_kit/report/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Swetha Anand (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 . +# +############################################################################### +from . import sale_order_report diff --git a/all_in_one_sales_kit/report/invoice_analysis_templates.xml b/all_in_one_sales_kit/report/invoice_analysis_templates.xml new file mode 100644 index 000000000..d1e3c75ad --- /dev/null +++ b/all_in_one_sales_kit/report/invoice_analysis_templates.xml @@ -0,0 +1,97 @@ + + + + + diff --git a/all_in_one_sales_kit/report/sale_order_document_templates.xml b/all_in_one_sales_kit/report/sale_order_document_templates.xml new file mode 100644 index 000000000..cca45206e --- /dev/null +++ b/all_in_one_sales_kit/report/sale_order_document_templates.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/all_in_one_sales_kit/report/sale_order_report.py b/all_in_one_sales_kit/report/sale_order_report.py new file mode 100644 index 000000000..bbada1a22 --- /dev/null +++ b/all_in_one_sales_kit/report/sale_order_report.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2022-TODAY Cybrosys Technologies(). +# +# 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 . +# +############################################################################# +from odoo import api, models + + +class SaleOrderReport(models.AbstractModel): + """It is to add new abstract model for sale_order_report.""" + _name = 'report.all_in_one_sales_kit.sale_order_report' + _description = "Sale Order Report" + + @api.model + def _get_report_values(self, docids, data=None): + """It is to pass report values.""" + if self.env.context.get('sale_order_report'): + if data.get('report_data'): + report_lines = data.get('report_data')['report_lines'] + total_amount = sum( + line.get('amount_total', 0) for line in report_lines) + data.update({'report_main_line_data': report_lines, + 'Filters': data.get('report_data')['filters'], + 'company': self.env.company, + 'total_amount': total_amount, + }) + return data diff --git a/all_in_one_sales_kit/report/sale_order_report_templates.xml b/all_in_one_sales_kit/report/sale_order_report_templates.xml new file mode 100644 index 000000000..4106a73e6 --- /dev/null +++ b/all_in_one_sales_kit/report/sale_order_report_templates.xml @@ -0,0 +1,397 @@ + + + + + + + + + + + diff --git a/all_in_one_sales_kit/report/sale_profit_templates.xml b/all_in_one_sales_kit/report/sale_profit_templates.xml new file mode 100644 index 000000000..4cf0e79c7 --- /dev/null +++ b/all_in_one_sales_kit/report/sale_profit_templates.xml @@ -0,0 +1,353 @@ + + + + + diff --git a/all_in_one_sales_kit/report/sale_reports.xml b/all_in_one_sales_kit/report/sale_reports.xml new file mode 100644 index 000000000..144e49b78 --- /dev/null +++ b/all_in_one_sales_kit/report/sale_reports.xml @@ -0,0 +1,59 @@ + + + + + Sales + sale.report.advance + qweb-pdf + all_in_one_sales_kit.sale_profit_report_view + all_in_one_sales_kit.sale_profit_report_view + + + + Invoice Analysis + sale.report.invoice + qweb-pdf + all_in_one_sales_kit.invoice_analysis_view + all_in_one_sales_kit.invoice_analysis_view + + + + Sales Category + sale.report.category + qweb-pdf + all_in_one_sales_kit.sales_category_view + all_in_one_sales_kit.sales_category_view + + + + Product Sales Indent + sale.report.indent + qweb-pdf + all_in_one_sales_kit.sales_indent_view + all_in_one_sales_kit.sales_indent_view + + + + Sales Analysis Report + sale.report.analysis + qweb-pdf + all_in_one_sales_kit.sales_analysis_view + all_in_one_sales_kit.sales_analysis_view + + + + Hourly Sales Report + sale.report.weekly + qweb-pdf + all_in_one_sales_kit.sales_hourly_view + all_in_one_sales_kit.sales_hourly_view + + + + Sales All In One Report + sales.report + qweb-pdf + all_in_one_sales_kit.sale_order_report + all_in_one_sales_kit.sale_order_report + + diff --git a/all_in_one_sales_kit/report/sales_analysis_templates.xml b/all_in_one_sales_kit/report/sales_analysis_templates.xml new file mode 100644 index 000000000..bddedc67b --- /dev/null +++ b/all_in_one_sales_kit/report/sales_analysis_templates.xml @@ -0,0 +1,173 @@ + + + + + diff --git a/all_in_one_sales_kit/report/sales_category_templates.xml b/all_in_one_sales_kit/report/sales_category_templates.xml new file mode 100644 index 000000000..6a910de62 --- /dev/null +++ b/all_in_one_sales_kit/report/sales_category_templates.xml @@ -0,0 +1,114 @@ + + + + + diff --git a/all_in_one_sales_kit/report/sales_indent_templates.xml b/all_in_one_sales_kit/report/sales_indent_templates.xml new file mode 100644 index 000000000..73a4512b7 --- /dev/null +++ b/all_in_one_sales_kit/report/sales_indent_templates.xml @@ -0,0 +1,61 @@ + + + + + diff --git a/all_in_one_sales_kit/report/sales_weekly_templates.xml b/all_in_one_sales_kit/report/sales_weekly_templates.xml new file mode 100644 index 000000000..aa6901ed9 --- /dev/null +++ b/all_in_one_sales_kit/report/sales_weekly_templates.xml @@ -0,0 +1,68 @@ + + + + + diff --git a/all_in_one_sales_kit/security/all_in_one_sales_kit_groups.xml b/all_in_one_sales_kit/security/all_in_one_sales_kit_groups.xml new file mode 100644 index 000000000..c07a46a07 --- /dev/null +++ b/all_in_one_sales_kit/security/all_in_one_sales_kit_groups.xml @@ -0,0 +1,18 @@ + + + + + + Sale Order Discount Approval + Category for Discount Approval + + + Discount Approval Manager + + + + Create Custom Fields in Sale + + + diff --git a/all_in_one_sales_kit/security/ir.model.access.csv b/all_in_one_sales_kit/security/ir.model.access.csv new file mode 100644 index 000000000..0bff65dbb --- /dev/null +++ b/all_in_one_sales_kit/security/ir.model.access.csv @@ -0,0 +1,14 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_sale_report_advance_user,access.sale.report.advance.user,model_sale_report_advance,base.group_user,1,1,1,1 +access_sale_report_invoice_user,access.sale.report.invoice.user,model_sale_report_invoice,base.group_user,1,1,1,1 +access_sale_report_category_user,access.sale.report.category.user,model_sale_report_category,base.group_user,1,1,1,1 +access_sale_report_indent_user,access.sale.report.indent.user,model_sale_report_indent,base.group_user,1,1,1,1 +access_sale_report_analysis_user,access.sale.report.analysis.user,model_sale_report_analysis,base.group_user,1,1,1,1 +access_sale_report_weekly_user,access.sale.report.weekly.user,model_sale_report_weekly,base.group_user,1,1,1,1 +access_product_sale_history_line_id_user,access.product.sale.history.line.name.user,model_product_sale_history_line,base.group_user,1,1,1,1 +access_product_sale_order_history_id_user,access.product.sale.order.history.name.user,model_product_sale_order_history,base.group_user,1,1,1,1 +access_wizard_sale_order_dynamic_fields_user,access.wizard.sale.order.dynamic.fields.user,model_sale_order_dynamic_fields,base.group_user,1,1,1,1 +access_field_widget_user,access.wizard.field.widget.user,model_field_widget,base.group_user,1,1,1,1 +access_pack_product_user,access.pack.product.user,model_pack_product,base.group_user,1,1,1,1 +access_select_product_pack_user,access.select.product.pack.user,model_select_product_pack,base.group_user,1,1,1,1 +access_sales_report_user,access.sales.report.user,model_sales_report,base.group_user,1,1,1,1 diff --git a/all_in_one_sales_kit/static/description/assets/icons/check.png b/all_in_one_sales_kit/static/description/assets/icons/check.png new file mode 100644 index 000000000..c8e85f51d Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/icons/check.png differ diff --git a/all_in_one_sales_kit/static/description/assets/icons/chevron.png b/all_in_one_sales_kit/static/description/assets/icons/chevron.png new file mode 100644 index 000000000..2089293d6 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/icons/chevron.png differ diff --git a/all_in_one_sales_kit/static/description/assets/icons/cogs.png b/all_in_one_sales_kit/static/description/assets/icons/cogs.png new file mode 100644 index 000000000..95d0bad62 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/icons/cogs.png differ diff --git a/all_in_one_sales_kit/static/description/assets/icons/consultation.png b/all_in_one_sales_kit/static/description/assets/icons/consultation.png new file mode 100644 index 000000000..8319d4baa Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/icons/consultation.png differ diff --git a/all_in_one_sales_kit/static/description/assets/icons/ecom-black.png b/all_in_one_sales_kit/static/description/assets/icons/ecom-black.png new file mode 100644 index 000000000..a9385ff13 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/icons/ecom-black.png differ diff --git a/all_in_one_sales_kit/static/description/assets/icons/education-black.png b/all_in_one_sales_kit/static/description/assets/icons/education-black.png new file mode 100644 index 000000000..3eb09b27b Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/icons/education-black.png differ diff --git a/all_in_one_sales_kit/static/description/assets/icons/hotel-black.png b/all_in_one_sales_kit/static/description/assets/icons/hotel-black.png new file mode 100644 index 000000000..130f613be Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/icons/hotel-black.png differ diff --git a/all_in_one_sales_kit/static/description/assets/icons/license.png b/all_in_one_sales_kit/static/description/assets/icons/license.png new file mode 100644 index 000000000..a5869797e Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/icons/license.png differ diff --git a/all_in_one_sales_kit/static/description/assets/icons/lifebuoy.png b/all_in_one_sales_kit/static/description/assets/icons/lifebuoy.png new file mode 100644 index 000000000..658d56ccc Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/icons/lifebuoy.png differ diff --git a/all_in_one_sales_kit/static/description/assets/icons/manufacturing-black.png b/all_in_one_sales_kit/static/description/assets/icons/manufacturing-black.png new file mode 100644 index 000000000..697eb0e9f Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/icons/manufacturing-black.png differ diff --git a/all_in_one_sales_kit/static/description/assets/icons/pos-black.png b/all_in_one_sales_kit/static/description/assets/icons/pos-black.png new file mode 100644 index 000000000..97c0f90c1 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/icons/pos-black.png differ diff --git a/all_in_one_sales_kit/static/description/assets/icons/puzzle.png b/all_in_one_sales_kit/static/description/assets/icons/puzzle.png new file mode 100644 index 000000000..65cf854e7 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/icons/puzzle.png differ diff --git a/all_in_one_sales_kit/static/description/assets/icons/restaurant-black.png b/all_in_one_sales_kit/static/description/assets/icons/restaurant-black.png new file mode 100644 index 000000000..4a35eb939 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/icons/restaurant-black.png differ diff --git a/all_in_one_sales_kit/static/description/assets/icons/service-black.png b/all_in_one_sales_kit/static/description/assets/icons/service-black.png new file mode 100644 index 000000000..301ab51cb Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/icons/service-black.png differ diff --git a/all_in_one_sales_kit/static/description/assets/icons/trading-black.png b/all_in_one_sales_kit/static/description/assets/icons/trading-black.png new file mode 100644 index 000000000..9398ba2f1 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/icons/trading-black.png differ diff --git a/all_in_one_sales_kit/static/description/assets/icons/training.png b/all_in_one_sales_kit/static/description/assets/icons/training.png new file mode 100644 index 000000000..884ca024d Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/icons/training.png differ diff --git a/all_in_one_sales_kit/static/description/assets/icons/update.png b/all_in_one_sales_kit/static/description/assets/icons/update.png new file mode 100644 index 000000000..ecbc5a01a Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/icons/update.png differ diff --git a/all_in_one_sales_kit/static/description/assets/icons/user.png b/all_in_one_sales_kit/static/description/assets/icons/user.png new file mode 100644 index 000000000..6ffb23d9f Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/icons/user.png differ diff --git a/all_in_one_sales_kit/static/description/assets/icons/wrench.png b/all_in_one_sales_kit/static/description/assets/icons/wrench.png new file mode 100644 index 000000000..6c04dea0f Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/icons/wrench.png differ diff --git a/all_in_one_sales_kit/static/description/assets/misc/categories.png b/all_in_one_sales_kit/static/description/assets/misc/categories.png new file mode 100644 index 000000000..bedf1e0b1 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/misc/categories.png differ diff --git a/all_in_one_sales_kit/static/description/assets/misc/check-box.png b/all_in_one_sales_kit/static/description/assets/misc/check-box.png new file mode 100644 index 000000000..42caf24b9 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/misc/check-box.png differ diff --git a/all_in_one_sales_kit/static/description/assets/misc/compass.png b/all_in_one_sales_kit/static/description/assets/misc/compass.png new file mode 100644 index 000000000..d5fed8faa Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/misc/compass.png differ diff --git a/all_in_one_sales_kit/static/description/assets/misc/corporate.png b/all_in_one_sales_kit/static/description/assets/misc/corporate.png new file mode 100644 index 000000000..2eb13edbf Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/misc/corporate.png differ diff --git a/all_in_one_sales_kit/static/description/assets/misc/customer-support.png b/all_in_one_sales_kit/static/description/assets/misc/customer-support.png new file mode 100644 index 000000000..79efc72ed Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/misc/customer-support.png differ diff --git a/all_in_one_sales_kit/static/description/assets/misc/cybrosys-logo.png b/all_in_one_sales_kit/static/description/assets/misc/cybrosys-logo.png new file mode 100644 index 000000000..cc3cc0ccf Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/misc/cybrosys-logo.png differ diff --git a/all_in_one_sales_kit/static/description/assets/misc/features.png b/all_in_one_sales_kit/static/description/assets/misc/features.png new file mode 100644 index 000000000..b41769f77 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/misc/features.png differ diff --git a/all_in_one_sales_kit/static/description/assets/misc/logo.png b/all_in_one_sales_kit/static/description/assets/misc/logo.png new file mode 100644 index 000000000..478462d3e Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/misc/logo.png differ diff --git a/all_in_one_sales_kit/static/description/assets/misc/pictures.png b/all_in_one_sales_kit/static/description/assets/misc/pictures.png new file mode 100644 index 000000000..56d255fe9 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/misc/pictures.png differ diff --git a/all_in_one_sales_kit/static/description/assets/misc/pie-chart.png b/all_in_one_sales_kit/static/description/assets/misc/pie-chart.png new file mode 100644 index 000000000..426e05244 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/misc/pie-chart.png differ diff --git a/all_in_one_sales_kit/static/description/assets/misc/right-arrow.png b/all_in_one_sales_kit/static/description/assets/misc/right-arrow.png new file mode 100644 index 000000000..730984a06 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/misc/right-arrow.png differ diff --git a/all_in_one_sales_kit/static/description/assets/misc/star.png b/all_in_one_sales_kit/static/description/assets/misc/star.png new file mode 100644 index 000000000..2eb9ab29f Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/misc/star.png differ diff --git a/all_in_one_sales_kit/static/description/assets/misc/support.png b/all_in_one_sales_kit/static/description/assets/misc/support.png new file mode 100644 index 000000000..4f18b8b82 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/misc/support.png differ diff --git a/all_in_one_sales_kit/static/description/assets/misc/whatsapp.png b/all_in_one_sales_kit/static/description/assets/misc/whatsapp.png new file mode 100644 index 000000000..d513a5356 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/misc/whatsapp.png differ diff --git a/all_in_one_sales_kit/static/description/assets/modules/1.png b/all_in_one_sales_kit/static/description/assets/modules/1.png new file mode 100644 index 000000000..e571015b1 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/modules/1.png differ diff --git a/all_in_one_sales_kit/static/description/assets/modules/2.png b/all_in_one_sales_kit/static/description/assets/modules/2.png new file mode 100644 index 000000000..f14dc60c5 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/modules/2.png differ diff --git a/all_in_one_sales_kit/static/description/assets/modules/3.png b/all_in_one_sales_kit/static/description/assets/modules/3.png new file mode 100644 index 000000000..8658f1826 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/modules/3.png differ diff --git a/all_in_one_sales_kit/static/description/assets/modules/4.png b/all_in_one_sales_kit/static/description/assets/modules/4.png new file mode 100644 index 000000000..febd12d15 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/modules/4.png differ diff --git a/all_in_one_sales_kit/static/description/assets/modules/5.png b/all_in_one_sales_kit/static/description/assets/modules/5.png new file mode 100644 index 000000000..af96cc10a Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/modules/5.png differ diff --git a/all_in_one_sales_kit/static/description/assets/modules/6.png b/all_in_one_sales_kit/static/description/assets/modules/6.png new file mode 100644 index 000000000..06d2c16ba Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/modules/6.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/hero.gif b/all_in_one_sales_kit/static/description/assets/screenshots/hero.gif new file mode 100644 index 000000000..5689b2b81 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/hero.gif differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_01.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_01.png new file mode 100644 index 000000000..afb4c2cdd Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_01.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_02.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_02.png new file mode 100644 index 000000000..0bfb73c9b Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_02.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_03.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_03.png new file mode 100644 index 000000000..8ed06e192 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_03.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_04.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_04.png new file mode 100644 index 000000000..fb5ab7c7f Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_04.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_05.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_05.png new file mode 100644 index 000000000..0e835af92 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_05.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_06.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_06.png new file mode 100644 index 000000000..a8ffaea3b Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_06.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_07.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_07.png new file mode 100644 index 000000000..98da37459 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_07.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_08.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_08.png new file mode 100644 index 000000000..d74cee9ee Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_08.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_09.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_09.png new file mode 100644 index 000000000..5c69819b8 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_09.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_10.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_10.png new file mode 100644 index 000000000..e5237490a Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_10.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_11.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_11.png new file mode 100644 index 000000000..d50346dae Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_11.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_12.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_12.png new file mode 100644 index 000000000..c5343cce9 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_12.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_13.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_13.png new file mode 100644 index 000000000..53de5a556 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_13.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_14.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_14.png new file mode 100644 index 000000000..1b52c1d97 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_14.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_15.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_15.png new file mode 100644 index 000000000..2c404300c Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_15.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_16.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_16.png new file mode 100644 index 000000000..fe9f36a7e Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_16.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_17.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_17.png new file mode 100644 index 000000000..6d943e172 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_17.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_18.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_18.png new file mode 100644 index 000000000..9b7f878de Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_18.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_19.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_19.png new file mode 100644 index 000000000..f0c2a66a6 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_19.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_20.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_20.png new file mode 100644 index 000000000..5549c0cf7 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_20.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_21.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_21.png new file mode 100644 index 000000000..5015fda79 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_21.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_22.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_22.png new file mode 100644 index 000000000..2ed6b89b0 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_22.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_23.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_23.png new file mode 100644 index 000000000..62106d709 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_23.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_24.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_24.png new file mode 100644 index 000000000..317269ec4 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_24.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_25.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_25.png new file mode 100644 index 000000000..0c9c72568 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_25.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_26.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_26.png new file mode 100644 index 000000000..ee08d8860 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_26.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_27.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_27.png new file mode 100644 index 000000000..312d3397e Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_27.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_28.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_28.png new file mode 100644 index 000000000..bc7380376 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_28.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_29.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_29.png new file mode 100644 index 000000000..b884aaf0b Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_29.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_30.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_30.png new file mode 100644 index 000000000..e2cf44088 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_30.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_31.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_31.png new file mode 100644 index 000000000..c588870f9 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_31.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_32.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_32.png new file mode 100644 index 000000000..1c1d37729 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_32.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_33.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_33.png new file mode 100644 index 000000000..8d5197be6 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_33.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_34.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_34.png new file mode 100644 index 000000000..911f26308 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_34.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_35.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_35.png new file mode 100644 index 000000000..33e77b1c1 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_35.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_36.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_36.png new file mode 100644 index 000000000..c89122c9c Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_36.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_37.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_37.png new file mode 100644 index 000000000..9187fdf40 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_37.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_38.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_38.png new file mode 100644 index 000000000..42012cfc1 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_38.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_39.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_39.png new file mode 100644 index 000000000..d2440316e Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_39.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_40.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_40.png new file mode 100644 index 000000000..40d98138a Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_40.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_41.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_41.png new file mode 100644 index 000000000..e02e799c6 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_41.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_42.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_42.png new file mode 100644 index 000000000..363480931 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_42.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_43.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_43.png new file mode 100644 index 000000000..a1ed96440 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_43.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_44.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_44.png new file mode 100644 index 000000000..a7d37556c Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_44.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_45.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_45.png new file mode 100644 index 000000000..b661736c3 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_45.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_46.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_46.png new file mode 100644 index 000000000..5d11d37d6 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_46.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_47.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_47.png new file mode 100644 index 000000000..f0b4d99fb Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_47.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_48.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_48.png new file mode 100644 index 000000000..5f1e2de66 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_48.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_49.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_49.png new file mode 100644 index 000000000..6a602d308 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_49.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_50.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_50.png new file mode 100644 index 000000000..64715e6f9 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_50.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_51.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_51.png new file mode 100644 index 000000000..602685d74 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_51.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_52.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_52.png new file mode 100644 index 000000000..4edb1fade Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_52.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_53.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_53.png new file mode 100644 index 000000000..d0db6f1fe Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_53.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_54.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_54.png new file mode 100644 index 000000000..0108c278b Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_54.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_55.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_55.png new file mode 100644 index 000000000..88aa6e314 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_55.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_56.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_56.png new file mode 100644 index 000000000..0f1937fc3 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_56.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_57.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_57.png new file mode 100644 index 000000000..8d961dab8 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_57.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_58.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_58.png new file mode 100644 index 000000000..cc83d22f0 Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_58.png differ diff --git a/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_59.png b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_59.png new file mode 100644 index 000000000..20a06e23d Binary files /dev/null and b/all_in_one_sales_kit/static/description/assets/screenshots/s_kit_59.png differ diff --git a/all_in_one_sales_kit/static/description/banner.jpg b/all_in_one_sales_kit/static/description/banner.jpg new file mode 100644 index 000000000..c55f231ca Binary files /dev/null and b/all_in_one_sales_kit/static/description/banner.jpg differ diff --git a/all_in_one_sales_kit/static/description/icon.png b/all_in_one_sales_kit/static/description/icon.png new file mode 100644 index 000000000..88679622e Binary files /dev/null and b/all_in_one_sales_kit/static/description/icon.png differ diff --git a/all_in_one_sales_kit/static/description/index.html b/all_in_one_sales_kit/static/description/index.html new file mode 100644 index 000000000..4144fea5c --- /dev/null +++ b/all_in_one_sales_kit/static/description/index.html @@ -0,0 +1,1553 @@ +
+ +
+ +
+
+ Community +
+
+ Enterprise +
+
+ Odoo.sh +
+
+
+ +
+
+
+ +

+ Odoo16 All in One Sales Kit

+

+ This Module Combines A Variety Of Sales Features.

+ + +
+
+
+ + +
+ + +
+
+ +
+

+ Explore This + Module

+
+ + + + +
+
+ +
+

+ Overview +

+
+
+
+ This module gives features like Sale Order Line Images, Barcode Scan + Support,Advanced Sale Reports (Product Profit Report, Sales Invoice + Analysis Report, Sales Category Report, Sales Indent Report, Sales + Analysis Report, Hourly Sales Report), Product Pack, and Salesperson + Signature for Confirm Order are some of the features included in this + module. Previous Sale Product Rate, Create Various Sale Order Versions, + Create Custom Fields for Sale Orders, Recognise Previous Sales of + Products,A separate quotation number,Multiple warehouses in sale order + lines, sales order and quotation line views,approval of the sale order + discount,sales restrictions for out-of-stock items depending on + forecast and stock level,automate the sale process,Sales one-stop + report generation,Add more than one item to the quotation,pivot view + for partner sales,sale order archive,Dashboard. +
+
+ + + +
+
+ +
+

+ Features +

+
+
+
+
+ + Community & + Enterprise Support. +
+
+ + Allows you see the product images in sale order line + and also allow us to print the product image in the send report. +
+
+ + Supports barcode scanning in sales. +
+
+ + Helps you to print reports various reports. +
+
+ + Provides opportunity to sell products in pack. +
+ +
+ + It allows the salesperson's to add his signature and also available the option for making the validate option in sales visible/invisible based on the salesperson's signature. +
+ +
+ + It is intended to display the prior sales and purchases of a specific product for a specific partner. +
+ +
+ + It is intended to display the prior sales of a specific product for a specific partner. +
+ +
+ + A sale order can have various versions that can be seen through the smart tabs inside the sale order. +
+ +
+ + A sale order custom field option helps in creating custom fields without coding. +
+ +
+ + In this each order line of a sale order will have a history button that, when clicked, will open a wizard displaying the sale history of that product for the individual customer selected. +
+
+
+
+ + A separate sequence is added for quotation and sale order. +
+ +
+ + A field warehouses is added in sale order lines and when confirming the order if multiple warehouses are selected then separate delivery orders are created. +
+ +
+ + Under order two new menu items are added Sale order lines, Quotation lines. +
+ +
+ + Adds a field for the user to set the manager for discount approval. +
+ +
+ + Based on the quantity on hand and forecasted, this module controls out-of-stock products. +
+ +
+ + Options to create and validate invoices automatically. +
+ +
+ + It shows the sales analysis of a company in many aspects such as by order,order details,salesman and so on. +
+ +
+ + Add multiple products to quotation. +
+ +
+ + Display client sales analysis in their view as pivot. +
+ +
+ + Option to archive and unarchive sale order. +
+ +
+ + Sales Dashboard. +
+ +
+
+ + + +
+
+ +
+

+ Screenshots +

+
+
+
+ +
+

+ Sales Dashboard +

+

+ To filter the tile values, choose a start date and an end + date.

+ + + + + +
+ + +
+
+
+
+ +
+

+ Sale Order Line Product Images +

+

+ Go to settings -> Enable Show product image in report under + product catalog to print image in the send report. + We can add product image in order line in sale order. We can + view these images on the quotation send to customer also. +

+ + + + + +
+ + +
+
+
+
+ +
+

+ Sales Reports +

+

+ Different sale analysis reports are available. +

+ + + + + +
+ + +
+
+
+
+ +
+

+ Product Pack +

+

+ Go to products -> Product Pack.We can create new product + packages here. While creating one 'is a pack will be enabled' + also a new page to add products under this pack is + available.From there itself pack price and pack quantity will + be known.While creating a new quotation we can add the product + pack using the button 'Add product pack'. +

+ + + + + +
+ + +
+
+
+
+ +
+

+ Salesperson's signature approval +

+

+ Go to settings -> Enable Sale Document Approve to make + salesperson's signature to confirm sale order.Only after adding + sales person's signature confirm button will become visible. +

+ + + + + +
+ + +
+
+
+
+ +
+

+ Previous sales rate and customer product report. +

+

+ Two new buttons are available to know previous sales rate of a + product and customer product report for a product selected from + the order line. +

+ + + + + +
+ + +
+
+
+
+ +
+

+ Sale order versions +

+

+ Different sale orders versions can be created using the 'create + version' button,and they can be viewed from the smart tab + 'version'.If one of the versions are confirmed the remaining + will automatically move to cancelled state. +

+ + + + + +
+ + +
+
+
+
+ +
+

+ Create custom fields +

+

+ Go to users -> Enable Create custom fields in sale,Go to + sale->configuration -> create fields a wizard to daa features + of new field is shown fill the requirements and apply 'create' + to create custom fields in sale order form view. +

+ + + + + +
+ + +
+
+
+
+ +
+

+ Separate sequence for quotation and multiple warehouses. +

+

+ Quotation sequence is generated while creating a new + quotation.Also in the order lines different warehouses can be + chosen for different products and delivery orders for multiple + warehouses are created separately. +

+ + + + + +
+ + +
+
+
+
+ +
+

+ Sale order and quotation line views +

+

+ Orders -> Quotation line views , this will show all the + quotation lines with product image.Orders -> Order line + views , this will show all the order lines with product image. +

+ + + + + +
+ + +
+
+
+
+ +
+

+ Sale order discount approval +

+

+ Go to users -> Select Sale order discount approval as Discount + approval manager,Go under Discount control enable discount + control , set discount limit in percentage.While confirming a + sale order if the discount is greater than allowed discount + limit then the order moves to waiting for approval state and + the discount approval manager can decide whether to choose or + cancel the order. +

+ + + + + +
+ + +
+
+
+
+ +
+

+ Out of stock product restriction,Automate orders,Add multiple + products to quotation +

+

+ Go to settings -> Choose the grounds on which the stock + restriction should be implemented.While confirming the sale + order if the restriction enabled prevails then error message is + shown.By selecting Add to catalog , we are directed to the + product view and using add quotation button the products are + added to the current sale order lines. +

+ + + + + +
+ + +
+
+
+
+ +
+

+ Sales pivot view in customer,Sale order archive,Barcode + scanning for sales +

+

+ Orders -> Customer -> sales smart tab we can get sales analysis + of that customer in pivot view also.Select a sale order -> + Action -> archive by using this we can archive a sale order and + using unarchive option under it, we can unarchive the archived + sale orders.In the filters option we can filter archived sale + orders.Products -> General information we can enter the + product's barcode,and we just need to scan the barcode of the + product to enter the product to sale order line. +

+ + + + + +
+ + +
+
+ + + +
+
+ +
+

+ Related + Products +

+
+
+
+ +
+
+ + + + +
+
+ +
+

+ Our Services +

+
+ +
+
+
+
+ +
+
+ Odoo + Customization
+
+ +
+
+ +
+
+ Odoo + Implementation
+
+ +
+
+ +
+
+ Odoo + Support
+
+ + +
+
+ +
+
+ Hire + Odoo + Developer
+
+ +
+
+ +
+
+ Odoo + Integration
+
+ +
+
+ +
+
+ Odoo + Migration
+
+ + +
+
+ +
+
+ Odoo + Consultancy
+
+ +
+
+ +
+
+ Odoo + Implementation
+
+ +
+
+ +
+
+ Odoo + Licensing Consultancy
+
+
+ +
+ + + + + +
+
+ +
+

+ Our + Industries +

+
+ +
+
+
+
+ +
+ Trading +
+

+ Easily procure + and + sell your products

+
+
+ +
+
+ +
+ POS +
+

+ Easy + configuration + and convivial experience

+
+
+ +
+
+ +
+ Education +
+

+ A platform for + educational management

+
+
+ +
+
+ +
+ Manufacturing +
+

+ Plan, track and + schedule your operations

+
+
+ +
+
+ +
+ E-commerce & Website +
+

+ Mobile + friendly, + awe-inspiring product pages

+
+
+ +
+
+ +
+ Service Management +
+

+ Keep track of + services and invoice

+
+
+ +
+
+ +
+ Restaurant +
+

+ Run your bar or + restaurant methodically

+
+
+ +
+
+ +
+ Hotel Management +
+

+ An + all-inclusive + hotel management application

+
+
+
+
+ + + + +
+
+ +
+

+ Support +

+
+
+
+
+
+
+ +
+
+

Need Help?

+

Got questions or need help? + Get in touch.

+ +

+ odoo@cybrosys.com

+
+
+
+
+
+
+
+ +
+
+

WhatsApp

+

Say hi to us on WhatsApp!

+ +

+ +91 86068 + 27707

+
+
+
+
+
+
+
+ +
+
+
+ diff --git a/all_in_one_sales_kit/static/src/css/sale_report.css b/all_in_one_sales_kit/static/src/css/sale_report.css new file mode 100644 index 000000000..8809317a0 --- /dev/null +++ b/all_in_one_sales_kit/static/src/css/sale_report.css @@ -0,0 +1,148 @@ +.time_range_pr { + width: 125px; + border: 2px solid #ccc; + border-radius: 5px; + padding: 10px; + +} +.apply_sale { + margin-right: 5px; + padding: 4px; +} +#apply_filter { + margin-right: 25px; +} +#pdf, #xlsx, #apply_filter { + color: white; + background-color: #7c7bad; + border-color: #7c7bad; +} +a.dropdown-toggle.report-type { + margin: 10px; +} +.search-Result-Selection { + border: 2px solid #ccc; + max-width: 360px; + min-width: 250px; + margin-right: 10px; + margin-left: 10px; + border-radius: 5px; + min-height: 40px; +} +.search-Result-Selection:hover{ + border:2px solid #eaeaea; +} +.dropdown-togglereport-type{ + min-height: 40px; + padding-top: 10px; +} +.low_case, #report_res { + text-transform: capitalize; + text-align: center; +} +#report_res{ + padding-right: 3px; +} +.print-btns { + margin-bottom: 30px; +} +.table_pr_head { + background-color: #7c7bad; + color: #fff; + padding: 20px; + margin: 20px; + height: 57px; + width: 100%; + border: 1px solid #000; +} +tr.pr-line { + height: 48px; +} +tr.table_pr_head th { + font-size: 18px; + text-align: center; +} +.table_view_sr { + overflow-y: scroll; + position: relative; + height: 500px; + max-width: 100%; +} +.my_custom_dropdown{ + min-width:200px; + height:150px; + padding: 20px; +} +.my_custom_dropdown input{ + height:25px; +} +.report_type{ + min-height: 100px; + min-width: 150px; + padding: 30px 10px 10px; +} +.sub_container_right, .print-btns { + display: flex; + justify-content: space-around; + align-items: center; +} +.form-inline { + display: flex; + flex-direction: row; + align-items: center; +} +input[type="date"]{ + font-family: "Roboto Mono",monospace; + font-size: 14px; + border: none; + outline: none; + border-radius: 5px; +} +@media screen and (max-width: 768px) { + .sub_container_right, .print-btns { + flex-flow: column; + align-items: center; + } + .sub_container_left { + order: 2; + margin: 0; + } + .sub_container_right { + flex-flow: row; + margin-bottom: 10px; + flex-wrap: wrap !important; + width: 100%; + } +} +@media screen and (max-width: 576px) { + .apply_filter { + width: 100%; + position: relative; + } + #apply_filter { + position: absolute; + margin: 30px 0 0; + top: 50%; + left: 50%; + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + } + #date_chose { + margin-bottom: 15px; + } + .search-Result-Selection { + text-align: center; + } + .sub_container_left { + order: 2; + display: flex; + margin-top: 50px; + width: 100%; + justify-content: center; + align-items: center; + flex-wrap: wrap !important; + } + .sub_container_right { + flex-direction: column; + } +} diff --git a/all_in_one_sales_kit/static/src/img/Products.png b/all_in_one_sales_kit/static/src/img/Products.png new file mode 100644 index 000000000..5f71b84b6 Binary files /dev/null and b/all_in_one_sales_kit/static/src/img/Products.png differ diff --git a/all_in_one_sales_kit/static/src/img/Quotations-cancel.png b/all_in_one_sales_kit/static/src/img/Quotations-cancel.png new file mode 100644 index 000000000..d695d3267 Binary files /dev/null and b/all_in_one_sales_kit/static/src/img/Quotations-cancel.png differ diff --git a/all_in_one_sales_kit/static/src/img/Quotations-send.png b/all_in_one_sales_kit/static/src/img/Quotations-send.png new file mode 100644 index 000000000..45ed3b9e1 Binary files /dev/null and b/all_in_one_sales_kit/static/src/img/Quotations-send.png differ diff --git a/all_in_one_sales_kit/static/src/img/Quotations.png b/all_in_one_sales_kit/static/src/img/Quotations.png new file mode 100644 index 000000000..8a7c5ec47 Binary files /dev/null and b/all_in_one_sales_kit/static/src/img/Quotations.png differ diff --git a/all_in_one_sales_kit/static/src/img/custmers.png b/all_in_one_sales_kit/static/src/img/custmers.png new file mode 100644 index 000000000..81d8d5b64 Binary files /dev/null and b/all_in_one_sales_kit/static/src/img/custmers.png differ diff --git a/all_in_one_sales_kit/static/src/img/sales-orders.png b/all_in_one_sales_kit/static/src/img/sales-orders.png new file mode 100644 index 000000000..50a4e1844 Binary files /dev/null and b/all_in_one_sales_kit/static/src/img/sales-orders.png differ diff --git a/all_in_one_sales_kit/static/src/img/to-invoice.png b/all_in_one_sales_kit/static/src/img/to-invoice.png new file mode 100644 index 000000000..98ca37d29 Binary files /dev/null and b/all_in_one_sales_kit/static/src/img/to-invoice.png differ diff --git a/all_in_one_sales_kit/static/src/js/action_manager.js b/all_in_one_sales_kit/static/src/js/action_manager.js new file mode 100644 index 000000000..623b98b7a --- /dev/null +++ b/all_in_one_sales_kit/static/src/js/action_manager.js @@ -0,0 +1,19 @@ +/** @odoo-module */ +import { registry } from "@web/core/registry"; +import framework from 'web.framework'; +import session from 'web.session'; + +registry.category("ir.actions.report handlers").add("xlsx", async (action) => { + /*It is to pass data to the controller for printing xlsx report.*/ + if (action.report_type === 'xlsx') { + framework.blockUI(); + var def = $.Deferred(); + session.get_file({ + url: '/xlsx_reports', + data: action.data, + success: def.resolve.bind(def), + complete: framework.unblockUI, + }); + return def; + } +}); diff --git a/all_in_one_sales_kit/static/src/js/dashboard.js b/all_in_one_sales_kit/static/src/js/dashboard.js new file mode 100644 index 000000000..9cef44abb --- /dev/null +++ b/all_in_one_sales_kit/static/src/js/dashboard.js @@ -0,0 +1,484 @@ +odoo.define("all_in_one_sales_kit.dashboard", function (require) { + "use strict"; + var AbstractAction = require('web.AbstractAction'); + var core = require('web.core'); + var QWeb = core.qweb; + var session = require('web.session'); + var rpc = require('web.rpc'); + var self = this; + var DashBoard = AbstractAction.extend({ + contentTemplate: 'DashboardDashboard', + events: { + 'click .quotation': 'on_dashboard_quotation_action', + 'click .my_sale_order': 'on_dashboard_my_sale_order_action', + 'click .quotation_sent': 'on_dashboard_quotation_sent_action', + 'click .quotation_cancel': 'on_dashboard_quotation_cancel_action', + 'click .customers': 'on_dashboard_customers_action', + 'click .products': 'on_dashboard_products_action', + 'click .to_invoice': 'on_dashboard_to_invoice_action', + 'change #start_date': function (e) { + /*This function works on change of start date.*/ + e.stopPropagation(); + var $target = $(e.target); + var values = $target.val(); + this.onClick_this_values($target.val()); + }, + 'change #end_date': function (e) { + /*This function works on change of end date.*/ + e.stopPropagation(); + var $target = $(e.target); + var values = $target.val(); + this.onClick_this_values($target.val()); + }, + }, + /** + * Initializes the object with the given parent and context. + * + * @param {Object} parent - The parent object. + * @param {Object} context - The context object. + */ + init: function (parent, context) { + this._super(parent, context); + this.dashboards_templates = ['LoginUser', 'MainSection']; + }, + /** + * Initializes the object and renders various dashboards and graphs. + * Sets the title to 'Dashboard'. + * + * @returns {Promise} A promise that resolves after the initialization is complete. + */ + start: function () { + var self = this; + this.set("title", 'Dashboard'); + return this._super().then(function () { + self.render_dashboards(); + self.render_customer_leads_graph(); + self.render_product_leads_graph(); + self.render_quotation_leads_graph(); + self.render_sales_team_graph(); + self.render_my_monthly_comparison_graph(); + self.render_least_sold_graph(); + }); + }, + /** + * Executes before the object starts its initialization process. + * + * @returns {Promise} A promise that resolves after the pre-initialization is complete. + */ + willStart: function () { + var self = this; + return this._super() + }, + render_dashboards: function () { + /*It is to render data to the dashboard*/ + var self = this; + this.fetch_data() + var templates = [] + var templates = ['LoginUser', 'MainSection']; + _.each(templates, function (template) { + self.$('.o_hr_dashboard').append(QWeb.render(template, { widget: self })) + }); + }, + /** + * Fetches data related to sale orders from the server and updates corresponding HTML elements. + * + * @returns {Promise} A promise that resolves when the data fetching and updates are complete. + */ + fetch_data: function () { + var self = this + var def1 = this._rpc({ + model: 'sale.order', + method: "get_data", + }) + .then(function (result) { + $('#quotation_templates').append('' + result.quotation + ''); + $('#my_sale_order_templates').append('' + result.my_sale_order_templates + ''); + $('#quotation_sent').append('' + result.quotation_sent + ''); + $('#quotation_cancel').append('' + result.quotation_cancel + ''); + $('#customers').append('' + result.customers + ''); + $('#products').append('' + result.products + ''); + $('#to_invoice').append('' + result.to_invoice + ''); + }); + }, + on_dashboard_quotation_action: function (ev) { + /*This is to get quotation data according to the filters.*/ + var start_date = this.$('#start_date').val() + var end_date = this.$('#end_date').val() + if (start_date && end_date) { + var domain = [['user_id', '=', session.uid], ['state', '=', 'draft'], ['date_order', '>=', start_date], ['date_order', '<=', end_date]] + } + else if (start_date) { + var domain = [['user_id', '=', session.uid], ['state', '=', 'draft'], ['date_order', '>=', start_date]] + } + else if (end_date) { + var domain = [['user_id', '=', session.uid], ['state', '=', 'draft'], ['date_order', '<=', end_date]] + } + else { + var domain = [['user_id', '=', session.uid], ['state', '=', 'draft']] + } + this.do_action({ + type: 'ir.actions.act_window', + name: 'Quotations', + res_model: 'sale.order', + views: [[false, 'tree'], [false, 'form']], + domain: domain, + target: 'current', + }); + }, + on_dashboard_my_sale_order_action: function (ev) { + /*This is to get sale order data according to the filters.*/ + ev.preventDefault(); + var $action = $(ev.currentTarget); + var start_date = this.$('#start_date').val() + var end_date = this.$('#end_date').val() + if (start_date && end_date) { + var domain = [['user_id', '=', session.uid], ['state', '=', 'sale'], ['date_order', '>=', start_date], ['date_order', '<=', end_date]] + } + else if (start_date) { + var domain = [['user_id', '=', session.uid], ['state', '=', 'sale'], ['date_order', '>=', start_date]] + } + else if (end_date) { + var domain = [['user_id', '=', session.uid], ['state', '=', 'sale'], ['date_order', '<=', end_date]] + } + else { + var domain = [['user_id', '=', session.uid], ['state', '=', 'sale']] + } + this.do_action({ + type: 'ir.actions.act_window', + name: 'Sale orders', + res_model: 'sale.order', + views: [[false, 'tree'], [false, 'form']], + domain: domain, + target: 'current', + }); + }, + on_dashboard_quotation_sent_action: function (ev) { + /*This is to get quotation sent data according to the filters.*/ + ev.preventDefault(); + var $action = $(ev.currentTarget); + var start_date = this.$('#start_date').val() + var end_date = this.$('#end_date').val() + if (start_date && end_date) { + var domain = [['user_id', '=', session.uid], ['state', '=', 'sent'], ['date_order', '>=', start_date], ['date_order', '<=', end_date]] + } + else if (start_date) { + var domain = [['user_id', '=', session.uid], ['state', '=', 'sent'], ['date_order', '>=', start_date]] + } + else if (end_date) { + var domain = [['user_id', '=', session.uid], ['state', '=', 'sent'], ['date_order', '<=', end_date]] + } + else { + var domain = [['user_id', '=', session.uid], ['state', '=', 'sent']] + } + this.do_action({ + type: 'ir.actions.act_window', + name: 'Quotations Sent', + res_model: 'sale.order', + views: [[false, 'tree'], [false, 'form']], + domain: domain, + target: 'current', + }); + }, + on_dashboard_quotation_cancel_action: function (ev) { + /*This is to get quotation cancel data according to the filters.*/ + ev.preventDefault(); + var $action = $(ev.currentTarget); + this.do_action({ + type: 'ir.actions.act_window', + name: 'Quotations Cancel', + res_model: 'sale.order', + views: [[false, 'tree'], [false, 'form']], + domain: [['user_id', '=', session.uid], ['state', '=', 'cancel']], + target: 'current', + }); + }, + on_dashboard_customers_action: function (ev) { + /*This is to get customers.*/ + ev.preventDefault(); + var $action = $(ev.currentTarget); + this.do_action({ + type: 'ir.actions.act_window', + name: 'Customers', + res_model: 'res.partner', + views: [[false, 'kanban'], [false, 'tree'], [false, 'form']], + target: 'current', + }); + }, + on_dashboard_to_invoice_action: function (ev) { + /*This is to get quotation + to be invoiced data according to the filters.*/ + ev.preventDefault(); + var $action = $(ev.currentTarget); + var start_date = this.$('#start_date').val() + var end_date = this.$('#end_date').val() + if (start_date && end_date) { + var domain = [['user_id', '=', session.uid], ['invoice_status', '=', 'to invoice'], ['date_order', '>=', start_date], ['date_order', '<=', end_date]] + } + else if (start_date) { + var domain = [['user_id', '=', session.uid], ['invoice_status', '=', 'to invoice'], ['date_order', '>=', start_date]] + } + else if (end_date) { + var domain = [['user_id', '=', session.uid], ['invoice_status', '=', 'to invoice'], ['date_order', '<=', end_date]] + } + else { + var domain = [['user_id', '=', session.uid], ['invoice_status', '=', 'to invoice']] + } + this.do_action({ + type: 'ir.actions.act_window', + name: 'To invoice', + res_model: 'sale.order', + views: [[false, 'tree'], [false, 'form']], + domain: domain, + target: 'current', + }); + }, + on_dashboard_products_action: function (ev) { + /*This is to get products.*/ + ev.preventDefault(); + var $action = $(ev.currentTarget); + this.do_action({ + type: 'ir.actions.act_window', + name: 'Products', + res_model: 'product.template', + views: [[false, 'kanban'], [false, 'tree'], [false, 'form']], + target: 'current', + }); + }, + /** + * Event handler for a button click, fetching and displaying values based on user input. + * Clears existing content in specified HTML elements and updates them with data retrieved from the server. + */ + onClick_this_values() { + // Clear existing content in HTML elements + this.$('#quotation_templates').empty(); + this.$('#my_sale_order_templates').empty(); + this.$('#quotation_sent').empty(); + this.$('#quotation_cancel').empty(); + this.$('#customers').empty(); + this.$('#products').empty(); + this.$('#to_invoice').empty(); + var self = this + var start_date = this.$('#start_date').val() + var end_date = this.$('#end_date').val() + // Use rpc.query to fetch data from the server + rpc.query({ + model: 'sale.order', + method: 'get_value', + args: [start_date, end_date], + }) + .then(function (data) { + // Update HTML elements with fetched data + $('#quotation_templates').append('' + data.quotation + ''); + $('#my_sale_order_templates').append('' + data.my_sale_order_templates + ''); + $('#quotation_sent').append('' + data.quotation_sent + ''); + $('#quotation_cancel').append('' + data.quotation_cancel + ''); + $('#customers').append('' + data.customers + ''); + $('#products').append('' + data.products + ''); + $('#to_invoice').append('' + data.to_invoice + ''); + + }) + }, + render_customer_leads_graph: function () { + /*This is to show a customer lead graph.*/ + var self = this; + var ctx = self.$("#lead_customer"); + rpc.query({ + model: 'sale.order', + method: 'get_lead_customer', + args: [], + }) + .then(function (results) { + var lead_templates = results.lead_templates; + var chart = new Chart(ctx, { + type: "doughnut", + data: { + labels: Object.keys(lead_templates), + datasets: [{ + + backgroundColor: [ + 'rgb(255,20,147)', + 'rgb(186,85,211)', + 'rgb(0,0,255)', + 'rgb(0,191,255)', + 'rgb(0,206,209)', + 'rgb(32,178,170)', + 'rgb(173,255,47)', + 'rgb(205,92,92)', + 'rgb(178,34,34)', + 'rgb(0,128,128)', + ], + data: Object.values(lead_templates), + }] + }, + }); + }); + }, + render_product_leads_graph: function () { + /*This is to show a product lead graph.*/ + var self = this; + var ctx = self.$("#lead_product"); + rpc.query({ + model: 'sale.order', + method: 'get_lead_product', + args: [], + }) + .then(function (results) { + var lead_templates = results.lead_templates; + var chart = new Chart(ctx, { + type: "doughnut", + data: { + labels: Object.keys(lead_templates), + datasets: [{ + + backgroundColor: [ + 'rgb(255, 99, 132)', + 'rgb(255, 159, 64)', + 'rgb(75, 192, 192)', + 'rgb(54, 162, 235)', + 'rgb(255, 209, 219)', + 'rgb(255, 205, 86)', + 'rgb(99, 255, 222)', + 'rgb(132, 255, 99)', + 'rgb(139, 139, 184)', + 'rgb(40, 255, 40)', + ], + data: Object.values(lead_templates), + }] + }, + }); + }); + }, + render_quotation_leads_graph: function () { + /*This is to show lead quotations graph.*/ + var self = this; + var ctx = self.$("#lead_order"); + rpc.query({ + model: 'sale.order', + method: 'get_lead_order', + args: [], + }) + .then(function (results) { + var lead_templates = results.lead_templates; + var chart = new Chart(ctx, { + type: "bar", + data: { + labels: Object.keys(lead_templates), + datasets: [{ + label: 'Sale Amount', + backgroundColor: [ + 'rgb(255, 99, 132)', + 'rgb(255, 159, 64)', + 'rgb(75, 192, 192)', + 'rgb(54, 162, 235)', + 'rgb(255, 209, 219)', + 'rgb(255, 205, 86)', + 'rgb(99, 255, 222)', + 'rgb(132, 255, 99)', + 'rgb(139, 139, 184)', + 'rgb(40, 255, 40)', + ], + data: Object.values(lead_templates), + }] + }, + }); + }); + }, + render_sales_team_graph: function () { + /*This is to show sales team graph.*/ + var self = this; + var ctx = self.$("#team"); + rpc.query({ + model: 'sale.order', + method: 'get_sales_team', + args: [], + }) + .then(function (results) { + var lead_templates = results.lead_templates; + var chart = new Chart(ctx, { + type: "pie", + data: { + labels: Object.keys(lead_templates), + datasets: [{ + backgroundColor: [ + 'rgb(25,25,112)', + 'rgb(135,206,235)', + 'rgb(75, 192, 192)', + 'rgb(54, 162, 235)', + 'rgb(255, 209, 219)', + 'rgb(255, 205, 86)', + 'rgb(99, 255, 222)', + 'rgb(132, 255, 99)', + 'rgb(139, 139, 184)', + 'rgb(40, 255, 40)', + ], + data: Object.values(lead_templates), + }] + }, + }); + }); + }, + render_my_monthly_comparison_graph: function () { + /*It is to show monthly sale count graph.*/ + var self = this; + var ctx = self.$("#my_monthly_comparison"); + rpc.query({ + model: 'sale.order', + method: 'get_my_monthly_comparison', + args: [], + }) + .then(function (results) { + var lead_templates = results.lead_templates; + var chart = new Chart(ctx, { + type: "line", + data: { + labels: Object.keys(lead_templates), + datasets: [{ + label: 'Quotation Count', + backgroundColor: [ + 'rgb(25,25,112)', + ], + data: Object.values(lead_templates), + }] + }, + }); + }); + }, + render_least_sold_graph: function () { + /*This is to show least sold products graph.*/ + var self = this; + var ctx = self.$("#least_sold"); + rpc.query({ + model: 'sale.order', + method: 'get_least_sold', + args: [], + }) + .then(function (results) { + var lead_templates = results.lead_templates; + var chart = new Chart(ctx, { + type: "bar", + data: { + labels: Object.keys(lead_templates), + datasets: [{ + label: 'Product Count', + backgroundColor: [ + 'rgb(25,25,112)', + 'rgb(135,206,235)', + 'rgb(75, 192, 192)', + 'rgb(54, 162, 235)', + 'rgb(255, 209, 219)', + 'rgb(255, 205, 86)', + 'rgb(99, 255, 222)', + 'rgb(132, 255, 99)', + 'rgb(139, 139, 184)', + 'rgb(40, 255, 40)', + ], + data: Object.values(lead_templates), + }] + }, + }); + }); + }, + }); + core.action_registry.add('sale_dashboard', DashBoard); + return DashBoard; +}); diff --git a/all_in_one_sales_kit/static/src/js/sale_report.js b/all_in_one_sales_kit/static/src/js/sale_report.js new file mode 100644 index 000000000..30d344b11 --- /dev/null +++ b/all_in_one_sales_kit/static/src/js/sale_report.js @@ -0,0 +1,231 @@ +odoo.define('all_in_one_sales_kit.sale_report', function(require) { + 'use strict'; + var AbstractAction = require('web.AbstractAction'); + var core = require('web.core'); + var rpc = require('web.rpc'); + var QWeb = core.qweb; + var _t = core._t; + var time = require('web.time'); + var framework = require('web.framework'); + var session = require('web.session'); + /** + * SaleReport class for handling sales report actions. + * Extends AbstractAction. + * + * @property {string} template - The template associated with this action. + * @property {Object} events - Event handlers for various HTML elements. + * @property {Array} report_lines - Array of report lines. + * @property {number|null} wizard_id - ID of the associated wizard or null if not available. + */ + var SaleReport = AbstractAction.extend({ + template: 'SaleReport', + events: { + 'click #apply_filter': 'apply_filter', + 'click #pdf': 'print_pdf', + 'click #xlsx': 'print_xlsx', + 'click .view_sale_order': 'button_view_order', + 'mousedown div.input-group.date[data-target-input="nearest"]': '_onCalendarIconClick', + }, + /** + * Constructor for the SaleReport class. + * + * @param {Object} parent - The parent object. + * @param {Object} action - The action object. + */ + init: function(parent, action) { + this._super(parent, action); + this.report_lines = action.report_lines; + this.wizard_id = action.context.wizard | null; + }, + /** + * Method called when the object is started or initialized. + * Sets up the initial state, creates a sales report, and loads data. + */ + start: function() { + var self = this; + self.initial_render = true; + rpc.query({ + model: 'sales.report', + method: 'create', + args: [{ + }] + }).then(function(res) { + self.wizard_id = res; + self.load_data(self.initial_render); + self.apply_filter(); + }) + }, + _onCalendarIconClick: function(ev) { + /*It is to get selected date.*/ + var $calendarInputGroup = $(ev.currentTarget); + var calendarOptions = { + minDate: moment({ + y: 1000 + }), + maxDate: moment().add(200, 'y'), + calendarWeeks: true, + defaultDate: moment().format(), + sideBySide: true, + buttons: { + showClear: true, + showClose: true, + showToday: true, + }, + icons: { + date: 'fa fa-calendar', + }, + locale: moment.locale(), + format: time.getLangDateFormat(), + widgetParent: 'body', + allowInputToggle: true, + }; + $calendarInputGroup.datetimepicker(calendarOptions); + }, + /** + * Loads data for the sales report, including filters and order details. + * + * @param {boolean} initial_render - Flag indicating whether it's the initial rendering. + */ + load_data: function(initial_render = true) { + var self = this; + self._rpc({ + model: 'sales.report', + method: 'sale_report', + args: [ + [this.wizard_id] + ], + }).then(function(datas) { + if (initial_render) { + self.$('.filter_view_sr').html(QWeb.render('saleFilterView', { + filter_data: datas['filters'], + })); + self.$el.find('.report_type').select2({ + placeholder: ' Report Type...', + }); + } + if (datas['orders']) + self.$('.table_view_sr').html(QWeb.render('SaleOrderTable', { + filter: datas['filters'], + order: datas['orders'], + report_lines: datas['report_lines'], + main_lines: datas['report_main_line'] + })); + }) + }, + print_pdf: function(e) { + /*This is to pass data need to print in pdf report*/ + e.preventDefault(); + var self = this; + var action_title = self._title; + self._rpc({ + model: 'sales.report', + method: 'sale_report', + args: [ + [self.wizard_id] + ], + }).then(function(data) { + var action = { + 'type': 'ir.actions.report', + 'report_type': 'qweb-pdf', + 'report_name': 'all_in_one_sales_kit.sale_order_report', + 'report_file': 'all_in_one_sales_kit.sale_order_report', + 'data': { + 'report_data': data + }, + 'context': { + 'active_model': 'sales.report', + 'landscape': 1, + 'sale_order_report': true + + }, + 'display_name': 'Sale Order', + }; + return self.do_action(action); + }); + }, + print_xlsx: function() { + /*This is to pass data need to print in xlsx report*/ + var self = this; + self._rpc({ + model: 'sales.report', + method: 'sale_report', + args: [ + [self.wizard_id] + ], + }).then(function(data) { + var action = { + 'data': { + 'model': 'sales.report', + 'options': JSON.stringify(data), + 'output_format': 'xlsx', + 'report_name': 'Sale Report', + }, + }; + self.downloadXlsx(action); + }); + }, + downloadXlsx: function (action){ + /*It is to pass data needed to print xlsx report*/ + framework.blockUI(); + session.get_file({ + url: '/xlsx_reports', + data: action.data, + complete: framework.unblockUI, + error: (error) => this.call('crash_manager', 'rpc_error', error), + }); + }, + button_view_order: function(event) { + /*It is to show the corresponding order on button click*/ + event.preventDefault(); + var self = this; + var context = {}; + this.do_action({ + name: _t("Sale Order"), + type: 'ir.actions.act_window', + res_model: 'sale.order', + view_type: 'form', + domain: [ + ['id', '=', $(event.target).closest('.view_sale_order').attr('id')] + ], + views: [ + [false, 'list'], + [false, 'form'] + ], + target: 'current' + }); + }, + apply_filter: function() { + /*It is to get data according to the filters applied*/ + var self = this; + self.initial_render = false; + var filter_data_selected = {}; + if (this.$el.find('.datetimepicker-input[name="date_from"]').val()) { + filter_data_selected.date_from = moment(this.$el.find('.datetimepicker-input[name="date_from"]').val(), time.getLangDateFormat()).locale('en').format('YYYY-MM-DD'); + } + if (this.$el.find('.datetimepicker-input[name="date_to"]').val()) { + filter_data_selected.date_to = moment(this.$el.find('.datetimepicker-input[name="date_to"]').val(), time.getLangDateFormat()).locale('en').format('YYYY-MM-DD'); + } + if ($(".report_type").length) { + var report_res = document.getElementById("report_res") + filter_data_selected.report_type = $(".report_type")[1].value + report_res.value = $(".report_type")[1].value + report_res.innerHTML = report_res.value; + if ($(".report_type")[1].value == "") { + report_res.innerHTML = "report_by_order"; + } + } + rpc.query({ + model: 'sales.report', + method: 'write', + args: [ + self.wizard_id, filter_data_selected + ], + }).then(function(res) { + self.initial_render = false; + self.load_data(self.initial_render); + }); + }, + }); + core.action_registry.add("s_r", SaleReport); + return SaleReport; +}); diff --git a/all_in_one_sales_kit/static/src/scss/dashboard.scss b/all_in_one_sales_kit/static/src/scss/dashboard.scss new file mode 100644 index 000000000..0d7ec4486 --- /dev/null +++ b/all_in_one_sales_kit/static/src/scss/dashboard.scss @@ -0,0 +1,123 @@ +p, span, a, ul, li, button { + font-size: inherit; + font-weight: inherit; + line-height: inherit; +} + +strong { + font-weight: 600; +} + +h1, h2, h3, h4, h5, h6 { + line-height: 1.5em; + font-weight: 500; +} + +strong { + font-weight: 400; +} + +div span { + font-weight: 400; +} + +:root { + /* Primary */ + --mauve: #7D7EAF; + + /* Light */ + --mauve-light: #e5e5ef; + --mauve-taupe: #B0FFDF; +} +.bg-mauve-light { + background-color: var(--mauve-taupe); +} +.text-mauve { + color: var(--mauve); +} +.dashboard-card { + border-radius: 0.3rem; + display: flex; + justify-content: left; + padding: 1.1rem 1.5rem 1.5rem 1.5rem; + margin: 1rem auto; + height: 90px; +} + +.dashboard-card__icon-container { + height: 50px; + width: 50px; + border-radius: 15px; +} + +.dashboard-card__icon-container i { + font-size: 20px; +} +.dashboard-card__details h3 { + font-weight: 700; + font-size: 1.2rem; + text-indent: 20px; +} + +.dashboard-card__details h4 { + font-weight: 700; + font-size: 1.0rem; + margin-top: -3px; + text-indent: 20px; +} +h2.section-header { + font-weight: 500; + font-size: 1.5rem; +} +.card-shadow { + box-shadow: 1px 3px 5px 0px rgba(222, 222, 222, 1); +} +.lead_customer{ + background-color: #f2f2f2; + height:500px; + width:48%; + float:left; +} +.lead_product{ + background-color: #f2f2f2; + height:500px; + width:48%; + float:right; +} +.lead_order{ + background-color: #f2f2f2; + height:500px; + width:48%; + float:left; +} +.bar{ + margin-top:40px; +} +.graph{ + margin-top:40px; +} +.team{ + background-color: #f2f2f2; + height:500px; + width:48%; + float:right; +} +.my_monthly_comparison{ + background-color: #f2f2f2; + height:500px; + width:48%; + float:right; +} +.least_sold{ + background-color: #f2f2f2; + height:500px; + width:48%; + float:left; +} +input, button, select, optgroup, textarea { + margin: 0; + font-family: inherit; + color: grey; + font-size: inherit; + line-height: inherit; +} diff --git a/all_in_one_sales_kit/static/src/xml/dashboard_templates.xml b/all_in_one_sales_kit/static/src/xml/dashboard_templates.xml new file mode 100644 index 000000000..1f0bbf4cf --- /dev/null +++ b/all_in_one_sales_kit/static/src/xml/dashboard_templates.xml @@ -0,0 +1,201 @@ + + + + +
+
+
+ + +
+
+
+
+
+
+ Start Date: + + End Date: + +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+

+ +
+ +

+

Quotations

+
+
+
+
+
+
+ +
+
+

+ +
+ +

+

Sale Orders

+
+
+
+
+
+
+ +
+
+

+ +
+ +

+

Quotation Sent

+
+
+
+
+
+
+ +
+
+

+ +
+ +

+

Quotation Cancel

+
+
+
+
+
+
+ +
+
+

+ +
+ +

+

Customers

+
+
+
+
+
+
+ +
+
+

+ +
+ +

+

Products

+
+
+
+
+
+
+ +
+
+

+ +
+ +

+

To Invoice

+
+
+
+
+
+
+

Top Customers

+
+ +
+
+
+
+
+

Top Products

+
+ +
+
+
+
+
+
+
+

Top Sale Orders

+
+ +
+
+
+
+
+

Sales Team Revenue

+
+ +
+
+
+
+
+
+
+

Least Sold Products

+
+ +
+
+
+
+
+

My Monthly Quotation Count

+
+ +
+
+
+
+
+
+
+ diff --git a/all_in_one_sales_kit/static/src/xml/sale_report_templates.xml b/all_in_one_sales_kit/static/src/xml/sale_report_templates.xml new file mode 100644 index 000000000..28a05276c --- /dev/null +++ b/all_in_one_sales_kit/static/src/xml/sale_report_templates.xml @@ -0,0 +1,475 @@ + + + + +
+
+
+

Sale Report

+
+
+
+
+
+
+
+
+ +
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
OrderDate OrderCustomerSales RepresentativeTotal QtyAmount TotalNote
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OrderDate OrderCustomerSales RepresentativeProduct CodeProduct NamePrice unitQtyPrice Subtotal
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
CategoryProduct CodeProduct NameQtyAmount Total
+ + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + +
CategoryQtyAmount Total
+ + + + + + + + + + + + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + +
Sales RepresentativeTotal OrderTotal QtyTotal Amount
+ + + + + + + + + + + + + + + + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + +
StateTotal CountQuantityAmount
+ + + + + + + + + + + + + + + + +
+
+
+
+ diff --git a/all_in_one_sales_kit/views/all_in_one_sales_kit_menus.xml b/all_in_one_sales_kit/views/all_in_one_sales_kit_menus.xml new file mode 100644 index 000000000..6d54cb5d4 --- /dev/null +++ b/all_in_one_sales_kit/views/all_in_one_sales_kit_menus.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + diff --git a/all_in_one_sales_kit/views/dashboard_menu.xml b/all_in_one_sales_kit/views/dashboard_menu.xml new file mode 100644 index 000000000..c3a31afea --- /dev/null +++ b/all_in_one_sales_kit/views/dashboard_menu.xml @@ -0,0 +1,11 @@ + + + + + Dashboard + sale_dashboard + + + diff --git a/all_in_one_sales_kit/views/ir_fields_search_views.xml b/all_in_one_sales_kit/views/ir_fields_search_views.xml new file mode 100644 index 000000000..61699f6e7 --- /dev/null +++ b/all_in_one_sales_kit/views/ir_fields_search_views.xml @@ -0,0 +1,16 @@ + + + + + ir.model.fields.view.form.inherit.all.in.one.sales.kit + ir.model.fields + + + + + + + + + diff --git a/all_in_one_sales_kit/views/product_product_views.xml b/all_in_one_sales_kit/views/product_product_views.xml new file mode 100644 index 000000000..4066531c7 --- /dev/null +++ b/all_in_one_sales_kit/views/product_product_views.xml @@ -0,0 +1,193 @@ + + + + + Product Kanban + product.product + + + + + + + + + +
+
+ Product +
+
+ + + + + + + +
+ +
+
    +
  • + Price: + + +
  • +
+
+
+
+ + + + + + + + product.product.form + product.product + primary + + + +
+ Product Variant + false +
+ +
+ + + + + + + + + + + + + + + + +
+
+ + Product Pack + ir.actions.act_window + product.template + kanban,form + [('is_pack','=',True)] + {'default_is_pack' : True} + +

+ There is no product pack to show...! +

+
+
+ + + product.template.view.search.inherit.all.in.one.sales.kit + + product.template + + + + + + + + +
diff --git a/all_in_one_sales_kit/views/res_config_settings_views.xml b/all_in_one_sales_kit/views/res_config_settings_views.xml new file mode 100644 index 000000000..e6c838df1 --- /dev/null +++ b/all_in_one_sales_kit/views/res_config_settings_views.xml @@ -0,0 +1,105 @@ + + + + + res.config.settings.view.form.inherit.all.in.one.sales.kit + res.config.settings + + + +
+
+ +
+
+
+
+
+ +

Sale Document Approval

+
+
+
+ +
+
+
+
+
+
+ +

Out Of Stock Product Restriction

+
+
+
+ +
+
+
+
+
+
+
+
+ +

Automate Orders

+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
diff --git a/all_in_one_sales_kit/views/res_users_views.xml b/all_in_one_sales_kit/views/res_users_views.xml new file mode 100644 index 000000000..2854ee095 --- /dev/null +++ b/all_in_one_sales_kit/views/res_users_views.xml @@ -0,0 +1,24 @@ + + + + + res.users.view.form.inherit.all.in.one.sales.kit + res.users + + + + + + + + + + + + + + + diff --git a/all_in_one_sales_kit/views/sale_order_line_views.xml b/all_in_one_sales_kit/views/sale_order_line_views.xml new file mode 100644 index 000000000..d2532eb4d --- /dev/null +++ b/all_in_one_sales_kit/views/sale_order_line_views.xml @@ -0,0 +1,478 @@ + + + + + sale.order.line.form + sale.order.line + +
+ + + + +

+ +

+
+ + + + + + +
+ + + + + + + + + + + + +
+
+
+
+
+ + + sale.lines.tree + sale.order.line + + + + + + + + + + + + + + + + + + + + + sale.order.line.tree + sale.order.line + + + + + + + + + + + +
+
+ +
+
+
+
+ + + +
+
+
+ Product: + +
+ Quantity: + +
+ Price + +
+ Sub Total: + + +
+
+
+
+
+
+
+ + + sale.order.line.pivot + sale.order.line + + + + + + + + + + + + sale.order.line.graph.view + sale.order.line + + + + + + + + + + sale.order.line.calender + sale.order.line + + + + + + + + + + + sale.order.line.search + sale.order.line + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sale Order Lines + ir.actions.act_window + sale.order.line + tree,kanban,form,pivot,graph,calendar + [('state', 'not in', ('draft', 'sent'))] + + + + + + sale.lines.quotation.form + sale.order.line + +
+ + + + +

+ +

+
+ + + + + + +
+ + + + + + + + + + + +
+
+
+
+
+ + + sale.lines.quotation.tree + sale.order.line + + + + + + + + + + + + + + + + + + + + + sale.lines.quotation.kanban + sale.order.line + + + + + + + + + + + +
+
+ +
+
+
+
+ + + +
+
+
+ Product: + +
+ Quantity: + +
+ Price + +
+ Sub Total: + + +
+
+
+
+
+
+
+ + + sale.lines.quotation.pivot + sale.order.line + + + + + + + + + + + + + sale.lines.quotation.graph.view + sale.order.line + + + + + + + + + + sale.lines.quotation.calender + sale.order.line + + + + + + + + + + + sale.lines.quotation.search + sale.order.line + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Quotation Lines + ir.actions.act_window + sale.order.line + tree,kanban,form,pivot,graph,calendar + [ + ('state', 'in', ('draft', 'sent', 'cancel'))] + + + + + + last.product.prices.view + sale.order.line + + + + + + + + + + + + + + + +
diff --git a/all_in_one_sales_kit/views/sale_order_views.xml b/all_in_one_sales_kit/views/sale_order_views.xml new file mode 100644 index 000000000..ba2c673d8 --- /dev/null +++ b/all_in_one_sales_kit/views/sale_order_views.xml @@ -0,0 +1,184 @@ + + + + + sale.order.view.form.inherit.all.in.one.sales.kit + sale.order + + + + +
+ + + + + + {'invisible': ['|',('state', 'not in','cancel'),('is_version','=', True)]} + + + + + + + + + + + + +