diff --git a/import_dashboard/README.rst b/import_dashboard/README.rst new file mode 100644 index 000000000..2671e5c2b --- /dev/null +++ b/import_dashboard/README.rst @@ -0,0 +1,46 @@ +.. image:: https://img.shields.io/badge/license-LGPL--3-green.svg + :target: https://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 + +Import Dashboard +================ +* All in one import dashboards + +Configuration +============= +No additional configuration required + +Company +------- +* `Cybrosys Techno Solutions `__ + +License +------- +General Public License, Version 3 (LGPL v3). +(https://www.gnu.org/licenses/lgpl-3.0-standalone.html) + +Credits +------- +Developers: (V15) Raneesha MK, 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/import_dashboard/__init__.py b/import_dashboard/__init__.py new file mode 100644 index 000000000..d4b63604d --- /dev/null +++ b/import_dashboard/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +from . import models +from . import wizard diff --git a/import_dashboard/__manifest__.py b/import_dashboard/__manifest__.py new file mode 100644 index 000000000..4b31fe48d --- /dev/null +++ b/import_dashboard/__manifest__.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +{ + 'name': "Import Dashboard", + 'version': '15.0.1.0.0', + 'category': 'Extra Tools', + 'summary': 'This module facilitates the import of data from various ' + 'modules through a single window', + 'description': """The Import Dashboard feature in Odoo is a powerful tool + that can save users a significant amount of time when importing large + amounts of data into the system. It streamlines the import process and + reduces the likelihood of errors, making it a valuable feature for + businesses that rely on accurate and timely data.""", + 'author': 'Cybrosys Techno Solutions', + 'company': 'Cybrosys Techno Solutions', + 'maintainer': 'Cybrosys Techno Solutions', + 'website': "https://www.cybrosys.com", + 'depends': ['base'], + 'data': [ + 'security/ir.model.access.csv', + 'views/import_dashboard_views.xml', + 'views/res_config_settings_views.xml', + 'wizard/import_attendance_views.xml', + 'wizard/import_bill_of_material_views.xml', + 'wizard/import_invoice_views.xml', + 'wizard/import_message_views.xml', + 'wizard/import_partner_views.xml', + 'wizard/import_payment_views.xml', + 'wizard/import_pos_views.xml', + 'wizard/import_product_pricelist_views.xml', + 'wizard/import_product_template_views.xml', + 'wizard/import_purchase_order_views.xml', + 'wizard/import_sale_order_views.xml', + 'wizard/import_task_views.xml', + 'wizard/import_vendor_pricelist_views.xml' + ], + 'assets': { + 'web.assets_backend': [ + 'import_dashboard/static/src/js/import_dashboard.js', + 'import_dashboard/static/src/css/style.scss', + ], + 'web.assets_qweb': [ + 'import_dashboard/static/src/xml/dashboard_templates.xml', + ], + }, + 'external_dependencies': { + 'python': ['xlrd'] + }, + 'images': ['static/description/banner.jpg'], + 'license': 'LGPL-3', + 'installable': True, + 'auto_install': False, + 'application': True, +} diff --git a/import_dashboard/doc/RELEASE_NOTES.md b/import_dashboard/doc/RELEASE_NOTES.md new file mode 100644 index 000000000..14e60b463 --- /dev/null +++ b/import_dashboard/doc/RELEASE_NOTES.md @@ -0,0 +1,6 @@ +## Module + +#### 11.04.2024 +#### Version 15.0.1.0.0 +#### ADD +- Initial commit for Import Dashboard diff --git a/import_dashboard/models/__init__.py b/import_dashboard/models/__init__.py new file mode 100644 index 000000000..a810a92f1 --- /dev/null +++ b/import_dashboard/models/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +from . import ir_config_parameter +from . import ir_module_module +from . import res_config_settings diff --git a/import_dashboard/models/ir_config_parameter.py b/import_dashboard/models/ir_config_parameter.py new file mode 100644 index 000000000..f953cdf65 --- /dev/null +++ b/import_dashboard/models/ir_config_parameter.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +from odoo import api, models, _ +from odoo.exceptions import ValidationError + + +class IrConfigParameter(models.Model): + """ Inheriting ir config parameter model for checking the modules are + installed and enable dashboard title for installed models """ + _inherit = 'ir.config_parameter' + + @api.model + def create(self, vals_list): + """For checking the necessary modules are installed""" + if vals_list.get('key') == 'import_dashboard.import_bom': + if vals_list['value']: + check = self.env["ir.module.module"].search( + [('name', '=', 'mrp')]) + if check.state == 'uninstalled': + vals_list['value'] = False + raise ValidationError(_('The module is not found. Please ' + 'make sure you have it installed')) + else: + pass + if vals_list.get('key') == 'import_dashboard.import_pos': + if vals_list['value']: + check = self.env["ir.module.module"].search( + [('name', '=', 'point_of_sale')]) + if check.state == 'uninstalled': + vals_list['value'] = False + raise ValidationError(_('The module is not found. Please ' + 'make sure you have it installed')) + else: + pass + if vals_list.get('key') == 'import_dashboard.import_sale': + if vals_list['value']: + check = self.env["ir.module.module"].search( + [('name', '=', 'sale_management')]) + if check.state == 'uninstalled': + vals_list['value'] = False + raise ValidationError(_('The module is not found. Please ' + 'make sure you have it installed')) + else: + pass + if vals_list.get('key') == 'import_dashboard.import_attendance': + if vals_list['value']: + check = self.env["ir.module.module"].search( + [('name', '=', 'hr_attendance')]) + if check.state == 'uninstalled': + vals_list['value'] = False + raise ValidationError(_('The module is not found. Please ' + 'make sure you have it installed')) + else: + pass + if vals_list.get('key') == 'import_dashboard.import_purchase_order': + if vals_list['value']: + check = self.env["ir.module.module"].search( + [('name', '=', 'purchase')]) + if check.state == 'uninstalled': + vals_list['value'] = False + raise ValidationError(_('The module is not found. Please ' + 'make sure you have it installed')) + else: + pass + if vals_list.get('key') == 'import_dashboard.import_vendor_pricelist': + if vals_list['value']: + check = self.env["ir.module.module"].search( + [('name', '=', 'purchase')]) + if check.state == 'uninstalled': + vals_list['value'] = False + raise ValidationError(_('The module is not found. Please ' + 'make sure you have it installed')) + else: + pass + if vals_list.get('key') == 'import_dashboard.import_invoice': + if vals_list['value']: + check = self.env["ir.module.module"].search( + [('name', '=', 'account')]) + if check.state == 'uninstalled': + vals_list['value'] = False + raise ValidationError( + 'The module is not found. Please make sure you have ' + 'it installed.') + else: + pass + if vals_list.get('key') == 'import_dashboard.import_payment': + if vals_list['value']: + check = self.env["ir.module.module"].search( + [('name', '=', 'account')]) + if check.state == 'uninstalled': + vals_list['value'] = False + raise ValidationError(_( + 'The module is not found. Please make sure you have ' + 'it installed.')) + else: + pass + if vals_list.get('key') == 'import_dashboard.import_task': + if vals_list['value']: + check = self.env["ir.module.module"].search( + [('name', '=', 'project')]) + if check.state == 'uninstalled': + vals_list['value'] = False + raise ValidationError(_( + 'The module is not found. Please make sure you have ' + 'it installed.')) + else: + pass + if vals_list.get('key') == 'import_dashboard.import_product_template ': + if vals_list['value']: + check = self.env["ir.module.module"].search( + [('name', '=', 'product')]) + if check.state == 'uninstalled': + vals_list['value'] = False + raise ValidationError(_( + 'The module is not found. Please make sure you have ' + 'it installed.')) + else: + pass + return super(IrConfigParameter, self).create(vals_list) + + @api.model + def check_user_group(self): + """ For enabling the corresponding tiles in the dashboard + returns: dict of values with true or false""" + return { + 'bill_of_material': self.env[ + 'ir.config_parameter'].sudo().get_param( + "import_dashboard.import_bom"), + 'pos': self.env['ir.config_parameter'].sudo().get_param( + "import_dashboard.import_pos"), + 'import_attendance': self.env[ + 'ir.config_parameter'].sudo().get_param( + "import_dashboard.import_attendance"), + 'import_payment': self.env['ir.config_parameter'].sudo().get_param( + "import_dashboard.import_payment"), + 'import_task': self.env['ir.config_parameter'].sudo().get_param( + "import_dashboard.import_task"), + 'import_sale': self.env['ir.config_parameter'].sudo().get_param( + "import_dashboard.import_sale"), + 'import_purchase': self.env[ + 'ir.config_parameter'].sudo().get_param( + "import_dashboard.import_purchase_order"), + 'import_product_template': self.env[ + 'ir.config_parameter'].sudo().get_param( + "import_dashboard.import_product_template"), + 'import_partner': self.env['ir.config_parameter'].sudo().get_param( + "import_dashboard.import_partner"), + 'import_invoice': self.env['ir.config_parameter'].sudo().get_param( + "import_dashboard.import_invoice"), + 'import_pricelist': self.env[ + 'ir.config_parameter'].sudo().get_param( + "import_dashboard.import_pricelist"), + 'import_vendor_pricelist': self.env[ + 'ir.config_parameter'].sudo().get_param( + "import_dashboard.import_vendor_pricelist"), + } diff --git a/import_dashboard/models/ir_module_module.py b/import_dashboard/models/ir_module_module.py new file mode 100644 index 000000000..dc4cf2405 --- /dev/null +++ b/import_dashboard/models/ir_module_module.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +from odoo import models + + +class Uninstall(models.Model): + """ Inheriting the ir module for setting fields as false if any of the + modules are uninstalled """ + _inherit = 'ir.module.module' + + def button_uninstall(self): + """ When a module is uninstalled set the corresponding boolean field + to False """ + if self.name == 'mrp': + self.env['ir.config_parameter'].sudo().set_param( + "import_dashboard.import_bom", + False) + if self.name == 'point_of_sale': + self.env['ir.config_parameter'].sudo().set_param( + "import_dashboard.import_pos", + False) + if self.name == 'hr_attendance': + self.env['ir.config_parameter'].sudo().set_param( + "import_dashboard.import_attendance", False) + if self.name == 'sale_management': + self.env['ir.config_parameter'].sudo().set_param( + "import_dashboard.import_sale", + False) + if self.name == 'purchase': + self.env['ir.config_parameter'].sudo().set_param( + "import_dashboard.import_purchase_order", False) + self.env['ir.config_parameter'].sudo().set_param( + "import_vendor_pricelist", False) + if self.name == 'account': + self.env['ir.config_parameter'].sudo().set_param( + "import_dashboard.import_invoice", + False) + self.env['ir.config_parameter'].sudo().set_param( + "import_dashboard.import_payment", + False) + if self.name == 'project': + self.env['ir.config_parameter'].sudo().set_param( + "import_dashboard.import_task", + False) + if self.name == 'product': + self.env['ir.config_parameter'].sudo().set_param( + "import_dashboard.import_product_template", False) + return super().button_uninstall() diff --git a/import_dashboard/models/res_config_settings.py b/import_dashboard/models/res_config_settings.py new file mode 100644 index 000000000..edc1fb0c1 --- /dev/null +++ b/import_dashboard/models/res_config_settings.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + """ Inheriting configuration settings for adding fields that indicate + which data to import""" + _inherit = 'res.config.settings' + + import_bom = fields.Boolean(string="Import BoM", + config_parameter='import_dashboard.import_bom', + help='For importing bom files') + import_pos = fields.Boolean(string="Import POS", + config_parameter='import_dashboard.import_pos', + help='For importing pos') + import_attendance = fields.Boolean(string="Import Attendance", + config_parameter='import_dashboard.import_attendance', + help='For importing attendance') + import_payment = fields.Boolean(string="Import Payments", + config_parameter='import_dashboard.import_payment', + help='For importing payments') + import_task = fields.Boolean(string="Import Task", + config_parameter='import_dashboard.import_task', + help='For importing tasks') + import_sale = fields.Boolean(string="Import Sale", + config_parameter='import_dashboard.import_sale', + help='For importing sales orders') + import_purchase_order = fields.Boolean(string="Import Purchase Order", + config_parameter='import_dashboard.import_purchase_order', + help='For importing purchase orders') + import_product_template = fields.Boolean(string="Import Products", + config_parameter='import_dashboard.import_product_template', + help='For importing Products') + import_partner = fields.Boolean(string="Import Partner", + config_parameter='import_dashboard.import_partner', + help='For importing partners') + import_invoice = fields.Boolean(string="Import Invoice", + config_parameter='import_dashboard.import_invoice', + help='For importing invoices') + import_pricelist = fields.Boolean(string="Import Pricelist", + config_parameter='import_dashboard.import_pricelist', + help='For importing price lists') + import_vendor_pricelist = fields.Boolean(string="Import Vendor Pricelist", + config_parameter='import_dashboard.import_vendor_pricelist', + help='For importing vendor price ' + 'lists') diff --git a/import_dashboard/security/ir.model.access.csv b/import_dashboard/security/ir.model.access.csv new file mode 100644 index 000000000..ece88d290 --- /dev/null +++ b/import_dashboard/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_import_sale_order_user,access.import.sale.order.user,model_import_sale_order,base.group_user,1,1,1,1 +access_import_purchase_order_user,access.import.purchase.order.user,model_import_purchase_order,base.group_user,1,1,1,1 +access_import_invoice_user,access.import.invoice.user,model_import_invoice,base.group_user,1,1,1,1 +access_import_partner_user,access.import.partner.user,model_import_partner,base.group_user,1,1,1,1 +access_import_product_pricelist_user,access.import.product.pricelist.user,model_import_product_pricelist,base.group_user,1,1,1,1 +access_import_message_user,access.import.message.user,model_import_message,base.group_user,1,1,1,1 +access_import_bill_of_material_user,access.import.bill.of.material.user,model_import_bill_of_material,base.group_user,1,1,1,1 +access_import_product_template_user,access.import.product.template.user,model_import_product_template,base.group_user,1,1,1,1 +access_import_pos_user,access.import.pos.user,model_import_pos,base.group_user,1,1,1,1 +access_import_attendance_user,access.import.attendance.user,model_import_attendance,base.group_user,1,1,1,1 +access_import_payment_user,access.import.payment.user,model_import_payment,base.group_user,1,1,1,1 +access_import_vendor_pricelist_user,access.import.vendor.pricelist.user,model_import_vendor_pricelist,base.group_user,1,1,1,1 +access_import_task_user,access.import.task.user,model_import_task,base.group_user,1,1,1,1 diff --git a/import_dashboard/static/description/assets/icons/check.png b/import_dashboard/static/description/assets/icons/check.png new file mode 100644 index 000000000..c8e85f51d Binary files /dev/null and b/import_dashboard/static/description/assets/icons/check.png differ diff --git a/import_dashboard/static/description/assets/icons/chevron.png b/import_dashboard/static/description/assets/icons/chevron.png new file mode 100644 index 000000000..2089293d6 Binary files /dev/null and b/import_dashboard/static/description/assets/icons/chevron.png differ diff --git a/import_dashboard/static/description/assets/icons/cogs.png b/import_dashboard/static/description/assets/icons/cogs.png new file mode 100644 index 000000000..95d0bad62 Binary files /dev/null and b/import_dashboard/static/description/assets/icons/cogs.png differ diff --git a/import_dashboard/static/description/assets/icons/consultation.png b/import_dashboard/static/description/assets/icons/consultation.png new file mode 100644 index 000000000..8319d4baa Binary files /dev/null and b/import_dashboard/static/description/assets/icons/consultation.png differ diff --git a/import_dashboard/static/description/assets/icons/ecom-black.png b/import_dashboard/static/description/assets/icons/ecom-black.png new file mode 100644 index 000000000..a9385ff13 Binary files /dev/null and b/import_dashboard/static/description/assets/icons/ecom-black.png differ diff --git a/import_dashboard/static/description/assets/icons/education-black.png b/import_dashboard/static/description/assets/icons/education-black.png new file mode 100644 index 000000000..3eb09b27b Binary files /dev/null and b/import_dashboard/static/description/assets/icons/education-black.png differ diff --git a/import_dashboard/static/description/assets/icons/hotel-black.png b/import_dashboard/static/description/assets/icons/hotel-black.png new file mode 100644 index 000000000..130f613be Binary files /dev/null and b/import_dashboard/static/description/assets/icons/hotel-black.png differ diff --git a/import_dashboard/static/description/assets/icons/license.png b/import_dashboard/static/description/assets/icons/license.png new file mode 100644 index 000000000..a5869797e Binary files /dev/null and b/import_dashboard/static/description/assets/icons/license.png differ diff --git a/import_dashboard/static/description/assets/icons/lifebuoy.png b/import_dashboard/static/description/assets/icons/lifebuoy.png new file mode 100644 index 000000000..658d56ccc Binary files /dev/null and b/import_dashboard/static/description/assets/icons/lifebuoy.png differ diff --git a/import_dashboard/static/description/assets/icons/manufacturing-black.png b/import_dashboard/static/description/assets/icons/manufacturing-black.png new file mode 100644 index 000000000..697eb0e9f Binary files /dev/null and b/import_dashboard/static/description/assets/icons/manufacturing-black.png differ diff --git a/import_dashboard/static/description/assets/icons/pos-black.png b/import_dashboard/static/description/assets/icons/pos-black.png new file mode 100644 index 000000000..97c0f90c1 Binary files /dev/null and b/import_dashboard/static/description/assets/icons/pos-black.png differ diff --git a/import_dashboard/static/description/assets/icons/puzzle.png b/import_dashboard/static/description/assets/icons/puzzle.png new file mode 100644 index 000000000..65cf854e7 Binary files /dev/null and b/import_dashboard/static/description/assets/icons/puzzle.png differ diff --git a/import_dashboard/static/description/assets/icons/restaurant-black.png b/import_dashboard/static/description/assets/icons/restaurant-black.png new file mode 100644 index 000000000..4a35eb939 Binary files /dev/null and b/import_dashboard/static/description/assets/icons/restaurant-black.png differ diff --git a/import_dashboard/static/description/assets/icons/service-black.png b/import_dashboard/static/description/assets/icons/service-black.png new file mode 100644 index 000000000..301ab51cb Binary files /dev/null and b/import_dashboard/static/description/assets/icons/service-black.png differ diff --git a/import_dashboard/static/description/assets/icons/trading-black.png b/import_dashboard/static/description/assets/icons/trading-black.png new file mode 100644 index 000000000..9398ba2f1 Binary files /dev/null and b/import_dashboard/static/description/assets/icons/trading-black.png differ diff --git a/import_dashboard/static/description/assets/icons/training.png b/import_dashboard/static/description/assets/icons/training.png new file mode 100644 index 000000000..884ca024d Binary files /dev/null and b/import_dashboard/static/description/assets/icons/training.png differ diff --git a/import_dashboard/static/description/assets/icons/update.png b/import_dashboard/static/description/assets/icons/update.png new file mode 100644 index 000000000..ecbc5a01a Binary files /dev/null and b/import_dashboard/static/description/assets/icons/update.png differ diff --git a/import_dashboard/static/description/assets/icons/user.png b/import_dashboard/static/description/assets/icons/user.png new file mode 100644 index 000000000..6ffb23d9f Binary files /dev/null and b/import_dashboard/static/description/assets/icons/user.png differ diff --git a/import_dashboard/static/description/assets/icons/wrench.png b/import_dashboard/static/description/assets/icons/wrench.png new file mode 100644 index 000000000..6c04dea0f Binary files /dev/null and b/import_dashboard/static/description/assets/icons/wrench.png differ diff --git a/import_dashboard/static/description/assets/misc/categories.png b/import_dashboard/static/description/assets/misc/categories.png new file mode 100644 index 000000000..bedf1e0b1 Binary files /dev/null and b/import_dashboard/static/description/assets/misc/categories.png differ diff --git a/import_dashboard/static/description/assets/misc/check-box.png b/import_dashboard/static/description/assets/misc/check-box.png new file mode 100644 index 000000000..42caf24b9 Binary files /dev/null and b/import_dashboard/static/description/assets/misc/check-box.png differ diff --git a/import_dashboard/static/description/assets/misc/compass.png b/import_dashboard/static/description/assets/misc/compass.png new file mode 100644 index 000000000..d5fed8faa Binary files /dev/null and b/import_dashboard/static/description/assets/misc/compass.png differ diff --git a/import_dashboard/static/description/assets/misc/corporate.png b/import_dashboard/static/description/assets/misc/corporate.png new file mode 100644 index 000000000..2eb13edbf Binary files /dev/null and b/import_dashboard/static/description/assets/misc/corporate.png differ diff --git a/import_dashboard/static/description/assets/misc/customer-support.png b/import_dashboard/static/description/assets/misc/customer-support.png new file mode 100644 index 000000000..79efc72ed Binary files /dev/null and b/import_dashboard/static/description/assets/misc/customer-support.png differ diff --git a/import_dashboard/static/description/assets/misc/cybrosys-logo.png b/import_dashboard/static/description/assets/misc/cybrosys-logo.png new file mode 100644 index 000000000..cc3cc0ccf Binary files /dev/null and b/import_dashboard/static/description/assets/misc/cybrosys-logo.png differ diff --git a/import_dashboard/static/description/assets/misc/features.png b/import_dashboard/static/description/assets/misc/features.png new file mode 100644 index 000000000..b41769f77 Binary files /dev/null and b/import_dashboard/static/description/assets/misc/features.png differ diff --git a/import_dashboard/static/description/assets/misc/logo.png b/import_dashboard/static/description/assets/misc/logo.png new file mode 100644 index 000000000..478462d3e Binary files /dev/null and b/import_dashboard/static/description/assets/misc/logo.png differ diff --git a/import_dashboard/static/description/assets/misc/pictures.png b/import_dashboard/static/description/assets/misc/pictures.png new file mode 100644 index 000000000..56d255fe9 Binary files /dev/null and b/import_dashboard/static/description/assets/misc/pictures.png differ diff --git a/import_dashboard/static/description/assets/misc/pie-chart.png b/import_dashboard/static/description/assets/misc/pie-chart.png new file mode 100644 index 000000000..426e05244 Binary files /dev/null and b/import_dashboard/static/description/assets/misc/pie-chart.png differ diff --git a/import_dashboard/static/description/assets/misc/right-arrow.png b/import_dashboard/static/description/assets/misc/right-arrow.png new file mode 100644 index 000000000..730984a06 Binary files /dev/null and b/import_dashboard/static/description/assets/misc/right-arrow.png differ diff --git a/import_dashboard/static/description/assets/misc/star.png b/import_dashboard/static/description/assets/misc/star.png new file mode 100644 index 000000000..2eb9ab29f Binary files /dev/null and b/import_dashboard/static/description/assets/misc/star.png differ diff --git a/import_dashboard/static/description/assets/misc/support.png b/import_dashboard/static/description/assets/misc/support.png new file mode 100644 index 000000000..4f18b8b82 Binary files /dev/null and b/import_dashboard/static/description/assets/misc/support.png differ diff --git a/import_dashboard/static/description/assets/misc/whatsapp.png b/import_dashboard/static/description/assets/misc/whatsapp.png new file mode 100644 index 000000000..d513a5356 Binary files /dev/null and b/import_dashboard/static/description/assets/misc/whatsapp.png differ diff --git a/import_dashboard/static/description/assets/modules/budget_image.png b/import_dashboard/static/description/assets/modules/budget_image.png new file mode 100644 index 000000000..b50130c7d Binary files /dev/null and b/import_dashboard/static/description/assets/modules/budget_image.png differ diff --git a/import_dashboard/static/description/assets/modules/credit_image.png b/import_dashboard/static/description/assets/modules/credit_image.png new file mode 100644 index 000000000..3ad04ecfd Binary files /dev/null and b/import_dashboard/static/description/assets/modules/credit_image.png differ diff --git a/import_dashboard/static/description/assets/modules/employee_image.png b/import_dashboard/static/description/assets/modules/employee_image.png new file mode 100644 index 000000000..30ad58232 Binary files /dev/null and b/import_dashboard/static/description/assets/modules/employee_image.png differ diff --git a/import_dashboard/static/description/assets/modules/export_image.png b/import_dashboard/static/description/assets/modules/export_image.png new file mode 100644 index 000000000..492980ad0 Binary files /dev/null and b/import_dashboard/static/description/assets/modules/export_image.png differ diff --git a/import_dashboard/static/description/assets/modules/gantt_image.png b/import_dashboard/static/description/assets/modules/gantt_image.png new file mode 100644 index 000000000..1ae7cfe3b Binary files /dev/null and b/import_dashboard/static/description/assets/modules/gantt_image.png differ diff --git a/import_dashboard/static/description/assets/modules/quotation_image.png b/import_dashboard/static/description/assets/modules/quotation_image.png new file mode 100644 index 000000000..499b1a72f Binary files /dev/null and b/import_dashboard/static/description/assets/modules/quotation_image.png differ diff --git a/import_dashboard/static/description/assets/modules/sales_discount.png b/import_dashboard/static/description/assets/modules/sales_discount.png new file mode 100644 index 000000000..004c61a9e Binary files /dev/null and b/import_dashboard/static/description/assets/modules/sales_discount.png differ diff --git a/import_dashboard/static/description/assets/screenshots/1.png b/import_dashboard/static/description/assets/screenshots/1.png new file mode 100644 index 000000000..0fbefd6ee Binary files /dev/null and b/import_dashboard/static/description/assets/screenshots/1.png differ diff --git a/import_dashboard/static/description/assets/screenshots/hero.gif b/import_dashboard/static/description/assets/screenshots/hero.gif new file mode 100644 index 000000000..1e78d8041 Binary files /dev/null and b/import_dashboard/static/description/assets/screenshots/hero.gif differ diff --git a/import_dashboard/static/description/assets/screenshots/screenshot2.png b/import_dashboard/static/description/assets/screenshots/screenshot2.png new file mode 100644 index 000000000..e6a87eeff Binary files /dev/null and b/import_dashboard/static/description/assets/screenshots/screenshot2.png differ diff --git a/import_dashboard/static/description/assets/screenshots/screenshot3.png b/import_dashboard/static/description/assets/screenshots/screenshot3.png new file mode 100644 index 000000000..7c64ae7a3 Binary files /dev/null and b/import_dashboard/static/description/assets/screenshots/screenshot3.png differ diff --git a/import_dashboard/static/description/assets/screenshots/screenshot4.png b/import_dashboard/static/description/assets/screenshots/screenshot4.png new file mode 100644 index 000000000..b88a91b5c Binary files /dev/null and b/import_dashboard/static/description/assets/screenshots/screenshot4.png differ diff --git a/import_dashboard/static/description/assets/screenshots/screenshot5.png b/import_dashboard/static/description/assets/screenshots/screenshot5.png new file mode 100644 index 000000000..685070348 Binary files /dev/null and b/import_dashboard/static/description/assets/screenshots/screenshot5.png differ diff --git a/import_dashboard/static/description/assets/screenshots/screenshot6.png b/import_dashboard/static/description/assets/screenshots/screenshot6.png new file mode 100644 index 000000000..67832419f Binary files /dev/null and b/import_dashboard/static/description/assets/screenshots/screenshot6.png differ diff --git a/import_dashboard/static/description/assets/screenshots/screenshot7.png b/import_dashboard/static/description/assets/screenshots/screenshot7.png new file mode 100644 index 000000000..cf0af2480 Binary files /dev/null and b/import_dashboard/static/description/assets/screenshots/screenshot7.png differ diff --git a/import_dashboard/static/description/banner.jpg b/import_dashboard/static/description/banner.jpg new file mode 100644 index 000000000..d4f230df8 Binary files /dev/null and b/import_dashboard/static/description/banner.jpg differ diff --git a/import_dashboard/static/description/icon.png b/import_dashboard/static/description/icon.png new file mode 100644 index 000000000..ec88ccb62 Binary files /dev/null and b/import_dashboard/static/description/icon.png differ diff --git a/import_dashboard/static/description/index.html b/import_dashboard/static/description/index.html new file mode 100644 index 000000000..14ffa1865 --- /dev/null +++ b/import_dashboard/static/description/index.html @@ -0,0 +1,621 @@ +
+ +
+ +
+
+ Community +
+
+ Enterprise +
+
+ Odoo.sh +
+
+
+ + +
+
+
+

