diff --git a/enhanced_document_management/README.rst b/enhanced_document_management/README.rst new file mode 100755 index 000000000..6e0708c3f --- /dev/null +++ b/enhanced_document_management/README.rst @@ -0,0 +1,46 @@ +.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 + +Document Management +=================== +The Document Management module provide a quick access to create, share and delete. +The module requires beautiful Soup python library + +License +------- +General Public License, Version 3 (LGPL v3). +(https://www.odoo.com/documentation/user/17.0/legal/licenses/licenses.html) + +Company +------- +* `Cybrosys Techno Solutions `__ + +Credits +------- +* Developers: (V16) Mohamed Savad, Gokul PI, Megha AP, Javid, Nisiya, Farhana Jahan PT + (V17) Gayathri V, + (V18) Mruthul Raj, +* 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/enhanced_document_management/__init__.py b/enhanced_document_management/__init__.py new file mode 100755 index 000000000..ba9e7d989 --- /dev/null +++ b/enhanced_document_management/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Mruthul Raj() +# +# 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 controllers +from . import models +from . import wizards diff --git a/enhanced_document_management/__manifest__.py b/enhanced_document_management/__manifest__.py new file mode 100755 index 000000000..90528fc9d --- /dev/null +++ b/enhanced_document_management/__manifest__.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Mruthul Raj() +# +# 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': 'Document Management', + 'version': '18.0.1.0.0', + 'category': 'Document Management', + 'summary': 'The Document Management module to access document tools', + 'description': 'The Document Management module provides a quick access to ' + 'create, share and delete.', + 'author': 'Cybrosys Techno Solutions', + 'company': 'Cybrosys Techno Solutions', + 'maintainer': 'Cybrosys Techno Solutions', + 'website': 'https://cybrosys.com', + 'depends': ['mail', 'website', 'hr'], + 'data': [ + 'security/enhanced_document_management_groups.xml', + 'security/ir.model.access.csv', + 'data/document_data.xml', + 'data/ir_cron_data.xml', + 'views/document_tag_views.xml', + 'views/document_request_views.xml', + 'views/document_request_template_views.xml', + 'views/document_delete_trash_views.xml', + 'views/document_lock_views.xml', + 'views/document_workspace_views.xml', + 'views/document_file_views.xml', + 'views/document_portal_templates.xml', + 'views/document_request_wizard_view.xml', + 'views/outgoing_request_document_view.xml', + 'views/portal_document_templates.xml', + 'views/document_trash_views.xml', + 'views/document_request_portal_templates.xml', + 'views/document_request_portal_form.xml', + 'views/document_template_request_portal_templates.xml', + 'wizards/document_request_accept_view.xml', + 'wizards/document_request_reject_view.xml', + 'views/incoming_request_document_views.xml', + 'views/res_config_settings_views.xml', + 'views/enhanced_document_management_menu.xml', + 'reports/document_download.xml', + 'wizards/document_url_view.xml', + 'wizards/document_share_view.xml', + 'wizards/work_space_views.xml', + ], + 'assets': { + 'web.assets_backend': [ + 'enhanced_document_management/static/src/css/kanban.css', + 'enhanced_document_management/static/src/css/style.css', + 'enhanced_document_management/static/src/xml/KanbanController.xml', + 'enhanced_document_management/static/src/xml/document_preview_template.xml', + 'enhanced_document_management/static/src/js/kanban_record.js', + 'enhanced_document_management/static/src/js/kanban_renderer.js', + 'enhanced_document_management/static/src/js/kanbancontroller.js', + 'enhanced_document_management/static/src/js/document_preview_action.js', + 'https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js', + 'https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js', + 'https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3.5.6/dist/jquery.fancybox.min.css', + 'https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3.5.6/dist/jquery.fancybox.min.js' + ], + 'web.assets_frontend': [ + 'enhanced_document_management/static/src/js/portal.js', + ] + }, + 'external_dependencies': { + 'python': ['bs4'] + }, + 'images': ['static/description/banner.jpg'], + 'license': 'LGPL-3', + 'installable': True, + 'auto_install': False, + 'application': True, +} diff --git a/enhanced_document_management/controllers/__init__.py b/enhanced_document_management/controllers/__init__.py new file mode 100644 index 000000000..92c3e042c --- /dev/null +++ b/enhanced_document_management/controllers/__init__.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Mruthul Raj() +# +# 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 document_file +from . import document_portal_view +from . import document_request_portal +from . import document_templates_request +from . import website_customer_portal +from . import website_documents_upload diff --git a/enhanced_document_management/controllers/document_file.py b/enhanced_document_management/controllers/document_file.py new file mode 100644 index 000000000..98bda82df --- /dev/null +++ b/enhanced_document_management/controllers/document_file.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Mruthul Raj() +# +# 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 io +import zipfile +from odoo import http +from odoo.http import request + + +class DocumentFile(http.Controller): + """Http Controller to create sharable view for selected documents """ + + @http.route('/web/content/share/', type='http', auth='public', + website='True') + def document_share(self, **kwargs): + """Share a document and prepare the context for rendering. + :param kwargs: A dictionary containing the 'unique' key for + identifying the document to share. + :type kwargs: Dict + :return: Rendered document_share_preview + template with the prepared context. + :rtype: Http.Response""" + folder_ids = request.env['document.share'].sudo().search([ + ('unique_id', '=', kwargs.get('unique'))]) + context = ({ + 'doc_id': document.id, + 'doc_name': document.name, + 'doc_extension': document.extension, + 'doc_owner': document.user_id, + 'doc_date': document.date, + 'doc_url': document.content_url, + } for document in folder_ids.document_ids) + return http.request.render( + 'enhanced_document_management.document_share_preview', {'context': context} + ) + + @http.route("/get/document", type="http", auth='public', + website='True') + def download_zip(self): + """ + HTTP controller to download selected files as a ZIP archive. + This controller takes a list of document IDs as input, + creates a ZIP archive + containing those documents, and sends it as a response for download. + :return: HTTP response containing the ZIP archive. + :rtype: Http.Response + """ + param_value = request.params.get('param') + param_list = eval(param_value) + zip_data = io.BytesIO() + with zipfile.ZipFile(zip_data, 'w', zipfile.ZIP_DEFLATED) as zipf: + for doc in request.env['document.file'].sudo().browse(param_list): + if doc.content_type != "url": + zipf.write(doc.attachment_id._full_path( + doc.attachment_id.store_fname), doc.attachment_id.name) + headers = [ + ('Content-Type', 'application/zip'), + ('Content-Disposition', http.content_disposition('archive.zip')), + ] + return request.make_response(zip_data.getvalue(), headers) diff --git a/enhanced_document_management/controllers/document_portal_view.py b/enhanced_document_management/controllers/document_portal_view.py new file mode 100644 index 000000000..c6f7de45f --- /dev/null +++ b/enhanced_document_management/controllers/document_portal_view.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Mruthul Raj() +# +# 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 http +from odoo.http import request + + +class DocumentPortalView(http.Controller): + @http.route('/my/documents', type="http", auth="user", website=True) + def document_in_portal(self): + """Http controller to all user document from portal + :return Http response with all Documents data""" + document_ids = request.env['document.file'].search([ + ('user_id.id', '=', request.uid) + ]) + extensions = set(item.extension for item in document_ids) + groups = [[rec for rec in document_ids if rec.extension == item + ] for item in extensions] + return request.render("enhanced_document_management.portal_my_documents", + { + 'extensions': extensions, + 'document_ids': groups, + 'page_name': 'document', + }) + + @http.route('/my/document_request', type="http", auth="user", + website=True) + def document_request_in_portal(self): + """Http controller to access user requests for document from portal + :return Http response with all Documents data""" + request_ids = request.env['request.document'].search([ + ('user_id.id', '=', request.uid), + ('state', '=', 'requested')]) + context = [{ + 'id': item.id, + 'needed_doc': item.needed_doc, + 'workspace_id': [item.workspace_id.id, item.workspace_id.name], + 'requested_by_id': [item.requested_by_id.id, + item.requested_by_id.name], + 'user_id': [item.user_id.id, item.user_id.name], + 'date': item.create_date.date() + } for item in request_ids] + return request.render("enhanced_document_management.portal_my_document_request", + { + 'requests': context, + 'page_name': 'document_requests', + }) + + @http.route('/my/documents/', type="http", + auth="user", website=True) + def document_view(self, doc): + """ + Http controller to access document from portal + :param doc: primary key of a record + :return Http response with the selected Documents data + """ + url = f"""{request.httprequest.host_url[:-1]}/web/content/ + {doc.attachment_id.id}/{doc.name}""" + context = { + 'page_name': 'document', + 'document': True, + 'name': doc.name, + 'id': doc.id, + 'owner': doc.user_id, + 'attachment_id': doc.attachment_id.id, + 'brochure_url': doc.brochure_url, + 'workspace_id': doc.workspace_id.name, + 'date': doc.date, + 'url': url, + 'partner_id': doc.partner_id.name, + 'extension': doc.extension, + 'preview': doc.preview, + 'content_url': doc.content_url, + } + return request.render("enhanced_document_management.portal_my_document_view", context) diff --git a/enhanced_document_management/controllers/document_request_portal.py b/enhanced_document_management/controllers/document_request_portal.py new file mode 100644 index 000000000..be9cfc6e1 --- /dev/null +++ b/enhanced_document_management/controllers/document_request_portal.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Mruthul Raj() +# +# 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.http import request +from odoo import http +from odoo.addons.portal.controllers.portal import CustomerPortal + + +class DocumentRequestCount(CustomerPortal): + """Controller handling document request counts and downloads.""" + def _prepare_home_portal_values(self, counters): + """Prepare home portal values including document request count. + Args:counters (dict): Dictionary of counters. + Returns:Dictionary of values for the home portal.""" + vals = super()._prepare_home_portal_values(counters) + if 'doc_req_count' in counters: + vals['doc_req_count'] = request.env[ + 'document.template.request'].sudo().search_count( + []) + return vals + + @http.route(['/document_request/download/'], type='http', + auth="public", website=True) + def portal_document_download(self, order_id): + """Route to handle downloading of requested documents. + Args:order_id (int): ID of the requested document. + Returns:HTTP response for the requested document download.""" + document_request = (request.env['document.template.request']. + browse(order_id)) + return self._show_report(model=document_request, + report_type='pdf', + report_ref='enhanced_document_management.action_download_doc', + download=True) diff --git a/enhanced_document_management/controllers/document_templates_request.py b/enhanced_document_management/controllers/document_templates_request.py new file mode 100644 index 000000000..e7af0fd79 --- /dev/null +++ b/enhanced_document_management/controllers/document_templates_request.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Mruthul Raj() +# +# 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 http +from odoo.http import request + + +class DocumentRequest(http.Controller): + + @http.route(['/document_request'], type='http', auth="user", website=True) + def get_document_request(self): + """This route is called whenever the user clicks on the + 'Document Request' menu on the website. + It checks if the user is an internal user and retrieves + their document requests, if authorized. + :return: HTTP response containing the 'my_document_request' + template with relevant data for the user's document requests. + """ + if request.env.user.has_group( + 'enhanced_document_management.view_all_document'): + values = {'document_requests': request.env[ + 'document.template.request'].sudo().search( + [('employee_id', '=', request.env.user.employee_id.id)]), + 'document_req_count': request.env[ + 'document.template.request'].sudo(). + search_count( + [('employee_id', '=', request.env.user.employee_id.id)])} + return request.render( + "enhanced_document_management.document_request_template", + values) + else: + # Return an error message or redirect to an unauthorized page + return "You are not authorized to access this page." + + @http.route(['/document_request/all'], type='http', auth="user", + website=True) + def get_all_document_request(self): + """ This route is called whenever the user clicks on 'My' menu""" + values = { + 'document_requests': request.env['document.template.request']. + sudo().search([]), 'document_req_count': request.env[ + 'document.template.request'].sudo().search_count([])} + return request.render("enhanced_document_management.document_request_template", + values) + + @http.route(['/document_request/draft'], type='http', auth="user", + website=True) + def get_draft_document_request(self): + """ This route is called whenever the user clicks on 'New' menu""" + values = {'document_requests': request.env[ + 'document.template.request'].sudo().search( + [('state', '=', 'new')]), + 'document_req_count': request.env[ + 'document.template.request'].sudo().search_count( + [('state', '=', 'new')])} + return request.render("enhanced_document_management.document_request_template", + values) + + @http.route(['/document_request/document_approval'], type='http', + auth="user", + website=True) + def get_approval_document_request(self): + """ This route is called whenever the user clicks on + 'Document Approval' menu""" + values = {'document_requests': request.env[ + 'document.template.request'].sudo().search( + [('state', '=', 'document_approval')]), + 'document_req_count': request.env[ + 'document.template.request'].sudo().search_count( + [('state', '=', 'document_approval')])} + return request.render("enhanced_document_management.document_request_template", + values) + + @http.route(['/document_request/new'], type='http', auth="user", + website=True) + def document_request_form(self): + """ This route is called whenever the user clicks on + 'Document Request' menu in website""" + values = { + 'employees': request.env['hr.employee'].sudo().search([]), + 'users': request.env['res.users'].sudo().search([]), + 'templates': request.env[ + 'document.request.template'].sudo().search( + []), + } + return request.render("enhanced_document_management.document_request_form", + values) + + @http.route(['/document_request/submit'], type='http', auth="user", + website=True) + def document_request_submit(self, **kwargs): + """ This route is called whenever the user hits the 'Submit' button + after filling the Service Request Form.""" + request_template = request.env['document.request.template'].browse( + int(kwargs.get('document_request_template_id'))) + request.env['document.template.request'].sudo().create({ + 'document_id': request_template.id, + 'manager_id': request_template.manager_id, + 'template': request_template.template, + 'stamp': request_template.stamp, + }) + return request.render( + 'enhanced_document_management.document_request_submit_template') + + @http.route('/document_request/templates', type='json', auth="public") + def get_document_request_template(self, **kw): + """This route is called whenever a template is selected to render + that template in UI""" + return request.env['document.request.template'].browse( + int(kw.get('template_id'))).template + + @http.route(['/document_request/details/request/'], + type='http', auth="user", website=True) + def get_document_request_details(self, req_id): + """ This route is called whenever the user clicks on + 'Document Request' menu in website""" + values = { + 'document_request': request.env[ + 'document.template.request'].sudo().browse( + req_id)} + return request.render("enhanced_document_management.document_request_details", + values) diff --git a/enhanced_document_management/controllers/website_customer_portal.py b/enhanced_document_management/controllers/website_customer_portal.py new file mode 100644 index 000000000..6068ff236 --- /dev/null +++ b/enhanced_document_management/controllers/website_customer_portal.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Mruthul Raj() +# +# 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.addons.portal.controllers.portal import CustomerPortal +from odoo.http import request + + +class WebsiteCustomerPortal(CustomerPortal): + """ functon : Prepare portal values, datas are searched from document.file + :return document count, request count""" + + def _prepare_home_portal_values(self, counters): + """ + Prepare the values for the home portal page. + :param counters(dict): A dictionary containing various counters for + different features on the portal. + :return: A dictionary containing the prepared values for the home + portal page. + """ + values = (super(WebsiteCustomerPortal, self). + _prepare_home_portal_values(counters)) + if 'document_count' in counters: + values['document_count'] = request.env[ + 'document.file'].sudo().search_count([ + ('user_id.id', '=', request.uid)]) + values['request_count'] = request.env[ + 'request.document'].sudo().search_count([ + ('user_id.id', '=', request.uid), + ('state', '=', 'requested')]) + return values diff --git a/enhanced_document_management/controllers/website_documents_upload.py b/enhanced_document_management/controllers/website_documents_upload.py new file mode 100644 index 000000000..31c568268 --- /dev/null +++ b/enhanced_document_management/controllers/website_documents_upload.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Mruthul Raj() +# +# 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 +from odoo import fields +from odoo import http +from odoo.http import request + + +class WebsiteDocumentsUpload(http.Controller): + """Controller for accept document upload form submission""" + + @http.route('/website/documents', type="http", auth="user", + website=True, csrf=False) + def website_docs(self, **post): + """ + Function : website form submit controller, + it creates a record in document.file + :param post: form-data + :return: redirect + """ + val_list = { + 'name': post['file'].filename, + 'attachment': base64.b64encode(post['file'].read()), + 'workspace_id': int(post['workspace']), + 'date': fields.Date.today(), + 'user_id': request.uid, + 'description': post['reason'], + 'security': 'private', + 'extension': post['file'].filename.split(".")[ + len(post['file'].filename.split(".")) - 1] + } + if post['security'] == 'Private': + val_list['security'] = 'private' + else: + val_list['security'] = 'managers_and_owner' + file_id = request.env['document.file'].create(val_list) + file_id.action_upload_document() + return request.redirect("/my/documents") + + @http.route('/website/documents_request', type="http", auth="user", + website=True, csrf=False) + def website_docs_request(self, **post): + """ + Function : website form submit controller for requested documents, + it creates a record in document.file + :param post: form-data + :return: redirect to /my/document_request + """ + request_id = request.env['request.document'].browse( + int(post['rec_id'])) + file_id = request.env['document.file'].sudo().create({ + 'name': post['file'].filename, + 'attachment': base64.b64encode(post['file'].read()), + 'workspace_id': int(post['workspace']), + 'date': fields.Date.today(), + 'user_id': request.uid, + 'description': post['reason'], + 'security': 'specific_users', + 'user_ids': [post['requested_by_id']] if post['requested_by_id'] else [], + 'extension': post['file'].filename.split(".")[ + len(post['file'].filename.split(".")) - 1] + }) + file_id.action_upload_document() + request_id.state = 'accepted' + return request.redirect("/my/document_request") + + @http.route('/website/documents_request_reject', type="http", + auth="user", website=True, csrf=False) + def document_request_reject(self, **post): + """ + Function accept document reject and update document.request + :param post: form-data + :return: redirect to /my/document_request + """ + request_id = request.env['request.document'].browse( + int(post['req_id'])) + request_id.state = 'rejected' + request_id.reject_reason = post['reason'] + return request.redirect("/my/document_request") diff --git a/enhanced_document_management/data/document_data.xml b/enhanced_document_management/data/document_data.xml new file mode 100755 index 000000000..aaee4c795 --- /dev/null +++ b/enhanced_document_management/data/document_data.xml @@ -0,0 +1,19 @@ + + + + + + My Workspace + My Workspace + The workspace here is the default + + + + Document Details + document.request + DOC/REQ + 3 + + + + diff --git a/enhanced_document_management/data/ir_cron_data.xml b/enhanced_document_management/data/ir_cron_data.xml new file mode 100644 index 000000000..a5be56673 --- /dev/null +++ b/enhanced_document_management/data/ir_cron_data.xml @@ -0,0 +1,23 @@ + + + + + Document Delete + + code + model.delete_doc() + + 1 + days + + + + Auto Document Delete + + code + model.auto_delete_doc() + + 1 + days + + diff --git a/enhanced_document_management/doc/RELEASE_NOTES.md b/enhanced_document_management/doc/RELEASE_NOTES.md new file mode 100755 index 000000000..a12525400 --- /dev/null +++ b/enhanced_document_management/doc/RELEASE_NOTES.md @@ -0,0 +1,7 @@ +## Module + +#### 26.02.2025 +#### Version 17.0.1.0.0 +#### ADD + +- Initial commit for Document Management diff --git a/enhanced_document_management/models/__init__.py b/enhanced_document_management/models/__init__.py new file mode 100644 index 000000000..3b7d7b631 --- /dev/null +++ b/enhanced_document_management/models/__init__.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Mruthul Raj() +# +# 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 document_delete_trash +from . import document_file +from . import document_lock +from . import document_template_request +from . import document_tag +from . import document_request_template +from . import document_trash +from . import document_workspace +from . import request_document +from . import res_config_settings diff --git a/enhanced_document_management/models/document_delete_trash.py b/enhanced_document_management/models/document_delete_trash.py new file mode 100644 index 000000000..d02bbbbcc --- /dev/null +++ b/enhanced_document_management/models/document_delete_trash.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Mruthul Raj() +# +# 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 DocumentDeleteTrash(models.Model): + _name = "document.delete.trash" + _description = "document permanent delete or move to trash" + + document_file_id = fields.Many2one("document.file", + string='document file id', + help='file if of document to delete') + + def document_move_trash(self): + """ + Move a document to the trash if it is not locked, otherwise return an + action to unlock the document. + This method checks if a document is locked. If it is not locked, it + moves the document to the trash. + If the document is locked, it returns an action to unlock the document. + :return: Action to move to the trash or unlock the document. + :rtype: dict or None + """ + if not self.document_file_id.is_locked: + self.document_file_id.document_file_delete( + self.document_file_id.id) + else: + return { + 'type': 'ir.actions.act_window', + 'name': 'Lock', + 'res_model': 'document.lock', + 'view_mode': 'form', + 'view_id': self.env.ref( + 'enhanced_document_management.document_lock_move_trash_view_form').id, + 'target': 'new', + 'context': { + 'default_document_file_id': self.document_file_id.id, + } + } + + def document_permanent_delete(self): + """ + Permanently delete a document if it is not locked, or return an action + to unlock the document. + This method checks if a document is locked. If it is not locked, it + permanently deletes the document. + If the document is locked, it returns an action to unlock the document. + :return: Action to permanently delete or unlock the document. + :rtype: dict or None + """ + if not self.document_file_id.is_locked: + self.document_file_id.unlink() + else: + return { + 'type': 'ir.actions.act_window', + 'name': 'Lock', + 'res_model': 'document.lock', + 'view_mode': 'form', + 'view_id': self.env.ref( + 'enhanced_document_management.document_lock_delete_view_form').id, + 'target': 'new', + 'context': { + 'default_document_file_id': self.document_file_id.id, + } + } diff --git a/enhanced_document_management/models/document_file.py b/enhanced_document_management/models/document_file.py new file mode 100644 index 000000000..092433985 --- /dev/null +++ b/enhanced_document_management/models/document_file.py @@ -0,0 +1,721 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Mruthul Raj() +# +# 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, fields, models + + +class DocumentFile(models.Model): + """ Model used to store documents, perform document related functions """ + _name = 'document.file' + _inherit = ['mail.thread', 'mail.activity.mixin'] + _description = 'Documents' + + name = fields.Char(string="Name", help="Document name") + attachment = fields.Binary(string='File', help="Document data") + date = fields.Datetime(string='Date', help="Document create date") + workspace_id = fields.Many2one('document.workspace', + string='Workspace', + required=True, help="workspace name") + security = fields.Selection( + selection=[ + ('private', 'Private'), + ('managers_and_owner', 'Managers & Owner'), + ('specific_users', 'Specific Users') + ], default='managers_and_owner', string="Security", + help="""Privet : only the uploaded user can view + Managers & Owner : Document shared with Managers """) + user_id = fields.Many2one('res.users', string='Owner', + default=lambda self: self.env.user, + help="""Document owner name, if the document + belongs to a specific partner""") + brochure_url = fields.Char(string="URL", help="Document sharable URL") + extension = fields.Char(string='Extension', help="""Document extension, + helps to determine the file type""") + priority = fields.Selection( + [('0', 'None'), ('1', 'Favorite')], + string="Priority", help="Favorite button") + activity_ids = fields.One2many('mail.activity', string='Activities', + help="List of activity ids") + attachment_id = fields.Many2one('ir.attachment', ondelete='restrict', + help="Attached item") + content_url = fields.Char(string='Content Url', + help='content url') + content_type = fields.Selection( + selection=[('file', 'File'), ('url', 'Url')], + help="Document content type", string="Content type") + preview = fields.Char(string='Preview', help="Preview URL") + active = fields.Boolean(string='Active', default=True, + help="document is active or not") + deleted_date = fields.Date(string="Deleted Date", + help="Document auto delete date") + mimetype = fields.Char(string='Mime Type', help="Document mimetype") + description = fields.Text(string='Description', + help="Write the description here") + user_ids = fields.Many2many('res.users', string="User", + help="Document related users") + partner_id = fields.Many2one('res.partner', string="Partner", + help="Document related") + auto_delete = fields.Boolean(string='Auto Delete', default=False, + help="Document delete status") + days = fields.Integer(string='Days', help="auto delete in days") + trash = fields.Boolean(string='Trash', help="To specify deleted items") + delete_date = fields.Date(string='Date Delete', readonly=True, store=True) + file_url = fields.Char(string='File URL', help="""it store url while adding + an url document""") + size = fields.Char(string='Size', compute='_compute_size', + help='computed size of document') + is_locked = fields.Boolean(string="Lock document", + help="To make document lock", + default=False) + document_file_id = fields.Many2one('document.file', string='Document File', + help="Add document here") + google_drive_file_key = fields.Char(string='Google Drive file id', + help='id of file from google drive') + one_drive_file_key = fields.Char(string='One Drive file id', + help='id of file from one drive') + is_crm_install = fields.Boolean(string="is crm installed", + help="field to check crm installed or not") + is_project_install = fields.Boolean(string="is project installed", + help="field to check project installed " + "or not") + document_tag_id = fields.Many2one('document.tag', string='Tags') + + def auto_delete_doc(self): + """Function to delete document automatically using schedule action""" + self.search([ + ('auto_delete', '=', True), + ('delete_date', '<=', fields.Date.today())]).unlink() + + @api.depends('attachment_id') + def _compute_size(self): + """Function is used to fetch the file size of an attachment""" + for rec in self: + rec.size = str(rec.attachment_id.file_size / 1000) + ' Kb' + + def action_upload_document(self): + """Function it works while uploading a file, and it adds some basic + information about the file""" + # important to maintain extension and name as different + attachment_id = self.env['ir.attachment'].sudo().create({ + 'name': self.name, + 'datas': self.attachment, + 'res_model': 'document.file', + 'res_id': self.id, + 'public': True, + }) + self.sudo().write({ + 'name': self.name, + 'date': fields.Date.today(), + 'user_id': self.env.uid, + 'extension': self.name.split(".")[len(self.name.split(".")) - 1], + 'content_url': + f"/web/content/{attachment_id.id}/{self.name}", + 'mimetype': attachment_id.mimetype, + 'attachment_id': attachment_id.id, + 'brochure_url': attachment_id.local_url, + 'is_crm_install': True if self.env[ + 'ir.module.module'].sudo().search( + [('name', '=', 'crm')]).state == 'installed' else False, + 'is_project_install': True if self.env[ + 'ir.module.module'].sudo().search( + [('name', '=', 'project')]).state == 'installed' else False + }) + if self.env.context.get('active_model') == "request.document": + self.env['request.document'].search( + [('id', '=', self.env.context.get('active_id'))]).write({ + 'state': 'accepted' + }) + return { + 'type': 'ir.actions.client', + 'tag': 'reload' + } + + @api.model + def action_document_request_upload_document(self, *args): + """ + Upload a document and add basic information about the file to the + database.This function is used to upload a file and create a + corresponding database record with basic information about the + uploaded file, such as name, date, extension, and more. + :param args: A tuple containing a dictionary with information about + the uploaded file. + :type args: Tuple, + :return: None + """ + attachment_id = self.env['ir.attachment'].sudo().create({ + 'name': args[0]['file_name'], + 'datas': args[0]['file'], + 'res_model': 'document.file', + 'res_id': self.id, + 'public': True, + }) + base_url = (self.env['ir.config_parameter'].sudo(). + get_param('web.base.url')) + self.sudo().create({ + 'attachment': attachment_id.datas, + 'workspace_id': args[0]['workspace_id'] if args[0][ + 'workspace_id'] else 1, + 'name': args[0]['file_name'], + 'date': fields.Datetime.today().now(), + 'user_id': self.env.uid, + 'extension': args[0]['file_name'].split(".")[ + len(args[0]['file_name'].split(".")) - 1], + 'content_url': f"""{base_url}/web/content/{attachment_id.id}/ + {args[0]['file_name']}""", + 'mimetype': attachment_id.mimetype, + 'attachment_id': attachment_id.id, + 'is_crm_install': True if self.env[ + 'ir.module.module'].sudo().search( + [('name', '=', 'crm')]).state == 'installed' else False, + 'is_project_install': True if self.env[ + 'ir.module.module'].sudo().search( + [('name', '=', 'project')]).state == 'installed' else False + }) + + @api.model + def download_zip_function(self, document_selected): + """ + Download selected documents as a ZIP archive. + This function generates a URL to download the selected documents + as a ZIP archive. + :param document_selected: A list of document IDs to be included in + the ZIP archive. + :type document_selected: list + :return: Action to open url a for downloading the ZIP archive. + """ + url = "/get/document?param=" + str(document_selected) + return { + 'type': 'ir.actions.act_url', + 'url': url, + 'target': 'new', + } + + @api.model + def document_file_delete(self, doc_ids): + """ + Delete documents and move them to the document.trash. + This function is responsible for deleting documents and creating + corresponding records + in the 'document.trash' table to keep track of the deleted documents. + :param doc_ids: A list of document IDs to be deleted and moved + to the trash. + :type doc_ids: list + :return: None + """ + for docs in self.browse(doc_ids): + self.env['document.trash'].create({ + 'name': docs.name, + 'attachment': docs.attachment, + 'document_create_date': docs.date, + 'workspace_id': docs.workspace_id.id, + 'user_id': docs.user_id.id, + 'brochure_url': docs.brochure_url, + 'extension': docs.extension, + 'priority': docs.priority, + 'attachment_id': docs.attachment_id.id, + 'content_url': docs.content_url, + 'content_type': docs.content_type, + 'preview': docs.preview, + 'active': docs.active, + 'deleted_date': fields.Date.today(), + 'mimetype': docs.mimetype, + 'description': docs.description, + 'security': docs.security, + 'user_ids': docs.user_ids.ids, + 'partner_id': docs.partner_id.id, + 'days': docs.days, + 'file_url': docs.file_url, + }) + docs.unlink() + + @api.model + def document_file_archive(self, documents_selected): + """ + Archive documents and toggle their active status or delete date. + This function is used to archive documents. Depending on the current + state of + the documents, it either toggles the 'active' status to mark them as + archived + or clears the 'delete_date' and sets them as active again. + :param documents_selected: document ID to be archived. + :type documents_selected: int + :return: None + """ + for docs in self.browse(documents_selected): + + if docs.active: + docs.active = False + elif docs.delete_date: + docs.delete_date = False + docs.active = True + else: + docs.active = True + + @api.model + def on_mail_document(self, doc_ids): + """ + Open a new email compose window to send selected documents + as attachments.This function opens a new email compose window with + selected documents as attachments.The 'doc_ids' parameter + contains a list of document IDs to be attached to the email. + :param doc_ids: A list of document IDs to be attached to the email. + :type doc_ids: list + :return: Action to open a new email compose window. + """ + default_composition_mode = self.env.context.get( + 'default_composition_mode', + self.env.context.get('composition_mode', 'comment')) + compose_ctx = dict( + default_composition_mode=default_composition_mode, + default_model='document.file', + default_res_ids=doc_ids, + mail_tz=self.env.user.tz, + default_attachment_ids=self.browse(doc_ids).mapped( + 'attachment_id').ids + ) + return { + 'type': 'ir.actions.act_window', + 'name': 'Sent document', + 'view_mode': 'form', + 'res_model': 'mail.compose.message', + 'views': [(False, 'form')], + 'view_id': False, + 'target': 'new', + 'context': compose_ctx, + } + + @api.model + def action_btn_create_task(self, doc): + """ + Create a task based on a document if the 'project' module is installed. + This function checks if the 'project' module is installed and, if so, + creates a task with the name of the document. It also associates the + document's attachment with the task. + :param doc: The document for which a task should be created. + :type doc: recordset + :return: True if the task is created, False if the 'project' module + is not installed. + :rtype: bool + """ + module_id = self.env['ir.module.module'].sudo().search( + [('name', '=', 'project')]) + if module_id.state == 'installed': + for rec in self.browse(doc): + task_id = self.env['project.task'].create({ + 'name': rec['name'] + }) + rec.attachment_id.res_model = 'project.task' + rec.attachment_id.res_id = task_id + return True + return False + + @api.model + def action_btn_create_lead(self, doc): + """ + Create a CRM lead based on a document if the 'crm' module is installed. + This function checks if the 'crm' module is installed and, if so, + creates a lead with the name of the document. It also associates + the document's attachment with the lead. + :param doc: The document for which a CRM lead should be created. + :type doc: recordset + :return: True if the lead is created, False if the 'crm' module is + not installed. + :rtype: bool + """ + module_id = self.env['ir.module.module'].sudo().search( + [('name', '=', 'crm')]) + if module_id.state == 'installed': + for rec in self.browse(doc): + lead_id = self.env['crm.lead'].create({ + 'name': rec['name'] + }) + rec.attachment_id.res_model = 'crm.lead' + rec.attachment_id.res_id = lead_id + return True + return False + + @api.model + def delete_doc(self): + """ + Delete documents from the trash based on the configured + retention period.This function deletes documents from the + 'document.trash' table based on the configured retention period + specified in the 'enhanced_document_management.trash' parameter.It checks + the 'deleted_date' of each document in the trash and deletes those + that have reached or exceeded the retention + period. + :return: None + """ + limit = self.env['ir.config_parameter'].sudo().get_param( + 'enhanced_document_management.trash') + for rec in self.env['document.trash'].search( + [('deleted_date', '!=', False)]): + delta = fields.Date.today() - rec.deleted_date + if delta.days == limit: + rec.unlink() + + def click_share(self): + """ + Share a single document by generating a shareable URL or requesting a + password for locked documents. + This function allows users to share a single document. + If the document is not locked,it generates a shareable URL using the + 'document.share' model. If the document is locked, + it opens a window to prompt the user to enter a password for access. + :return: Shareable URL or action to enter a password. + """ + document_id = self.env.context.get('document_id', False) + if not self.browse(document_id).is_locked: + return self.env['document.share'].create_url([document_id]) + else: + return { + 'type': 'ir.actions.act_window', + 'name': 'Please Enter Password', + 'res_model': 'document.lock', + 'view_mode': 'form', + 'view_id': self.env.ref( + 'enhanced_document_management.document_lock_share_view_form').id, + 'target': 'new', + 'context': { + 'default_document_file_id': document_id, + } + } + + def click_create_lead(self): + """ + Create a CRM lead from the document's dropdown menu. + This method allows users to create a CRM lead from a document if the + document is not locked. + If the document is locked, it prompts the user to enter a password. + :return: Action to create a lead or a notification to install the CRM + module. + """ + document_id = self.env.context.get('document_id', False) + if not self.browse(document_id).is_locked: + result = self.action_btn_create_lead(document_id) + if not result: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "Install CRM Module to use this function", + 'type': 'info', + 'sticky': False, + } + } + else: + return { + 'type': 'ir.actions.act_window', + 'name': 'Please Enter Password', + 'res_model': 'document.lock', + 'view_mode': 'form', + 'target': 'new', + 'view_id': self.env.ref( + 'enhanced_document_management.document_lock_lead_view_form').id, + 'context': { + 'default_document_file_id': document_id, + } + } + + def click_create_task(self): + """ + Create a task from the document's dropdown menu. + This method allows users to create a task from a document if + the document is not locked. + If the document is locked, it prompts the user to enter a password. + :return: Action to create a task or a notification to install the + Project module. + """ + document_id = self.env.context.get('document_id', False) + if not self.browse(document_id).is_locked: + result = self.action_btn_create_task(document_id) + if not result: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "Install Project Module to use this " + "function", + 'type': 'info', + 'sticky': False, + } + } + else: + return { + 'type': 'ir.actions.act_window', + 'name': 'Please Enter Password', + 'res_model': 'document.lock', + 'view_mode': 'form', + 'target': 'new', + 'view_id': self.env.ref( + 'enhanced_document_management.document_lock_task_view_form').id, + 'context': { + 'default_document_file_id': document_id, + } + } + + def click_create_mail(self): + """ + Create an email from the document's dropdown menu. + This method allows users to create an email from a document if the + document is not locked. + If the document is locked, it prompts the user to enter a password. + :return: Action to create an email or a window to enter a password. + """ + document_id = self.env.context.get('document_id', False) + if not self.browse(document_id).is_locked: + return self.on_mail_document([document_id]) + else: + return { + 'type': 'ir.actions.act_window', + 'name': 'Please Enter Password', + 'res_model': 'document.lock', + 'view_mode': 'form', + 'target': 'new', + 'view_id': self.env.ref( + 'enhanced_document_management.document_lock_mail_view_form').id, + 'context': { + 'default_document_file_id': document_id, + } + } + + def click_copy_move(self): + """ + Copy or move a document from one workspace to another using the + document's dropdown.His method allows users with appropriate + permissions to copy or move a document from one workspace to another. + It checks if the document is locked and if the user has the + 'enhanced_document_management.view_all_document' group. If the user has permission + and the document is not locked, it opens a window for copying the + document. If the document is locked, it prompts the user to enter a + password. If the user does not have + permission, it displays a notification. + :return: Action to copy or move a document, or a notification for + permission denied. + """ + document_id = self.env.context.get('document_id', False) + user_group = self.env.user.has_group( + 'enhanced_document_management.view_all_document') + if user_group: + if not self.browse(document_id).is_locked: + return { + 'type': 'ir.actions.act_window', + 'name': 'copy', + 'res_model': 'work.space', + 'view_mode': 'form', + 'target': 'new', + 'views': [[False, 'form']], + 'context': { + 'default_doc_ids': [document_id] + } + } + else: + return { + 'type': 'ir.actions.act_window', + 'name': 'Please Enter Password', + 'res_model': 'document.lock', + 'view_mode': 'form', + 'target': 'new', + 'view_id': self.env.ref( + 'enhanced_document_management.document_lock_copy_view_form').id, + 'context': { + 'default_document_file_id': document_id, + } + } + else: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "You don't have permission to " + "perform this action", + 'type': 'danger', + 'sticky': False, + } + } + + def click_document_archive(self): + """Archive a document from the document's dropdown menu. + This method allows users to archive a document if the document is not + locked. + If the document is locked, it prompts the user to enter a password. + :return: None or action to enter a password. + """ + document_id = self.env.context.get('document_id', False) + if not self.browse(document_id).is_locked: + self.document_file_archive(document_id) + else: + return { + 'type': 'ir.actions.act_window', + 'name': 'Please Enter Password', + 'res_model': 'document.lock', + 'view_mode': 'form', + 'target': 'new', + 'view_id': self.env.ref( + 'enhanced_document_management.document_lock_archive_view_form').id, + 'context': { + 'default_document_file_id': document_id, + } + } + + def click_document_delete(self): + """ + Delete a document from the document's dropdown menu. + + This method allows users to delete a document if they have the + 'enhanced_document_management.view_all_document' group. It opens a window for + users to choose between moving the document to the trash or + permanently deleting it. + :return: Action to choose between moving to trash or permanent + deletion. + """ + document_id = self.env.context.get('document_id', False) + user_group = self.env.user.has_group( + 'enhanced_document_management.view_all_document') + if user_group: + return { + 'type': 'ir.actions.act_window', + 'name': 'Choose One', + 'res_model': 'document.delete.trash', + 'view_mode': 'form', + 'target': 'new', + 'view_id': self.env.ref( + 'enhanced_document_management.document_delete_trash_view_form').id, + 'context': { + 'default_document_file_id': document_id, + } + } + + def click_document_lock(self): + """ + Lock a document from the document's dropdown menu. + This method allows users to lock a document by opening a window where + they can set a password for the document. Once locked, + the document can only be accessed with the provided password. + :return: Action to set a password and lock the document. + """ + document_id = self.env.context.get('document_id', False) + document = self.env.ref('enhanced_document_management.document_lock_view_form') + return { + 'type': 'ir.actions.act_window', + 'name': 'Lock', + 'res_model': 'document.lock', + 'view_mode': 'form', + 'view_id': document.id, + 'target': 'new', + 'context': { + 'default_document_file_id': document_id, + } + } + + def click_document_unlock(self): + """ + Unlock a document from the document's dropdown menu. + This method allows users to unlock a locked document by opening a + window where they can enter + the document's password for access. + :return: Action to enter the document's password and unlock it. + """ + document_id = self.env.context.get('document_id', False) + return { + 'type': 'ir.actions.act_window', + 'name': 'Unlock', + 'res_model': 'document.lock', + 'view_mode': 'form', + 'target': 'new', + 'view_id': self.env.ref( + 'enhanced_document_management.document_lock_unlock_view_form').id, + 'context': { + 'default_document_file_id': document_id, + } + } + + @api.model + def get_documents_list(self, *args): + """Get a list of documents to open the document viewer. + This method retrieves information about a specific document and a + list of other documents that can be viewed in the document viewer. + It provides details such as the document's name, download URL, + extension, and more. It also checks if a document is viewable based + on its extension and whether it's locked. + :param args: A list of arguments, with the first argument being the + document ID to retrieve. + :type args: list + :return: Information about the specified document and a list of other + viewable documents. + :rtype: tuple""" + document = self.browse(int(args[0])) + attachment = ({ + 'defaultSource': document.brochure_url, + 'displayName': document.name, + 'downloadUrl': document.brochure_url, + 'extension': document.extension, + 'filename': document.name, + 'id': document.id, + 'originThreadLocalId': 'document.file,' + str( + document.id), + 'uid': str(document.create_uid.id), + 'isPdf': True if document.extension == 'pdf' else False, + 'isImage': True if document.mimetype == 'image/jpeg' else False, + 'isViewable': True if not document.is_locked and ( + document.mimetype == 'image/jpeg' or document.extension == + 'pdf') else False, + 'mimetype': document.mimetype, + 'urlRoute': "/web/image/" + str(document.attachment_id.id), + }) + attachment_list = [{ + 'defaultSource': record.brochure_url, + 'displayName': str(record.name), + 'downloadUrl': record.brochure_url, + 'extension': record.extension, + 'filename': record.name, + 'id': record.id, + 'originThreadLocalId': 'document.file,' + str( + record.id), + 'uid': str(record.create_uid.id), + 'isPdf': True if record.extension == 'pdf' else False, + 'isImage': True if record.mimetype == 'image/jpeg' else False, + 'isViewable': True if not record.is_locked and ( + record.mimetype == 'image/jpeg' or record.extension == + 'pdf') else False, + 'mimetype': record.mimetype, + 'urlRoute': "/web/image/" + str(record.attachment_id.id), + } for record in self.search([]) if record.extension == 'pdf' or + record.mimetype == 'image/jpeg'] + return attachment, attachment_list + + @api.model + def get_document_count(self, *args): + """ + Get the count of documents in a workspace and all documents. + This method counts the number of documents in a specific workspace + and the total number of documents across all workspaces. + :param args: A list of arguments, with the first argument being the + workspace ID. + :type args: list + :return: A tuple containing the count of documents in the specified + workspace and the count of all documents. + :rtype: tuple + """ + document_count_in_workspace = self.search_count( + [('workspace_id', '=', args[0])]) + all_document_count = self.sudo().search_count([]) + return document_count_in_workspace, all_document_count diff --git a/enhanced_document_management/models/document_lock.py b/enhanced_document_management/models/document_lock.py new file mode 100644 index 000000000..bfc2f6b7e --- /dev/null +++ b/enhanced_document_management/models/document_lock.py @@ -0,0 +1,424 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Mruthul Raj() +# +# 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 hashlib +from odoo import fields, models + + +class DocumentLock(models.Model): + """Model used to lock documents and do other functions + for locked documents""" + _name = 'document.lock' + _description = 'Document Lock' + _rec_name = 'document_file_id' + + document_file_id = fields.Many2one('document.file', string='document file', + help="id of document file") + password = fields.Char(string='password', required=True, + help="Password to lock document") + is_lock = fields.Boolean(string="Lock", + help='field for document is lock or not') + + def lock_doc(self): + """ + Lock a document. + This method allows authorized users to lock a document by setting a + password for it.It checks if the user has the + 'enhanced_document_management.view_all_document' group. If the user has permission, + it hashes the provided password and sets the 'is_lock' flag to True for + both the document and its associated file. + :return: None or a notification if the user does not have permission. + """ + if self.env.user.has_group('enhanced_document_management.view_all_document'): + self.write({ + 'password': hashlib.sha256(self.password.encode()).hexdigest(), + 'is_lock': True + + }) + self.document_file_id.is_locked = True + else: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "You dont have permission to" + " lock the documents", + 'type': 'info', + 'sticky': False, + } + } + + def unlock_doc(self): + """ + Unlock a document. + This method allows users to unlock a locked document by verifying the + provided password.It hashes the provided password and checks if it + matches the stored password for the document. If the passwords match, + the document is unlocked, and the associated lock records are removed. + If the passwords do not match, an incorrect password + notification is displayed. + :return: None or a notification if the password is incorrect. + :rtype: None or dict + """ + password = hashlib.sha256(self.password.encode()).hexdigest() + self.write({ + 'password': hashlib.sha256(self.password.encode()).hexdigest() + }) + if password == self.env['document.lock'].search( + [('document_file_id', '=', self.document_file_id.id), + ('is_lock', '=', True)], + order="id desc").password: + self.document_file_id.is_locked = False + self.env['document.lock'].search( + [('document_file_id', '=', self.document_file_id.id), + ('is_lock', '=', True)], + order="id desc").unlink() + else: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "Incorrect Password", + 'type': 'danger', + 'sticky': False, + } + } + + def document_share(self): + """ + Share a locked document. + This method allows users to share a locked document by verifying the + provided password.It hashes the provided password and checks if it + matches the stored password for the document. If the passwords match, + the document is shared using the 'document.share' model. + If the passwords do not match, an incorrect password notification is + displayed. + :return: URL for sharing the document or a notification + if the password is incorrect. + """ + password = hashlib.sha256(self.password.encode()).hexdigest() + self.write({ + 'password': hashlib.sha256(self.password.encode()).hexdigest() + }) + if password == self.env['document.lock'].search( + [('document_file_id', '=', self.document_file_id.id), + ('is_lock', '=', True)], + order="id desc").password: + return self.env['document.share'].create_url( + [self.document_file_id.id]) + else: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "Incorrect Password", + 'type': 'danger', + 'sticky': False, + } + } + + def document_download(self): + """ + Download a locked document. + This method allows users to download a locked document by verifying + the provided password.It hashes the provided password and checks + if it matches the stored password for the document. If the passwords + match, the document is downloaded using 'ir.actions.act_url'. + If the passwords do not match, an incorrect password notification is + displayed. + :return: Action to download the document or a notification + if the password is incorrect. + """ + password = hashlib.sha256(self.password.encode()).hexdigest() + self.write({ + 'password': hashlib.sha256(self.password.encode()).hexdigest() + }) + if password == self.env['document.lock'].search( + [('document_file_id', '=', self.document_file_id.id), + ('is_lock', '=', True)], + order="id desc").password: + document_url = self.env.context.get('document_url', False) + return { + 'type': 'ir.actions.act_url', + 'url': document_url + '?download=true', + } + else: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "Incorrect Password", + 'type': 'danger', + 'sticky': False, + } + } + + def document_create_lead(self): + """ + Create a lead for a locked document. + This method allows users to create a lead for a locked document by + verifying the provided password. + It hashes the provided password and checks if it matches the stored + password for the document. If the passwords match, it calls the + 'action_btn_create_lead' method of the associated document file + to create a lead. If the passwords do not match, an incorrect + password notification is displayed. + :return: Action to create a lead, a notification for CRM module + installation,or a notification for an incorrect password. + """ + password = hashlib.sha256(self.password.encode()).hexdigest() + self.write({ + 'password': hashlib.sha256(self.password.encode()).hexdigest() + }) + if password == self.env['document.lock'].search( + [('document_file_id', '=', self.document_file_id.id), + ('is_lock', '=', True)], + order="id desc").password: + result = self.document_file_id.action_btn_create_lead( + self.document_file_id.id) + if not result: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "Install CRM Module to use this function", + 'type': 'info', + 'sticky': False, + } + } + else: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "Incorrect Password", + 'type': 'danger', + 'sticky': False, + } + } + + def document_create_task(self): + """ + Create a task for a locked document. + This method allows users to create a task for a locked document by + verifying the provided password.It hashes the provided password and + checks if it matches the stored password for the document. If the + passwords match, it calls the 'action_btn_create_task' method of the + associated document file to create a task. If the passwords do not + match, an incorrect password notification is displayed. + :return: Action to create a task, a notification for Project module + installation,or a notification for an incorrect password. + """ + password = hashlib.sha256(self.password.encode()).hexdigest() + self.write({ + 'password': hashlib.sha256(self.password.encode()).hexdigest() + }) + if password == self.env['document.lock'].search( + [('document_file_id', '=', self.document_file_id.id), + ('is_lock', '=', True)], + order="id desc").password: + result = self.document_file_id.action_btn_create_task( + self.document_file_id.id) + if not result: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "Install Project Module to use this " + "function", + 'type': 'info', + 'sticky': False, + } + } + else: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "Incorrect Password", + 'type': 'danger', + 'sticky': False, + } + } + + def document_lock_mail(self): + """ + Create mail for locked documents.This method allows users to create a + mail for a locked document by verifying the provided password.It + hashes the provided password and checks if it matches the stored + password for the document. If the passwords match, it calls the + 'on_mail_document' method of the associated document file to create a + mail. If the passwords do not match, an incorrect password notification + is displayed. + :return: Action to create a mail or a notification for an incorrect + password. + """ + password = hashlib.sha256(self.password.encode()).hexdigest() + self.write({ + 'password': hashlib.sha256(self.password.encode()).hexdigest() + }) + if password == self.env['document.lock'].search( + [('document_file_id', '=', self.document_file_id.id), + ('is_lock', '=', True)], + order="id desc").password: + return self.document_file_id.on_mail_document( + [self.document_file_id.id]) + else: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "Incorrect Password", + 'type': 'danger', + 'sticky': False, + } + } + + def document_copy_mail(self): + """ + Copy or move locked documents. + This method allows users to copy or move locked documents from one + workspace to another by verifying the provided password. It hashes the + provided password and checks if it matches the stored password + for the document. If the passwords match, it opens a window to copy or + move the document. If the passwords do not match, an incorrect + password notification is displayed. + :return: Action to copy or move documents or a notification for an + incorrect password. + """ + password = hashlib.sha256(self.password.encode()).hexdigest() + self.write({ + 'password': hashlib.sha256(self.password.encode()).hexdigest() + }) + if password == self.env['document.lock'].search( + [('document_file_id', '=', self.document_file_id.id), + ('is_lock', '=', True)], + order="id desc").password: + return { + 'type': 'ir.actions.act_window', + 'name': 'copy', + 'res_model': 'work.space', + 'view_mode': 'form', + 'target': 'new', + 'views': [[False, 'form']], + 'context': { + 'default_doc_ids': [self.document_file_id.id] + } + } + else: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "Incorrect Password", + 'type': 'danger', + 'sticky': False, + } + } + + def document_lock_archive(self): + """ + Archive locked documents after verifying the password. + This method archives locked documents by checking the provided password + against the stored password.If the passwords match, it archives the + document using the 'document_file_archive' method of the associated + document file. If not, it displays an incorrect password notification. + :return: Archive action or incorrect password notification. + """ + password = hashlib.sha256(self.password.encode()).hexdigest() + self.write({ + 'password': hashlib.sha256(self.password.encode()).hexdigest() + }) + if password == self.env['document.lock'].search( + [('document_file_id', '=', self.document_file_id.id), + ('is_lock', '=', True)], + order="id desc").password: + self.document_file_id.document_file_archive( + self.document_file_id.id) + else: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "Incorrect Password", + 'type': 'danger', + 'sticky': False, + } + } + + def document_move_to_trash(self): + """ + Move locked documents to the trash folder after password verification. + This method moves locked documents to the trash folder by verifying the + provided password against the stored password. If the passwords match, + it performs the document deletion using the 'document_file_delete' + method of the associated document file. If not, it displays an + incorrect password notification. + :return: Trash action or incorrect password notification. + """ + password = hashlib.sha256(self.password.encode()).hexdigest() + self.write({ + 'password': hashlib.sha256(self.password.encode()).hexdigest() + }) + if password == self.env['document.lock'].search( + [('document_file_id', '=', self.document_file_id.id), + ('is_lock', '=', True)], + order="id desc").password: + self.document_file_id.document_file_delete( + self.document_file_id.id) + else: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "Incorrect Password", + 'type': 'danger', + 'sticky': False, + } + } + + def document_delete_permanent(self): + """ + Permanently delete locked documents after password verification. + This method permanently deletes locked documents by verifying the + provided password against the stored password. If the passwords match, + it deletes the document using the 'unlink' method of the associated + document file. If not, it displays an incorrect password notification. + :return: Deletion action or incorrect password notification. + """ + password = hashlib.sha256(self.password.encode()).hexdigest() + self.write({ + 'password': hashlib.sha256(self.password.encode()).hexdigest() + }) + if password == self.search( + [('document_file_id', '=', self.document_file_id.id), + ('is_lock', '=', True)], order="id desc").password: + self.document_file_id.unlink() + else: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "Incorrect Password", + 'type': 'danger', + 'sticky': False, + } + } diff --git a/enhanced_document_management/models/document_request_template.py b/enhanced_document_management/models/document_request_template.py new file mode 100644 index 000000000..7ed93188b --- /dev/null +++ b/enhanced_document_management/models/document_request_template.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Mruthul Raj() +# +# 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, fields, models + + +class DocumentRequestTemplate(models.Model): + """Model representing document request templates.""" + _name = "document.request.template" + _description = "Document request template" + _inherit = ['mail.thread', 'mail.activity.mixin'] + + name = fields.Char(string="Template name", + required=True, + help="name of template") + user_ids = fields.Many2many('res.users', string="User", help="Choose User", + compute='_get_managers') + company_id = fields.Many2one('res.company', string='Company', + help='choose company',default=lambda self: self.env.company) + manager_id = fields.Many2one("res.users", string="Managers", + help="Choose Manager", required=True) + stamp = fields.Image(string="Stamp", max_width=170, max_height=170, + help="Add your stamp") + template = fields.Html(string="Template", help="Add the template here") + + @api.depends('name') + def _get_managers(self): + """function to get user's with document request template + manager group""" + self.user_ids = self.manager_id.search([]).filtered( + lambda managers: managers.has_group( + 'enhanced_document_management.view_all_document')) diff --git a/enhanced_document_management/models/document_tag.py b/enhanced_document_management/models/document_tag.py new file mode 100644 index 000000000..7c15625ca --- /dev/null +++ b/enhanced_document_management/models/document_tag.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Mruthul Raj() +# +# 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 DocumentTag(models.Model): + _name = "document.tag" + _description ="Document tag" + + name = fields.Char(string="name",required=True) + + _sql_constraints = [ + ('name_uniq', 'unique (name)', "Tag name already exists!"), + ] diff --git a/enhanced_document_management/models/document_template_request.py b/enhanced_document_management/models/document_template_request.py new file mode 100644 index 000000000..dbbe7b740 --- /dev/null +++ b/enhanced_document_management/models/document_template_request.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Mruthul Raj() +# +# 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, fields, models, _ + + +class DocumentTemplateRequest(models.Model): + """Model representing document requests.""" + _name = "document.template.request" + _description = "Document Request" + _rec_name = "display_name" + _inherit = 'mail.thread' + + display_name = fields.Char(string="Display Name", + help='display name of document request',require=True ) + document_id = fields.Many2one('document.request.template', + help="Selected document template", + string="Document", required=True) + user_id = fields.Many2one('res.users', + string="User", help="User creating the request", + default=lambda self: self.env.user) + manager_id = fields.Many2one('res.users', string="Manager", + help="Manager of the document", + related="document_id.manager_id") + requested_date = fields.Date(string="Requested Date", + help="Date when the request was made", + default=fields.Date.today()) + template = fields.Html(string="Template", + help="HTML template content") + stamp = fields.Image(string="Image", + help="Image associated with the document") + create_user_avatar = fields.Binary(help="Users image", + string="Author", + related='create_uid.image_1920') + state = fields.Selection( + [('new', 'New'), + ('document_approval', 'Document Approval'), + ('approved', 'Approved')], default='new', help="Avatar of the author", + tracking=True, string="State") + employee_id = fields.Many2one('hr.employee', string="Employee") + + @api.model_create_multi + def create(self, vals_list): + """Super the create function in document.template.request to generate + sequences""" + for vals in vals_list: + if not vals.get('display_name') or vals['display_name'] == _( + 'New'): + vals['display_name'] = self.env['ir.sequence'].next_by_code( + 'document.template.request') or _('New') + return super().create(vals_list) + + def action_sent_document_approval(self): + """Function to change the state to 'document_approval'.""" + self.write({'state': 'document_approval'}) + + def action_approve(self): + """Function to change the state to 'approved'.""" + self.write({'state': 'approved'}) + + def action_preview_document(self): + """Function to preview the document + Return: + client action to view preview of document + """ + return { + 'type': 'ir.actions.client', + 'tag': 'preview_document', + 'params': { + 'body_html': self.template, + 'stamp': self.stamp + } + } + + def action_download_document(self): + """method to download the document""" + data = {'template': self.template, + 'image': self.stamp} + return self.env.ref( + 'enhanced_document_management.action_download_doc').report_action( + None, + data=data) + + def _get_report_base_filename(self): + """Method to return document's name""" + self.ensure_one() + return f'{self.document_id.name}' + + @api.onchange('document_id') + def _onchange_document(self): + """Onchange function to set the template and stamp based on the + document template selected""" + if self.document_id: + self.template = self.document_id.template + self.stamp = self.document_id.stamp diff --git a/enhanced_document_management/models/document_trash.py b/enhanced_document_management/models/document_trash.py new file mode 100644 index 000000000..4af7997dc --- /dev/null +++ b/enhanced_document_management/models/document_trash.py @@ -0,0 +1,191 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-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, fields, models, _ + + +class DocumentTrash(models.Model): + """Module to store deleted documents for a specific time, + then it automatically""" + _name = "document.trash" + _description = "Document Trash" + + name = fields.Char(string="Name", help="Document name") + attachment = fields.Binary(string="File", readonly=True, + help="Document data") + document_create_date = fields.Datetime(string="Date", + help="Document create date") + workspace_id = fields.Many2one( + "document.workspace", string="Workspace", required=True, + help="workspace name" + ) + user_id = fields.Many2one( + "res.users", + string="Owner", + default=lambda self: self.env.user, + help="""owner name, if the document belongs to a specific user""", + ) + brochure_url = fields.Char(string="URL", store=True, + help="Document sharable URL") + extension = fields.Char(string="Extension", + help="helps to determine the file type") + priority = fields.Selection( + selection=[("0", "None"), ("1", "Favorite")], + string="Priority", + help="Favorite button", + ) + attachment_id = fields.Many2one( + "ir.attachment", + string="Attachment", + help="Used to access datas without search function", + ) + content_url = fields.Char( + string="Content Url", help="It store the URL for url type documents" + ) + content_type = fields.Selection( + [("file", "File"), ("url", "Url")], + help="Document content type", + string="Content type", + ) + preview = fields.Char( + string="Preview", help="Used to show a preview for URL file type" + ) + active = fields.Boolean( + string="Active", default=True, help="It specify archived file" + ) + days = fields.Integer(string="Days", help="auto delete in days") + deleted_date = fields.Date(string="Deleted Date", help="File deleted date") + mimetype = fields.Char(string="Mime Type", help="Document mimetype") + description = fields.Text(string="Description", help="Short description") + security = fields.Selection( + string="Security", + selection=[ + ("private", "Private"), + ("managers_and_owner", "Managers & Owner"), + ("specific_users", "Specific Users"), + ], + default="managers_and_owner", + help="""Privet : only the uploaded user can view + Managers & Owner : Document shared with Managers """, + ) + user_ids = fields.Many2many( + "res.users", help="Can access the documents", string="User Access" + ) + partner_id = fields.Many2one( + "res.partner", help="Document related partner name", + string="Related Partner" + ) + auto_delete = fields.Boolean( + string="Auto Delete", default=False, help="Document delete status" + ) + delete_date = fields.Date( + string="Date Delete", + readonly=True, + help="Used to calculate file remove date from trash", + ) + file_url = fields.Char( + string="File URL", help="""it store url while adding an url document""" + ) + size = fields.Char(string="Size", help="it store size of the document") + company_id = fields.Many2one( + related='workspace_id.company_id', string='Company', + help="Company Name") + + def delete_doc(self): + """Function to delete all the documents after the trash date""" + trash_limit = ( + self.env["ir.config_parameter"] + .sudo() + .get_param("document_management.trash") + ) + if trash_limit: + for rec in self.search([]): + if fields.Date.today() == fields.Date.add( + rec.deleted_date, days=int(trash_limit) + ): + rec.unlink() + + def action_restore_document(self): + """ + Restore a previously deleted document from the trash. + This function restores a deleted document by creating a new record in + the 'document.file' model with the same attributes as the deleted + document. It then unlinks the deleted document and returns to the + 'Trash' view. + :return: Window action to view the 'Trash' or restore the document. + :rtype: dict + """ + doc_id = self.env['document.file'].create({ + 'name': self.name, + 'extension': self.extension, + 'attachment': self.attachment, + 'date': fields.Date.today(), + 'workspace_id': self.workspace_id.id, + 'user_id': self.user_id.id, + 'content_type': self.content_type, + 'brochure_url': self.brochure_url, + 'active': self.active, + 'mimetype': self.mimetype, + 'description': self.description, + 'content_url': self.content_url, + 'user_ids': self.user_ids.ids, + 'partner_id': self.partner_id, + 'days': self.days, + }) + attachment_id = self.env['ir.attachment'].sudo().create( + {'name': self.name, + 'datas': self.attachment, + 'res_model': 'document.file', + 'res_id': self.id, + } + ) + doc_id.attachment_id = attachment_id.id + self.unlink() + return { + 'name': _('Trash'), + 'target': 'main', + 'view_mode': 'tree,form', + 'res_model': 'document.trash', + 'type': 'ir.actions.act_window', + } + + @api.onchange('days') + def _onchange_days(self): + """ + Set the delete date for a record based on the specified number of days. + This function calculates and sets the delete date for a record by + adding the specified number of days to the current date. + The record will be automatically deleted on the calculated delete date. + :return: None + """ + self.write({'delete_date': fields.Date.add(fields.Date.today(), + days=self.days)}) + + def auto_delete_doc(self): + """ + Automatically delete documents based on a schedule action. + This function searches for documents marked for automatic deletion + (auto_delete=True) and with a delete date less than or equal to the + current date. It then deletes these documents from the system. + :return: None + """ + self.search([('auto_delete', '=', True), + ('delete_date', '<=', fields.Date.today())]).unlink() diff --git a/enhanced_document_management/models/document_workspace.py b/enhanced_document_management/models/document_workspace.py new file mode 100644 index 000000000..fb6fa485c --- /dev/null +++ b/enhanced_document_management/models/document_workspace.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Mruthul Raj() +# +# 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 DocumentWorkspace(models.Model): + """ Model to store document workspace """ + _name = 'document.workspace' + _description = 'Document Workspace' + _inherit = 'mail.thread' + + name = fields.Char(string='Name', required=True, + help="Name of the WorkSpace.") + display_name = fields.Char(string='Workspace', + compute='_compute_display_name', + help="Name of the workSpace.") + company_id = fields.Many2one('res.company', string='Company', + help="WorkSpace belongs to this company",default=lambda self: self.env.company) + description = fields.Text(string='Description', + help="Description about the workSpace") + document_count = fields.Integer(compute='_compute_document_count', + string='Document Count', + help="Number of documents uploaded " + "under this workSpace") + privacy_visibility = fields.Selection([ + ('followers', 'Invited internal users (private)'), + ('employees', 'All internal users'), ], + string='Visibility', required=True, + default='employees', + help='- Invited internal users: when following a workspace, internal ' + 'users will get access to all of its documents without ' + 'distinction \n\n' + 'All internal users: all internal users can access the ' + 'workspace and all of its documents without distinction.\n\n') + google_drive_folder_id = fields.Char( + string='Google drive folder id', + help='Id of workspace in google drive if created', + copy=False, readonly=True) + onedrive_folder_id = fields.Char( + string='One drive folder id', + help='Id of workspace in one drive if created', + copy=False, readonly=True) + + def button_view_document(self): + """ + Open the Kanban view of associated documents. + This function opens the Kanban view displaying documents associated + with the current workspace. + :return: Action to open the Kanban view + """ + return { + 'type': 'ir.actions.act_window', + 'res_model': 'document.file', + 'name': self.name, + 'view_mode': 'kanban,form', + 'view_type': 'form', + 'target': 'current', + 'domain': [('workspace_id', '=', self.id)] + } + + def _compute_document_count(self): + """ + Calculate the number of documents associated with this workspace. + This function computes the count of documents that belong to the + current workspace. + """ + for record in self: + record.document_count = self.env['document.file'].search_count( + [('workspace_id', '=', self.id)]) diff --git a/enhanced_document_management/models/request_document.py b/enhanced_document_management/models/request_document.py new file mode 100644 index 000000000..3dba45edf --- /dev/null +++ b/enhanced_document_management/models/request_document.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Mruthul Raj() +# +# 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, fields, models + + +class RequestDocument(models.Model): + """ module to store document requests """ + _name = 'request.document' + _description = 'Request document from user' + _rec_name = 'display_name' + _inherit = ['mail.thread', 'mail.activity.mixin'] + + display_name = fields.Char(string="Display Name", + help='display name document request',required=True) + user_id = fields.Many2one('res.users', string='User', help="Choose User", + required=True) + requested_by_id = fields.Many2one('res.users', + help="User who created request", + string="Requested User", + default=lambda self: self.env.user, + readonly=True) + needed_doc = fields.Text(string='Document Needed', required=True, + help="Document needed by requestor") + workspace_id = fields.Many2one('document.workspace', + string='Workspace', + required=True, + help="Select the workspace associated with" + " this item.") + manager_id = fields.Many2one('res.users', string='Manager', + help="Select Manager") + workspace = fields.Char(related='workspace_id.name', + string='Workspace Name', + help='Workspace name') + reject_reason = fields.Text(string='Reason', help="Reason for rejection") + state = fields.Selection(selection=[('draft', 'Draft'), + ('requested', 'Requested'), + ('accepted', 'Accepted'), + ('rejected', 'Rejected')], + default='draft', string="State", + help="Choose the current state of the item: Requested, Accepted, or " + "Rejected.") + + def action_send_document_request(self): + """ function to send document request through email """ + self.state = 'requested' + user_id = self.env['res.users'].browse(self.env.uid) + mail_content = f'Hello
{user_id.name} Requested Document
' \ + f'{self.needed_doc}' + main_content = { + 'subject': _('Document Request'), + 'body_html': mail_content, + 'email_to': self.user_id.partner_id.email, + } + self.env['mail.mail'].sudo().create(main_content).send() + + @api.model + def get_request(self): + """ + Function to fetch all requests for the currently logged-in user. + This function retrieves all requests related to the current user from + the 'request.document' model and formats + the data into a list of dictionaries containing relevant information + about each request. + Returns: + list of dict: A list of dictionaries containing information + about the requests. + """ + request_ids = self.env['request.document'].search( + [('user_id', '=', self.env.uid)]) + context = [{ + 'request_id': rec.id, + 'user_id': rec.user_id.name, + 'manager_id': rec.manager_id.name, + 'needed_doc': rec.needed_doc, + 'workspace': rec.workspace, + 'workspace_id': rec.workspace_id.id, + } for rec in request_ids] + return context + + @api.model + def get_wizard_view(self, view_id): + """ Method is used to get the wizard view xml id for wizard """ + return self.env.ref(view_id).id + + @api.model_create_multi + def create(self, vals_list): + """ Super the create function to generate sequences for document.request""" + for vals in vals_list: + if not vals.get('display_name') or vals['display_name'] == _('New'): + vals['display_name'] = self.env['ir.sequence'].next_by_code( + 'document.request') or _('New') + return super().create(vals_list) diff --git a/enhanced_document_management/models/res_config_settings.py b/enhanced_document_management/models/res_config_settings.py new file mode 100644 index 000000000..e8d3cccea --- /dev/null +++ b/enhanced_document_management/models/res_config_settings.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Mruthul Raj() +# +# 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, fields, models + + +class ResConfigSettings(models.TransientModel): + """ Inherited res.config.settings to add trash limit field""" + _inherit = 'res.config.settings' + + trash = fields.Integer( + string='Trash Limit', + default=30, + help="set the time limit for the deleted files", + config_parameter='enhanced_document_management.trash') + + @api.model_create_multi + def create(self, vals_list): + """Create method to set scheduled action's interval number for + Google Drive and OneDrive synchronization. + This method is used to create or update the interval number of + scheduled actions for Google Drive and OneDrive + synchronization based on the provided configuration values. + Args: + vals_list (list): List of dictionaries containing values to + create the configuration settings. + Returns: + RecordSet: The created configuration settings record.""" + self.is_crm_installed(vals_list) + self.is_project_installed(vals_list) + res = super(ResConfigSettings, self).create(vals_list) + return res + + def is_crm_installed(self, vals_list): + """ + Set 'is_crm_install' field for all 'document.file' records. + Args: + vals_list (list): List of dictionaries containing values. + Returns: + None + """ + records = self.env['document.file'].search([]) + is_crm_install = vals_list[0].get('module_crm') + for record in records: + record.write({'is_crm_install': is_crm_install}) + + def is_project_installed(self, vals_list): + """ + Set 'is_project_install' field for all 'document.file' records. + Args: + vals_list (list): List of dictionaries containing values. + Returns: + None + """ + records = self.env['document.file'].search([]) + is_project_install = vals_list[0].get('module_project') + for record in records: + record.write({'is_project_install': is_project_install}) diff --git a/enhanced_document_management/reports/document_download.xml b/enhanced_document_management/reports/document_download.xml new file mode 100644 index 000000000..ccc70d28a --- /dev/null +++ b/enhanced_document_management/reports/document_download.xml @@ -0,0 +1,26 @@ + + + + + Document Download + document.template.request + qweb-pdf + enhanced_document_management.document_download + enhanced_document_management.document_download + + + + diff --git a/enhanced_document_management/security/enhanced_document_management_groups.xml b/enhanced_document_management/security/enhanced_document_management_groups.xml new file mode 100755 index 000000000..75ad9b2af --- /dev/null +++ b/enhanced_document_management/security/enhanced_document_management_groups.xml @@ -0,0 +1,102 @@ + + + + Document Management + Access for Document module + 50 + + + + User + + + + + Manager + + + + + + + Document Manager + + ['|',('privacy_visibility', '=', + 'followers'), ('privacy_visibility', '=', 'employees')] + + + + + + + + + + + View visible Workspace + + ['|',('privacy_visibility', '=', + 'employees'),('message_partner_ids', 'in', [user.partner_id.id])] + + + + + + + + + + + Document File Visibility + + ['|',('workspace_id.privacy_visibility', + '=', 'employees'),('workspace_id.message_partner_ids', 'in', + [user.partner_id.id])] + + + + + + + + + + + View Own Request + + + ['|',('requested_by_id', '=', user.id), + ('user_id', '=', user.id)] + + + + + + + + + + + View All Request + + [(1, '=', 1)] + + + + + + + + + + + + + + Document template multi-company + + ['|', ('company_id', 'parent_of', company_ids), ('company_id', '=', False)] + + + \ No newline at end of file diff --git a/enhanced_document_management/security/enhanced_document_management_security.xml b/enhanced_document_management/security/enhanced_document_management_security.xml new file mode 100755 index 000000000..06e1d9a42 --- /dev/null +++ b/enhanced_document_management/security/enhanced_document_management_security.xml @@ -0,0 +1,113 @@ + + + + + Document Manager + + ['|', '|', ('security', '=', 'specific_users'), + ('security', '=', 'managers_and_owner'), '&', + ('security', '=', 'private'), + ('user_id', '=', user.id)] + + + + + + + + + + View Own Document + + ['|', '|', '|', '|', ('user_ids', 'in', user.id), '&', + ('security', '=', 'specific_users'), + ('user_ids', '=', False), '&', + ('security', '=', 'private'), + ('user_id', '=', user.id), '&', + ('security', '=', 'managers_and_owner'), + ('user_id', '=', user.id), '&', + ('user_ids', 'not in', user.id), + ('user_id', '=', user.id)] + + + + + + + + + + View Own Request + + + ['|',('requested_by', '=', user.id), + ('user_id', '=', user.id)] + + + + + + + + + + + View All Request + + [(1, '=', 1)] + + + + + + + + + + + Documents multy-company + + ['|',('company_id', '=',False),('company_id', 'in', company_ids)] + + + + + + + + + + Document workspace multy-company + + ['|',('company_id', '=',False),('company_id', 'in', company_ids)] + + + + + + + + + + Document trash multy-company + + ['|',('company_id', '=',False),('company_id', 'in', company_ids)] + + + + + + + + + + Document requests multy-company + + ['|',('company_id', '=',False),('company_id', 'in', company_ids)] + + + + + + + diff --git a/enhanced_document_management/security/ir.model.access.csv b/enhanced_document_management/security/ir.model.access.csv new file mode 100755 index 000000000..2ab9f9326 --- /dev/null +++ b/enhanced_document_management/security/ir.model.access.csv @@ -0,0 +1,26 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_document_file,access.document.file,model_document_file,view_all_document,1,1,1,1 +access_document_trash,access.document.trash,model_document_trash,view_all_document,1,1,1,1 +access_document_workspace,access.document.workspace,model_document_workspace,view_all_document,1,1,1,1 +access_document_share,access.document.share,model_document_share,view_all_document,1,1,1,1 +access_document_url,access.document.url,model_document_url,view_all_document,1,1,1,1 +access_work_space,access.work.space,model_work_space,view_all_document,1,1,1,1 +access_request_document,access.work.space,model_request_document,view_all_document,1,1,1,1 +access_document_lock,access.document.lock,model_document_lock,base.group_user,1,1,1,1 +access_document_delete_trash_manager,access.document.delete.trash,model_document_delete_trash,view_all_document,1,1,1,1 +access_document_file_portal,access.document.file.portal,model_document_file,base.group_portal,1,1,1,0 +access_document_file_user,access.document.file.user,model_document_file,view_own_document,1,1,1,0 +access_document_trash_user,access.document.trash.user,model_document_trash,view_own_document,1,1,1,0 +access_document_workspace_user,access.document.workspace.user,model_document_workspace,view_own_document,1,0,0,0 +access_document_workspace_portal,access.document.workspace.user.portal,model_document_workspace,base.group_portal,1,1,0,0 +access_document_share1,access.document.share,model_document_share,view_own_document,1,1,1,1 +access_document_url_user,access.document.url,model_document_url,view_own_document,1,1,1,1 +access_request_document1,access.work.space,model_request_document,view_own_document,1,1,1,1 +access_request_document_portal,access.work.space,model_request_document,base.group_portal,1,1,0,0 +access_document_delete_trash_user,access.document.delete.trash,model_document_delete_trash,view_own_document,1,1,1,1 +access_document_request_template,access.document.request.template,model_document_request_template,view_all_document,1,1,1,1 +access_document_request_template_user,access.document.request.template,model_document_request_template,view_own_document,1,1,0,0 +access_document_template_request,access.document.template.request,model_document_template_request,base.group_user,1,1,1,1 +access_document_request_reject,access.document.request.reject,model_document_request_reject,base.group_user,1,1,1,1 +access_document_request_accept,access.document.request.accept,model_document_request_accept,base.group_user,1,1,1,1 +access_document_tag,access.document.tag,model_document_tag,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/enhanced_document_management/static/description/assets/icons/arrows-repeat.svg b/enhanced_document_management/static/description/assets/icons/arrows-repeat.svg new file mode 100644 index 000000000..1d7efabc5 --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/arrows-repeat.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/banner-1.png b/enhanced_document_management/static/description/assets/icons/banner-1.png new file mode 100644 index 000000000..c180db172 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/banner-1.png differ diff --git a/enhanced_document_management/static/description/assets/icons/banner-2.svg b/enhanced_document_management/static/description/assets/icons/banner-2.svg new file mode 100644 index 000000000..e606d97d9 --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/banner-2.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/banner-bg.png b/enhanced_document_management/static/description/assets/icons/banner-bg.png new file mode 100644 index 000000000..a8238d3c0 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/banner-bg.png differ diff --git a/enhanced_document_management/static/description/assets/icons/banner-bg.svg b/enhanced_document_management/static/description/assets/icons/banner-bg.svg new file mode 100644 index 000000000..b1378103e --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/banner-bg.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/banner-call.svg b/enhanced_document_management/static/description/assets/icons/banner-call.svg new file mode 100644 index 000000000..96c687e81 --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/banner-call.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/banner-mail.svg b/enhanced_document_management/static/description/assets/icons/banner-mail.svg new file mode 100644 index 000000000..cbf0d158d --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/banner-mail.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/banner-pattern.svg b/enhanced_document_management/static/description/assets/icons/banner-pattern.svg new file mode 100644 index 000000000..9c1c7e101 --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/banner-pattern.svg @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/banner-promo.svg b/enhanced_document_management/static/description/assets/icons/banner-promo.svg new file mode 100644 index 000000000..d52791b11 --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/banner-promo.svg @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/brand-pair.svg b/enhanced_document_management/static/description/assets/icons/brand-pair.svg new file mode 100644 index 000000000..d8db7fc1e --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/brand-pair.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/check.png b/enhanced_document_management/static/description/assets/icons/check.png new file mode 100644 index 000000000..c8e85f51d Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/check.png differ diff --git a/enhanced_document_management/static/description/assets/icons/chevron.png b/enhanced_document_management/static/description/assets/icons/chevron.png new file mode 100644 index 000000000..2089293d6 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/chevron.png differ diff --git a/enhanced_document_management/static/description/assets/icons/close-icon.svg b/enhanced_document_management/static/description/assets/icons/close-icon.svg new file mode 100644 index 000000000..df8cce37a --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/close-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/cogs.png b/enhanced_document_management/static/description/assets/icons/cogs.png new file mode 100644 index 000000000..95d0bad62 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/cogs.png differ diff --git a/enhanced_document_management/static/description/assets/icons/collabarate-icon.svg b/enhanced_document_management/static/description/assets/icons/collabarate-icon.svg new file mode 100644 index 000000000..dd4e10518 --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/collabarate-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/enhanced_document_management/static/description/assets/icons/consultation.png b/enhanced_document_management/static/description/assets/icons/consultation.png new file mode 100644 index 000000000..8319d4baa Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/consultation.png differ diff --git a/enhanced_document_management/static/description/assets/icons/cybro-logo.png b/enhanced_document_management/static/description/assets/icons/cybro-logo.png new file mode 100644 index 000000000..ff4b78220 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/cybro-logo.png differ diff --git a/enhanced_document_management/static/description/assets/icons/down.svg b/enhanced_document_management/static/description/assets/icons/down.svg new file mode 100644 index 000000000..f21c36271 --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/enhanced_document_management/static/description/assets/icons/ecom-black.png b/enhanced_document_management/static/description/assets/icons/ecom-black.png new file mode 100644 index 000000000..a9385ff13 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/ecom-black.png differ diff --git a/enhanced_document_management/static/description/assets/icons/education-black.png b/enhanced_document_management/static/description/assets/icons/education-black.png new file mode 100644 index 000000000..3eb09b27b Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/education-black.png differ diff --git a/enhanced_document_management/static/description/assets/icons/faq.png b/enhanced_document_management/static/description/assets/icons/faq.png new file mode 100644 index 000000000..4250b5b81 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/faq.png differ diff --git a/enhanced_document_management/static/description/assets/icons/feature-icon.svg b/enhanced_document_management/static/description/assets/icons/feature-icon.svg new file mode 100644 index 000000000..fa0ea6850 --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/feature-icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/feature.png b/enhanced_document_management/static/description/assets/icons/feature.png new file mode 100644 index 000000000..ac7a785c0 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/feature.png differ diff --git a/enhanced_document_management/static/description/assets/icons/gear.svg b/enhanced_document_management/static/description/assets/icons/gear.svg new file mode 100644 index 000000000..0cc66b6ea --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/gear.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/hero.gif b/enhanced_document_management/static/description/assets/icons/hero.gif new file mode 100644 index 000000000..380654dfe Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/hero.gif differ diff --git a/enhanced_document_management/static/description/assets/icons/hire-odoo.svg b/enhanced_document_management/static/description/assets/icons/hire-odoo.svg new file mode 100644 index 000000000..e1ac089b0 --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/hire-odoo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/hotel-black.png b/enhanced_document_management/static/description/assets/icons/hotel-black.png new file mode 100644 index 000000000..130f613be Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/hotel-black.png differ diff --git a/enhanced_document_management/static/description/assets/icons/license.png b/enhanced_document_management/static/description/assets/icons/license.png new file mode 100644 index 000000000..a5869797e Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/license.png differ diff --git a/enhanced_document_management/static/description/assets/icons/life-ring-icon.svg b/enhanced_document_management/static/description/assets/icons/life-ring-icon.svg new file mode 100644 index 000000000..3ae6e1d89 --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/life-ring-icon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/lifebuoy.png b/enhanced_document_management/static/description/assets/icons/lifebuoy.png new file mode 100644 index 000000000..658d56ccc Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/lifebuoy.png differ diff --git a/enhanced_document_management/static/description/assets/icons/mail.svg b/enhanced_document_management/static/description/assets/icons/mail.svg new file mode 100644 index 000000000..1eedde695 --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/mail.svg @@ -0,0 +1,3 @@ + + + diff --git a/enhanced_document_management/static/description/assets/icons/manufacturing-black.png b/enhanced_document_management/static/description/assets/icons/manufacturing-black.png new file mode 100644 index 000000000..697eb0e9f Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/manufacturing-black.png differ diff --git a/enhanced_document_management/static/description/assets/icons/notes.png b/enhanced_document_management/static/description/assets/icons/notes.png new file mode 100644 index 000000000..ee5e95404 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/notes.png differ diff --git a/enhanced_document_management/static/description/assets/icons/notification icon.svg b/enhanced_document_management/static/description/assets/icons/notification icon.svg new file mode 100644 index 000000000..053189973 --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/notification icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/odoo-consultancy.svg b/enhanced_document_management/static/description/assets/icons/odoo-consultancy.svg new file mode 100644 index 000000000..e05f65bde --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/odoo-consultancy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/enhanced_document_management/static/description/assets/icons/odoo-licencing.svg b/enhanced_document_management/static/description/assets/icons/odoo-licencing.svg new file mode 100644 index 000000000..2606c88b0 --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/odoo-licencing.svg @@ -0,0 +1,3 @@ + + + diff --git a/enhanced_document_management/static/description/assets/icons/odoo-logo.png b/enhanced_document_management/static/description/assets/icons/odoo-logo.png new file mode 100644 index 000000000..0e4d0eb5a Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/odoo-logo.png differ diff --git a/enhanced_document_management/static/description/assets/icons/patter.svg b/enhanced_document_management/static/description/assets/icons/patter.svg new file mode 100644 index 000000000..25c9c0a8f --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/patter.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/pattern1.png b/enhanced_document_management/static/description/assets/icons/pattern1.png new file mode 100644 index 000000000..09ab0fb2d Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/pattern1.png differ diff --git a/enhanced_document_management/static/description/assets/icons/pos-black.png b/enhanced_document_management/static/description/assets/icons/pos-black.png new file mode 100644 index 000000000..97c0f90c1 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/pos-black.png differ diff --git a/enhanced_document_management/static/description/assets/icons/puzzle-piece-icon.svg b/enhanced_document_management/static/description/assets/icons/puzzle-piece-icon.svg new file mode 100644 index 000000000..3e9ad9373 --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/puzzle-piece-icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/puzzle.png b/enhanced_document_management/static/description/assets/icons/puzzle.png new file mode 100644 index 000000000..65cf854e7 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/puzzle.png differ diff --git a/enhanced_document_management/static/description/assets/icons/replace-icon.svg b/enhanced_document_management/static/description/assets/icons/replace-icon.svg new file mode 100644 index 000000000..d0e3a7af1 --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/replace-icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/restaurant-black.png b/enhanced_document_management/static/description/assets/icons/restaurant-black.png new file mode 100644 index 000000000..4a35eb939 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/restaurant-black.png differ diff --git a/enhanced_document_management/static/description/assets/icons/screenshot-main.png b/enhanced_document_management/static/description/assets/icons/screenshot-main.png new file mode 100644 index 000000000..575f8e676 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/screenshot-main.png differ diff --git a/enhanced_document_management/static/description/assets/icons/screenshot.png b/enhanced_document_management/static/description/assets/icons/screenshot.png new file mode 100644 index 000000000..cef272529 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/screenshot.png differ diff --git a/enhanced_document_management/static/description/assets/icons/service-black.png b/enhanced_document_management/static/description/assets/icons/service-black.png new file mode 100644 index 000000000..301ab51cb Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/service-black.png differ diff --git a/enhanced_document_management/static/description/assets/icons/skype-fill.svg b/enhanced_document_management/static/description/assets/icons/skype-fill.svg new file mode 100644 index 000000000..c17423639 --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/skype-fill.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/skype.png b/enhanced_document_management/static/description/assets/icons/skype.png new file mode 100644 index 000000000..51b409fb3 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/skype.png differ diff --git a/enhanced_document_management/static/description/assets/icons/skype.svg b/enhanced_document_management/static/description/assets/icons/skype.svg new file mode 100644 index 000000000..df3dad39b --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/skype.svg @@ -0,0 +1,3 @@ + + + diff --git a/enhanced_document_management/static/description/assets/icons/star-1.svg b/enhanced_document_management/static/description/assets/icons/star-1.svg new file mode 100644 index 000000000..7e55ab162 --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/star-1.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/star-2.svg b/enhanced_document_management/static/description/assets/icons/star-2.svg new file mode 100644 index 000000000..5ae9f507a --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/star-2.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/support.png b/enhanced_document_management/static/description/assets/icons/support.png new file mode 100644 index 000000000..4f18b8b82 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/support.png differ diff --git a/enhanced_document_management/static/description/assets/icons/test-1 - Copy.png b/enhanced_document_management/static/description/assets/icons/test-1 - Copy.png new file mode 100644 index 000000000..f6a902663 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/test-1 - Copy.png differ diff --git a/enhanced_document_management/static/description/assets/icons/test-1.png b/enhanced_document_management/static/description/assets/icons/test-1.png new file mode 100644 index 000000000..0908add2b Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/test-1.png differ diff --git a/enhanced_document_management/static/description/assets/icons/test-2.png b/enhanced_document_management/static/description/assets/icons/test-2.png new file mode 100644 index 000000000..4671fe91e Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/test-2.png differ diff --git a/enhanced_document_management/static/description/assets/icons/trading-black.png b/enhanced_document_management/static/description/assets/icons/trading-black.png new file mode 100644 index 000000000..9398ba2f1 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/trading-black.png differ diff --git a/enhanced_document_management/static/description/assets/icons/training.png b/enhanced_document_management/static/description/assets/icons/training.png new file mode 100644 index 000000000..884ca024d Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/training.png differ diff --git a/enhanced_document_management/static/description/assets/icons/translate.svg b/enhanced_document_management/static/description/assets/icons/translate.svg new file mode 100644 index 000000000..af9c8a1aa --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/translate.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/update.png b/enhanced_document_management/static/description/assets/icons/update.png new file mode 100644 index 000000000..ecbc5a01a Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/update.png differ diff --git a/enhanced_document_management/static/description/assets/icons/user.png b/enhanced_document_management/static/description/assets/icons/user.png new file mode 100644 index 000000000..6ffb23d9f Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/user.png differ diff --git a/enhanced_document_management/static/description/assets/icons/video.png b/enhanced_document_management/static/description/assets/icons/video.png new file mode 100644 index 000000000..576705b17 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/video.png differ diff --git a/enhanced_document_management/static/description/assets/icons/whatsapp.png b/enhanced_document_management/static/description/assets/icons/whatsapp.png new file mode 100644 index 000000000..d513a5356 Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/whatsapp.png differ diff --git a/enhanced_document_management/static/description/assets/icons/wrench-icon.svg b/enhanced_document_management/static/description/assets/icons/wrench-icon.svg new file mode 100644 index 000000000..174b5a465 --- /dev/null +++ b/enhanced_document_management/static/description/assets/icons/wrench-icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/enhanced_document_management/static/description/assets/icons/wrench.png b/enhanced_document_management/static/description/assets/icons/wrench.png new file mode 100644 index 000000000..6c04dea0f Binary files /dev/null and b/enhanced_document_management/static/description/assets/icons/wrench.png differ diff --git a/enhanced_document_management/static/description/assets/modules/1.jpg b/enhanced_document_management/static/description/assets/modules/1.jpg new file mode 100644 index 000000000..3cb15fe01 Binary files /dev/null and b/enhanced_document_management/static/description/assets/modules/1.jpg differ diff --git a/enhanced_document_management/static/description/assets/modules/2.jpg b/enhanced_document_management/static/description/assets/modules/2.jpg new file mode 100644 index 000000000..662cadcc3 Binary files /dev/null and b/enhanced_document_management/static/description/assets/modules/2.jpg differ diff --git a/enhanced_document_management/static/description/assets/modules/3.png b/enhanced_document_management/static/description/assets/modules/3.png new file mode 100644 index 000000000..7c67e2eec Binary files /dev/null and b/enhanced_document_management/static/description/assets/modules/3.png differ diff --git a/enhanced_document_management/static/description/assets/modules/4.png b/enhanced_document_management/static/description/assets/modules/4.png new file mode 100644 index 000000000..696582fa8 Binary files /dev/null and b/enhanced_document_management/static/description/assets/modules/4.png differ diff --git a/enhanced_document_management/static/description/assets/modules/5.jpg b/enhanced_document_management/static/description/assets/modules/5.jpg new file mode 100644 index 000000000..4bd9278e3 Binary files /dev/null and b/enhanced_document_management/static/description/assets/modules/5.jpg differ diff --git a/enhanced_document_management/static/description/assets/modules/6.jpg b/enhanced_document_management/static/description/assets/modules/6.jpg new file mode 100644 index 000000000..580ea075d Binary files /dev/null and b/enhanced_document_management/static/description/assets/modules/6.jpg differ diff --git a/enhanced_document_management/static/description/assets/screenshots/1.png b/enhanced_document_management/static/description/assets/screenshots/1.png new file mode 100644 index 000000000..bfe1d8634 Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/1.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/10.png b/enhanced_document_management/static/description/assets/screenshots/10.png new file mode 100755 index 000000000..d82109a02 Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/10.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/11.png b/enhanced_document_management/static/description/assets/screenshots/11.png new file mode 100755 index 000000000..94c11d458 Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/11.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/12.png b/enhanced_document_management/static/description/assets/screenshots/12.png new file mode 100755 index 000000000..ddfa1aa43 Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/12.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/13.png b/enhanced_document_management/static/description/assets/screenshots/13.png new file mode 100755 index 000000000..443461ad3 Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/13.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/14.png b/enhanced_document_management/static/description/assets/screenshots/14.png new file mode 100644 index 000000000..89115abb2 Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/14.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/16.png b/enhanced_document_management/static/description/assets/screenshots/16.png new file mode 100644 index 000000000..12a152a73 Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/16.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/17.png b/enhanced_document_management/static/description/assets/screenshots/17.png new file mode 100644 index 000000000..63aeebaec Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/17.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/18.png b/enhanced_document_management/static/description/assets/screenshots/18.png new file mode 100644 index 000000000..4bd00eaff Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/18.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/19.png b/enhanced_document_management/static/description/assets/screenshots/19.png new file mode 100644 index 000000000..658af825d Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/19.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/2.png b/enhanced_document_management/static/description/assets/screenshots/2.png new file mode 100755 index 000000000..affbe5639 Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/2.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/20.png b/enhanced_document_management/static/description/assets/screenshots/20.png new file mode 100644 index 000000000..95110fa56 Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/20.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/21.png b/enhanced_document_management/static/description/assets/screenshots/21.png new file mode 100644 index 000000000..9c548b7e3 Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/21.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/3.png b/enhanced_document_management/static/description/assets/screenshots/3.png new file mode 100755 index 000000000..372dc98a9 Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/3.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/4.png b/enhanced_document_management/static/description/assets/screenshots/4.png new file mode 100755 index 000000000..fb276da9e Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/4.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/5.png b/enhanced_document_management/static/description/assets/screenshots/5.png new file mode 100755 index 000000000..c80420954 Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/5.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/6.png b/enhanced_document_management/static/description/assets/screenshots/6.png new file mode 100755 index 000000000..bea8976e0 Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/6.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/7.png b/enhanced_document_management/static/description/assets/screenshots/7.png new file mode 100755 index 000000000..e27de964d Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/7.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/8.png b/enhanced_document_management/static/description/assets/screenshots/8.png new file mode 100755 index 000000000..86ffd2204 Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/8.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/9.png b/enhanced_document_management/static/description/assets/screenshots/9.png new file mode 100755 index 000000000..5a04f79e4 Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/9.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/d2.png b/enhanced_document_management/static/description/assets/screenshots/d2.png new file mode 100644 index 000000000..73916aba0 Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/d2.png differ diff --git a/enhanced_document_management/static/description/assets/screenshots/hero.gif b/enhanced_document_management/static/description/assets/screenshots/hero.gif new file mode 100644 index 000000000..30fb2a301 Binary files /dev/null and b/enhanced_document_management/static/description/assets/screenshots/hero.gif differ diff --git a/enhanced_document_management/static/description/banner.jpg b/enhanced_document_management/static/description/banner.jpg new file mode 100644 index 000000000..cf873fc44 Binary files /dev/null and b/enhanced_document_management/static/description/banner.jpg differ diff --git a/enhanced_document_management/static/description/icon.png b/enhanced_document_management/static/description/icon.png new file mode 100644 index 000000000..0775c3770 Binary files /dev/null and b/enhanced_document_management/static/description/icon.png differ diff --git a/enhanced_document_management/static/description/index.html b/enhanced_document_management/static/description/index.html new file mode 100644 index 000000000..f66a53ab9 --- /dev/null +++ b/enhanced_document_management/static/description/index.html @@ -0,0 +1,1440 @@ + + + + + + + Document Management + + + + + + + + + + + +
+
+ + + +
+
+ Community +
+
+
+ +
+
+
+
+

+ The Document Management module provide quick access to create, share and delete. +

+

Document Management +

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

Key + Highlights

+
+
+
+
+ +
+
+Easily to share documents to other users.
+
+
+
+
+
+ +
+
+Document Portal View +
+ +
+
+
+
+
+ +
+
+Portal document upload access. +
+ +
+
+
+
+
+ +
+
+Upload URL as document. +
+ +
+
+
+
+ +
+
+Upload URL as document. +
+ +
+
+
+
+ + +
+
+
+ Document Management. +

+ Are you ready to make your business more + organized? +
Improve now! +

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

+ + + + Document user interface. +

+
+
+

+

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

+ + User can categories files using + workspace +

+
+
+

+

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

+ User can make favorite their + document. +

+
+
+

+

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

+ User can upload their document into document management module and able to add file using url. +

+
+
+

+

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

+ User can upload their document into document management module, give access to specific users, able to choose workspace and + auto delete option. +

+
+
+

+

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

+ User can add file using + + + url. +

+
+
+

+

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

+ User can request file to other + users. +

+
+
+

+

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

+ User can easily filter files using file type and + + + favorite. +

+
+
+

+

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

+ User can create unlimited + + + workspace. +

+
+
+

+

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

+ User can easily retrieve files after delete trash facility + + + available. +

+
+
+

+

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

+ Users can access these additional options from the dropdown. + (You can visible lead and task options based on the installed modules.). +

+
+
+

+

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

+ User can request document to other users. +

+
+
+

+

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

+ The user can specify the number of days for files to remain in the + trash. +

+
+
+

+

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

+ The user can view the document that was requested. +

+
+
+

+

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

+ By using the "Accept" button, the user can accept the request. + +

+
+
+

+

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

+ By clicking that button you will get a popup window like this to upload the document. + +

+
+
+

+

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

+ After accepting the request the state will change to accepted. +

+
+
+

+

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

+ From their customer account, the user can view the requested document as follows. +

+
+
+

+

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

+ The user can choose to accept or refuse the document request right there. + +

+
+
+

+

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

+ The user can download the document request right there. + +

+
+
+

+

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

+ Trash feature available – easily manage and restore deleted documents when needed. +

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

+ Upload URL as document – add external links directly as documents for easy access. +

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

+ Document Portal View – organize and browse your files in a clear, user-friendly portal. +

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

+ Easily share documents with other users – secure and seamless collaboration. +

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

+ The Trash feature allows you to temporarily store deleted documents before + permanently removing them. You can restore documents from Trash if needed. +

+
+
+ + +
+ +
+

+ Yes. The module lets you upload a URL as a document, so you can keep external + resources and references organized alongside your files. +

+
+
+ + +
+ +
+

+ The Document Portal View provides a user-friendly interface where users can + browse, organize, and access documents more efficiently. +

+
+
+ + +
+ +
+

+ You can easily share documents with specific users or groups, ensuring secure + access and smooth collaboration inside Odoo. +

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

+ Latest Release 18.0.1.0.0 +

+ + 26th Aug, 2025 + +
+
+
+
+
+ Add +
+
+
+
    +
  • + Initial Commit +
  • + +
+
+
+
+
+
+
+
+
+
+ + + + + + +
+
+

+ Our Services

+
+ +
+
+ +
+
+ .... +
+
+ +
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/enhanced_document_management/static/images/csv.png b/enhanced_document_management/static/images/csv.png new file mode 100755 index 000000000..4c54cdd91 Binary files /dev/null and b/enhanced_document_management/static/images/csv.png differ diff --git a/enhanced_document_management/static/images/doc.png b/enhanced_document_management/static/images/doc.png new file mode 100755 index 000000000..dd94ad754 Binary files /dev/null and b/enhanced_document_management/static/images/doc.png differ diff --git a/enhanced_document_management/static/images/file.png b/enhanced_document_management/static/images/file.png new file mode 100755 index 000000000..19dc9cec0 Binary files /dev/null and b/enhanced_document_management/static/images/file.png differ diff --git a/enhanced_document_management/static/images/img.gif b/enhanced_document_management/static/images/img.gif new file mode 100755 index 000000000..e3f144ca3 Binary files /dev/null and b/enhanced_document_management/static/images/img.gif differ diff --git a/enhanced_document_management/static/images/pdf.png b/enhanced_document_management/static/images/pdf.png new file mode 100755 index 000000000..3d22ff10f Binary files /dev/null and b/enhanced_document_management/static/images/pdf.png differ diff --git a/enhanced_document_management/static/images/ppt.gif b/enhanced_document_management/static/images/ppt.gif new file mode 100755 index 000000000..3b0afc34b Binary files /dev/null and b/enhanced_document_management/static/images/ppt.gif differ diff --git a/enhanced_document_management/static/images/share.png b/enhanced_document_management/static/images/share.png new file mode 100755 index 000000000..56a09cbb7 Binary files /dev/null and b/enhanced_document_management/static/images/share.png differ diff --git a/enhanced_document_management/static/images/text.png b/enhanced_document_management/static/images/text.png new file mode 100755 index 000000000..2a540b53d Binary files /dev/null and b/enhanced_document_management/static/images/text.png differ diff --git a/enhanced_document_management/static/images/url.gif b/enhanced_document_management/static/images/url.gif new file mode 100755 index 000000000..385327bd6 Binary files /dev/null and b/enhanced_document_management/static/images/url.gif differ diff --git a/enhanced_document_management/static/images/xls.png b/enhanced_document_management/static/images/xls.png new file mode 100755 index 000000000..c14f97861 Binary files /dev/null and b/enhanced_document_management/static/images/xls.png differ diff --git a/enhanced_document_management/static/src/css/kanban.css b/enhanced_document_management/static/src/css/kanban.css new file mode 100644 index 000000000..4a683cbc1 --- /dev/null +++ b/enhanced_document_management/static/src/css/kanban.css @@ -0,0 +1,286 @@ +.o_legacy_kanban_view.o_kanban_ungrouped +.o_kanban_record { + flex-direction: column; + min-height: 230px; + width: 166px; + border-radius: 10px; +} + +.o_kanban_renderer .o_kanban_record .o_kanban_attachment .o_kanban_image { + width:200px; +} +.o_kanban_attachment { + padding: 0; + height: 140px; + align-items: center; +} + +.o_kanban_record_title { + margin-bottom: 4px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.fa { + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + font-size: 15px; + text-rendering: auto; +} + +.check_box { + margin-left: 10px; +} + +.o_activity_btn span.fa { + color: inherit; + font-size: large; + margin-right: 1px; + margin-left: 10px; +} + +.new_container { + top: 10%; + -fill-mode: backwards; + position: absolute; + right: 16%; + align-items: center; + width: fit-content; + max-height: 600px; + overflow-x: hidden; + display: flex; + flex-direction: row; + gap: 15px; + border: none; + box-shadow: none; +} + +.new_container button { + padding-left: 0; + width: 10%; + font-size: 200%; + border-radius:5px !important; +} + +.new_container button:hover { + background-color: LightGray; +} + +.kanban_document .o_kanban_record { + box-shadow: 0px 0px 0px 0px rgba(0, 0, 0, 0) !important; + max-width: 234px !important; + transform: translateX(-2%); +} + +.o_kanban_renderer .oe_file_request { + display: flex !important; + flex-direction: column; +} + +.kanban_table { + height: 0px; +} + +.kanban_document { + cursor: pointer !important; + width: 93% !important; + display: flex; + justify-content: center; + border-radius: 10px; + border: 1px solid #dee2e6 !important; + box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.1); +} + +.document_icon { + max-width: 32px; + max-height: 50px; +} + +.document_lock_wizard { + width: 48%; + background: #dde2e6; +} + +.document_cancel_wizard { + width: 50%; + background: #dde2e6; +} + +.preview_image { + max-width: 266px !important; + max-height: 188px; + height: 188px; + width: 266px; + border-radius: 10px; + position: absolute; + top: -29px; + left: -89.5px; +} + +.selected { + opacity: 1 !important; + border-color: #017e84 !important; + background-color: #e6f2f3 !important; +} + +.document_details { + border: 0 !important; +} + +.document_image { + border: 0 !important; +} + +.document_table { + border: 0 !important; + +} + +.move_trash_wizard { + width: 97% !important; +} + +.o_form_renderer:has(.move_trash_heading) { + display: flex !important; + align-items: center; + position: inherit; + justify-content: center; + +} + +.o_document_kanban_renderer { + max-width: 100%; + background-color: white; + display: grid !important; + grid-template-columns: 1fr 1fr 1fr 1fr; + row-gap: 80px; + box-shadow: 0px 3px 10px -4px rgba(0, 0, 0, 0.1); +} + +.document_management_record { + max-width: 100%; + display: grid; + justify-content: center; + background: white !important; +} + +.document_management_record .o-dropdown { + visibility: visible !important; + bottom: 12px; + left: 85%; +} + +.document-modal-content { + background-color: #fafafa; + position: fixed; + top: 50% !important; + left: 50% !important; + transform: translate(-50%, -50%); + width: 25%; +} + +.document-modal-content .o_form_renderer { + background-color: #fafafa; +} + +.document-modal-content .o_form_renderer .o_form_label { + display: none; +} + +.document-modal-content .o_form_renderer .o_input { + width: 45vh; + border: none; + height: 35px; + border-radius: 5px; + background-color: #f2f2f2; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); + font-size: 25px; + transition: box-shadow 0.3s, background-color 0.3s; +} + +.document-modal-content .o_form_renderer .o_input:hover { + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + background-color: #eaeaea; +} + + +.document-modal-content .o_form_renderer .o_input:focus { + outline: none; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + background-color: #fff; +} + +.document-modal-content .move_trash_heading { + width: max-content; + max-width: 329px; +} + + +.document_lock_icon { + position: absolute; + top: 0; +} + +/*.o_button_in_kanban_view_view .o_control_panel .o_control_panel_navigation { + margin-right: 4%; +}*/ + +.cy-document-right-sidebar { + position: fixed; + z-index: 1; + background: white; + right: 0; + bottom: -125px; + border-top: 1px solid #DFDFDF; + width: 18%; + height: 100%; + border-left: 1px solid #DFDFDF; +} + +.o_kanban_view .workspace_panel { + width: 15% !important; +} + +.o_button_in_kanban_view_view .o_component_with_workspace_panel xpath{ + position: fixed; + width: 100%; + z-index: 2; + height: 5%; + align-items: center; + display: flex; + background: white; + border-bottom: 1.5px solid #DFDFDF; +} + +.o_button_in_kanban_view_view .o_component_with_workspace_panel .o_document_kanban_renderer{ + top: 5%; +} + +.document_image .on_preview_button{ + height:100%; + width:100%; + border-radius: 10px; + top: -29px; + left: -89.5px; +} + +.document-modal-content .o_form_renderer .o_wrap_field .o_cell.o_wrap_label.flex-grow-1.flex-sm-grow-0.w-100.text-break.text-900{ + display:none; +} + +@media (max-width:768px) { + .o_kanban_view .workspace_panel { + width: 70% !important; + } + + .o_document_kanban_renderer { + grid-template-columns: 1fr 1fr; + } +} + +@media (max-width:658px) { + .o_document_kanban_renderer { + grid-template-columns: 1fr; + } +} \ No newline at end of file diff --git a/enhanced_document_management/static/src/css/style.css b/enhanced_document_management/static/src/css/style.css new file mode 100644 index 000000000..e3c93a2f2 --- /dev/null +++ b/enhanced_document_management/static/src/css/style.css @@ -0,0 +1,13 @@ +.document_preview_template { + border: 1px solid #000; + height: 100%; + padding: 6%; + margin: 10%; +} + +.document_preview_action{ + overflow-y: auto !important; +} +.nav-item :hover{ + cursor: pointer; +} \ No newline at end of file diff --git a/enhanced_document_management/static/src/js/document_preview_action.js b/enhanced_document_management/static/src/js/document_preview_action.js new file mode 100644 index 000000000..6a0210b1d --- /dev/null +++ b/enhanced_document_management/static/src/js/document_preview_action.js @@ -0,0 +1,38 @@ +/** @odoo-module */ +import { registry } from "@web/core/registry"; +import { onMounted, useRef } from "@odoo/owl"; +const actionRegistry = registry.category("actions"); + +class DocumentPreview extends owl.Component { + setup() { + super.setup(...arguments); + this.template_div = useRef("document_preview_template_content"); + this.template = this.props.action.params['body_html']; + this.stamp = this.props.action.params['stamp']; + onMounted(async () => { + this.render_template(); + this.prevent_reload(); + }); + } + render_template() { + /* Method to render the template*/ + this.__owl__.bdom.el.parentElement.classList.add('document_preview_action') + if (localStorage.getItem("myContent")) { + this.template_div.el.innerHTML = localStorage.getItem("myContent"); + } + if (this.template) { + this.template_div.el.innerHTML = this.template; + } + } + prevent_reload() { + /* Method to prevent reload and set the template contents into local storage */ + var self = this + window.addEventListener('beforeunload', function (event) { + if (self.template_div.el) { + localStorage.setItem("myContent", self.template_div.el.innerHTML); + } + }); + } +} +DocumentPreview.template = "DocumentPreviewTemplate"; +actionRegistry.add('preview_document', DocumentPreview); diff --git a/enhanced_document_management/static/src/js/kanban_record.js b/enhanced_document_management/static/src/js/kanban_record.js new file mode 100644 index 000000000..3a703587a --- /dev/null +++ b/enhanced_document_management/static/src/js/kanban_record.js @@ -0,0 +1,84 @@ +/** @odoo-module */ +import { KanbanRecord } from '@web/views/kanban/kanban_record'; +const { onMounted } = owl; +import { onWillDestroy } from "@odoo/owl"; +import { registry } from '@web/core/registry'; +import { useService, useBus } from "@web/core/utils/hooks"; +import { FileViewer } from "@web/core/file_viewer/file_viewer"; +// Define a unique identifier for the FileViewer +var fileViewer_id = 1; +// Create a custom class 'DocumentKanbanRecord' that extends 'KanbanRecord' +export class DocumentKanbanRecord extends KanbanRecord { + setup() { + super.setup(); + this.containsSelectionOn = false; + this.orm = useService('orm'); + // Subscribe to custom bus events + useBus(this.env.bus, 'cancel_button_click', (ev) => this.cancel_button_click()); + useBus(this.env.bus, 'select_button_click', () => this.select_button_click()); + onMounted(() => { + this.AddClassStyle(); + this.env.bus.trigger('workspace_changed'); + }); + onWillDestroy(() => { + // Remove event listener and trigger a bus event when destroying the component + this.__owl__.bdom.el.querySelector('.kanban_document').removeEventListener('click', function (event) { + if (self.containsSelectionOn) { + self.DivSelect(event); + } + }); + this.env.bus.trigger('workspace_switched'); + }); + } + AddClassStyle() { + // Add a custom class to the element + this.__owl__.bdom.el.classList.add('document_management_record'); + } + cancel_button_click() { + /* Function to make this.containsSelectionOn False */ + this.containsSelectionOn = false; + } + select_button_click() { + /* Function to make this.containsSelectionOn False */ + this.containsSelectionOn = true; + } + onGlobalClick(ev) { + /* Function to call FileOpen class on a global click of Kanban */ + if (this.containsSelectionOn == false) { + var self = this; + var record_id = (ev.target.children[0] && ev.target.children[0].getAttribute('data-id')) || ev.target.getAttribute('data-id'); + if (record_id) { + this.orm.call('document.file', 'get_documents_list', [record_id]).then(function (result) { + const attachment = result[0]; + const attachment_list = result[1]; + self.FileOpen(attachment, attachment_list); + }); + } + } + } + FileOpen(file, files = [file]) { + /* Method to open the file viewer */ + var self = this; + let id = 1; + fileViewer_id = `web.file_viewer${id++}`; + if (!file.isViewable) { + return; + } + if (files.length > 0) { + const viewableFiles = files.filter((file) => file.isViewable); + const index = files.findIndex((item) => item.id === file.id); + registry.category("main_components").add(fileViewer_id, { + Component: FileViewer, + props: { + files: viewableFiles, + startIndex: index, + close: self.close + }, + }); + } + } + close() { + /* Function to remove fileViewer_id from main_components registry */ + registry.category("main_components").remove(fileViewer_id); + } +} diff --git a/enhanced_document_management/static/src/js/kanban_renderer.js b/enhanced_document_management/static/src/js/kanban_renderer.js new file mode 100644 index 000000000..b5a44ad2f --- /dev/null +++ b/enhanced_document_management/static/src/js/kanban_renderer.js @@ -0,0 +1,95 @@ +/** @odoo-module */ +// Import necessary modules and utilities from Odoo +import { DocumentKanbanRecord } from './kanban_record'; +import { KanbanRenderer } from '@web/views/kanban/kanban_renderer'; +const { onMounted } = owl; +import { useBus } from "@web/core/utils/hooks"; +// Create a custom class 'CustomKanbanRenderer' that extends 'KanbanRenderer' +export class CustomKanbanRenderer extends KanbanRenderer { + async setup() { + super.setup(...arguments); + // Subscribe to custom bus events + useBus(this.env.bus, 'workspace_switched', (ev) => this.eventListener = false); + onMounted(() => { + // Select all elements with class 'select_document' and add click event listeners + const selectDocumentButtons = this.rootRef.el.ownerDocument.querySelectorAll('.select_document'); + selectDocumentButtons.forEach(button => { + button.addEventListener('click', event => this.Select_button_click(event, this)); + }); + // Select all elements with class 'cancel_btn' and add click event listeners + const cancelBtns = this.rootRef.el.ownerDocument.querySelectorAll('.cancel_btn'); + cancelBtns.forEach(btn => { + btn.addEventListener('click', event => this.cancel_button_click(event, this)); + }); + // Call a function to add CSS styles or classes + this.AddClassStyle(); + // Set the eventListener property to false + this.eventListener = false; + }); + } + AddClassStyle() { + // Add custom classes to elements + this.__owl__.bdom.el.classList.add('o_document_kanban_renderer'); + this.__owl__.bdom.parentEl.classList.add('o_component_with_workspace_panel'); + } + Select_button_click(ev) { + /** + * Handles the click event of the 'Select' button. Adds a CSS class to kanban records + * and sets up event listeners for clicking on 'kanban_document' elements if not already done. + * Triggers a custom event 'select_button_click' on the component's event bus. + * + * @param {Event} ev - The click event.*/ + var self = this; + this.__owl__.bdom.el.querySelectorAll('.o_kanban_record').forEach((kanban_record) => { + kanban_record.classList.add("selection_on"); + self.containsSelectionOn = true; + }); + if (!this.eventListener) { + this.__owl__.bdom.el.querySelectorAll('.kanban_document').forEach((selection_on_div) => { + selection_on_div.addEventListener('click', function (event) { + if (self.containsSelectionOn) { + self.DivSelect(event); + } + }); + }); + this.eventListener = true; + } + this.env.bus.trigger('select_button_click'); + } + cancel_button_click() { + /** + * Handles the click event of the 'Cancel' button. Resets the selection state and + * triggers a custom event 'cancel_button_click' on the component's event bus. + */ + this.containsSelectionOn = false; + this.env.bus.trigger('cancel_button_click'); + } + DivSelect(event) { + /** + * Handles the click event on a 'kanban_document' element within a selection context. + * Toggles the checked state of a checkbox element within the clicked element and triggers + * a custom event 'docs_check_box' on the component's event bus. + * + * @param {Event} event - The click event on the 'kanban_document' element. + */ + event.preventDefault(); + if (this.containsSelectionOn) { + const checkboxElement = event.currentTarget.querySelector('.docs_check_box'); + if (checkboxElement) { + const isChecked = !checkboxElement.checked; + checkboxElement.checked = isChecked; + const eventData = { + event: checkboxElement, + self: this, + clickEvent: event + }; + this.env.bus.trigger("docs_check_box", eventData); + } + } + } +} +// Extend the components of CustomKanbanRenderer to include DocumentKanbanRecord +CustomKanbanRenderer.components = { + ...KanbanRenderer.components, + KanbanRecord: DocumentKanbanRecord, +}; diff --git a/enhanced_document_management/static/src/js/kanbancontroller.js b/enhanced_document_management/static/src/js/kanbancontroller.js new file mode 100755 index 000000000..9d2a18b75 --- /dev/null +++ b/enhanced_document_management/static/src/js/kanbancontroller.js @@ -0,0 +1,386 @@ +/** @odoo-module */ +import {Dialog} from "@web/core/dialog/dialog"; +import {_t} from "@web/core/l10n/translation"; +import {KanbanController} from "@web/views/kanban/kanban_controller"; +import {registry} from '@web/core/registry'; +import {CustomKanbanRenderer} from './kanban_renderer' +import {patch} from "@web/core/utils/patch"; +const {onMounted} = owl; +import {DocumentKanbanRecord} from './kanban_record' +import {useRef, useState} from "@odoo/owl"; +import {kanbanView} from '@web/views/kanban/kanban_view'; +import {useService, useBus} from "@web/core/utils/hooks"; +import { user } from "@web/core/user"; + +patch(KanbanController.prototype, { + setup() { + this.actionService = useService("action"); + this.user = user; + useBus(this.env.bus, 'searchPanel_toggle', (ev) => this.searchPanelToggle(ev)); + useBus(this.env.bus, 'docs_check_box', (ev) => this._onSelectDocs(ev)); + useBus(this.env.bus, 'searchPanel_toggle', (ev) => this.HideSelectButton()); + this.document_multi_checkbox = useRef("document_multi_checkbox"); + this.lead_button = useRef('create_lead') + this.task_button = useRef('create_task') + super.setup(); + this.orm = useService('orm'); + this.userSettings = useService("action") + this.viewState = useState({ + view_id: null + }) + onMounted(() => { + this.ViewLead_ProjectButton() + this.Workspace_id = 1 + this.Workspace_name = 'All' + this.HideSelectButton() + this.documents_selected = []; + this.containsSelectionOn = false; + }); + }, + async ViewLead_ProjectButton() { + /* method to make create crm and create lead button show and hide if crm and project is installed or not */ + try { + var settings_value = await this.orm.call('res.config.settings', 'search_read', [], {fields: ["module_crm", "module_project"]}); + if (settings_value.length > 0) { + var module_crm = settings_value[settings_value.length - 1]['module_crm'] + var module_lead = settings_value[settings_value.length - 1]['module_project'] + if (this.lead_button.el) { + this.lead_button.el.style.display = module_crm ? 'block' : 'none'; + } + if (this.task_button.el) { + this.task_button.el.style.display = module_lead ? 'block' : 'none'; + } + } + } catch (error) { + if (this.lead_button.el) { + this.lead_button.el.style.display = 'block'; + } + if (this.task_button.el) { + this.task_button.el.style.display = 'block'; + } + } + }, + searchPanelToggle(ev) { + /** + * method to get workspace_id + */ + this.Workspace_id = ev.detail['Id'] + this.Workspace_name = ev.detail['workspace'].display_name + }, + _onUpload() { + /** + * method to open file upload wizard + */ + this.actionService.doAction({ + name: "Upload Documents", + type: 'ir.actions.act_window', + res_model: 'document.file', + view_mode: 'form', + views: [ + [false, 'form'] + ], + target: 'new', + }) + }, + _onAddUrl() { + /** + * Performs an action to add a URL. + * Opens a new form view for the 'document.url' model. + */ + this.actionService.doAction({ + 'type': 'ir.actions.act_window', + 'name': _t('Add Url'), + 'res_model': 'document.url', + 'view_mode': 'form', + 'target': 'new', + 'views': [ + [false, "form"] + ], + }); + }, + async _onRequestDoc() { + /** + * Performs an action to request a document. + * Opens a new form view for the 'request.document' model. + */ + + var self = this + await this.orm.call('request.document', 'get_wizard_view', ['enhanced_document_management.request_document_wizard_view_form']).then(function (result) { + self.viewState.view_id = result + }) + this.actionService.doAction({ + 'type': 'ir.actions.act_window', + 'name': 'Add Document Request', + 'res_model': 'request.document', + 'view_mode': 'form', + 'target': 'new', + 'views': [[parseInt(self.viewState.view_id) || false, "form"]], + }); + }, + _onShare() { + /** + * Performs an action to share a document. + * Creates a URL for sharing the selected documents and triggers an action with the result. + */ + var self = this; + this.orm.call('document.share', + 'create_url', + [this.documents_selected]).then(function (result) { + self.actionService.doAction(result); + }); + }, + HideSelectButton() { + /** + * Method to Hide Select Button + */ + const self = this; + const selectDocumentsbutton = this.rootRef.el.querySelectorAll('.select_document'); + const kanbanDocumentsCount = this.rootRef.el.querySelectorAll('.kanban_document').length; + if (this.props.resModel === 'document.file') { + this.orm.call('document.file', 'get_document_count', [this.Workspace_id]) + .then(result => { + selectDocumentsbutton.forEach((selectDocumentsbutton) => { + selectDocumentsbutton.style.display = self.Workspace_name === 'All' ? (result[1] >= 2 ? 'block' : 'none') : (result[0] >= 2 ? 'block' : 'none'); + }); + }); + } + }, + _onCreateTask() { + /** + * method to create task based on selected document + */ + var self = this; + this.orm.call('document.file', 'action_btn_create_task', [this.documents_selected]).then(function (result) { + if (result) { + self.documents_selected = [] + location.reload(); + } else { + self.actionService.doAction({ + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "Install Project Module to use this function", + 'type': 'info', + 'sticky': false, + } + }) + } + }); + }, + _onSelectDocs(ev) { + /** + * method to add selected document in a list + */ + var toast = $(this.__owl__.bdom.el).find('.toast') + var record1 = ev.detail['event'] + var record2 = ev.detail['event'].dataset.content_type + var record_id = parseInt(ev.detail['event'].dataset.id); + if (ev.detail['event'].checked) { + /** + * Handles the logic when a checkbox is checked. + * Adds the 'selected' class to parent and sibling elements. + * Adds the record ID to the documents_selected array. + */ + $(ev.detail['event']).closest(".document_management_record").addClass("selected"); + $(ev.detail['event']).closest(".document_table").addClass("selected"); + $(ev.detail['event']).closest(".kanban_document").find('.document_image').addClass("selected"); + $(ev.detail['event']).closest(".kanban_document").addClass("selected"); + $(ev.detail['event']).closest(".kanban_document").find('.document_details').addClass("selected"); + toast.addClass('d-flex'); +// this.documents_selected.push({ id: record_id, contentType: content_type }); + this.documents_selected.push(record_id); + } else { + /** + * Handles the logic when a checkbox is unchecked. + * Removes the 'selected' class from parent and sibling elements. + * Removes the record ID from the documents_selected array. + * Removes the 'show' class from the toast element if no more documents are selected. + */ + $(ev.detail['event']).closest(".document_management_record").removeClass("selected"); + $(ev.detail['event']).closest(".document_table").removeClass("selected"); + $(ev.detail['event']).closest(".kanban_document").find('.document_image').removeClass("selected"); + $(ev.detail['event']).closest(".kanban_document").removeClass("selected"); + $(ev.detail['event']).closest(".kanban_document").find('.document_details').removeClass("selected"); + let index = this.documents_selected.indexOf(record_id); + this.documents_selected.splice(index, 1) + if (this.documents_selected.length == 0) { + toast.removeClass('d-flex'); + } + } + }, + _onDownloadArchive() { + /** + * Method to download selected file as a Zip + */ + var self = this; + if (this.documents_selected.length > 0) { + this.orm.call('document.file', 'download_zip_function', [this.documents_selected]).then(function (res) { + self.actionService.doAction(res) + }) + } + }, + _onArchiveDocument() { + /** + * method to archive selected document + */ + if (this.documents_selected.length != 0) { + var self = this; + this.orm.call('document.file', 'document_file_archive', [this.documents_selected]).then(function (result) { + self.documents_selected = [] + location.reload(); + }); + } else { + Dialog.alert(this, "Please select least one document"); + } + }, + _onCreateLead() { + /** + * method to create lead based on selected document + */ + var self = this; + this.orm.call('document.file', 'action_btn_create_lead', [this.documents_selected]).then(function (result) { + if (result) { + self.documents_selected = [] + location.reload(); + } else { + self.actionService.doAction({ + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "Install CRM Module to use this function", + 'type': 'info', + 'sticky': false, + } + }) + } + }); + }, + _onMailDocument: function (ev) { + /** + * method to open email composer + */ + var self = this; + this.orm.call('document.file', 'on_mail_document', [this.documents_selected]).then(function (result) { + self.documents_selected = [] + self.actionService.doAction(result); + }); + }, + _onCopyDocument() { + /** + * method to open copy/cut wizard + */ + var self = this; + this.user.hasGroup('enhanced_document_management.view_all_document').then( + (has_group) => { + if (has_group) { + self.actionService.doAction({ + 'type': 'ir.actions.act_window', + 'name': 'copy', + 'res_model': 'work.space', + 'view_mode': 'form', + 'target': 'new', + 'views': [ + [false, 'form'] + ], + 'context': { + 'default_doc_ids': this.documents_selected + } + }); + } else { + self.actionService.doAction({ + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "You don't have permission to perform this action", + 'type': 'danger', + 'sticky': false, + } + }) + } + }) + }, + _onDelete(ev) { + /** + * method to delete selected records + */ + this.user.hasGroup('enhanced_document_management.view_all_document').then( + (has_group) => { + if (has_group) { + var self = this; + var record_id = parseInt(ev.target.dataset.id) + this.orm.call('document.file', 'document_file_delete', [this.documents_selected]).then(function (result) { + self.documents_selected = [] + location.reload(); + }); + } else { + this.actionService.doAction({ + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'message': "You don't have permission to perform this action", + 'type': 'danger', + 'sticky': false, + } + }) + } + }) + }, + Select_Doc() { + /** + * Handles the selection of documents. + * Adds the 'selection_on' class to all kanban records, displays the cancel button, and hides the select button. + * Disables the upload document button and dropdown toggle. + * Disables the search panels. + * Sets up a click event handler for the elements with the 'selection_on' class. + * Executes the '_onDivSelect' function if any kanban record has the 'selection_on' class. + */ + this.rootRef.el.querySelectorAll('.cancel_btn').forEach(btn => btn.style.display = 'block'); + this.rootRef.el.querySelectorAll('.select_document').forEach(doc => doc.style.display = 'none'); + const elements = this.rootRef.el.querySelectorAll( + '.on_upload_doc, .dropdown-toggle-split, .o_search_panel_category_value'); + elements.forEach((element) => { + element.classList.add('disabled'); + }); + }, + CancelSelect() { + /** + * Handles the cancellation of document selection. + * Resets the state by clearing the 'containsSelectionOn' flag, removing the 'selection_on' class from kanban records, + * removing the 'selected' class from selected documents, and emptying the 'documents_selected' array. + * Hides the toast element, hides the cancel button, and shows the select button. + * Enables the upload document button and dropdown toggle. + * Enables the search panels. + */ + this.containsSelectionOn = false + this.documents_selected = [] + this.rootRef.el.querySelectorAll('.o_kanban_record').forEach((kanban_record) => { + kanban_record.classList.remove("selection_on") + }); + this.rootRef.el.querySelectorAll('.selected').forEach((selected_doc) => { + selected_doc.classList.remove('selected') + }); + this.rootRef.el.querySelectorAll('.o_search_panel_category_value').forEach((search_panel) => { + search_panel.classList.remove('disabled') + }); + this.rootRef.el.querySelectorAll('.toast').forEach(toast => { + toast.classList.remove('d-flex'); + }); + this.rootRef.el.querySelectorAll('.cancel_btn').forEach(btn => { + btn.style.display = 'none'; + }); + this.rootRef.el.querySelectorAll('.select_document').forEach(document => document.style.display = + 'block'); + this.rootRef.el.querySelectorAll('.on_upload_doc').forEach(element => { + element.classList.remove('disabled'); + }); + this.rootRef.el.querySelectorAll('.dropdown-toggle-split').forEach(element => { + element.classList.remove('disabled'); + }); + } +}) +registry.category('views').add('button_in_kanban_view', { + ...kanbanView, + Controller: KanbanController, + Renderer: CustomKanbanRenderer, + KanbanRecord: DocumentKanbanRecord +}); diff --git a/enhanced_document_management/static/src/js/portal.js b/enhanced_document_management/static/src/js/portal.js new file mode 100755 index 000000000..cb69de069 --- /dev/null +++ b/enhanced_document_management/static/src/js/portal.js @@ -0,0 +1,30 @@ +/** @odoo-module **/ + + + import publicWidget from "@web/legacy/js/public/public_widget"; + publicWidget.registry.DocumentPortal = publicWidget.Widget.extend({ + selector: '#document_portal', + events: { + 'click .fa-share': '_onShare', + 'click .re-upload': '_onRequestAccept', + 'click .re-reject': '_onRequestReject', + }, + _onRequestAccept: function(ev){ + /** + * Function to open file upload modal + */ + this.$el.find('#req_upload_form').modal('show'); + this.$el.find('#workspace').val(ev.target.dataset.workspace) + this.$el.find('#requested_by').val(ev.target.dataset.requested_by) + this.$el.find('#workspace_id').val(ev.target.dataset.workspace_id) + this.$el.find('#rec_id').val(ev.target.dataset.id) + }, + _onRequestReject: function(ev){ + /** + * Function to reject file upload request + */ + this.$el.find('#req_id').val(ev.target.dataset.id) + this.$el.find('#req_reject_form').modal('show'); + } + }) + diff --git a/enhanced_document_management/static/src/xml/KanbanController.xml b/enhanced_document_management/static/src/xml/KanbanController.xml new file mode 100755 index 000000000..c9abc7199 --- /dev/null +++ b/enhanced_document_management/static/src/xml/KanbanController.xml @@ -0,0 +1,130 @@ + + + + + + + + +
+ + + + +
+ +
+
+
+
+
+
diff --git a/enhanced_document_management/static/src/xml/document_preview_template.xml b/enhanced_document_management/static/src/xml/document_preview_template.xml new file mode 100644 index 000000000..43b39d412 --- /dev/null +++ b/enhanced_document_management/static/src/xml/document_preview_template.xml @@ -0,0 +1,15 @@ + + + diff --git a/enhanced_document_management/views/document_delete_trash_views.xml b/enhanced_document_management/views/document_delete_trash_views.xml new file mode 100644 index 000000000..5bc55a1b4 --- /dev/null +++ b/enhanced_document_management/views/document_delete_trash_views.xml @@ -0,0 +1,33 @@ + + + + + document.delete.trash.view.form + + document.delete.trash + + +
+ + + +

Move to Trash/Delete Document Permanently

+ + + +
+ +
+ + + + + +
+
+ +
+
+
diff --git a/enhanced_document_management/views/document_file_views.xml b/enhanced_document_management/views/document_file_views.xml new file mode 100644 index 000000000..b8c9b9d01 --- /dev/null +++ b/enhanced_document_management/views/document_file_views.xml @@ -0,0 +1,380 @@ + + + + + Documents + ir.actions.act_window + document.file + kanban,list,form,calendar + +

+ Save your documents... +

+
+
+ + + document.file.view.calendar + document.file + + + + + + + + + + document.file.view.kanban + document.file + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + +
+ + + +
+
+
+
+
+
+ + + xlsx + + + pdf + + + + txt + + + + pptx + + + + url + + + + docx + + + jpg + + + jpeg + + + + png + + + + csv + + + png + +
+
+
+
+
+ +
+
+
+ +
+
+ + +
+
+ +
+
+
+
+
+ +
+
+ + +
+
+
+ +
+
+
+ + + + Share + + Create Lead + + Create Task + + Create Mail + + Copy/Move + + Archive/Unarchive + + Delete + + + Lock Document + + + Unlock Document + + +
+
+
+
+ + + document.file.view.list + document.file + + + + + + + + + + + + + + + + document.file.view.form + document.file + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ + + document.file.view.search + document.file + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/enhanced_document_management/views/document_lock_views.xml b/enhanced_document_management/views/document_lock_views.xml new file mode 100644 index 000000000..8fd5fb350 --- /dev/null +++ b/enhanced_document_management/views/document_lock_views.xml @@ -0,0 +1,266 @@ + + + + + document.lock.view.form + document.lock + +
+ + + + + + +
+ +
+
+
+
+ + + document.lock.unlock.view.form + document.lock + +
+ + + + + + +
+ +
+
+
+
+ + + document.lock.share.view.form + document.lock + +
+ + + + + + +
+ +
+
+
+
+ + + document.lock.download.view.form + document.lock + +
+ + + + + + +
+ +
+
+
+
+ + + document.lock.lead.view.form + document.lock + +
+ + + + + + +
+ +
+
+
+
+ + + document.lock.task.view.form + document.lock + +
+ + + + + + +
+ +
+
+
+
+ + + document.lock.mail.view.form + document.lock + +
+ + + + + + +
+ +
+
+
+
+ + + document.lock.copy.view.form + document.lock + +
+ + + + + + +
+ +
+
+
+
+ + + document.lock.archive.view.form + document.lock + +
+ + + + + + +
+ +
+
+
+
+ + + document.lock.delete.view.form + document.lock + +
+ + + + + + +
+ +
+
+
+
+ + + document.lock.move.trash.view.form + document.lock + +
+ + + + + + +
+ +
+
+
+
+
diff --git a/enhanced_document_management/views/document_portal_templates.xml b/enhanced_document_management/views/document_portal_templates.xml new file mode 100644 index 000000000..2f64878d3 --- /dev/null +++ b/enhanced_document_management/views/document_portal_templates.xml @@ -0,0 +1,155 @@ + + + + + +