+ Import Dashboard

+

+ Importing Data through a Single Window +

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

+ Explore This + Module

+
+ + + + +
+
+ +
+

+ Overview +

+
+
+
+ This module facilitates the import of data from various modules through + a single window. The "all-in-one" import feature can save time and + reduce errors when importing data into Odoo, making it a valuable tool + for users who need to import large amounts of data. We have provided + some sample templates for importing files, which users can utilize to + import data. +
+
+ + + +
+
+ +
+

+ Features +

+
+
+
+
+ + Community & + Enterprise Support. +
+
+ + Odoo supports various file formats, including CSV, Excel. +
+
+ + The import dashboard feature can be accessed through the "Import" button in the Odoo user interface. + +
+
+ + This feature supports the import of different types of data, including products, customers, suppliers, invoices, and more. +
+
+
+
+ + This has also includes validation rules to ensure that the imported data meets the required format and data types. +
+
+ + After the data has been imported, users can review the results and make any necessary adjustments. +
+
+ + The all in one import feature can save time and reduce errors when importing data into Odoo, making it a valuable tool for users who need to import large amounts of data. +
+
+
+ + + +
+
+ +
+

+ Screenshots +

+
+
+
+
+

Dashboard Settings

+

Go to the configuration settings of Dashboard and enable them based on your needs. Before enabling, ensure the corresponding module is active.

+ +
+
+

+ Dashboard View

+ +
+
+

+ Import Sale Order

+

+ We can see that deletion of the databases mentioned in the conf + file is restricted.

+ +
+
+
+ + +
+
+

Suggested 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/import_dashboard/static/sample_template/Attendance.xlsx b/import_dashboard/static/sample_template/Attendance.xlsx new file mode 100644 index 000000000..d7d1258cd Binary files /dev/null and b/import_dashboard/static/sample_template/Attendance.xlsx differ diff --git a/import_dashboard/static/sample_template/Attendance_Template.csv b/import_dashboard/static/sample_template/Attendance_Template.csv new file mode 100644 index 000000000..bb340032f --- /dev/null +++ b/import_dashboard/static/sample_template/Attendance_Template.csv @@ -0,0 +1,2 @@ +Employee,Check In,Check Out,Worked Hours +Anita Oliver,2023-07-30 08:00:24,2023-07-30 12:01:33,4.01916666666667 diff --git a/import_dashboard/static/sample_template/BOM_Template.csv b/import_dashboard/static/sample_template/BOM_Template.csv new file mode 100644 index 000000000..6988730e5 --- /dev/null +++ b/import_dashboard/static/sample_template/BOM_Template.csv @@ -0,0 +1,2 @@ +Product,Product/Internal Reference,Product/Barcode,Quantity,Reference,BoM Type,Components,Components/Internal Reference,Components/Barcode,BoM Lines/Quantity +TestProduct,TEST_0003,1111111,5,,Manufacture this product,Screw,CONS_25630,,10 diff --git a/import_dashboard/static/sample_template/BOM_Template.xlsx b/import_dashboard/static/sample_template/BOM_Template.xlsx new file mode 100644 index 000000000..ceb3f2694 Binary files /dev/null and b/import_dashboard/static/sample_template/BOM_Template.xlsx differ diff --git a/import_dashboard/static/sample_template/Invoice_Template.csv b/import_dashboard/static/sample_template/Invoice_Template.csv new file mode 100644 index 000000000..15ad02129 --- /dev/null +++ b/import_dashboard/static/sample_template/Invoice_Template.csv @@ -0,0 +1,2 @@ +Partner,Payment Reference,Invoice Date,Due Date,Salesperson,Number,Label,Product,Account Code,Quantity,Uom,Price,Disc.%,Taxes,Internal Reference,Variant Values,Barcode +Azure Interior,INV/2023/00024,08/09/2023,08/20/2023,Marc Demo,INV/2023/00024,[FURN_6741] Large Meeting Table Conference room table,Four Person Desk,400000,1,Units,400,,Tax 10%,FURN_8220,,6548523917 diff --git a/import_dashboard/static/sample_template/Invoice_Template.xlsx b/import_dashboard/static/sample_template/Invoice_Template.xlsx new file mode 100644 index 000000000..72fa55c3b Binary files /dev/null and b/import_dashboard/static/sample_template/Invoice_Template.xlsx differ diff --git a/import_dashboard/static/sample_template/Partner.xlsx b/import_dashboard/static/sample_template/Partner.xlsx new file mode 100644 index 000000000..b5f8f08d0 Binary files /dev/null and b/import_dashboard/static/sample_template/Partner.xlsx differ diff --git a/import_dashboard/static/sample_template/Partner_Template.csv b/import_dashboard/static/sample_template/Partner_Template.csv new file mode 100644 index 000000000..8b45ee23b --- /dev/null +++ b/import_dashboard/static/sample_template/Partner_Template.csv @@ -0,0 +1,3 @@ +Is company? (y/n),Related Company,Job Position,Title,Name,Street,Street2,City,Country,State,Zip,Tax ID,Phone,Mobile,Email,Website,Tags,Salesperson +n,Azure Interior,Manager,Mr,Abdu,street1,Street2,utopian_city1,Italy,Aveiro,12345,GR12345670,987654321,123456789,abc@abco.com,qwerty.com,"tag1, tag2",Mitchell Admin +y,,,,Sukus,street1,Street2,utopian_city2,Greece,Azuay,543210,GR123456709,907654321,123456787,suku@asdo.mail,sukus.com,"tag3, tag4, tag5",Mitchell Admin diff --git a/import_dashboard/static/sample_template/Payment_Template.csv b/import_dashboard/static/sample_template/Payment_Template.csv new file mode 100644 index 000000000..3acc5707e --- /dev/null +++ b/import_dashboard/static/sample_template/Payment_Template.csv @@ -0,0 +1,2 @@ +Date,Number,Journal,Amount,Customer/Vendor,Payment Type,Reference +2023-08-28 07:32:33,PBNK1/2023/00008,Bank,111,Billy Fox,Send,88888888888 diff --git a/import_dashboard/static/sample_template/Payments.xlsx b/import_dashboard/static/sample_template/Payments.xlsx new file mode 100644 index 000000000..f0515a884 Binary files /dev/null and b/import_dashboard/static/sample_template/Payments.xlsx differ diff --git a/import_dashboard/static/sample_template/Point of Sale Orders.xlsx b/import_dashboard/static/sample_template/Point of Sale Orders.xlsx new file mode 100644 index 000000000..addad120e Binary files /dev/null and b/import_dashboard/static/sample_template/Point of Sale Orders.xlsx differ diff --git a/import_dashboard/static/sample_template/Pos_Order_template.csv b/import_dashboard/static/sample_template/Pos_Order_template.csv new file mode 100644 index 000000000..af26955f2 --- /dev/null +++ b/import_dashboard/static/sample_template/Pos_Order_template.csv @@ -0,0 +1,4 @@ +Order Ref,Session,Receipt Number,Order Date,Customer,Responsible,Total,Tax Amount,Amount Returned,Paid Amount,State,Product,Product Code,Barcode,Quantity,Unit Price,Discount %,Sub Total,Company,Pricelist +Shop/0086,POS/00001,Order 00001-001-0001,2023-08-24,Billy Fox,Mitchell Admin,108,2,0,110,done,Cabinet With Doors,E-COM11,,1,140,,142,My Company (San Francisco),Public Pricelist +,,,,,,,,,,,Desk Pad,FURN_0002,,10,1.98,,19.8,, +,,,,,,,,,,,New Desk Organizer,TEST_CODE01,,2,10,,,, diff --git a/import_dashboard/static/sample_template/Product_Pricelist_Template.csv b/import_dashboard/static/sample_template/Product_Pricelist_Template.csv new file mode 100644 index 000000000..1412ebe90 --- /dev/null +++ b/import_dashboard/static/sample_template/Product_Pricelist_Template.csv @@ -0,0 +1,2 @@ +Name,Product,Internal Reference ,Barcode,Variant Values,Fixed Price,Minimum Quantity,Start_date,End_date,Discount%,Extra Fee,Rounding Method,Min. Margin,Max. Margin,Other Pricelist,Product Category +Test Price List,Cabinet with Doors,E-COM11,1254,,222,3,2023-08-28 07:32:33,2023-08-30 07:32:33,,,,,,, diff --git a/import_dashboard/static/sample_template/Product_Pricelist_Template.xlsx b/import_dashboard/static/sample_template/Product_Pricelist_Template.xlsx new file mode 100644 index 000000000..82d5199c6 Binary files /dev/null and b/import_dashboard/static/sample_template/Product_Pricelist_Template.xlsx differ diff --git a/import_dashboard/static/sample_template/Product_Template_Template.csv b/import_dashboard/static/sample_template/Product_Template_Template.csv new file mode 100644 index 000000000..a6c09cb07 --- /dev/null +++ b/import_dashboard/static/sample_template/Product_Template_Template.csv @@ -0,0 +1,3 @@ +Name,Internal Reference,Product Type,Sales Price,Cost +Office Chair,TEST_0003,product,120,90 +ProductTemp,TEST_0014,product,100,88 diff --git a/import_dashboard/static/sample_template/Product_Template_Template.xlsx b/import_dashboard/static/sample_template/Product_Template_Template.xlsx new file mode 100644 index 000000000..1da8a2158 Binary files /dev/null and b/import_dashboard/static/sample_template/Product_Template_Template.xlsx differ diff --git a/import_dashboard/static/sample_template/Purchase_Template.csv b/import_dashboard/static/sample_template/Purchase_Template.csv new file mode 100644 index 000000000..ad625606b --- /dev/null +++ b/import_dashboard/static/sample_template/Purchase_Template.csv @@ -0,0 +1,2 @@ +Order Reference,Vendor,Vendor Reference,Order Deadline,Receipt Date,Purchase Representative,Description,Delivery Date,Quantity,Uom,Unit Price,Taxes,Product,Variant Values,Barcode +P00020,Billy Fox,,08/29/2023,08/31/2023,Marc Demo,[FURN_6666] Acoustic Bloc Screens,08/31/2023,2,Units,800,Tax 10%,My Acoustic Bloc Screens,Leg: Steel,5824693 diff --git a/import_dashboard/static/sample_template/Purchase_Template.xlsx b/import_dashboard/static/sample_template/Purchase_Template.xlsx new file mode 100644 index 000000000..ae0d4d1ed Binary files /dev/null and b/import_dashboard/static/sample_template/Purchase_Template.xlsx differ diff --git a/import_dashboard/static/sample_template/Sale_Order_Template.csv b/import_dashboard/static/sample_template/Sale_Order_Template.csv new file mode 100644 index 000000000..82463bc97 --- /dev/null +++ b/import_dashboard/static/sample_template/Sale_Order_Template.csv @@ -0,0 +1,2 @@ +Order Reference,Customer,Quotation Date,Salesperson,Product,Description,Quantity,Uom,Unit Price,Taxes,Disc.%,Internal Reference,Variant Values +S00008,Billy Fox,08/24/2023,Mitchell Admin,Customizable Desk,"[FURN_0096] Customizable Desk (Steel, White) 160x80cm, with large legs. ",2,Units,750,Tax 15%,0,,"Legs: Steel, Color: White" diff --git a/import_dashboard/static/sample_template/Task_Template.csv b/import_dashboard/static/sample_template/Task_Template.csv new file mode 100644 index 000000000..16861891d --- /dev/null +++ b/import_dashboard/static/sample_template/Task_Template.csv @@ -0,0 +1,2 @@ +Project,Title,Customer,Deadline,Parent Task +Test,new_test,Billy Fox,2023-08-24, diff --git a/import_dashboard/static/sample_template/Task_Template.xlsx b/import_dashboard/static/sample_template/Task_Template.xlsx new file mode 100644 index 000000000..398c090f3 Binary files /dev/null and b/import_dashboard/static/sample_template/Task_Template.xlsx differ diff --git a/import_dashboard/static/sample_template/Vendor Pricelist.xlsx b/import_dashboard/static/sample_template/Vendor Pricelist.xlsx new file mode 100644 index 000000000..acf0d53f8 Binary files /dev/null and b/import_dashboard/static/sample_template/Vendor Pricelist.xlsx differ diff --git a/import_dashboard/static/sample_template/Vendor_Pricelist.csv b/import_dashboard/static/sample_template/Vendor_Pricelist.csv new file mode 100644 index 000000000..f2e7c7721 --- /dev/null +++ b/import_dashboard/static/sample_template/Vendor_Pricelist.csv @@ -0,0 +1,2 @@ +Vendor,Product Template,Currency,Quantity,Price,Delivery Lead Time +Lumber Inc,Large Desk,USD,5,200,10 diff --git a/import_dashboard/static/sample_template/sale.order.xlsx b/import_dashboard/static/sample_template/sale.order.xlsx new file mode 100644 index 000000000..7b8bff81f Binary files /dev/null and b/import_dashboard/static/sample_template/sale.order.xlsx differ diff --git a/import_dashboard/static/src/css/style.scss b/import_dashboard/static/src/css/style.scss new file mode 100644 index 000000000..e9c320798 --- /dev/null +++ b/import_dashboard/static/src/css/style.scss @@ -0,0 +1,87 @@ +.main-container{ + overflow-y: auto; + height: 100vh; + overflow-x: none; +} +.card-shadow { + height: 65%; + -webkit-box-shadow: 1px 3px 5px 0px rgba(222, 222, 222, 1); + -moz-box-shadow: 1px 3px 5px 0px rgba(222, 222, 222, 1); + box-shadow: 1px 3px 5px 0px rgba(222, 222, 222, 1); + color: #243c64; + size: 10rem; + text-align: center; + padding:15px; + background:white; + border: 2px solid #71639e; + width:300px; +} +.card3{ + display:flex; + justify-content:center; + align-items:center; + height: 32vh; +} +.card6{ + display:flex; + justify-content:center; + align-items:center; + height: 32vh; +} +.card12{ + display:flex; + justify-content:center; + align-items:center; + height: 32vh; +} +.stat-widget-one{ + display:flex; + flex-direction: column; + height:100%; + justify-content: space-around; +} +.stat-icon{ + background: white; + width: 85px; + height: 80px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 3px; +} +.row{ + padding-right: 2%; + padding-left: 2%; +} +.card-shadow-container{ + display: flex; + justify-content: flex-start; +} +.btn { + width: fit-content; + align-self: center; +} +.stat-content{ + margin-left: 35px; + font-size: 20px; + font-weight: 600; +} +.stat-head{ + margin-center: 35px; + font-size: 20px; + font-weight: 600; +} +.o_action_manager{ + direction: ltr; + -webkit-box-flex: 1; +} +@media (max-width: 767.98px) { + .stat-icon{ + height: 70px; + } + } +@media (max-height: 671px) { + .stat-icon{ + height: 10px; + } + } diff --git a/import_dashboard/static/src/img/calendar.png b/import_dashboard/static/src/img/calendar.png new file mode 100644 index 000000000..1452bb8ca Binary files /dev/null and b/import_dashboard/static/src/img/calendar.png differ diff --git a/import_dashboard/static/src/img/cart.png b/import_dashboard/static/src/img/cart.png new file mode 100644 index 000000000..0b7d9167d Binary files /dev/null and b/import_dashboard/static/src/img/cart.png differ diff --git a/import_dashboard/static/src/img/icons8-invoice-64 .png b/import_dashboard/static/src/img/icons8-invoice-64 .png new file mode 100644 index 000000000..582c0330a Binary files /dev/null and b/import_dashboard/static/src/img/icons8-invoice-64 .png differ diff --git a/import_dashboard/static/src/img/icons8-list-48.png b/import_dashboard/static/src/img/icons8-list-48.png new file mode 100644 index 000000000..0046af448 Binary files /dev/null and b/import_dashboard/static/src/img/icons8-list-48.png differ diff --git a/import_dashboard/static/src/img/icons8-paid-bill-64.png b/import_dashboard/static/src/img/icons8-paid-bill-64.png new file mode 100644 index 000000000..e7eee2f11 Binary files /dev/null and b/import_dashboard/static/src/img/icons8-paid-bill-64.png differ diff --git a/import_dashboard/static/src/img/icons8-partner-64.png b/import_dashboard/static/src/img/icons8-partner-64.png new file mode 100644 index 000000000..73745c04e Binary files /dev/null and b/import_dashboard/static/src/img/icons8-partner-64.png differ diff --git a/import_dashboard/static/src/img/icons8-vendor-64.png b/import_dashboard/static/src/img/icons8-vendor-64.png new file mode 100644 index 000000000..320f693eb Binary files /dev/null and b/import_dashboard/static/src/img/icons8-vendor-64.png differ diff --git a/import_dashboard/static/src/img/payment-terminal.png b/import_dashboard/static/src/img/payment-terminal.png new file mode 100644 index 000000000..6df6043bb Binary files /dev/null and b/import_dashboard/static/src/img/payment-terminal.png differ diff --git a/import_dashboard/static/src/img/payment.png b/import_dashboard/static/src/img/payment.png new file mode 100644 index 000000000..e875bd52a Binary files /dev/null and b/import_dashboard/static/src/img/payment.png differ diff --git a/import_dashboard/static/src/img/tasks.png b/import_dashboard/static/src/img/tasks.png new file mode 100644 index 000000000..9c82816a0 Binary files /dev/null and b/import_dashboard/static/src/img/tasks.png differ diff --git a/import_dashboard/static/src/js/import_dashboard.js b/import_dashboard/static/src/js/import_dashboard.js new file mode 100644 index 000000000..aea65e49a --- /dev/null +++ b/import_dashboard/static/src/js/import_dashboard.js @@ -0,0 +1,332 @@ +odoo.define('import_dashboard.dashboard', function(require) { + "use strict"; + var AbstractAction = require('web.AbstractAction'); + var core = require('web.core'); + var check_bill_of_material = '' + var check_pos = '' + var check_attendance = '' + var check_payment = '' + var check_task = '' + var check_sale = '' + var check_purchase = '' + var check_product = '' + var check_partner = '' + var check_invoice = '' + var check_pricelist = '' + var check_vendor_pricelist = '' + var ImportDashBoard = AbstractAction.extend({ + template: 'imp_dash', + events: { + 'click .import_sale': 'import_sale', + 'click .import_purchase': 'import_purchase', + 'click .import_invoice': 'import_invoice', + 'click .import_partner': 'import_partner', + 'click .import_product_pricelist': 'import_product_pricelist', + 'click .import_bill_of_material': 'import_bill_of_material', + 'click .import_product_template': 'import_product_template', + 'click .import_vendor_pricelist': 'import_vendor_pricelist', + 'click .import_pos': 'import_pos', + 'click .import_attendance': 'import_attendance', + 'click .import_payment': 'import_payment', + 'click .import_task': 'import_task', + }, + willStart: function() { + //For enabling the corresponding boolean field for modules in the dashboard + var self = this; + return this._super() + .then(function() { + var def0 = self._rpc({ + model: 'ir.config_parameter', + method: 'check_user_group' + }).then(function(result) { + if (result['bill_of_material']) { + self.check_bill_of_material = true; + } else { + self.check_bill_of_material = false; + } + if (result['pos']) { + self.check_pos = true; + } else { + self.check_pos = false; + } + if (result['import_attendance']) { + self.check_attendance = true; + } else { + self.check_attendance = false; + } + if (result['import_payment']) { + self.check_payment = true; + } else { + self.check_payment = false; + } + if (result['import_task']) { + self.check_task = true; + } else { + self.check_task = false; + } + if (result['import_sale']) { + self.check_sale = true; + } else { + self.check_sale = false; + } + if (result['import_purchase']) { + self.check_purchase = true; + } else { + self.check_purchase = false; + } + if (result['import_product_template']) { + self.check_product = true; + } else { + self.check_product = false; + } + if (result['import_partner']) { + self.check_partner = true; + } else { + self.check_partner = false; + } + if (result['import_invoice']) { + self.check_invoice = true; + } else { + self.check_invoice = false; + } + if (result['import_pricelist']) { + self.check_pricelist = true; + } else { + self.check_pricelist = false; + } + if (result['import_vendor_pricelist']) { + self.check_vendor_pricelist = true; + } else { + self.check_vendor_pricelist = false; + } + }); + return $.when(def0); + }); + }, + start: function() { + //super the start function for calling the check_data() function + var self = this; + return this._super().then(function() { + self.check_data(); + }); + }, + check_data: function() { + //For showing the corresponding tiles in the dashboard + var self = this; + if (self.check_bill_of_material == true) { + self.$("card_bill_of_material").show(); + self.$('.start_msg').hide(); + } else { + self.$("#card_bill_of_material").hide(); + } + if (self.check_pos == true) { + self.$("#card_pos").show(); + self.$('.start_msg').hide(); + } else { + self.$("#card_pos").hide(); + } + if (self.check_attendance == true) { + self.$("#card_attendance").show(); + self.$('.start_msg').hide(); + } else { + self.$("#card_attendance").hide(); + } + if (self.check_payment == true) { + self.$("#card_payment").show(); + self.$('.start_msg').hide(); + } else { + self.$("#card_payment").hide(); + } + if (self.check_task == true) { + self.$("#card_task").show(); + self.$('.start_msg').hide(); + } else { + self.$("#card_task").hide(); + } + if (self.check_sale == true) { + self.$("#card_sale").show(); + self.$('.start_msg').hide(); + } else { + self.$("#card_sale").hide(); + } + if (self.check_purchase == true) { + self.$("#card_purchase").show(); + self.$('.start_msg').hide(); + } else { + self.$("#card_purchase").hide(); + } + if (self.check_product == true) { + self.$("#card_product").show(); + self.$('.start_msg').hide(); + } else { + self.$("#card_product").hide(); + } + if (self.check_partner == true) { + self.$("#card_partner").show(); + self.$('.start_msg').hide(); + } else { + self.$("#card_partner").hide(); + } + if (self.check_invoice == true) { + self.$("#card_invoice").show(); + self.$('.start_msg').hide(); + } else { + self.$("#card_invoice").hide(); + } + if (self.check_pricelist == true) { + self.$("#card_pricelist").show(); + self.$('.start_msg').hide(); + } else { + self.$("#card_pricelist").hide(); + } + if (self.check_vendor_pricelist == true) { + self.$("#card_vendor_pricelist").show(); + self.$('.start_msg').hide(); + } else { + self.$("#card_vendor_pricelist").hide(); + } + }, + import_sale: function(e) { + //For loading the import of sales order form view + var self = this; + this.do_action({ + name: "Import Sale Order", + type: 'ir.actions.act_window', + res_model: 'import.sale.order', + view_mode: 'form', + views: [[false, 'form']], + target: 'new', + }) + }, + import_purchase: function(e) { + //For loading the import of purchase order form view + this.do_action({ + name: "Import Purchase Order", + type: 'ir.actions.act_window', + res_model: 'import.purchase.order', + view_mode: 'form', + views: [[false, 'form']], + target: 'new', + }) + }, + import_invoice: function(e) { + //For loading the import of invoice form view + this.do_action({ + name: "Import Invoice", + type: 'ir.actions.act_window', + res_model: 'import.invoice', + view_mode: 'form', + views: [[false, 'form']], + target: 'new', + }) + }, + import_partner: function(e) { + //For loading the import of partner form view + this.do_action({ + name: "Import Partner", + type: 'ir.actions.act_window', + res_model: 'import.partner', + view_mode: 'form', + views: [[false, 'form']], + target: 'new', + }) + }, + import_product_pricelist: function(e) { + //For loading the import of product price-list form view + this.do_action({ + name: "Import Product Pricelist", + type: 'ir.actions.act_window', + res_model: 'import.product.pricelist', + view_mode: 'form', + views: [[false, 'form']], + target: 'new', + }) + }, + import_bill_of_material: function(e) { + //For loading the import of BOM form view + var self = this; + this.do_action({ + name: "Import Bill of Material", + type: 'ir.actions.act_window', + res_model: 'import.bill.of.material', + view_mode: 'form', + views: [[false, 'form']], + target: 'new', + }) + }, + import_product_template: function(e) { + //For loading the import of product template form view + var self = this; + this.do_action({ + name: "Import Product", + type: 'ir.actions.act_window', + res_model: 'import.product.template', + view_mode: 'form', + views: [[false, 'form']], + target: 'new', + }) + }, + import_vendor_pricelist: function(e) { + //For loading the import of vendor price-list form view + var self = this; + this.do_action({ + name: "Import Vendor Pricelist", + type: 'ir.actions.act_window', + res_model: 'import.vendor.pricelist', + view_mode: 'form', + views: [[false, 'form']], + target: 'new', + }) + }, + import_pos: function(e) { + //For loading the import of pos orders form view + var self = this; + this.do_action({ + name: "Import POS", + type: 'ir.actions.act_window', + res_model: 'import.pos', + view_mode: 'form', + views: [[false, 'form']], + target: 'new', + }) + }, + import_attendance: function(e) { + //For loading the import of attendance form view + var self = this; + this.do_action({ + name: "Import Attendance", + type: 'ir.actions.act_window', + res_model: 'import.attendance', + view_mode: 'form', + views: [[false, 'form']], + target: 'new', + }) + }, + import_payment: function(e) { + //For loading the import of payments form view + var self = this; + this.do_action({ + name: "Import Payment", + type: 'ir.actions.act_window', + res_model: 'import.payment', + view_mode: 'form', + views: [[false, 'form']], + target: 'new', + }) + }, + import_task: function(e) { + //For loading the import of task form view + var self = this; + this.do_action({ + name: "Import Task", + type: 'ir.actions.act_window', + res_model: 'import.task', + view_mode: 'form', + views: [[false, 'form']], + target: 'new', + }) + }, + }) + core.action_registry.add('import_dashboard_tag', ImportDashBoard); + return ImportDashBoard; +}); diff --git a/import_dashboard/static/src/xml/dashboard_templates.xml b/import_dashboard/static/src/xml/dashboard_templates.xml new file mode 100644 index 000000000..7ed04cca6 --- /dev/null +++ b/import_dashboard/static/src/xml/dashboard_templates.xml @@ -0,0 +1,385 @@ + + + + +
+
+
+

+ Empty Dashboard +

+

+ Enable Settings To View Your Dashboard. +

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

Sale

+
+ Import Sale Order +
+
+
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
Purchase
+
+ Import Purchase Order +
+
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
Invoice
+
+ Import Invoice +
+
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
Partner +
+
+ Import Partner +
+
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
Product + Pricelist +
+
+ Import Product Pricelist +
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+

Bill of Material

+
+ Import Bill of Material +
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+

POS

+
+ Import POS Orders +
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+

Attendance

+
+ Import Attendance +
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+

Payment

+
+ Import Payment +
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+
+

Task

+
+ Import Task +
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
+

Product Template

+
+ Import Product Template +
+
+
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
+

Vendor Pricelist

+
+ Import Vendor Pricelist +
+
+
+
+ +
+
+
+
+
+
+
+
diff --git a/import_dashboard/views/import_dashboard_views.xml b/import_dashboard/views/import_dashboard_views.xml new file mode 100644 index 000000000..d68eacb6e --- /dev/null +++ b/import_dashboard/views/import_dashboard_views.xml @@ -0,0 +1,12 @@ + + + + + Dashboard + import_dashboard_tag + + + + diff --git a/import_dashboard/views/res_config_settings_views.xml b/import_dashboard/views/res_config_settings_views.xml new file mode 100644 index 000000000..05d8b2d99 --- /dev/null +++ b/import_dashboard/views/res_config_settings_views.xml @@ -0,0 +1,190 @@ + + + + + + res.config.settings.view.form.inherit.import.dashboard + + res.config.settings + + + + +
+

Import

+
+
+
+ +
+
+
+
+ Import Bill of + Materials + +
+
+
+
+ +
+
+
+
+ Import Sale +
+
+
+
+ +
+
+
+
+ Import Partner + +
+
+
+
+ +
+
+
+
+ Import Purchase + +
+
+
+
+ +
+
+
+
+ Import POS Orders + +
+
+
+
+ +
+
+
+
+ Import Invoice + +
+
+
+
+ +
+
+
+
+ Import Product + Template + +
+
+
+
+ +
+
+
+
+ Import Vendor + Pricelist + +
+
+
+
+ +
+
+
+
+ Import Product + Pricelist + +
+
+
+
+ +
+
+
+
+ Import Attendance + +
+
+
+
+ +
+
+
+
+ Import Payment + +
+
+
+
+ +
+
+
+
+ Import Project + Tasks + +
+
+
+
+
+
+
+ + + Settings + ir.actions.act_window + res.config.settings + + form + inline + {'module' : 'import_dashboard', 'bin_size': False} + + + + + +
diff --git a/import_dashboard/wizard/__init__.py b/import_dashboard/wizard/__init__.py new file mode 100644 index 000000000..ee5bd9c1a --- /dev/null +++ b/import_dashboard/wizard/__init__.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +from . import import_attendance +from . import import_bill_of_material +from . import import_invoice +from . import import_message +from . import import_partner +from . import import_payment +from . import import_pos +from . import import_product_pricelist +from . import import_product_template +from . import import_purchase_order +from . import import_sale_order +from . import import_task +from . import import_vendor_pricelist diff --git a/import_dashboard/wizard/import_attendance.py b/import_dashboard/wizard/import_attendance.py new file mode 100644 index 000000000..2ba290f07 --- /dev/null +++ b/import_dashboard/wizard/import_attendance.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +import base64 +import binascii +import csv +import io +import tempfile +import xlrd +from odoo import fields, models, _ +from odoo.exceptions import ValidationError + + +class ImportAttendance(models.TransientModel): + """For handling importing of employee attendance""" + _name = 'import.attendance' + _description = 'A model for handling importing of attendance' + + name = fields.Char(string="Name", help="Name", default="Import Attendance") + file_type = fields.Selection([('csv', 'CSV File'), + ('xls', 'XLS File')], + string='Select File Type', default='csv', + help="It helps to select File Type") + file_upload = fields.Binary(string="Upload File", + help="For uploading files") + + def action_attendance_import(self): + """Creating attendance record using uploaded xl/csv files""" + hr_employee = self.env['hr.employee'] + hr_attendance = self.env['hr.attendance'] + datas = {} + if self.file_type == 'csv': + try: + csv_data = base64.b64decode(self.file_upload) + data_file = io.StringIO(csv_data.decode("utf-8")) + data_file.seek(0) + datas = csv.DictReader(data_file, delimiter=',') + except: + raise ValidationError(_( + "File not Valid.\n\nPlease check the type and format " + "of the file and try again!")) + if self.file_type == 'xls': + try: + fp = tempfile.NamedTemporaryFile(delete=False, + suffix=".xlsx") + fp.write(binascii.a2b_base64(self.file_upload)) + fp.seek(0) + workbook = xlrd.open_workbook(fp.name) + sheet = workbook.sheet_by_index(0) + except: + raise ValidationError(_( + """File not Valid.\n\nPlease check the """ + """type and format of the file and try again!""")) + headers = sheet.row_values(0) + data = [] + for row_index in range(1, sheet.nrows): + row = sheet.row_values(row_index) + data += [{k: v for k, v in zip(headers, row)}] + datas = data + error_msg = "" + imported = 0 + row = 0 + for item in datas: + vals = {} + row_not_import_msg = "\n❌Row {rn} not imported.".format(rn=row) + if item.get('Employee'): + employee = hr_employee.search( + [('name', '=', item.get('Employee'))]) + if employee: + vals['employee_id'] = employee.id + else: + error_msg += row_not_import_msg + ( + "\n\t⚠ There is no employee with that name." + "found!" + % item.get('Employee')) + if item.get('Check In'): + if self.file_type == 'csv': + vals['check_in'] = item.get('Check In') + else: + vals['check_in'] = xlrd.xldate_as_datetime( + item.get('Check In'), 0) + if item.get('Check Out'): + if self.file_type == 'csv': + vals['check_out'] = item.get('Check Out') + else: + vals['check_out'] = xlrd.xldate_as_datetime( + item.get('Check Out'), 0) + if item.get('Worked Hours'): + vals['worked_hours'] = item.get('Worked Hours') + hr_attendance.create(vals) + imported += 1 + if error_msg: + error_msg = "\n\n🏮 WARNING 🏮" + error_msg + msg = (("Imported %d records." + % imported) + error_msg) + message = self.env['import.message'].create( + {'message': msg}) + if message: + return { + 'effect': { + 'fadeout': 'slow', + 'message': 'Imported Successfully', + 'type': 'rainbow_man', + } + } diff --git a/import_dashboard/wizard/import_attendance_views.xml b/import_dashboard/wizard/import_attendance_views.xml new file mode 100644 index 000000000..ced4f287e --- /dev/null +++ b/import_dashboard/wizard/import_attendance_views.xml @@ -0,0 +1,23 @@ + + + + + import.attendance.view.form + import.attendance + +
+ + + + + + +
+
+
+
+
+
diff --git a/import_dashboard/wizard/import_bill_of_material.py b/import_dashboard/wizard/import_bill_of_material.py new file mode 100644 index 000000000..5f296b060 --- /dev/null +++ b/import_dashboard/wizard/import_bill_of_material.py @@ -0,0 +1,189 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +import base64 +import binascii +import csv +import io +import tempfile +import xlrd +from odoo import fields, models, _ +from odoo.exceptions import ValidationError + + +class ImportBillOfMaterial(models.TransientModel): + """ For handling importing product bill of materials""" + _name = 'import.bill.of.material' + _description = 'For handling importing of bill of material' + + name = fields.Char(string="Name", help="Name", default="Import BoM") + file_type = fields.Selection([('csv', 'CSV File'), + ('xls', 'XLS File')], default='csv', + string='Select File Type', + help="Uploading file Type") + file_upload = fields.Binary(string='Upload File', + help="Helps to upload file") + import_product_by = fields.Selection([('default_code', + 'Internal Reference'), + ('barcode', 'Barcode')], + default='default_code', + string="Import Products By", + help="Helps to import product") + bom_type = fields.Selection( + [('manufacture_this_product', 'Manufacture this Product'), + ('kit', 'Kit'), ('both', 'Both')], string="Bom Type", default='both', + help="Helps to choose the bom type") + bom_component = fields.Selection([('add', 'Add Components'), + ('do_not', 'Do not add Components')], + default='add', string="Bom Component", + help="Helps to choose the bom component") + + def action_bom_import(self): + """Creating BOM record using uploaded xl/csv files""" + previous_bom = 0 + datas = {} + if self.file_type == 'csv': + try: + csv_data = base64.b64decode(self.file_upload) + data_file = io.StringIO(csv_data.decode("utf-8")) + data_file.seek(0) + datas = csv.DictReader(data_file, delimiter=',') + except: + raise ValidationError(_( + "File not Valid.\n\nPlease check the type and format " + "of the file and try again!")) + if self.file_type == 'xls': + try: + fp = tempfile.NamedTemporaryFile(delete=False, + suffix=".xlsx") + fp.write(binascii.a2b_base64(self.file_upload)) + fp.seek(0) + workbook = xlrd.open_workbook(fp.name) + sheet = workbook.sheet_by_index(0) + except: + raise ValidationError(_( + """File not Valid.\n\nPlease check the """ + """type and format of the file and try again!""")) + headers = sheet.row_values(0) + data = [] + for row_index in range(1, sheet.nrows): + row = sheet.row_values(row_index) + data += [{k: v for k, v in zip(headers, row)}] + datas = data + error_msg = "" + imported = 0 + row = 0 + for item in datas: + row += 1 + vals = {} + row_not_import_msg = "\n❌Row {rn} not imported.".format(rn=row) + if item.get('Product'): + if self.import_product_by == 'default_code' and item.get( + 'Product/Internal Reference'): + product_tmpl = self.env['product.template'].search( + [('default_code', + '=', item.get( + 'Product/Internal Reference'))]) + elif self.import_product_by == 'barcode' and item.get( + 'Product/Barcode'): + product_tmpl = self.env['product.template'].search( + [('barcode', '=', item.get('Product/Barcode'))]) + else: + product_tmpl = self.env['product.template'].search( + [('name', '=', item.get('Product'))]) + vals['product_tmpl_id'] = product_tmpl.id + if not product_tmpl: + product_template = self.env['product.template'].create({ + 'name': item.get('Product'), + 'default_code': item.get('Product/Internal Reference'), + 'barcode': item.get('Product/Barcode') + }) + vals['product_tmpl_id'] = product_template.id + if item.get('Quantity'): + vals['product_qty'] = item.get('Quantity') + if item.get('Reference'): + vals['code'] = item.get('Reference') + if self.bom_type == 'manufacture_this_product': + vals['type'] = 'normal' + elif self.bom_type == 'kit': + vals['type'] = 'phantom' + else: + if item.get('BoM Type'): + if item.get('BoM Type') == 'Manufacture this product': + vals['type'] = 'normal' + else: + vals['type'] = 'phantom' + components = {} + if self.bom_component == 'add': + if item.get('Components'): + if item.get('Components/Internal Reference'): + product_tmpl = self.env['product.product'].search([( + 'default_code', + '=', + item.get( + 'Components/Internal Reference'))]) + elif item.get('Components/Barcode'): + product_tmpl = self.env['product.product'].search( + [('barcode', '=', item.get('Components/Barcode'))]) + else: + product_tmpl = self.env['product.product'].search( + [('name', '=', item.get('Components'))]) + components['product_id'] = product_tmpl.id + if not product_tmpl: + product_template = self.env[ + 'product.product'].create({ + 'name': item.get('Components'), + 'default_code': item.get( + 'Components/Internal Reference'), + 'barcode': item.get('Components/Barcode') + }) + components['product_id'] = product_template.id + if item.get('BoM Lines/Quantity'): + components['product_qty'] = item.get( + 'BoM Lines/Quantity') + vals['bom_line_ids'] = [(0, 0, components)] + else: + error_msg += row_not_import_msg + ( + "\n\t⚠ Row \%s\ not contain any BoM Lines/Components." + "found!" + % row) + if item.get('Product'): + new_bom = self.env['mrp.bom'].create(vals) + previous_bom = new_bom + else: + if self.bom_component == 'add': + previous_bom.write({ + 'bom_line_ids': [(0, 0, components)] + }) + imported += 1 + if error_msg: + error_msg = "\n\n🏮 WARNING 🏮" + error_msg + msg = (("Imported %d records."% imported) + error_msg) + message = self.env['import.message'].create( + {'message': msg}) + if message: + return { + 'effect': { + 'fadeout': 'slow', + 'message': 'Imported Successfully', + 'type': 'rainbow_man', + } + } diff --git a/import_dashboard/wizard/import_bill_of_material_views.xml b/import_dashboard/wizard/import_bill_of_material_views.xml new file mode 100644 index 000000000..2df240fe6 --- /dev/null +++ b/import_dashboard/wizard/import_bill_of_material_views.xml @@ -0,0 +1,28 @@ + + + + + import.bill.of.material.view.form + import.bill.of.material + +
+ + + + + + + + + + + +
+
+
+
+
+
diff --git a/import_dashboard/wizard/import_invoice.py b/import_dashboard/wizard/import_invoice.py new file mode 100644 index 000000000..04eb09f80 --- /dev/null +++ b/import_dashboard/wizard/import_invoice.py @@ -0,0 +1,377 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +import base64 +import binascii +import csv +import datetime +import io +import re +import tempfile +import xlrd +from odoo import fields, models, _ +from odoo.exceptions import ValidationError + + +class ImportInvoice(models.TransientModel): + """For handling importing of invoices""" + _name = 'import.invoice' + _description = 'For handling importing of invoices' + + name = fields.Char(string="Name", help="Name", default="Import Invoice") + file_type = fields.Selection([('csv', 'CSV File'), + ('xlsx', 'XLSX File')], + string='Import File Type', default='csv', + help="It helps to choose the file type") + file = fields.Binary(string="File", help="Upload File") + update_posted = fields.Boolean(string='Update Posted Record?', + help='If enabled, the records in the ' + '"Posted" state will be converted to ' + 'draft and their values will be ' + 'updated. These records will be ' + 'reposted if the "Post Automatically" ' + 'is activated.') + auto_post = fields.Boolean(string='Post Automatically', + help="Automatically post the invoice") + journal = fields.Selection([('Bank', 'Bank'), ('Cash', 'Cash')], + string='Journal', default='Bank', + help='It helps to choose Journal type') + order_number = fields.Selection( + [('from_system', 'From System'), + ('from_file', 'From File')], + string='Number', default='from_file', help="Sequence number of orders") + import_product_by = fields.Selection( + [('name', 'Name'), + ('default_code', 'Internal Reference'), ('barcode', 'Barcode')], + string="Import Products BY", required=True, + help="Importing of products by internal reference or barcode") + type = fields.Selection( + [('out_invoice', 'Invoice'), ('in_invoice', 'Bill'), + ('out_refund', 'Credit Note'), ('in_refund', 'Refund')], + string='Invoicing Type', required=True, help="Type of invoice") + + def action_invoice_import(self): + """Creating Invoice record using uploaded xl/csv files""" + items = {} + if self.file_type == 'csv': + try: + csv_data = base64.b64decode(self.file) + data_file = io.StringIO(csv_data.decode("utf-8")) + data_file.seek(0) + csv_reader = csv.DictReader(data_file, delimiter=',') + except: + raise ValidationError(_( + "File not Valid.\n\nPlease check the type and format " + "of the file and try again!")) + items = csv_reader + if self.file_type == 'xlsx': + try: + fp = tempfile.NamedTemporaryFile(delete=False, + suffix=".xlsx") + fp.write(binascii.a2b_base64(self.file)) + fp.seek(0) + workbook = xlrd.open_workbook(fp.name) + sheet = workbook.sheet_by_index(0) + except: + raise ValidationError(_( + "File not Valid.\n\nPlease check the " + "type and format of the file and try again!")) + headers = sheet.row_values(0) + data = [] + for row_index in range(1, sheet.nrows): + row = sheet.row_values(row_index) + data += [{k: v for k, v in zip(headers, row)}] + items = data + row = 0 + imported = 0 + confirmed = 0 + imported_invoices = [] + error_msg = "" + partner_added_msg = "" + warning_msg = "" + for item in items: + row += 1 + vals = {} + row_not_import_msg = "\n❌Row {rn} not imported.".format(rn=row) + import_error_msg = "" + missing_fields_msg = "" + fields_msg = "\n\t🚫Missing required field(s):" + partner_msg = "\n🆕New Partner(s) added:" + vals['move_type'] = self.type + if item.get('Partner'): + partner = self.env['res.partner'].search( + [('name', '=', item['Partner'])]) + if not partner: + partner = self.env['res.partner'].create({ + 'name': item['Partner'] + }) + if partner_added_msg: + partner_added_msg += ( + "\n\t\trow {rn}: {partner}").format( + rn=row, partner=item['Partner']) + else: + partner_added_msg += ( + partner_msg + "\n\t\trow {rn}: " + "\"{partner}\"").format( + rn=row, partner=item['Partner']) + elif len(partner) > 1: + if import_error_msg: + import_error_msg += ( + "\n\t\t⚠ Multiple Partners with " + "name (%s) found!" + % item['Partner']) + else: + import_error_msg += row_not_import_msg + ( + "\n\t\t⚠ Multiple Partners with name (%s) found!" + % item['Partner']) + vals['partner_id'] = partner.id + else: + if missing_fields_msg: + missing_fields_msg += "\n\t\t❗ \"Partner\"" + else: + missing_fields_msg += (fields_msg + + "\n\t\t❗ \"Partner\"") + if import_error_msg: + import_error_msg += missing_fields_msg + elif missing_fields_msg: + import_error_msg += (row_not_import_msg + + missing_fields_msg) + if item.get('Payment Reference'): + vals['payment_reference'] = item['Payment Reference'] + if item.get('Invoice Date'): + date = item['Invoice Date'] + try: + inv_date = datetime.datetime.strptime( + date, '%m/%d/%Y') + vals['invoice_date'] = inv_date + except: + import_error_msg += ("\n\t\t⚠ Please check the " + "Quotation Date and format is " + "mm/dd/yyyy") + if item.get('Due Date'): + vals['invoice_date_due'] = inv_date = datetime.datetime.strptime( + item.get('Due Date'), '%m/%d/%Y') + if item.get('Salesperson'): + vals['invoice_user_id'] = self.env['res.users'].search( + [('name', '=', + item['Salesperson'])]).id + line_vals = {} + pro_vals = {} + if item.get('Label'): + line_vals['name'] = item['Label'] + else: + if not item.get('Product'): + import_error_msg += row_not_import_msg + ( + "\n\t⚠ Product and Label missing in " + "file!") + continue + if item.get('Account Code'): + line_vals['account_id'] = self.env['account.account'].search( + [('code', '=', int(item['Account Code']))]).id + if item.get('Quantity'): + line_vals['quantity'] = item['Quantity'] + if item.get('Uom'): + uom = self.env['uom.uom'].search([('name', '=', item['Uom'])]) + pro_vals['uom_id'] = line_vals['product_uom_id'] = uom.id + if item.get('Price'): + pro_vals['lst_price'] = line_vals['price_unit'] = item[ + 'Price'] + if item.get('Disc.%'): + line_vals['discount'] = item['Disc.%'] + if item.get('Taxes'): + tax_name = item['Taxes'] + tax_amount = (re.findall(r"(\d+)%", tax_name))[0] + tax = self.env['account.tax'].search([('name', '=', tax_name), + ('type_tax_use', '=', + 'sale')], + limit=1) + if not tax: + tax = self.env['account.tax'].create({ + 'name': tax_name, + 'type_tax_use': 'sale', + 'amount': tax_amount if tax_amount else 0.0 + }) + pro_vals['taxes_id'] = line_vals['tax_ids'] = [tax.id] + product = None + if self.import_product_by == 'name': + if item.get('Product'): + product = self.env['product.product'].search( + [('name', '=', item['Product'])]) + if not product: + pro_vals['name'] = item['Product'] + pro_vals['default_code'] = item['Internal Reference'] + product = self.env['product.product'].create(pro_vals) + if len(product) > 1: + if item.get('Variant Values'): + pro_tmpl_id = product.mapped('product_tmpl_id') + if len(pro_tmpl_id) > 1: + error_msg += row_not_import_msg + ( + "\n\t⚠ Multiple Product records are " + "linked with the product variant \"%s\"" + "." % item['Product']) + continue + variant_values = item['Variant Values'].split(',') + variant_value_ids = [] + for var in variant_values: + k_v = var.partition(":") + attr = k_v[0].strip() + attr_val = k_v[2].strip() + var_attr_ids = self.env[ + 'product.attribute'].search( + [('name', '=', attr)]).ids + var_attr_val_ids = self.env[ + 'product.attribute.value'].search( + [('name', '=', attr_val), + ('attribute_id', 'in', + var_attr_ids)]).ids + pro_temp_attr_val_id = self.env[ + 'product.attribute.value'].search( + [('product_attribute_value_id', 'in', + var_attr_val_ids), + ('product_tmpl_id', '=', + pro_tmpl_id.id)]).id + variant_value_ids += [pro_temp_attr_val_id] + if variant_value_ids: + product = product.filtered( + lambda p: + p.product_template_variant_value_ids.ids + == variant_value_ids) + else: + error_msg += row_not_import_msg + ( + "\n\t⚠ Product variant with variant " + "values \"%s\" not found." + % (item['Variant Values'])) + continue + if len(product) != 1: + error_msg += row_not_import_msg + ( + "\n\t⚠ Multiple variants with same " + "Variant Values \"%s\" found. Please " + "check if the product Variant Values" + " are unique." + % (item['Variant Values'])) + continue + else: + error_msg += row_not_import_msg + ( + "\n\t⚠ Multiple Products with same " + "Name \"%s\" found. Please " + "provide unique product \"Variant " + "Values\" to filter the records." + % (item['Product'])) + continue + else: + error_msg += row_not_import_msg + ( + "\n\t⚠ Product name missing in file!") + continue + if self.import_product_by == 'default_code': + if item.get('Internal Reference'): + product = self.env['product.product'].search( + [('default_code', '=', + item['Internal Reference'])]) + if not product: + if not item.get('Product'): + warning_msg += ( + "\n◼ A Product is created with " + "\"Internal Reference\" as " + "product name since \"Product\"" + " name is missing in file." + "(row %d)" % row) + pro_vals['name'] = item['Internal Reference'] + product = self.env['product.product'].create(pro_vals) + if len(product) > 1: + error_msg += row_not_import_msg + ( + "\n\t⚠ Multiple Products with same Internal" + " Reference(%s) found!" + % item['Internal Reference']) + continue + else: + error_msg += row_not_import_msg + ( + "\n\t⚠ Internal Reference missing in file!") + continue + # Product_by barcode + if self.import_product_by == 'barcode': + if item.get('Barcode'): + product = self.env['product.product'].search( + [('barcode', '=', item['Barcode'])]) + if not product: + if not item.get('Product'): + warning_msg += ( + "\n◼ No value under \"Product\" at " + "row %d, thus added \"Barcode\" as " + "product name" % row) + pro_vals['name'] = item['Barcode'] + product = self.env['product.product'].create(pro_vals) + if len(product) > 1: + error_msg += row_not_import_msg + ( + "\n\t⚠ Other Product(s) with same " + "Barcode (%s) found!" % item['Barcode']) + continue + else: + error_msg += row_not_import_msg + ( + "\n\t⚠ Barcode missing in file!") + continue + line_vals['product_id'] = product.id + vals['invoice_line_ids'] = [(0, 0, line_vals)] + vals['auto_post'] = self.auto_post + invoice = self.env['account.move'].search( + [('name', '=', item.get('Number')), + ('move_type', '=', vals['move_type'])]) + if invoice: + if len(invoice) > 1: + error_msg += row_not_import_msg + ( + "\n\t⚠ Multiple invoice with same Number(%s) " + "found!" + % item['Number']) + continue + if vals: + if self.update_posted and invoice.state == 'posted': + invoice.button_draft() + invoice.write(vals) + elif invoice.state == 'draft': + invoice.write(vals) + elif not invoice: + if self.order_number == 'from_file': + vals['name'] = item['Number'] + if item.get('Partner'): + invoice = self.env['account.move'].create(vals) + previous = invoice + if item.get('Product') and not item.get('Partner'): + previous.write({ + 'invoice_line_ids': [(0, 0, line_vals)] + }) + imported += 1 + imported_invoices += [invoice] + if self.auto_post and imported_invoices: + confirmed = len(imported_invoices) + if error_msg: + error_msg = "\n\n🏮 WARNING 🏮" + error_msg + msg = (("Imported %d records.\nConfirmed %d records" + % (imported, confirmed)) + partner_added_msg + error_msg + + warning_msg) + message = self.env['import.message'].create( + {'message': msg}) + if message: + return { + 'effect': { + 'fadeout': 'slow', + 'message': 'Imported Successfully', + 'type': 'rainbow_man', + } + } diff --git a/import_dashboard/wizard/import_invoice_views.xml b/import_dashboard/wizard/import_invoice_views.xml new file mode 100644 index 000000000..10780498f --- /dev/null +++ b/import_dashboard/wizard/import_invoice_views.xml @@ -0,0 +1,30 @@ + + + + + import.invoice.view.form + import.invoice + +
+ + + + + + + + + + + + + +
+
+
+
+
+
diff --git a/import_dashboard/wizard/import_message.py b/import_dashboard/wizard/import_message.py new file mode 100644 index 000000000..140e0a060 --- /dev/null +++ b/import_dashboard/wizard/import_message.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +from odoo import fields, models + + +class ImportMessage(models.TransientModel): + """Model for imported messages""" + _name = 'import.message' + _description = 'For showing messages after importing' + + name = fields.Char(string="Name", help="Name of the message", + default="Importing Completed") + message = fields.Text(string="Message", readonly=True, + help="Messages after importing") + + def action_ok(self): + """For returning the corresponding window""" + return {'type': 'ir.actions.act_window_close'} diff --git a/import_dashboard/wizard/import_message_views.xml b/import_dashboard/wizard/import_message_views.xml new file mode 100644 index 000000000..0e1cfd808 --- /dev/null +++ b/import_dashboard/wizard/import_message_views.xml @@ -0,0 +1,19 @@ + + + + + import.message.view.form + import.message + +
+

+ +

+
+
+
+
+
+
diff --git a/import_dashboard/wizard/import_partner.py b/import_dashboard/wizard/import_partner.py new file mode 100644 index 000000000..be51b6c20 --- /dev/null +++ b/import_dashboard/wizard/import_partner.py @@ -0,0 +1,301 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Raneesha MK @cybrosys(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see .UserError +# +############################################################################# +import base64 +import binascii +import csv +import io +import tempfile +import xlrd +from odoo import fields, models, _ +from odoo.exceptions import ValidationError + + +class ImportPartner(models.TransientModel): + """For handling importing of partners""" + _name = 'import.partner' + _description = 'For handling importing of partners' + + name = fields.Char(string="Name", help="Name", default="Import Partner") + file_type = fields.Selection([('csv', 'CSV File'), + ('xlsx', 'XLSX File')], default='csv', + string='Import File Type', help="File type") + method = fields.Selection( + [('create_update', 'Create or Update Customer/Vendor'), + ('create', 'Create Customer/Vendor')], + string='Import Method', default='create_update', + help="Helps to choose the import Method") + update_by = fields.Selection([('name', 'Name'), + ('email', 'Email'), + ('phone', 'Phone'), + ('mobile', 'Mobile')], + string='Update By', default='name', + help="Update using the fields") + file_upload = fields.Binary(string="Upload File", + help="It helps to upload files") + + def action_partner_import(self): + """Creating Partner record using uploaded xl/csv files""" + res_partner = self.env['res.partner'] + res_country_state = self.env['res.country.state'] + datas = {} + if self.file_type == 'csv': + try: + csv_data = base64.b64decode(self.file_upload) + data_file = io.StringIO(csv_data.decode("utf-8")) + data_file.seek(0) + datas = csv.DictReader(data_file, delimiter=',') + except: + raise ValidationError(_( + "File not Valid.\n\nPlease check the " + "type and format of the file and try again!")) + if self.file_type == 'xlsx': + try: + fp = tempfile.NamedTemporaryFile(delete=False, + suffix=".xlsx") + fp.write(binascii.a2b_base64(self.file_upload)) + fp.seek(0) + workbook = xlrd.open_workbook(fp.name) + sheet = workbook.sheet_by_index(0) + except: + raise ValidationError(_( + "File not Valid.\n\nPlease check the " + "type and format of the file and try again!")) + headers = sheet.row_values(0) + data = [] + for row_index in range(1, sheet.nrows): + row = sheet.row_values(row_index) + data += [{k: v for k, v in zip(headers, row)}] + datas = data + row = 0 + created = 0 + updated = 0 + error_msg = "" + warning_msg = "" + for item in datas: + row += 1 + vals = {} + row_not_import_msg = "\nRow {rn} not imported.".format(rn=row) + if item.get('Is company? (Yes/No)') == 'Yes': + vals['company_type'] = 'company' + else: + vals['company_type'] = 'person' + if item.get('Related Company'): + rel_company = res_partner.search( + [('name', '=', item['Related Company'])]) + if not rel_company: + rel_company = res_partner.create({ + 'name': item['Related Company'], + 'company_type': "company"}) + vals['parent_id'] = rel_company.id + if item.get('Name'): + vals['name'] = item['Name'] + if item.get('Street'): + vals['street'] = item['Street'] + if item.get('Street2'): + vals['street2'] = item['Street2'] + if item.get('City'): + vals['city'] = item['City'] + state_vals = {} + if item.get('Country'): + country = self.env['res.country'].search( + [('name', '=', item['Country'])]) + vals['country_id'] = state_vals['country_id'] = country.id + if item.get('State'): + country_state = res_country_state.search( + [('name', '=', item['State'])]) + if not country_state: + state_vals['name'] = state_vals['code'] = item['State'] + country_state = res_country_state.create(state_vals) + vals['state_id'] = country_state.id + if item.get('Zip'): + vals['zip'] = item['Zip'] + if item.get('Phone'): + vals['phone'] = item['Phone'] + if item.get('Mobile'): + vals['mobile'] = item['Mobile'] + if item.get('Email'): + vals['email'] = item['Email'] + if item.get('Tags'): + tags = item['Tags'].split(',') + tag_list = [] + for tag in tags: + tag_list += [tag.strip()] + tag_ids = self.env['res.partner.category'].search( + [('name', 'in', tag_list)]).ids + if not tag_ids: + tag_ids = [] + for tag in tag_list: + tag_id = self.env['res.partner.category'].create({ + 'name': tag + }) + tag_ids += [tag_id.id] + if tag_ids: + vals['category_id'] = tag_ids + if self.method == 'create_update': + if self.update_by == 'name': + if item.get('Name'): + partner = res_partner.search([('name', '=', + item['Name'])]) + if not partner: + res_partner.create(vals) + created += 1 + elif len(partner) > 1: + error_msg += row_not_import_msg + ( + "\n\tMultiple Partners with name " + "(%s) found!" % item['Name']) + continue + else: + partner.write(vals) + updated += 1 + else: + error_msg += row_not_import_msg + ( + "\n\tName missing!") + continue + if self.update_by == 'email': + if item.get('Email'): + partner = res_partner.search([('email', '=', + item['Email'])]) + if not partner: + if not vals.get('name'): + error_msg += row_not_import_msg + ( + "\n\tName missing!") + continue + else: + partner = res_partner.search( + [('name', '=', vals['name'])]) + if not partner: + res_partner.create(vals) + created += 1 + elif len(partner) > 1: + error_msg += row_not_import_msg + ( + "\n\tMultiple Partners with name " + "(%s) found!" % item['Name']) + continue + else: + partner.write(vals) + updated += 1 + elif len(partner) > 1: + error_msg += row_not_import_msg + ( + "\n\tMultiple Partners with Email " + "(%s) found!" % item['Email']) + continue + else: + partner.write(vals) + updated += 1 + else: + error_msg += row_not_import_msg + ( + "\n\tEmail missing!") + continue + if self.update_by == 'phone': + if item.get('Phone'): + partner = res_partner.search([('phone', '=', + item['Phone'])]) + if not partner: + if not vals.get('name'): + error_msg += row_not_import_msg + ( + "\n\tName missing!") + continue + else: + partner = res_partner.search( + [('name', '=', vals['name'])]) + if not partner: + res_partner.create(vals) + created += 1 + elif len(partner) > 1: + error_msg += row_not_import_msg + ( + "\n\tMultiple Partners with name " + "(%s) found!" % item['Name']) + continue + else: + partner.write(vals) + updated += 1 + elif len(partner) > 1: + error_msg += row_not_import_msg + ( + "\n\tMultiple Partners with Phone " + "(%s) found!" % item['Phone']) + continue + else: + partner.write(vals) + updated += 1 + else: + error_msg += row_not_import_msg + ( + "\n\tPhone missing!") + continue + if self.update_by == 'mobile': + if item.get('Mobile'): + partner = res_partner.search([('mobile', '=', + item['Mobile'])]) + if not partner: + if not vals.get('name'): + error_msg += row_not_import_msg + ( + "\n\tName missing!") + continue + else: + partner = res_partner.search( + [('name', '=', vals['name'])]) + if not partner: + res_partner.create(vals) + created += 1 + elif len(partner) > 1: + error_msg += row_not_import_msg + ( + "\n\tMultiple Partners with name " + "(%s) found!" % item['Name']) + continue + else: + partner.write(vals) + updated += 1 + elif len(partner) > 1: + error_msg += row_not_import_msg + ( + "\n\tMultiple Partners with Mobile " + "(%s) found!" % item['Mobile']) + continue + else: + partner.write(vals) + updated += 1 + else: + error_msg += row_not_import_msg + ( + "\n\tMobile missing!") + continue + elif self.method == 'create': + if vals.get('name'): + res_partner.create(vals) + created += 1 + else: + error_msg += row_not_import_msg + ( + "\n\tName missing!") + continue + if error_msg: + error_msg = "\n\n⚠ Warning ⚠" + error_msg + msg = (("Created %d records.\nUpdated %d records" + % (created, updated)) + error_msg + warning_msg) + rainbow_msg = ("Created %d records.\nUpdated %d records" + % (created, updated)) + message = self.env['import.message'].create( + {'message': msg}) + if message: + return { + 'effect': { + 'fadeout': 'slow', + 'message': rainbow_msg, + 'type': 'rainbow_man', + } + } diff --git a/import_dashboard/wizard/import_partner_views.xml b/import_dashboard/wizard/import_partner_views.xml new file mode 100644 index 000000000..5602b7794 --- /dev/null +++ b/import_dashboard/wizard/import_partner_views.xml @@ -0,0 +1,30 @@ + + + + + Import Dashboard Partner Import Wizard + import.partner + +
+ + + + + + + + + + + + +
+
+
+
+
+
diff --git a/import_dashboard/wizard/import_payment.py b/import_dashboard/wizard/import_payment.py new file mode 100644 index 000000000..799ae4785 --- /dev/null +++ b/import_dashboard/wizard/import_payment.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +import base64 +import binascii +import csv +from datetime import date +import io +import tempfile +import xlrd +from odoo import fields, models, _ +from odoo.exceptions import ValidationError + + +class ImportPayment(models.TransientModel): + """For handling importing of payments""" + _name = 'import.payment' + _description = 'For handling importing of payments' + + name = fields.Char(string="Name", help="Name", default="Import Payments") + file_type = fields.Selection([('csv', 'CSV File'), + ('xls', 'XLS File')], + string='Select File Type', default='csv', + help='It helps to choose the file type') + file_upload = fields.Binary(string='File Upload', + help='It helps to upload files') + + def action_payment_import(self): + """Creating payment record using uploaded xl/csv files""" + datas = {} + if self.file_type == 'csv': + try: + csv_data = base64.b64decode(self.file_upload) + data_file = io.StringIO(csv_data.decode("utf-8")) + data_file.seek(0) + datas = csv.DictReader(data_file, delimiter=',') + except: + raise ValidationError(_( + "File not Valid.\n\nPlease check the type and format " + "of the file and try again!")) + if self.file_type == 'xls': + try: + fp = tempfile.NamedTemporaryFile(delete=False, + suffix=".xlsx") + fp.write(binascii.a2b_base64(self.file_upload)) + fp.seek(0) + workbook = xlrd.open_workbook(fp.name) + sheet = workbook.sheet_by_index(0) + except: + raise ValidationError(_( + """File not Valid.\n\nPlease check the """ + """type and format of the file and try again!""")) + headers = sheet.row_values(0) + data = [] + for row_index in range(1, sheet.nrows): + row = sheet.row_values(row_index) + data += [{k: v for k, v in zip(headers, row)}] + datas = data + for item in datas: + vals = {} + if item.get('Date'): + if self.file_type == 'csv': + vals['date'] = item.get('Date') + else: + vals['date'] = date.fromordinal( + date(1900, 1, 1).toordinal() + int( + item.get('Date')) - 2) + if item.get('Journal'): + journal = self.env['account.journal'].search( + [('name', '=', item.get('Journal'))]) + if journal: + vals['journal_id'] = journal.id + else: + continue + if item.get('Customer/Vendor'): + partner = self.env['res.partner'].search( + [('name', '=', item.get('Customer/Vendor'))]) + if not partner: + partner = self.env['res.partner'].create({ + 'name': item.get('Customer/Vendor') + }) + vals['partner_id'] = partner.id + if item.get('Amount'): + vals['amount'] = float(item.get('Amount')) + if item.get('Reference'): + vals['ref'] = item.get('Reference') + if item.get('Payment Type') == 'Send': + vals['payment_type'] = 'outbound' + else: + vals['payment_type'] = 'inbound' + payment_ref = self.env['account.payment'].search( + [('name', '=', item.get('Number'))]) + if payment_ref: + payment_ref.write(vals) + else: + vals['name'] = item.get('Number') if item.get('Number') else '/' + self.env['account.payment'].create(vals) + return { + 'effect': { + 'fadeout': 'slow', + 'message': 'Imported Successfully', + 'type': 'rainbow_man', + } + } diff --git a/import_dashboard/wizard/import_payment_views.xml b/import_dashboard/wizard/import_payment_views.xml new file mode 100644 index 000000000..7757864bc --- /dev/null +++ b/import_dashboard/wizard/import_payment_views.xml @@ -0,0 +1,23 @@ + + + + + import.payment.view.form + import.payment + +
+ + + + + + +
+
+
+
+
+
diff --git a/import_dashboard/wizard/import_pos.py b/import_dashboard/wizard/import_pos.py new file mode 100644 index 000000000..7e149d4a1 --- /dev/null +++ b/import_dashboard/wizard/import_pos.py @@ -0,0 +1,227 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +import base64 +import binascii +import csv +import datetime +import io +import tempfile +import xlrd +from odoo import fields, models, _ +from odoo.exceptions import ValidationError + + +class ImportPOS(models.TransientModel): + """For handling importing of pos orders""" + _name = 'import.pos' + _description = 'For handling of importing of POS' + + name = fields.Char(string="Name", help="Name", default="Import PoS Order") + file_type = fields.Selection([('csv', 'CSV File'), ('xls', 'XLS File')], + string='Select File Type', default='csv', + help='It helps to choose the file type') + file_upload = fields.Binary(string='File Upload', + help="It helps to upload file") + + def action_pos_order_import(self): + """Creating sale order record using uploaded xl/csv files""" + datas = {} + if self.file_type == 'csv': + try: + csv_data = base64.b64decode(self.file_upload) + data_file = io.StringIO(csv_data.decode("utf-8")) + data_file.seek(0) + datas = csv.DictReader(data_file, delimiter=',') + except: + raise ValidationError(_( + "File not Valid.\n\nPlease check the type and format " + "of the file and try again!")) + if self.file_type == 'xls': + try: + fp = tempfile.NamedTemporaryFile(delete=False, + suffix=".xlsx") + fp.write(binascii.a2b_base64(self.file_upload)) + fp.seek(0) + workbook = xlrd.open_workbook(fp.name) + sheet = workbook.sheet_by_index(0) + except: + raise ValidationError(_( + """File not Valid.\n\nPlease check the """ + """type and format of the file and try again!""")) + headers = sheet.row_values(0) + data = [] + for row_index in range(1, sheet.nrows): + row = sheet.row_values(row_index) + data += [{k: v for k, v in zip(headers, row)}] + datas = data + previous_item = None + error_msg = '' + row = 0 + imported = 0 + for item in datas: + row += 1 + row_not_import_msg = "\n◼ Row {rn} not imported.".format( + rn=row) + vals = {} + order_ref = None + if item.get('Order Ref'): + order_ref = self.env['pos.order'].search( + [('name', '=', item.get('Order Ref'))]) + if order_ref: + raise ValidationError(_( + "\n\t⚠ Order with reference \"%s\" already exists." + " This record will not be imported." % item.get( + 'Order Ref'))) + else: + vals['name'] = item.get('Order Ref') + vals['amount_tax'] = item.get('Tax Amount') if item.get( + 'Tax Amount') else 0.0 + vals['amount_total'] = item.get('Total') if item.get( + 'Total') else 0.0 + vals['amount_paid'] = item.get('Paid Amount') if item.get( + 'Paid Amount') else 0.0 + vals['amount_return'] = item.get( + 'Amount Returned') if item.get( + 'Amount Returned') else 0.0 + vals['company_id'] = self.env['res.company'].search([('name', '=', item.get('Company'))]).id + vals['pricelist_id'] = self.env['product.pricelist'].search([('name', '=', item.get('Pricelist'))]).id + vals['session_id'] = self.env['pos.session'].search( + [('name', '=', item.get('Session'))]).id + if item.get('Receipt Number'): + vals['pos_reference'] = item.get('Receipt Number') + if item.get('Order Date'): + if self.file_type == 'csv': + vals['date_order'] = item.get('Order Date') + else: + vals[ + 'date_order'] = datetime.datetime.fromtimestamp( + item.get('Order Date')).strftime( + '%Y-%m-%d %H:%M:%S') + if item.get('Responsible'): + vals['user_id'] = self.env['res.users'].search( + [('name', '=', item.get('Responsible'))]).id + if item.get('Customer'): + partner_id = self.env['res.partner'].search( + [('name', '=', item.get('Customer'))]) + if not partner_id: + partner_id = self.env['res.partner'].create({ + 'name': item.get('Customer') + }) + vals['partner_id'] = partner_id.id + lines = {} + if item.get('Product'): + product = self.env['product.product'].search([('name', '=', + item[ + 'Product'])]) + if not product: + product = self.env['product.product'].create({ + 'name': item.get('Product') + }) + if len(product) > 1: + if item.get('Variant Values'): + pro_tmpl_id = product.mapped('product_tmpl_id') + if len(pro_tmpl_id) > 1: + error_msg += row_not_import_msg + ( + "\n\t⚠ Multiple Product records are " + "linked with the product variant \"%s\"" + "." % item['Product']) + continue + variant_values = item['Variant Values'].split( + ',') + variant_value_ids = [] + for var in variant_values: + k_v = var.partition(":") + attr = k_v[0].strip() + attr_val = k_v[2].strip() + var_attr_ids = self.env[ + 'product.attribute'].search( + [('name', '=', attr)]).ids + var_attr_val_ids = self.env[ + 'product.attribute.value'].search( + [('name', '=', attr_val), + ('attribute_id', 'in', + var_attr_ids)]).ids + pro_temp_attr_val_id = self.env[ + 'product.template.attribute.value'].search( + [('product_attribute_value_id', 'in', + var_attr_val_ids), + ('product_tmpl_id', '=', + pro_tmpl_id.id)]).id + variant_value_ids += [pro_temp_attr_val_id] + if variant_value_ids: + product = product.filtered( + lambda p: + p.product_template_variant_value_ids.ids + == variant_value_ids) + else: + error_msg += row_not_import_msg + ( + "\n\t⚠ Product variant with variant " + "values \"%s\" not found." + % (item['Variant Values'])) + continue + if len(product) != 1: + error_msg += row_not_import_msg + ( + "\n\t⚠ Multiple variants with same " + "Variant Values \"%s\" found. Please " + "check if the product Variant Values" + " are unique." + % (item['Variant Values'])) + continue + else: + error_msg += row_not_import_msg + ( + "\n\t⚠ Multiple Products with same " + "Name \"%s\" found. Please " + "provide unique product \"Variant " + "Values\" to filter the records." + % (item['Product'])) + continue + lines['product_id'] = product.id + lines['full_product_name'] = product.name + lines['qty'] = item.get('Quantity') + lines['price_unit'] = item.get('Unit Price') + lines['discount'] = item.get('Discount %') + lines['price_subtotal'] = item.get('Sub Total') + lines['price_subtotal_incl'] = 0.0 + vals['lines'] = [(0, 0, lines)] + if item.get('Session'): + pos_order = self.env['pos.order'].create(vals) + previous_item = pos_order + if not item.get('Session'): + if item.get('Product'): + previous_item.write({ + 'lines': [(0, 0, lines)] + }) + imported += 1 + if error_msg: + error_msg = "\n\n🏮 WARNING 🏮" + error_msg + msg = (("Imported %d records." + % imported) + error_msg) + message = self.env['import.message'].create( + {'message': msg}) + if message: + return { + 'effect': { + 'fadeout': 'slow', + 'message': 'Imported Successfully', + 'type': 'rainbow_man', + } + } diff --git a/import_dashboard/wizard/import_pos_views.xml b/import_dashboard/wizard/import_pos_views.xml new file mode 100644 index 000000000..2baf2689c --- /dev/null +++ b/import_dashboard/wizard/import_pos_views.xml @@ -0,0 +1,23 @@ + + + + + import.pos.view.form + import.pos + +
+ + + + + + +
+
+
+
+
+
diff --git a/import_dashboard/wizard/import_product_pricelist.py b/import_dashboard/wizard/import_product_pricelist.py new file mode 100644 index 000000000..59205b9c4 --- /dev/null +++ b/import_dashboard/wizard/import_product_pricelist.py @@ -0,0 +1,487 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +import base64 +import binascii +import csv +import io +import tempfile +import xlrd +from odoo import fields, models, _ +from odoo.exceptions import ValidationError + + +class ImportProductPricelist(models.TransientModel): + """For handling importing of product pricelist""" + _name = 'import.product.pricelist' + _description = 'Importing of product pricelist' + + name = fields.Char(string="Name", help="Name", + default="Import Product Pricelist") + file_type = fields.Selection([('csv', 'CSV File'), + ('xlsx', 'XLSX File')], default='csv', + string='Import File Type', + help="File types of importing") + import_product_by = fields.Selection([ + ('name', 'Name'), ('default_code', 'Internal Reference'), + ('barcode', 'Barcode')], string="Import Product By", required=True, + help="Import products by using internal reference or barcode") + product_pricelist_setting = fields.Selection( + [('basic', 'Multiple prices per product'), + ('advanced', 'Advanced price rules (discounts, formulas)')], + string='Pricelists Method', default='basic', + help="Pricelist method type") + compute_price = fields.Selection( + [('fixed', 'Fixed Price'), ('percentage', 'Discount'), + ('formula', 'Formula')], string='Computation', default='fixed', + help="Computation type of pricelist") + applied_on = fields.Selection([('3_global', 'All Products'), + ('2_product_category', 'Product Category'), + ('1_product', 'Product'), + ('0_product_variant', 'Product Variant')], + string='Apply On', default='3_global', + help="Apply which category products") + base = fields.Selection( + [('list_price', 'Sales Price'), ('standard_price', 'Cost'), + ('pricelist', 'Other Pricelist')], string="Based on", + help="Selection based on", default='list_price', required=True) + country_group_ids = fields.Many2many('res.country.group', + string='Country Groups', + help="Country groups for applying " + "pricelist") + company_id = fields.Many2one('res.company', + string="company", help="Company of the pricelist") + file_upload = fields.Binary(string='File Upload', + help="It helps to upload file") + + def action_product_pricelist_import(self): + """Creating pricelist record using uploaded xl/csv files""" + ir_config_parameter = self.env['ir.config_parameter'] + product_template = self.env['product.template'] + product_product = self.env['product.product'] + product_pricelist = self.env['product.pricelist'] + product_category = self.env['product.category'] + product_attribute = self.env['product.attribute'] + product_attribute_value = self.env['product.attribute.value'] + product_template_attribute_value = self.env[ + 'product.template.attribute.value'] + if self.product_pricelist_setting == 'basic': + ir_config_parameter.set_param('product.product_pricelist_setting', + 'basic') + elif self.product_pricelist_setting == 'advanced': + ir_config_parameter.set_param('product.product_pricelist_setting', + 'advanced') + if self.file_type == 'csv': + try: + csv_data = base64.b64decode(self.file_upload) + data_file = io.StringIO(csv_data.decode("utf-8")) + data_file.seek(0) + csv_reader = csv.DictReader(data_file, delimiter=',') + except: + raise ValidationError( + "File not Valid.\n\nPlease check the type and format " + "of the file, and try again!") + items = csv_reader + if self.file_type == 'xlsx': + try: + fp = tempfile.NamedTemporaryFile(delete=False, + suffix=".xlsx") + fp.write(binascii.a2b_base64(self.file_upload)) + fp.seek(0) + workbook = xlrd.open_workbook(fp.name) + sheet = workbook.sheet_by_index(0) + except: + raise ValidationError( + """File not Valid.\n\nPlease check the """ + """type and format of the file and try again!""") + headers = sheet.row_values(0) + data = [] + for row_index in range(1, sheet.nrows): + row = sheet.row_values(row_index) + data += [{k: v for k, v in zip(headers, row)}] + items = data + row = 0 + imported = 0 + created = 0 + error_msg = "" + info_msg = "" + if items: + for item in items: + row += 1 + vals = {} + row_not_import_msg = "\nRow {rn} not imported.".format(rn=row) + import_error_msg = "" + missing_fields_msg = "" + fields_msg = "\n\tMissing required field(s):" + if item.get('Name'): + vals['name'] = item['Name'] + else: + if missing_fields_msg: + missing_fields_msg += "\n\t\t\"Name\" " + else: + missing_fields_msg += ( + row_not_import_msg + fields_msg + + "\n\t\t\"Name\"") + if self.company_id: + vals['company_id'] = self.company_id.id + if self.country_group_ids: + vals['country_group_ids'] = self.country_group_ids.ids + import_error_msg += missing_fields_msg + if import_error_msg: + error_msg += import_error_msg + continue + price_list = product_pricelist.search([('name', '=', + item['Name'])]) + if not price_list: + price_list = product_pricelist.create(vals) + created += 1 + elif len(price_list) > 1: + error_msg += row_not_import_msg + ( + "\n\tMultiple Pricelist with " + "name \"%s\" exists." + % item['Name']) + continue + else: + if vals.get('company_id'): + price_list.company_id = vals['company_id'] + info_msg += ("\n\tCompany value updated from row %d" + % row) + if vals.get('country_groups_ids'): + price_list.country_groups_ids = vals[ + 'country_groups_ids'] + info_msg += ("\n\tCountry Groups updated from row %d" + % row) + vals_list = {} + + def variant_search(var_vals, pro_tmpl): + """returns product_product record matching the + variant values""" + if not (var_vals and pro_tmpl): + return False + variant_values = var_vals.split(',') + variant_value_ids = [] + for var in variant_values: + k_v = var.partition(":") + attr = k_v[0].strip() + attr_val = k_v[2].strip() + var_attr_ids = product_attribute.search( + [('name', '=', attr)]).ids + var_attr_val_ids = product_attribute_value.search( + [('name', '=', attr_val), + ('attribute_id', 'in', var_attr_ids)]).ids + pro_temp_attr_val_id = ( + product_template_attribute_value.search([ + ('product_attribute_value_id', 'in', + var_attr_val_ids), ('product_tmpl_id', '=', + pro_tmpl.id)]).id) + variant_value_ids += [pro_temp_attr_val_id] + if variant_value_ids: + product_var = product_product.search( + [('product_template_variant_value_ids', '=', + variant_value_ids)]) + return product_var + + # Multiple prices per product + if self.product_pricelist_setting == 'basic': + if self.import_product_by == 'name': + if item.get('Product'): + pro_tmpl = product_template.search( + [('name', '=', item['Product'])]) + if not pro_tmpl: + pro_tmpl = product_template.create({ + 'name': (item['Product']).title() + }) + info_msg += ("\n\tNew Product (%s) created!" + "(row: %d)" + % ((item['Product']).title(), + row)) + elif len(pro_tmpl) > 1: + error_msg += row_not_import_msg + ( + "\n\tMultiple Product records with " + "name \"%s\" exists.(row: %d)" + % (item['Product'], row)) + continue + vals_list['product_tmpl_id'] = pro_tmpl.id + else: + error_msg += row_not_import_msg + ( + "\n\tProduct name missing in file!") + continue + if self.import_product_by == 'default_code': + if item.get('Internal Reference'): + pro_pro = product_product.search( + [('default_code', '=', item[ + 'Internal Reference'])]) + if not pro_pro: + error_msg += row_not_import_msg + ( + "\n\tProduct with Internal Reference %s" + " not found!" + % item['Internal Reference']) + continue + elif len(pro_pro) > 1: + error_msg += row_not_import_msg + ( + "\n\tMultiple Products with " + "Internal Reference \"%s\" exists." + % item['Internal Reference']) + continue + pro_tmpl = pro_pro.product_tmpl_id + vals_list['product_tmpl_id'] = pro_tmpl.id + else: + error_msg += row_not_import_msg + ( + "\n\tInternal Reference missing in file!") + continue + if self.import_product_by == 'barcode': + if item.get('Barcode'): + pro_pro = product_product.search( + [('barcode', '=', item['Barcode'])]) + if not pro_pro: + error_msg += row_not_import_msg + ( + "\n\tProduct with barcode %s not found!" + % item['Barcode']) + continue + elif len(pro_pro) > 1: + error_msg += row_not_import_msg + ( + "\n\tMultiple Product records with " + "same Barcode \"%s\" exists." + % item['Barcode']) + continue + pro_tmpl = pro_pro.product_tmpl_id + vals_list['product_tmpl_id'] = pro_tmpl.id + else: + error_msg += row_not_import_msg + ( + "\n\tBarcode missing in file!") + continue + if item.get('Variant Values') and pro_tmpl: + variant = variant_search(item['Variant Values'], + pro_tmpl) + if variant: + vals_list['product_id'] = variant.id + if item.get('Fixed Price'): + vals_list['fixed_price'] = item['Fixed Price'] + # Advanced price rules + if self.product_pricelist_setting == 'advanced': + vals_list['compute_price'] = self.compute_price + vals_list['base'] = self.base + vals_list['applied_on'] = self.applied_on + + def parent_category(category): + """return the parent category""" + if category: + parent_categ = category.rpartition('/')[0] + if parent_categ: + parent = product_category.search( + [('complete_name', '=', parent_categ)]) + if parent: + p_id = parent.id + else: + grand_parent_id = parent_category( + parent_categ) + parent = product_category.create({ + 'name': parent_categ.rpartition('/')[ + 2], + 'parent_id': grand_parent_id + }) + p_id = parent.id + return p_id + + # Price computation + if self.compute_price == 'fixed': + if item.get('Fixed Price'): + vals_list['fixed_price'] = item['Fixed Price'] + elif self.compute_price == 'percentage': + if item.get('Discount%'): + vals_list['percent_price'] = item['Discount'] + elif self.compute_price == 'formula': + if item.get('Discount%'): + vals_list['price_discount'] = item['Discount%'] + if item.get('Extra Fee'): + vals_list['price_surcharge'] = item['Extra Fee'] + if item.get('Rounding Method'): + vals_list['price_round'] = item['Rounding Method'] + if item.get('Min. Margin'): + vals_list['price_min_margin'] = item['Min. Margin'] + if item.get('Max. Margin'): + vals_list['price_max_margin'] = item['Max. Margin'] + if self.base == 'pricelist': + if item.get('Other Pricelist'): + other_pricelist = product_pricelist.search( + [('name', '=', item['Other Pricelist'])], + limit=1) + if other_pricelist: + vals_list['base_pricelist_id'] = ( + other_pricelist.id) + else: + error_msg += row_not_import_msg + ( + "\n\t\"Other Pricelist\" missing in file!") + continue + if self.applied_on == '2_product_category': + if item.get('Product Category'): + item_category = (item['Product Category']).replace( + " ", "").replace("/", " / ").title() + item_categ_name = item_category.rpartition('/')[2] + categ = product_category.search( + [('complete_name', '=', item_category)], + limit=1) + if not categ: + categ = product_category.create({ + 'name': item_categ_name, + 'parent_id': parent_category(item_category) + }) + vals_list['categ_id'] = categ.id + else: + error_msg += row_not_import_msg + ( + "\n\tProduct Category missing in file!") + continue + if self.applied_on == '1_product': + if self.import_product_by == 'name': + if item.get('Product'): + pro_tmpl = product_template.search( + [('name', '=', item['Product'])]) + if not pro_tmpl: + pro_tmpl = product_template.create({ + 'name': item['Product'] + }) + info_msg += ( + "\n\tNew Product (%s) created!" + "(row: %d)" + % (item['Product'], row)) + elif len(pro_tmpl) > 1: + error_msg += row_not_import_msg + ( + "\n\tMultiple Product records with " + "name \"%s\" exists.(row: %d)" + % (item['Product'], row)) + continue + vals_list['product_tmpl_id'] = pro_tmpl.id + else: + error_msg += row_not_import_msg + ( + "\n\tProduct name missing in file!") + continue + if self.import_product_by == 'default_code': + if item.get('Internal Reference'): + pro_pro = product_product.search( + [('default_code', '=', + item['Internal Reference'])]) + if not pro_pro: + error_msg += row_not_import_msg + ( + "\n\tProduct with Internal " + "Reference %s not found!" + % item['Internal Reference']) + continue + if len(pro_pro) > 1: + error_msg += row_not_import_msg + ( + "\n\tMultiple Product records with " + "Internal Reference \"%s\" exists." + % item['Internal Reference']) + continue + pro_tmpl = pro_pro.product_tmpl_id + vals_list['product_tmpl_id'] = pro_tmpl.id + else: + error_msg += row_not_import_msg + ( + "\n\tInternal Reference missing!") + continue + if self.import_product_by == 'barcode': + if item.get('Barcode'): + pro_pro = product_product.search( + [('barcode', '=', item['Barcode'])]) + if not pro_pro: + error_msg += row_not_import_msg + ( + "\n\tProduct with Barcode %s not " + "found!" + % item['Barcode']) + continue + if len(pro_pro) > 1: + error_msg += row_not_import_msg + ( + "\n\tMultiple Product records with " + "same Barcode \"%s\" exists." + % item['Barcode']) + continue + pro_tmpl = pro_pro.product_tmpl_id + vals_list['product_tmpl_id'] = pro_tmpl.id + else: + error_msg += row_not_import_msg + ( + "\n\tBarcode missing!") + continue + if self.applied_on == '0_product_variant': + if item.get('Product'): + product_variant = product_product.search( + [('name', '=', item['Product'])]) + if not product_variant: + error_msg += row_not_import_msg + ( + "\n\tProduct not found!") + continue + elif len(product_variant) > 1: + pro_tmpl_id = product_variant.mapped( + 'product_tmpl_id') + if len(pro_tmpl_id) > 1: + error_msg += row_not_import_msg + ( + "\n\tMultiple Product records are " + "linked with the product variant " + "\"%s\"" + ". (row: %d)" % ( + item['Product'], row)) + continue + if item.get('Variant Values'): + variant = variant_search( + item['Variant Values'], pro_tmpl_id) + if variant: + vals_list['product_id'] = variant.id + else: + error_msg += row_not_import_msg + ( + "\n\tVariant Values missing in " + "file!") + continue + else: + vals_list['product_id'] = product_variant.id + if item.get('Minimum Quantity'): + vals_list['min_quantity'] = item['Minimum Quantity'] + if item.get('Start_date'): + vals_list['date_start'] = item['Start_date'] + if item.get('End_date'): + vals_list['date_end'] = item['End_date'] + price_list.write({ + 'item_ids': [(0, 0, vals_list)] + }) + imported += 1 + if error_msg: + error_msg = "\n\n⚠⚠⚠Warning!!!⚠⚠⚠" + error_msg + error_message = self.env['import.message'].create( + {'message': error_msg}) + return { + 'name': 'Done!', + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'res_model': 'import.message', + 'res_id': error_message.id, + 'target': 'new', + } + if info_msg: + info_msg = "\n\nNotes:" + info_msg + msg = (("Imported %d records." + % imported) + info_msg) + message = self.env['import.message'].create( + {'message': msg}) + if message: + return { + 'effect': { + 'fadeout': 'slow', + 'message': msg, + 'type': 'rainbow_man', + } + } diff --git a/import_dashboard/wizard/import_product_pricelist_views.xml b/import_dashboard/wizard/import_product_pricelist_views.xml new file mode 100644 index 000000000..279cce6cd --- /dev/null +++ b/import_dashboard/wizard/import_product_pricelist_views.xml @@ -0,0 +1,52 @@ + + + + + import.product.pricelist.view.form + import.product.pricelist + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
diff --git a/import_dashboard/wizard/import_product_template.py b/import_dashboard/wizard/import_product_template.py new file mode 100644 index 000000000..b7046b5e1 --- /dev/null +++ b/import_dashboard/wizard/import_product_template.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +import base64 +import binascii +import csv +import io +import tempfile +import xlrd +from odoo import fields, models, _ +from odoo.exceptions import ValidationError + + +class ImportProduct(models.TransientModel): + """For handling importing of Product Template""" + _name = 'import.product.template' + _description = 'Importing Products' + + name = fields.Char(string="Name", help="Name", default="Import Products") + file_type = fields.Selection([('csv', 'CSV File'), + ('xls', 'Excel File')], + string='Select File Type', default='csv', + help="file type") + method = fields.Selection([('create_product', 'Create Product'), + ('update_product', 'Update Product')], + string='Method', default='create_product', + help="method") + import_product_by = fields.Selection( + [('internal_reference', 'Internal Reference'), + ('barcode', 'Barcode')], string="Product Update By", + default='internal_reference', help="It helps to import product") + file_upload = fields.Binary(string='File Upload', + help="It helps to upload file") + + def make_json_dict(self, column, row): + """"Converting json data to dictionary""" + return [{col: item[i] for i, col in enumerate(column)} for item in row] + + def action_product_template_import(self): + """Creating product record using uploaded xl/csv files""" + data = {} + if self.file_type == 'csv': + try: + csv_data = base64.b64decode(self.file_upload) + data_file = io.StringIO(csv_data.decode("utf-8")) + data_file.seek(0) + data = csv.DictReader(data_file, delimiter=',') + except: + raise ValidationError(_( + "File not Valid.\n\nPlease check the type and format " + "of the file and try again!")) + if self.file_type == 'xls': + try: + book_dict = {} + fp = tempfile.NamedTemporaryFile(delete=False, + suffix=".xlsx") + fp.write(binascii.a2b_base64(self.file_upload)) + fp.seek(0) + book = xlrd.open_workbook(fp.name) + sheets = book.sheets() + for sheet in sheets: + book_dict[sheet.name] = {} + columns = sheet.row_values(0) + rows = [] + for row_index in range(1, sheet.nrows): + row = sheet.row_values(row_index) + rows.append(row) + sheet_data = self.make_json_dict(columns, rows) + book_dict[sheet.name] = sheet_data + data = book_dict['Sheet1'] + except: + raise ValidationError(_( + "File not Valid.\n\nPlease check the type and format " + "of the file and try again!")) + for item in data: + search_field = 'default_code' \ + if self.import_product_by == 'internal_reference' else 'barcode' + product = self.env['product.template'].search([(search_field, '=', + str(item.get( + 'Internal Reference' if self.import_product_by == 'internal_reference' else 'Barcode')))]) + product_values = { + 'name': item.get('Name'), + 'default_code': item.get('Internal Reference'), + 'list_price': item.get('Sales Price'), + 'standard_price': item.get('Cost'), + 'barcode': item.get('Barcode'), + } + if not product: + if item.get('Product Type') == 'Consumable': + product_type = 'consu' + elif item.get('Product Type') == 'Storable Product': + product_type = 'product' + else: + product_type = 'service' + product_values.update({ + 'detailed_type': product_type + }) + self.env['product.template'].create(product_values) + else: + product.write(product_values) + return { + 'effect': { + 'fadeout': 'slow', + 'message': 'Imported Successfully', + 'type': 'rainbow_man', + } + } diff --git a/import_dashboard/wizard/import_product_template_views.xml b/import_dashboard/wizard/import_product_template_views.xml new file mode 100644 index 000000000..493fdf115 --- /dev/null +++ b/import_dashboard/wizard/import_product_template_views.xml @@ -0,0 +1,28 @@ + + + + + import.product.template.view.form + + import.product.template + +
+ + + + + + + + + + +
+
+
+
+
+
diff --git a/import_dashboard/wizard/import_purchase_order.py b/import_dashboard/wizard/import_purchase_order.py new file mode 100644 index 000000000..a9054e4b4 --- /dev/null +++ b/import_dashboard/wizard/import_purchase_order.py @@ -0,0 +1,336 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +import base64 +import datetime +import binascii +import csv +import io +import re +import tempfile +import xlrd +from odoo import fields, models, _ +from odoo.exceptions import ValidationError + + +class ImportPurchaseOrder(models.TransientModel): + """Model for importing purchase orders. This model facilitates the + importing of purchase orders from CSV or XLSX files.""" + _name = 'import.purchase.order' + _description = 'For importing purchase order' + + name = fields.Char(string="Name", help="Name", + default="Import Purchase Order") + file_type = fields.Selection([('csv', 'CSV File'), + ('xlsx', 'XLSX File')], default='csv', + string='Select File Type', + help="File type to import") + file_upload = fields.Binary(string="File", help="Upload file") + auto_confirm_quot = fields.Boolean( + string='Confirm Quotation Automatically', + help="Automatically confirm the quotation") + order_number = fields.Selection( + [('from_system', 'From System'), + ('from_file', 'From File')], default='from_file', + string='Reference', help="Order reference creation methods") + import_product_by = fields.Selection( + [('name', 'Name'), ('default_code', 'Internal Reference'), + ('barcode', 'Barcode')], string="Import Product By", default='name', + help="Import products by internal reference of barcode") + + def action_purchase_order_import(self): + """Creating purchase record using uploaded xl/csv files""" + items = {} + if self.file_type == 'csv': + try: + csv_data = base64.b64decode(self.file_upload) + data_file = io.StringIO(csv_data.decode("utf-8")) + data_file.seek(0) + csv_reader = csv.DictReader(data_file, delimiter=',') + except: + raise ValidationError(_( + "File not Valid.\n\nPlease check the type and format " + "of the file and try again!")) + items = csv_reader + if self.file_type == 'xlsx': + try: + fp = tempfile.NamedTemporaryFile(delete=False, + suffix=".xlsx") + fp.write(binascii.a2b_base64(self.file_upload)) + fp.seek(0) + workbook = xlrd.open_workbook(fp.name) + sheet = workbook.sheet_by_index(0) + except: + raise ValidationError(_( + "File not Valid.\n\nPlease check the type and format of " + "the file and try again!")) + headers = sheet.row_values(0) # list + data = [] + for row_index in range(1, sheet.nrows): + row = sheet.row_values(row_index) # list + data += [{k: v for k, v in zip(headers, row)}] + items = data + row = 0 + imported = 0 + error_msg = "" + vendor_added_msg = "" + date_missing_msg = "" + warning_msg = "" + previous_order = None + for item in items: + row += 1 + vals = {} + row_not_import_msg = "\n❌Row {rn} not imported.".format(rn=row) + import_error_msg = "" + missing_fields_msg = "" + vendor_msg = "\n🆕New Vendor(s) added:" + if item.get('Vendor'): + vendor = self.env['res.partner'].search( + [('name', '=', item['Vendor'])]) + if not vendor: + vendor = self.env['res.partner'].create({ + 'name': item['Vendor']}) + vendor_added_msg += ( + vendor_msg + "\n\t\trow {rn}: " + "\"{vendor}\"").format( + rn=row, vendor=item['Vendor']) + elif len(vendor) > 1: + import_error_msg += row_not_import_msg + ( + "\n\t\t❎Multiple Partners with name (%s) found!" + % item['Vendor']) + vals['partner_id'] = vendor.id + if missing_fields_msg: + import_error_msg += (row_not_import_msg + + missing_fields_msg) + if item.get('Vendor Reference'): + vals['partner_ref'] = item['Vendor Reference'] + if item.get('Order Deadline'): + date = item['Order Deadline'] + try: + order_deadline_date = datetime.datetime.strptime( + date, '%m/%d/%Y') + vals['date_order'] = order_deadline_date + except: + import_error_msg += ("\n\t\t⚠ Please check the " + "Order Deadline Date and format is " + "mm/dd/yyyy") + if item.get('Receipt Date'): + receipt_date = item['Receipt Date'] + try: + receipt_date_date = datetime.datetime.strptime( + receipt_date, '%m/%d/%Y') + vals['date_planned'] = receipt_date_date + except: + import_error_msg += ("\n\t\t⚠ Please check the " + "Receipt Date and format is " + "mm/dd/yyyy") + if item.get('Purchase Representative'): + vals['user_id'] = self.env['res.users'].search( + [('name', '=', item['Purchase Representative'])]).id + if import_error_msg: + error_msg += import_error_msg + continue + product = None + line_vals = {} + pro_vals = {} + if item.get('Quantity'): + line_vals['product_qty'] = item['Quantity'] + if item.get('Uom'): + uom = self.env['uom.uom'].search( + [('name', '=', item['Uom'])]) + pro_vals['uom_id'] = line_vals['product_uom'] = uom.id + if item.get('Unit Price'): + pro_vals['lst_price'] = line_vals['price_unit'] = item[ + 'Unit Price'] + if item.get('Taxes'): + tax_name = item['Taxes'] + tax_amount = (re.findall(r"(\d+)%", tax_name))[0] + tax = self.env['account.tax'].search( + [('name', '=', tax_name), + ('type_tax_use', '=', 'purchase')], limit=1) + if not tax: + tax = self.env['account.tax'].create({ + 'name': tax_name, + 'type_tax_use': 'purchase', + 'amount': tax_amount if tax_amount else 0.0 + }) + pro_vals['taxes_id'] = line_vals['taxes_id'] = [tax.id] + if self.import_product_by == 'name': + if item.get('Product'): + product = self.env['product.product'].search( + [('name', '=', + item['Product'])]) + if not product: + pro_vals['name'] = item['Product'] + product = self.env['product.product'].create( + pro_vals) + if len(product) > 1: + if item.get('Variant Values'): + pro_tmpl_id = product.mapped('product_tmpl_id') + if len(pro_tmpl_id) > 1: + error_msg += row_not_import_msg + ( + "\n\t❎Multiple Product records are " + "linked with the product variant \"%s\"" + "." % (item['Product'])) + continue + variant_values = item['Variant Values'].split( + ',') + variant_value_ids = [] + for var in variant_values: + k_v = var.partition(":") + attr = k_v[0].strip() + attr_val = k_v[2].strip() + var_attr_ids = self.env[ + 'product.attribute'].search( + [('name', '=', attr)]).ids + var_attr_val_ids = self.env[ + 'product.attribute.value'].search( + [('name', '=', attr_val), + ('attribute_id', 'in', + var_attr_ids)]).ids + pro_temp_attr_val_id = self.env[ + 'product.template.attribute.value'].search( + [('product_attribute_value_id', 'in', + var_attr_val_ids), + ('product_tmpl_id', '=', + pro_tmpl_id.id)]).id + variant_value_ids += [pro_temp_attr_val_id] + if variant_value_ids: + product = product.filtered( + lambda p: + p.product_template_variant_value_ids.ids + == variant_value_ids) + else: + error_msg += row_not_import_msg + ( + "\n\t❎Product variant with variant " + "values \"%s\" not found." + % (item['Variant Values'])) + continue + if len(product) != 1: + error_msg += row_not_import_msg + ( + "\n\t❎Multiple variants with same " + "Variant Values \"%s\" found. " + "Please check if the product " + "Variant Values are unique." + % (item['Variant Values'])) + continue + else: + error_msg += row_not_import_msg + ( + "\n\t⚠ Multiple Products with same " + "Name \"%s\" found. Please " + "provide unique product \"Variant " + "Values\" to filter the records." + % (item['Product'])) + continue + else: + error_msg += row_not_import_msg + ( + "\n\t❎Product name missing in file!") + continue + if self.import_product_by == 'default_code': + if item.get('Internal Reference'): + product = self.env['product.product'].search( + [('default_code', '=', + item['Internal Reference'])]) + if not product: + if not item.get('Product'): + warning_msg += ("\nℹA Product is created with " + "\"Internal Reference\" as " + "product name since \"Product\"" + " name is missing at row %d." + % row) + pro_vals['name'] = item['Internal Reference'] + product = self.env['product.product'].create( + pro_vals) + if len(product) > 1: + error_msg += row_not_import_msg + ( + "\n\t❎Multiple Products with same Internal " + "Reference(%s) found!" + % (item['Internal Reference'])) + continue + else: + error_msg += row_not_import_msg + ( + "\n\t❎Internal Reference missing in file!") + continue + if self.import_product_by == 'barcode': + if item.get('Barcode'): + product = self.env['product.product'].search( + [('barcode', '=', + item['Barcode'])]) + if not product: + if not item.get('Product'): + warning_msg += ( + "\nℹNo value under \"Product\" at " + "row %d, thus added \"Barcode\" as " + "product name" % row) + pro_vals['name'] = item['Barcode'] + product = self.env['product.product'].create( + pro_vals) + if len(product) > 1: + error_msg += row_not_import_msg + ( + "\n\t❎Other Product(s) with same " + "Barcode (%s) found!" % item['Barcode']) + continue + else: + error_msg += row_not_import_msg + ( + "\n\t❎Barcode missing in file!") + continue + line_vals['product_id'] = product.id + vals['order_line'] = [(0, 0, line_vals)] + purchase_order = self.env['purchase.order'].search( + [('name', '=', item.get('Order Reference'))]) + if purchase_order: + if len(purchase_order) > 1: + error_msg += row_not_import_msg + ( + "\n\t❎Multiple purchase order with same Order " + "Reference(%s) found!" + % (item.get('Order Reference'))) + continue + if vals and purchase_order.state in ['draft', 'sent']: + purchase_order.write(vals) + previous_order = purchase_order + elif not purchase_order: + if self.order_number == 'from_file': + vals['name'] = item.get('Order Reference') + if item.get('Vendor'): + purchase_order = self.env['purchase.order'].create(vals) + if self.auto_confirm_quot: + purchase_order.button_confirm() + previous_order = purchase_order + if item.get('Product') and not item.get('Vendor'): + previous_order.write({ + 'order_line': [(0, 0, line_vals)] + }) + imported += 1 + if error_msg: + error_msg = "\n\n⚠ WARNING ⚠" + error_msg + msg = (("Imported %d records." + % imported) + vendor_added_msg + + date_missing_msg + error_msg + warning_msg) + message = self.env['import.message'].create( + {'message': msg}) + if message: + return { + 'effect': { + 'fadeout': 'slow', + 'message': 'Imported Successfully', + 'type': 'rainbow_man', + } + } diff --git a/import_dashboard/wizard/import_purchase_order_views.xml b/import_dashboard/wizard/import_purchase_order_views.xml new file mode 100644 index 000000000..79c341600 --- /dev/null +++ b/import_dashboard/wizard/import_purchase_order_views.xml @@ -0,0 +1,30 @@ + + + + + import.purchase.order.view.form + import.purchase.order + +
+ + + + + + + + + + + +
+
+
+
+
+
diff --git a/import_dashboard/wizard/import_sale_order.py b/import_dashboard/wizard/import_sale_order.py new file mode 100644 index 000000000..5197506ec --- /dev/null +++ b/import_dashboard/wizard/import_sale_order.py @@ -0,0 +1,328 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +import base64 +import datetime +import binascii +import csv +import io +import re +import tempfile +import xlrd +from odoo import fields, models, _ +from odoo.exceptions import ValidationError + + +class ImportSaleOrder(models.TransientModel): + """Model for importing sale orders.This model facilitates the importing + of sale orders from CSV or XLSX files.""" + _name = 'import.sale.order' + _description = 'For importing sale order' + + name = fields.Char(string="Name", help="Name", default="Import Sale order") + file_type = fields.Selection([('csv', 'CSV File'), + ('xlsx', 'XLSX File')], default='csv', + string='Select File Type', + help='Importing file type') + file_upload = fields.Binary(string="Upload File", + help="Upload files for importing") + auto_confirm_quot = fields.Boolean( + string='Confirm Quotation Automatically', + help='Automatically confirm the quotation') + order_number = fields.Selection( + [('from_system', 'From System'), + ('from_file', 'From File')], + string='Order / Quotation Number', default='from_file', + help='Order or Quotation Number for sale order creation method') + import_product_by = fields.Selection( + [('name', 'Name'), + ('default_code', 'Internal Reference'), + ('barcode', 'Barcode')], string="Import Products By", + default='name', + help="import products by internal reference and barcode") + + def action_sale_order_import(self): + """Creating sale order record using uploaded xl/csv files""" + items = {} + if self.file_type == 'csv': + try: + csv_data = base64.b64decode(self.file_upload) + data_file = io.StringIO(csv_data.decode("utf-8")) + data_file.seek(0) + csv_reader = csv.DictReader(data_file, delimiter=',') + except: + raise ValidationError(_( + "File not Valid.\n\nPlease check the type and format of " + "the file and try again!")) + items = csv_reader + if self.file_type == 'xlsx': + try: + fp = tempfile.NamedTemporaryFile(delete=False, + suffix=".xlsx") + fp.write(binascii.a2b_base64(self.file_upload)) + fp.seek(0) + workbook = xlrd.open_workbook(fp.name) + sheet = workbook.sheet_by_index(0) + except: + raise ValidationError(_( + "File not Valid.\n\nPlease check the " + "type and format of the file and try again!")) + headers = sheet.row_values(0) + data = [] + for row_index in range(1, sheet.nrows): + row = sheet.row_values(row_index) # list + data += [{k: v for k, v in zip(headers, row)}] + items = data + row = 0 + imported = 0 + error_msg = "" + cust_added_msg = "" + date_missing_msg = "" + warning_msg = "" + previous_order = None + for item in items: + row += 1 + vals = {} + row_not_import_msg = "\n◼ Row {rn} not imported.".format( + rn=row) + import_error_msg = "" + missing_fields_msg = "" + cust_msg = "\n🆕New Customer(s) added:" + if item.get('Customer'): + customer = self.env['res.partner'].search( + [('name', '=', item['Customer'])]) + if not customer: + customer = self.env['res.partner'].create({ + 'name': item['Customer'] + }) + cust_added_msg += ( + cust_msg + "\n\t\trow {rn}: " + "\"{cust}\"").format( + rn=row, cust=item['Customer']) + elif len(customer) > 1: + import_error_msg += row_not_import_msg + ( + "\n\t\t⚠ Multiple Partners with name (%s) found!" + % item['Customer']) + vals['partner_id'] = customer.id + if missing_fields_msg: + import_error_msg += (row_not_import_msg + + missing_fields_msg) + if item.get('Quotation Date'): + date = item['Quotation Date'] + try: + quot_date = datetime.datetime.strptime( + date, '%m/%d/%Y') + vals['date_order'] = quot_date + except: + import_error_msg += ("\n\t\t⚠ Please check the " + "Quotation Date and format is " + "mm/dd/yyyy") + if item.get('Salesperson'): + vals['user_id'] = self.env['res.users'].search( + [('name', '=', item['Salesperson'])]).id + if import_error_msg: + error_msg += import_error_msg + continue + line_vals = {} + pro_vals = {} + if item.get('Quantity'): + line_vals['product_uom_qty'] = item['Quantity'] + if item.get('Uom'): + uom = self.env['uom.uom'].search([('name', '=', item['Uom'])]) + pro_vals['uom_id'] = line_vals['product_uom'] = uom.id + if item.get('Unit Price'): + pro_vals['lst_price'] = line_vals['price_unit'] = item[ + 'Unit Price'] + if item.get('Taxes'): + tax_name = item['Taxes'] + tax_amount = (re.findall(r"(\d+)%", tax_name))[0] + tax = self.env['account.tax'].search( + [('name', '=', tax_name), + ('type_tax_use', '=', 'sale')], limit=1) + if not tax: + tax = self.env['account.tax'].create({ + 'name': tax_name, + 'type_tax_use': 'sale', + 'amount': tax_amount if tax_amount else 0.0 + }) + pro_vals['taxes_id'] = line_vals['tax_id'] = [tax.id] + if item.get('Disc.%'): + line_vals['discount'] = item['Disc.%'] + product = None + if self.import_product_by == 'name': + if item.get('Product'): + pro_vals['name'] = item['Product'] + product = self.env['product.product'].search([('name', '=', + item[ + 'Product'])]) + if not product: + product = self.env['product.product'].create(pro_vals) + if len(product) > 1: + if item.get('Variant Values'): + pro_tmpl_id = product.mapped('product_tmpl_id') + if len(pro_tmpl_id) > 1: + error_msg += row_not_import_msg + ( + "\n\t⚠ Multiple Product records are " + "linked with the product variant \"%s\"" + "." % item['Product']) + continue + variant_values = item['Variant Values'].split( + ',') + variant_value_ids = [] + for var in variant_values: + k_v = var.partition(":") + attr = k_v[0].strip() + attr_val = k_v[2].strip() + var_attr_ids = self.env[ + 'product.attribute'].search( + [('name', '=', attr)]).ids + var_attr_val_ids = self.env[ + 'product.attribute.value'].search( + [('name', '=', attr_val), + ('attribute_id', 'in', + var_attr_ids)]).ids + pro_temp_attr_val_id = self.env[ + 'product.template.attribute.value'].search( + [('product_attribute_value_id', 'in', + var_attr_val_ids), + ('product_tmpl_id', '=', + pro_tmpl_id.id)]).id + variant_value_ids += [pro_temp_attr_val_id] + if variant_value_ids: + product = product.filtered( + lambda p: + p.product_template_variant_value_ids.ids + == variant_value_ids) + else: + error_msg += row_not_import_msg + ( + "\n\t⚠ Product variant with variant " + "values \"%s\" not found." + % (item['Variant Values'])) + continue + if len(product) != 1: + error_msg += row_not_import_msg + ( + "\n\t⚠ Multiple variants with same " + "Variant Values \"%s\" found. Please " + "check if the product Variant Values" + " are unique." + % (item['Variant Values'])) + continue + else: + error_msg += row_not_import_msg + ( + "\n\t⚠ Multiple Products with same " + "Name \"%s\" found. Please " + "provide unique product \"Variant " + "Values\" to filter the records." + % (item['Product'])) + continue + else: + error_msg += row_not_import_msg + ( + "\n\t⚠ Product name missing in file!") + continue + if self.import_product_by == 'default_code': + if item.get('Internal Reference'): + pro_vals['default_code'] = item['Internal Reference'] + product = self.env['product.product'].search( + [('default_code', '=', + item['Internal Reference'])]) + if not product: + if not item.get('Product'): + warning_msg += ( + "\n◼ A Product is created with " + "\"Internal Reference\" as " + "product name since \"Product\"" + " name is missing in file." + "(row %d)" % row) + pro_vals['name'] = item['Internal Reference'] + product = self.env['product.product'].create(pro_vals) + if len(product) > 1: + error_msg += row_not_import_msg + ( + "\n\t⚠ Multiple Products with same Internal" + " Reference(%s) found!" + % (item['Internal Reference'])) + continue + else: + error_msg += row_not_import_msg + ( + "\n\t⚠ Internal Reference missing in file!") + continue + if self.import_product_by == 'barcode': + if item.get('Barcode'): + product = self.env['product.product'].search( + [('barcode', '=', item['Barcode'])]) + if not product: + if not item.get('Product'): + warning_msg += ( + "\n◼ No value under \"Product\" at " + "row %d, thus added \"Barcode\" as " + "product name" % row) + pro_vals['name'] = item['Barcode'] + product = self.env['product.product'].create(pro_vals) + if len(product) > 1: + error_msg += row_not_import_msg + ( + "\n\t⚠ Other Product(s) with same " + "Barcode (%s) found!" % item['Barcode']) + continue + else: + error_msg += row_not_import_msg + ( + "\n\t⚠ Barcode missing in file!") + continue + line_vals['product_id'] = product.id + vals['order_line'] = [(0, 0, line_vals)] + sale_order = self.env['sale.order'].search( + [('name', '=', item['Order Reference'])]) + if sale_order: + if len(sale_order) > 1: + error_msg += row_not_import_msg + ( + "\n\t⚠ Multiple sale order with same Order " + "Reference(%s) found!" + % (item['Order Reference'])) + continue + if vals and sale_order.state in ['draft', 'sent']: + sale_order.write(vals) + previous_order = sale_order + elif not sale_order: + if self.order_number == 'from_file': + vals['name'] = item.get('Order Reference') + if item.get('Customer'): + sale_order = self.env['sale.order'].create(vals) + if self.auto_confirm_quot: + sale_order.action_confirm() + previous_order = sale_order + if item.get('Product') and not item.get('Customer'): + previous_order.write({ + 'order_line': [(0, 0, line_vals)] + }) + imported += 1 + if error_msg: + error_msg = "\n\n🏮 WARNING 🏮" + error_msg + msg = (("Imported %d records." + % imported) + cust_added_msg + + date_missing_msg + error_msg + warning_msg) + message = self.env['import.message'].create( + {'message': msg}) + if message: + return { + 'effect': { + 'fadeout': 'slow', + 'message': 'Imported Successfully', + 'type': 'rainbow_man', + } + } diff --git a/import_dashboard/wizard/import_sale_order_views.xml b/import_dashboard/wizard/import_sale_order_views.xml new file mode 100644 index 000000000..a8faa5925 --- /dev/null +++ b/import_dashboard/wizard/import_sale_order_views.xml @@ -0,0 +1,29 @@ + + + + + import.sale.order + import.sale.order + +
+ + + + + + + + + + + +
+
+
+
+
+
diff --git a/import_dashboard/wizard/import_task.py b/import_dashboard/wizard/import_task.py new file mode 100644 index 000000000..76c0c3079 --- /dev/null +++ b/import_dashboard/wizard/import_task.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +import base64 +import binascii +import csv +import io +import tempfile +import xlrd +from datetime import date +from odoo import fields, models, _ +from odoo.exceptions import ValidationError + + +class ImportTask(models.TransientModel): + """For handling importing of tasks""" + _name = 'import.task' + _description = 'Importing of task' + + name = fields.Char(string="Name", help="Name", default="Import Tasks") + file_type = fields.Selection([('csv', 'CSV File'), + ('xls', 'XLS File')], + string='Select File Type', default='csv', + help='File type') + file_upload = fields.Binary(string="Upload File", help="Upload your file") + user_id = fields.Many2one('res.users', string='Assigned to', + help="assigned to user") + + def make_json_dict(self, column, row): + """"Converting json data to dictionary""" + return [{col: item[i] for i, col in enumerate(column)} for item in row] + + def action_task_import(self): + """Creating task record using uploaded xl/csv files""" + res_partner = self.env['res.partner'] + project_project = self.env['project.project'] + project_task = self.env['project.task'] + datas = {} + if self.file_type == 'csv': + try: + csv_data = base64.b64decode(self.file_upload) + data_file = io.StringIO(csv_data.decode("utf-8")) + data_file.seek(0) + datas = csv.DictReader(data_file, delimiter=',') + except: + raise ValidationError(_( + "File not Valid.\n\nPlease check the type and format " + "of the file and try again!")) + if self.file_type == 'xls': + try: + book_dict = {} + fp = tempfile.NamedTemporaryFile(delete=False, + suffix=".xlsx") + fp.write(binascii.a2b_base64(self.file_upload)) + fp.seek(0) + book = xlrd.open_workbook(fp.name) + sheets = book.sheets() + for sheet in sheets: + book_dict[sheet.name] = {} + columns = sheet.row_values(0) + rows = [] + for row_index in range(1, sheet.nrows): + row = sheet.row_values(row_index) + rows.append(row) + sheet_data = self.make_json_dict(columns, rows) + book_dict[sheet.name] = sheet_data + datas = book_dict['Sheet1'] + except: + raise ValidationError(_( + "File not Valid.\n\nPlease check the type and format " + "of the file and try again!")) + for item in datas: + vals = {} + if item.get('Project'): + project = project_project.search( + [('name', '=', item.get('Project'))]) + if not project: + project = project_project.create({ + 'name': item.get('Project') + }) + vals['project_id'] = project.id + if item.get('Title'): + vals['name'] = item.get('Title') + if item.get('Customer'): + partner = res_partner.search( + [['name', '=', item.get('Customer')]]) + if not partner: + partner = res_partner.create({ + 'name': item.get('Customer') + }) + vals['partner_id'] = partner.id + if item.get('Deadline'): + if self.file_type == 'xlsx': + vals['date_deadline'] = date.fromordinal( + date(1900, 1, 1).toordinal() + int( + item.get('Deadline')) - 2) + else: + vals['date_deadline'] = item.get('Deadline') + if item.get('Parent Task'): + vals['parent_id'] = project_task.search( + [('name', '=', item.get('Parent Task'))]) + vals['user_ids'] = self.user_id + project_task.create(vals) + return { + 'effect': { + 'fadeout': 'slow', + 'message': 'Imported Successfully', + 'type': 'rainbow_man', + } + } diff --git a/import_dashboard/wizard/import_task_views.xml b/import_dashboard/wizard/import_task_views.xml new file mode 100644 index 000000000..9ee38c09e --- /dev/null +++ b/import_dashboard/wizard/import_task_views.xml @@ -0,0 +1,26 @@ + + + + + import.task.view.form + import.task + +
+ + + + + + + + + +
+
+
+
+
+
diff --git a/import_dashboard/wizard/import_vendor_pricelist.py b/import_dashboard/wizard/import_vendor_pricelist.py new file mode 100644 index 000000000..6f688b8f2 --- /dev/null +++ b/import_dashboard/wizard/import_vendor_pricelist.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2024-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +import base64 +import binascii +import csv +import io +import tempfile +import xlrd +from odoo import fields, models, _ +from odoo.exceptions import ValidationError + + +class ImportVendorPricelist(models.TransientModel): + """For handling importing of vendor pricelist""" + _name = 'import.vendor.pricelist' + _description = 'Importing of vendor pricelist' + + name = fields.Char(string="Name", help="Name", + default="Import Vendor Pricelist") + file_type = fields.Selection([('csv', 'CSV File'), + ('xls', 'Excel File')], + string='Select File Type', default='csv', + help='File type') + company_id = fields.Many2one('res.company', string='Company', + help="Company", required=True, + default=lambda self: self.env.company) + file_upload = fields.Binary(string="Upload File", help="Upload your file") + + def make_json_dict(self, column, row): + """Converting json data to dictionary""" + return [{col: item[i] for i, col in enumerate(column)} for item in row] + + def action_vendor_pricelist_import(self): + """Creating vendor pricelist record using uploaded xl/csv files""" + datas = {} + if self.file_type == 'csv': + try: + csv_data = base64.b64decode(self.file_upload) + data_file = io.StringIO(csv_data.decode("utf-8")) + data_file.seek(0) + datas = csv.DictReader(data_file, delimiter=',') + except: + raise ValidationError(_( + "File not Valid.\n\nPlease check the type and format " + "of the file, and try again!")) + if self.file_type == 'xls': + try: + book_dict = {} + fp = tempfile.NamedTemporaryFile(delete=False, + suffix=".xlsx") + fp.write(binascii.a2b_base64(self.file_upload)) + fp.seek(0) + book = xlrd.open_workbook(fp.name) + sheets = book.sheets() + for sheet in sheets: + book_dict[sheet.name] = {} + columns = sheet.row_values(0) + rows = [] + for row_index in range(1, sheet.nrows): + row = sheet.row_values(row_index) + rows.append(row) + sheet_data = self.make_json_dict(columns, rows) + book_dict[sheet.name] = sheet_data + datas = book_dict['Sheet1'] + except: + raise ValidationError(_( + "File not Valid.\n\nPlease check the type and format " + "of the file, and try again!")) + for item in datas: + vendor_name = None + product_name = None + currency = None + if item.get('Vendor'): + vendor_name = self.env['res.partner'].search( + [('name', '=', item.get('Vendor'))]) + if not vendor_name: + vendor_name = self.env['res.partner'].create({ + 'name': item.get('Vendor'), + }) + if item.get('Product Template'): + product_name = self.env['product.template'].search( + [('name', '=', item.get('Product Template'))]) + if not product_name: + product_name = self.env['product.template'].create( + {'name': item.get('Product Template')}) + if item.get('Currency'): + currency = self.env['res.currency'].search( + [('name', '=', item.get('Currency'))]) + self.env['product.supplierinfo'].create({ + "product_tmpl_id": product_name.id, + "name": vendor_name.id, + "min_qty": item.get('Quantity'), + "price": item.get('Price'), + "delay": item.get('Delivery Lead Time'), + 'company_id': self.company_id.id, + 'currency_id': currency.id + }) + return { + 'effect': { + 'fadeout': 'slow', + 'message': 'Imported Successfully', + 'type': 'rainbow_man', + } + } diff --git a/import_dashboard/wizard/import_vendor_pricelist_views.xml b/import_dashboard/wizard/import_vendor_pricelist_views.xml new file mode 100644 index 000000000..7cfd63976 --- /dev/null +++ b/import_dashboard/wizard/import_vendor_pricelist_views.xml @@ -0,0 +1,24 @@ + + + + + import.vendor.pricelist.view.form + import.vendor.pricelist + +
+ + + + + + + + +
+
+
+