├── license.txt ├── pibidav ├── patches.txt ├── www │ └── __init__.py ├── config │ ├── __init__.py │ ├── desktop.py │ └── docs.py ├── modules.txt ├── pibidav │ ├── __init__.py │ ├── doctype │ │ ├── __init__.py │ │ ├── folder_set │ │ │ ├── __init__.py │ │ │ ├── test_folder_set.py │ │ │ ├── folder_set.js │ │ │ ├── folder_set.py │ │ │ ├── folder_set.json │ │ │ └── folder_set_tree.js │ │ ├── attachment_item │ │ │ ├── __init__.py │ │ │ ├── attachment_item.py │ │ │ └── attachment_item.json │ │ ├── file_category │ │ │ ├── __init__.py │ │ │ ├── test_file_category.py │ │ │ ├── file_category.js │ │ │ ├── file_category.py │ │ │ └── file_category.json │ │ ├── pibidav_addon │ │ │ ├── __init__.py │ │ │ ├── test_pibidav_addon.py │ │ │ ├── pibidav_addon.py │ │ │ ├── pibidav_addon.js │ │ │ └── pibidav_addon.json │ │ ├── reference_item │ │ │ ├── __init__.py │ │ │ ├── reference_item.py │ │ │ └── reference_item.json │ │ └── nextcloud_settings │ │ │ ├── __init__.py │ │ │ ├── test_nextcloud_settings.py │ │ │ ├── nextcloud_settings.js │ │ │ ├── nextcloud_settings.json │ │ │ └── nextcloud_settings.py │ ├── overrides │ │ ├── __init__.py │ │ └── file.py │ ├── workspace │ │ ├── pibitools │ │ │ └── pibitools.json │ │ └── pibidav │ │ │ └── pibidav.json │ └── custom.py ├── templates │ ├── __init__.py │ └── pages │ │ └── __init__.py ├── __init__.py ├── public │ ├── js │ │ ├── pibidav.bundle.js │ │ └── dist │ │ │ ├── nc_upload.js │ │ │ ├── nc_browser │ │ │ ├── TreeNode.vue │ │ │ ├── nc_browser.bundle.js │ │ │ └── NcBrowser.vue │ │ │ └── nc_pibidav.js │ ├── image │ │ ├── background.png │ │ ├── pibiCo_engine_largo.png │ │ └── favicon.svg │ ├── dist │ │ └── js │ │ │ ├── pibidav.bundle.VLVM6LDX.js │ │ │ └── pibidav.bundle.VLVM6LDX.js.map │ └── icons │ │ └── timeless │ │ └── icons.svg ├── jinja.py ├── fixtures │ ├── client_script.json │ ├── workspace.json │ └── custom_field.json ├── hooks.py └── translations │ └── es.csv ├── .gitignore ├── requirements.txt ├── MANIFEST.in ├── setup.py ├── CLAUDE.md └── README.md /license.txt: -------------------------------------------------------------------------------- 1 | License: MIT -------------------------------------------------------------------------------- /pibidav/patches.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pibidav/www/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pibidav/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pibidav/modules.txt: -------------------------------------------------------------------------------- 1 | Pibidav -------------------------------------------------------------------------------- /pibidav/pibidav/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pibidav/templates/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pibidav/templates/pages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/folder_set/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/attachment_item/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/file_category/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/pibidav_addon/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/reference_item/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pibidav/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | __version__ = '0.0.1' 3 | 4 | -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/nextcloud_settings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pibidav/pibidav/overrides/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.egg-info 4 | *.swp 5 | tags 6 | pibidav/docs/current -------------------------------------------------------------------------------- /pibidav/public/js/pibidav.bundle.js: -------------------------------------------------------------------------------- 1 | // public/js/pibidav.bundle.js 2 | 3 | import './dist/nc_upload.js'; -------------------------------------------------------------------------------- /pibidav/public/image/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/pibiDAV/version-15/pibidav/public/image/background.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # frappe -- https://github.com/frappe/frappe is installed via 'bench init' 2 | frappe 3 | payments 4 | erpnext 5 | hrms -------------------------------------------------------------------------------- /pibidav/public/image/pibiCo_engine_largo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/pibiDAV/version-15/pibidav/public/image/pibiCo_engine_largo.png -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/folder_set/test_folder_set.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, pibiCo and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | import unittest 6 | 7 | class TestFolderSet(unittest.TestCase): 8 | pass 9 | -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/file_category/test_file_category.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, pibiCo and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | import unittest 6 | 7 | class TestFileCategory(unittest.TestCase): 8 | pass 9 | -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/pibidav_addon/test_pibidav_addon.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, pibiCo and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | import unittest 6 | 7 | class TestPibiDAVAddon(unittest.TestCase): 8 | pass 9 | -------------------------------------------------------------------------------- /pibidav/public/dist/js/pibidav.bundle.VLVM6LDX.js: -------------------------------------------------------------------------------- 1 | (()=>{frappe.require?frappe.require("nc_browser.bundle.js"):frappe.ready(function(){frappe.require("nc_browser.bundle.js")});})(); 2 | //# sourceMappingURL=pibidav.bundle.VLVM6LDX.js.map 3 | -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/nextcloud_settings/test_nextcloud_settings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, pibiCo and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | import unittest 6 | 7 | class TestNextCloudSettings(unittest.TestCase): 8 | pass 9 | -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/file_category/file_category.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, pibiCo and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on('File Category', { 5 | // refresh: function(frm) { 6 | 7 | // } 8 | }); 9 | -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/file_category/file_category.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, pibiCo and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | class FileCategory(Document): 8 | pass 9 | -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/folder_set/folder_set.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, pibiCo and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on('Folder Set', { 5 | select_nc_folder: function(frm) { 6 | new frappe.ui.pibiDocs; 7 | } 8 | }); -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/reference_item/reference_item.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, pibiCo and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | class ReferenceItem(Document): 8 | pass 9 | -------------------------------------------------------------------------------- /pibidav/config/desktop.py: -------------------------------------------------------------------------------- 1 | from frappe import _ 2 | 3 | def get_data(): 4 | return [ 5 | { 6 | "module_name": "Pibidav", 7 | "color": "grey", 8 | "icon": "octicon octicon-file-directory", 9 | "type": "module", 10 | "label": _("Pibidav") 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/attachment_item/attachment_item.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, pibiCo and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | class AttachmentItem(Document): 8 | pass 9 | -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/pibidav_addon/pibidav_addon.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, pibiCo and contributors 2 | # For license information, please see license.txt 3 | 4 | import frappe 5 | from frappe import _ 6 | from frappe.model.document import Document 7 | 8 | class PibiDAVAddon(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /pibidav/public/js/dist/nc_upload.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, pibiCo and Contributors 2 | // MIT License. See license.txt 3 | 4 | if (frappe.require) { 5 | frappe.require("nc_browser.bundle.js"); 6 | } else { 7 | frappe.ready(function () { 8 | frappe.require("nc_browser.bundle.js"); 9 | }); 10 | } -------------------------------------------------------------------------------- /pibidav/config/docs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Configuration for docs 3 | """ 4 | 5 | # source_link = "https://github.com/[org_name]/pibidav" 6 | # docs_base_url = "https://[org_name].github.io/pibidav" 7 | # headline = "App that does everything" 8 | # sub_heading = "Yes, you got that right the first time, everything" 9 | 10 | def get_context(context): 11 | context.brand_html = "Pibidav" 12 | -------------------------------------------------------------------------------- /pibidav/jinja.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import frappe 4 | from datetime import tzinfo, timedelta, datetime 5 | 6 | def timestamp_to_date(value, format='%a %H:%M'): 7 | if value: 8 | return datetime.fromtimestamp(int(value)).strftime(format) 9 | 10 | def ts_to_date(value, format='%a %d/%m/%y %H:%M'): 11 | if value: 12 | return datetime.fromtimestamp(int(value)).strftime(format) -------------------------------------------------------------------------------- /pibidav/public/dist/js/pibidav.bundle.VLVM6LDX.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../../../../../apps/pibidav/pibidav/public/js/dist/nc_upload.js"], 4 | "sourcesContent": ["// Copyright (c) 2024, pibiCo and Contributors\n// MIT License. See license.txt\n\nif (frappe.require) {\n\tfrappe.require(\"nc_browser.bundle.js\");\n} else {\n\tfrappe.ready(function () {\n\t\tfrappe.require(\"nc_browser.bundle.js\");\n\t});\n}"], 5 | "mappings": "MAGI,OAAO,QACV,OAAO,QAAQ,sBAAsB,EAErC,OAAO,MAAM,UAAY,CACxB,OAAO,QAAQ,sBAAsB,CACtC,CAAC", 6 | "names": [] 7 | } 8 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include MANIFEST.in 2 | include requirements.txt 3 | include *.json 4 | include *.md 5 | include *.py 6 | include *.txt 7 | recursive-include pibidav *.css 8 | recursive-include pibidav *.csv 9 | recursive-include pibidav *.html 10 | recursive-include pibidav *.ico 11 | recursive-include pibidav *.js 12 | recursive-include pibidav *.json 13 | recursive-include pibidav *.md 14 | recursive-include pibidav *.png 15 | recursive-include pibidav *.py 16 | recursive-include pibidav *.svg 17 | recursive-include pibidav *.txt 18 | recursive-exclude pibidav *.pyc -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open("requirements.txt") as f: 4 | install_requires = f.read().strip().split("\n") 5 | 6 | # get version from __version__ variable in pibidav/__init__.py 7 | from pibidav import __version__ as version 8 | 9 | setup( 10 | name="pibidav", 11 | version=version, 12 | description="WebDAV, CalDAV and CardDAV Integration between Frappe and NextCloud", 13 | author="pibiCo", 14 | author_email="pibico.sl@gmail.com", 15 | packages=find_packages(), 16 | zip_safe=False, 17 | include_package_data=True, 18 | install_requires=install_requires 19 | ) 20 | -------------------------------------------------------------------------------- /pibidav/pibidav/overrides/file.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2022, PibiCo and contributors 3 | # For license information, please see license.txt 4 | 5 | import frappe 6 | from frappe.core.doctype.file.file import File 7 | from pibidav.pibidav.custom import get_file_content as custom_get_file_content 8 | 9 | 10 | class CustomFile(File): 11 | def get_content(self): 12 | """Override to handle URL-based files (NextCloud share links and other external URLs)""" 13 | 14 | # First try our custom handler 15 | custom_content = custom_get_file_content(self) 16 | if custom_content is not None: 17 | # Our handler returned content, use it 18 | return custom_content 19 | 20 | # Otherwise, use the parent class method for normal files 21 | return super().get_content() -------------------------------------------------------------------------------- /pibidav/pibidav/workspace/pibitools/pibitools.json: -------------------------------------------------------------------------------- 1 | { 2 | "charts": [], 3 | "content": "[{\"id\":\"w6ozBdED5x\",\"type\":\"header\",\"data\":{\"text\":\"Inicio > pibiTools
\",\"col\":12}}]", 4 | "creation": "2025-01-03 17:07:37.996618", 5 | "custom_blocks": [], 6 | "docstatus": 0, 7 | "doctype": "Workspace", 8 | "for_user": "", 9 | "hide_custom": 0, 10 | "icon": "pibico", 11 | "idx": 0, 12 | "indicator_color": "", 13 | "is_hidden": 0, 14 | "label": "pibiTools", 15 | "links": [], 16 | "modified": "2025-05-22 09:31:20.118853", 17 | "modified_by": "francisco.alaez@pibico.es", 18 | "module": "Pibidav", 19 | "name": "pibiTools", 20 | "number_cards": [], 21 | "owner": "Administrator", 22 | "parent_page": "", 23 | "public": 1, 24 | "quick_lists": [], 25 | "roles": [], 26 | "sequence_id": 11.0, 27 | "shortcuts": [], 28 | "title": "pibiTools" 29 | } -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/pibidav_addon/pibidav_addon.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, pibiCo and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on('PibiDAV Addon', { 5 | refresh(frm) { 6 | frm.add_custom_button(__("Refresh Files List"), function() { 7 | frappe.call({ 8 | method: "pibidav.pibidav.custom.update_attachment_item", 9 | args: { 10 | dt: frm.doc.ref_doctype, 11 | dn: frm.doc.ref_docname, 12 | } 13 | }).then(function(r) { 14 | frappe.msgprint(r.message); 15 | window.location.reload(); 16 | }); 17 | },__('Actions')); 18 | 19 | if (frm.doc.nc_folder) { 20 | frm.add_custom_button(__('Fetch NC Folder Link'), function () { 21 | frm.trigger('fetch_nc_folder_link'); 22 | }, __('Actions')); 23 | } 24 | }, 25 | fetch_nc_folder_link: function (frm) { 26 | frappe.call({ 27 | method: 'pibidav.pibidav.custom.fetch_nc_folder_internal_link_from_addon', 28 | args: { 29 | addon_name: frm.doc.name 30 | }, 31 | callback: function (r) { 32 | if (!r.exc) { 33 | // Set the fetched link into the `nc_folder_internal_link` field 34 | frm.set_value('nc_folder_internal_link', r.message); 35 | frm.refresh_field('nc_folder_internal_link'); 36 | frappe.msgprint(__('NC Folder Internal Link fetched successfully.')); 37 | } else { 38 | frappe.msgprint(__('Failed to fetch NC Folder Internal Link.')); 39 | } 40 | } 41 | }); 42 | } 43 | }); 44 | -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/nextcloud_settings/nextcloud_settings.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, pibiCo and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on('NextCloud Settings', { 5 | refresh: function(frm) { 6 | if (frm.doc.nc_backup_enable && frm.doc.nc_backup_url && frm.doc.nc_backup_username && frm.doc.nc_backup_token) { 7 | // add check credentials button 8 | frm.add_custom_button(__("Check Credentials"), function() { 9 | frappe.call({ 10 | method: "pibidav.pibidav.custom.check_nextcloud", 11 | args: { 12 | doc: frm.doc 13 | } 14 | }); 15 | },__("NC Commands")); 16 | // add backup button 17 | frm.add_custom_button(__("Backup Now"), function() { 18 | frappe.call({ 19 | method: "pibidav.pibidav.doctype.nextcloud_settings.nextcloud_settings.take_backup", 20 | freeze: true 21 | }); 22 | }).addClass("btn-primary"); 23 | } 24 | // Fill DocType Included with Data From Reference Item Table 25 | var includeArr = frm.doc.reference_item; 26 | var inclusion = ''; 27 | for (var i=0; i 2 |
3 |
4 | 10 |
11 |
12 | {{ node.label }} 13 |
14 |
15 |
16 | 34 |
35 | 36 | 37 | 60 | 61 | -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/attachment_item/attachment_item.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "creation": "2022-04-17 09:45:52.593268", 5 | "doctype": "DocType", 6 | "editable_grid": 1, 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "attachment", 10 | "file_category", 11 | "filename", 12 | "cb_01", 13 | "uploaded_to_nc", 14 | "nc_path", 15 | "nc_link", 16 | "nc_private", 17 | "nc_url" 18 | ], 19 | "fields": [ 20 | { 21 | "fieldname": "attachment", 22 | "fieldtype": "Link", 23 | "in_list_view": 1, 24 | "label": "Attachment", 25 | "options": "File", 26 | "reqd": 1 27 | }, 28 | { 29 | "fieldname": "file_category", 30 | "fieldtype": "Link", 31 | "in_list_view": 1, 32 | "label": "File Type", 33 | "options": "File Category" 34 | }, 35 | { 36 | "fetch_from": "attachment.file_name", 37 | "fieldname": "filename", 38 | "fieldtype": "Data", 39 | "in_list_view": 1, 40 | "label": "Filename", 41 | "read_only": 1 42 | }, 43 | { 44 | "default": "0", 45 | "fetch_from": "attachment.uploaded_to_nextcloud", 46 | "fieldname": "uploaded_to_nc", 47 | "fieldtype": "Check", 48 | "in_list_view": 1, 49 | "label": "Uploaded To NC", 50 | "read_only": 1 51 | }, 52 | { 53 | "fetch_from": "attachment.folder_path", 54 | "fieldname": "nc_path", 55 | "fieldtype": "Text", 56 | "in_list_view": 1, 57 | "label": "NC Path", 58 | "read_only": 1 59 | }, 60 | { 61 | "fetch_from": "attachment.share_link", 62 | "fieldname": "nc_link", 63 | "fieldtype": "Data", 64 | "in_list_view": 1, 65 | "label": "NC Share Link", 66 | "read_only": 1 67 | }, 68 | { 69 | "fieldname": "nc_private", 70 | "fieldtype": "Data", 71 | "in_list_view": 1, 72 | "label": "NC Private Link", 73 | "read_only": 1 74 | }, 75 | { 76 | "fieldname": "nc_url", 77 | "fieldtype": "Read Only", 78 | "label": "NC URL" 79 | }, 80 | { 81 | "fieldname": "cb_01", 82 | "fieldtype": "Column Break", 83 | "label": "NexCloud" 84 | } 85 | ], 86 | "index_web_pages_for_search": 1, 87 | "istable": 1, 88 | "links": [], 89 | "modified": "2022-05-03 17:53:23.263098", 90 | "modified_by": "Administrator", 91 | "module": "Pibidav", 92 | "name": "Attachment Item", 93 | "owner": "Administrator", 94 | "permissions": [], 95 | "sort_field": "modified", 96 | "sort_order": "DESC" 97 | } -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/reference_item/reference_item.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "creation": "2022-04-28 17:06:28.791315", 5 | "doctype": "DocType", 6 | "editable_grid": 1, 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "reference_doctype", 10 | "reference_docfield", 11 | "cb_01", 12 | "nc_enable", 13 | "create_nc_folder", 14 | "use_default_folder", 15 | "nc_folder", 16 | "file_storage_mode" 17 | ], 18 | "fields": [ 19 | { 20 | "fieldname": "reference_doctype", 21 | "fieldtype": "Link", 22 | "in_list_view": 1, 23 | "label": "Reference DocType", 24 | "options": "DocType", 25 | "reqd": 1 26 | }, 27 | { 28 | "fieldname": "reference_docfield", 29 | "fieldtype": "Small Text", 30 | "in_list_view": 1, 31 | "label": "DocFields" 32 | }, 33 | { 34 | "depends_on": "eval:doc.use_default_folder;", 35 | "description": "Default NextCloud Folder Path Complete from Root / and logged as NC SuperUser", 36 | "fieldname": "nc_folder", 37 | "fieldtype": "Small Text", 38 | "in_list_view": 1, 39 | "label": "NC Default Folder" 40 | }, 41 | { 42 | "default": "0", 43 | "fieldname": "use_default_folder", 44 | "fieldtype": "Check", 45 | "in_list_view": 1, 46 | "label": "Use Default Folder" 47 | }, 48 | { 49 | "fieldname": "cb_01", 50 | "fieldtype": "Column Break" 51 | }, 52 | { 53 | "default": "0", 54 | "fieldname": "create_nc_folder", 55 | "fieldtype": "Check", 56 | "label": "Create NC Folder" 57 | }, 58 | { 59 | "default": "0", 60 | "fieldname": "nc_enable", 61 | "fieldtype": "Check", 62 | "label": "NC Enable" 63 | }, 64 | { 65 | "default": "keep_in_frappe", 66 | "fieldname": "file_storage_mode", 67 | "fieldtype": "Select", 68 | "label": "File Storage Mode", 69 | "options": "keep_in_frappe\nreplace_with_link", 70 | "description": "Keep in Frappe: Files remain in Frappe and are also uploaded to NextCloud\nReplace with Link: Files are uploaded to NextCloud and replaced with links in Frappe" 71 | } 72 | ], 73 | "index_web_pages_for_search": 1, 74 | "istable": 1, 75 | "links": [], 76 | "modified": "2022-05-11 11:23:23.362770", 77 | "modified_by": "Administrator", 78 | "module": "Pibidav", 79 | "name": "Reference Item", 80 | "owner": "Administrator", 81 | "permissions": [], 82 | "sort_field": "modified", 83 | "sort_order": "DESC" 84 | } -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/folder_set/folder_set.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "creation": "2022-04-21 19:00:30.884664", 5 | "doctype": "DocType", 6 | "document_type": "Setup", 7 | "editable_grid": 1, 8 | "engine": "InnoDB", 9 | "field_order": [ 10 | "is_group", 11 | "title", 12 | "cb_01", 13 | "select_nc_folder", 14 | "old_parent", 15 | "root_parent", 16 | "parent_folder_set", 17 | "nc_folder", 18 | "lft", 19 | "rgt" 20 | ], 21 | "fields": [ 22 | { 23 | "default": "0", 24 | "fieldname": "is_group", 25 | "fieldtype": "Check", 26 | "label": "Is Folder" 27 | }, 28 | { 29 | "fieldname": "title", 30 | "fieldtype": "Data", 31 | "in_list_view": 1, 32 | "label": "Title", 33 | "reqd": 1, 34 | "unique": 1 35 | }, 36 | { 37 | "fieldname": "cb_01", 38 | "fieldtype": "Column Break" 39 | }, 40 | { 41 | "fieldname": "old_parent", 42 | "fieldtype": "Link", 43 | "label": "Old Parent", 44 | "options": "Folder Set" 45 | }, 46 | { 47 | "fieldname": "root_parent", 48 | "fieldtype": "Link", 49 | "label": "Root Parent", 50 | "options": "Folder Set" 51 | }, 52 | { 53 | "fieldname": "parent_folder_set", 54 | "fieldtype": "Link", 55 | "label": "Parent Folder Set", 56 | "options": "Folder Set" 57 | }, 58 | { 59 | "fieldname": "nc_folder", 60 | "fieldtype": "Text", 61 | "label": "NC Folder" 62 | }, 63 | { 64 | "fieldname": "lft", 65 | "fieldtype": "Int", 66 | "hidden": 1, 67 | "label": "Left", 68 | "no_copy": 1, 69 | "read_only": 1 70 | }, 71 | { 72 | "fieldname": "rgt", 73 | "fieldtype": "Int", 74 | "hidden": 1, 75 | "label": "Right", 76 | "no_copy": 1, 77 | "read_only": 1 78 | }, 79 | { 80 | "fieldname": "select_nc_folder", 81 | "fieldtype": "Button", 82 | "label": "Select NC Folder" 83 | } 84 | ], 85 | "icon": "folder", 86 | "index_web_pages_for_search": 1, 87 | "is_tree": 1, 88 | "links": [], 89 | "modified": "2022-05-11 10:34:30.594028", 90 | "modified_by": "Administrator", 91 | "module": "Pibidav", 92 | "name": "Folder Set", 93 | "nsm_parent_field": "parent_folder_set", 94 | "owner": "Administrator", 95 | "permissions": [ 96 | { 97 | "create": 1, 98 | "delete": 1, 99 | "email": 1, 100 | "export": 1, 101 | "print": 1, 102 | "read": 1, 103 | "report": 1, 104 | "role": "System Manager", 105 | "share": 1, 106 | "write": 1 107 | }, 108 | { 109 | "create": 1, 110 | "delete": 1, 111 | "email": 1, 112 | "export": 1, 113 | "print": 1, 114 | "read": 1, 115 | "report": 1, 116 | "role": "All", 117 | "share": 1, 118 | "write": 1 119 | } 120 | ], 121 | "quick_entry": 1, 122 | "search_fields": "root_parent,parent_folder_set", 123 | "sort_field": "modified", 124 | "sort_order": "DESC", 125 | "title_field": "title" 126 | } -------------------------------------------------------------------------------- /pibidav/pibidav/workspace/pibidav/pibidav.json: -------------------------------------------------------------------------------- 1 | { 2 | "charts": [], 3 | "content": "[{\"id\":\"azQJesz1jX\",\"type\":\"header\",\"data\":{\"text\":\"Inicio > pibiTools > pibiDAV
\",\"col\":12}},{\"id\":\"XQTScKtokJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Documents\",\"col\":4}},{\"id\":\"OIx7Apgo_M\",\"type\":\"card\",\"data\":{\"card_name\":\"Configuraci\u00f3n\",\"col\":4}}]", 4 | "creation": "2025-01-03 17:07:38.077636", 5 | "custom_blocks": [], 6 | "docstatus": 0, 7 | "doctype": "Workspace", 8 | "for_user": "", 9 | "hide_custom": 0, 10 | "icon": "pibidav", 11 | "idx": 0, 12 | "indicator_color": "", 13 | "is_hidden": 0, 14 | "label": "pibiDAV", 15 | "links": [ 16 | { 17 | "hidden": 0, 18 | "is_query_report": 0, 19 | "label": "Documents", 20 | "link_count": 4, 21 | "link_type": "DocType", 22 | "onboard": 0, 23 | "type": "Card Break" 24 | }, 25 | { 26 | "hidden": 0, 27 | "is_query_report": 0, 28 | "label": "pibiDAV Addon", 29 | "link_count": 0, 30 | "link_to": "PibiDAV Addon", 31 | "link_type": "DocType", 32 | "onboard": 0, 33 | "type": "Link" 34 | }, 35 | { 36 | "hidden": 0, 37 | "is_query_report": 0, 38 | "label": "Tag", 39 | "link_count": 0, 40 | "link_to": "Tag", 41 | "link_type": "DocType", 42 | "onboard": 0, 43 | "type": "Link" 44 | }, 45 | { 46 | "hidden": 0, 47 | "is_query_report": 0, 48 | "label": "Contact", 49 | "link_count": 0, 50 | "link_to": "Contact", 51 | "link_type": "DocType", 52 | "onboard": 0, 53 | "type": "Link" 54 | }, 55 | { 56 | "hidden": 0, 57 | "is_query_report": 0, 58 | "label": "Event", 59 | "link_count": 0, 60 | "link_to": "Event", 61 | "link_type": "DocType", 62 | "onboard": 0, 63 | "type": "Link" 64 | }, 65 | { 66 | "hidden": 0, 67 | "is_query_report": 0, 68 | "label": "Configuraci\u00f3n", 69 | "link_count": 3, 70 | "link_type": "DocType", 71 | "onboard": 0, 72 | "type": "Card Break" 73 | }, 74 | { 75 | "hidden": 0, 76 | "is_query_report": 0, 77 | "label": "File Category", 78 | "link_count": 0, 79 | "link_to": "File Category", 80 | "link_type": "DocType", 81 | "onboard": 0, 82 | "type": "Link" 83 | }, 84 | { 85 | "hidden": 0, 86 | "is_query_report": 0, 87 | "label": "NextCloud Settings", 88 | "link_count": 0, 89 | "link_to": "NextCloud Settings", 90 | "link_type": "DocType", 91 | "onboard": 0, 92 | "type": "Link" 93 | }, 94 | { 95 | "hidden": 0, 96 | "is_query_report": 0, 97 | "label": "Folder Set", 98 | "link_count": 0, 99 | "link_to": "Folder Set", 100 | "link_type": "DocType", 101 | "onboard": 0, 102 | "type": "Link" 103 | } 104 | ], 105 | "modified": "2025-05-22 09:29:32.469945", 106 | "modified_by": "francisco.alaez@pibico.es", 107 | "module": "Pibidav", 108 | "name": "pibiDAV", 109 | "number_cards": [], 110 | "owner": "Administrator", 111 | "parent_page": "", 112 | "public": 1, 113 | "quick_lists": [], 114 | "roles": [ 115 | { 116 | "role": "Docs Controller" 117 | }, 118 | { 119 | "role": "Docs User" 120 | }, 121 | { 122 | "role": "NextCloud User" 123 | } 124 | ], 125 | "sequence_id": 6.0, 126 | "shortcuts": [], 127 | "title": "pibiDAV" 128 | } -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/pibidav_addon/pibidav_addon.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "autoname": "format:pbc_{ref_docname}", 5 | "creation": "2022-05-05 13:59:52.156946", 6 | "doctype": "DocType", 7 | "editable_grid": 1, 8 | "engine": "InnoDB", 9 | "field_order": [ 10 | "ref_doctype", 11 | "cb_00", 12 | "ref_docname", 13 | "sb_01", 14 | "nc_enable", 15 | "nc_folder", 16 | "sb_02", 17 | "nc_folder_internal_link", 18 | "nc_folder_share_link", 19 | "column_break_12", 20 | "secret", 21 | "sharing_option", 22 | "sb_03", 23 | "attachment_item" 24 | ], 25 | "fields": [ 26 | { 27 | "fieldname": "ref_doctype", 28 | "fieldtype": "Link", 29 | "in_list_view": 1, 30 | "label": "Reference DocType", 31 | "options": "DocType", 32 | "reqd": 1 33 | }, 34 | { 35 | "fieldname": "cb_00", 36 | "fieldtype": "Column Break" 37 | }, 38 | { 39 | "fetch_from": "doctype.name", 40 | "fieldname": "ref_docname", 41 | "fieldtype": "Dynamic Link", 42 | "in_list_view": 1, 43 | "label": "Reference DocName", 44 | "options": "ref_doctype", 45 | "reqd": 1 46 | }, 47 | { 48 | "fieldname": "sb_01", 49 | "fieldtype": "Section Break", 50 | "label": "NC File Upload" 51 | }, 52 | { 53 | "fieldname": "nc_folder", 54 | "fieldtype": "Small Text", 55 | "label": "NC Folder" 56 | }, 57 | { 58 | "fieldname": "sb_02", 59 | "fieldtype": "Section Break", 60 | "label": "NC Folder Creation" 61 | }, 62 | { 63 | "fieldname": "secret", 64 | "fieldtype": "Data", 65 | "label": "Secret", 66 | "read_only": 1 67 | }, 68 | { 69 | "fieldname": "column_break_12", 70 | "fieldtype": "Column Break" 71 | }, 72 | { 73 | "fieldname": "nc_folder_internal_link", 74 | "fieldtype": "Read Only", 75 | "label": "NC Folder Internal Link" 76 | }, 77 | { 78 | "fieldname": "nc_folder_share_link", 79 | "fieldtype": "Read Only", 80 | "label": "NC Folder Share Link" 81 | }, 82 | { 83 | "default": "31-Upload and Edit", 84 | "fieldname": "sharing_option", 85 | "fieldtype": "Select", 86 | "label": "Sharing Option", 87 | "options": "\n4-Upload Only\n17-Read Only\n31-Upload and Edit", 88 | "read_only": 1 89 | }, 90 | { 91 | "fieldname": "sb_03", 92 | "fieldtype": "Section Break", 93 | "label": "NC File List" 94 | }, 95 | { 96 | "fieldname": "attachment_item", 97 | "fieldtype": "Table", 98 | "label": "Attachment Item", 99 | "options": "Attachment Item" 100 | }, 101 | { 102 | "default": "0", 103 | "fieldname": "nc_enable", 104 | "fieldtype": "Check", 105 | "label": "NC Enable" 106 | } 107 | ], 108 | "index_web_pages_for_search": 1, 109 | "links": [], 110 | "modified": "2022-05-10 13:26:58.019254", 111 | "modified_by": "Administrator", 112 | "module": "Pibidav", 113 | "name": "PibiDAV Addon", 114 | "owner": "Administrator", 115 | "permissions": [ 116 | { 117 | "create": 1, 118 | "delete": 1, 119 | "email": 1, 120 | "export": 1, 121 | "print": 1, 122 | "read": 1, 123 | "report": 1, 124 | "role": "System Manager", 125 | "share": 1, 126 | "write": 1 127 | }, 128 | { 129 | "create": 1, 130 | "delete": 1, 131 | "email": 1, 132 | "export": 1, 133 | "print": 1, 134 | "read": 1, 135 | "report": 1, 136 | "role": "All", 137 | "share": 1, 138 | "write": 1 139 | } 140 | ], 141 | "sort_field": "modified", 142 | "sort_order": "DESC", 143 | "track_changes": 1 144 | } -------------------------------------------------------------------------------- /pibidav/public/js/dist/nc_browser/nc_browser.bundle.js: -------------------------------------------------------------------------------- 1 | import { createApp, watch } from "vue"; 2 | import NcBrowserComponent from './NcBrowser.vue'; 3 | 4 | class Browser { 5 | constructor(options = {}) { 6 | const { wrapper, targetFolder, ...componentProps } = options; 7 | this.targetFolder = targetFolder; 8 | this.initializeWrapper(wrapper); 9 | this.initializeVueComponent({ 10 | ...componentProps, 11 | root_folder: "/", // Set root folder to / for full navigation 12 | initial_folder: targetFolder // Set initial folder to target 13 | }); 14 | } 15 | 16 | initializeWrapper(wrapper) { 17 | if (!wrapper) { 18 | this.make_dialog(); 19 | } else { 20 | this.wrapper = wrapper.get ? wrapper.get(0) : wrapper; 21 | } 22 | } 23 | 24 | initializeVueComponent(props) { 25 | const app = createApp(NcBrowserComponent, { 26 | show_upload_button: !this.dialog, 27 | root_folder: "/", // Allow full navigation from root 28 | initial_folder: this.targetFolder, // Start at target folder 29 | ...props 30 | }); 31 | this.browser = app.mount(this.wrapper); 32 | this.setupWatchers(); 33 | } 34 | 35 | setupWatchers() { 36 | watch( 37 | () => this.browser.close_dialog, 38 | (close_dialog) => { 39 | if (close_dialog) this.dialog?.hide(); 40 | } 41 | ); 42 | } 43 | 44 | select_folder() { 45 | this.dialog?.get_primary_btn().prop('disabled', true); 46 | return this.browser.select_folder(); 47 | } 48 | 49 | make_dialog() { 50 | this.dialog = new frappe.ui.Dialog({ 51 | title: __('Select NextCloud Folder'), 52 | size: 'large', 53 | primary_action_label: __('Select'), 54 | primary_action: () => this.handlePrimaryAction() 55 | }); 56 | this.wrapper = this.dialog.body; 57 | 58 | this.initializeVueComponent({ 59 | show_browser: true, 60 | root_folder: "/", // Full navigation from root 61 | initial_folder: this.targetFolder // Start at target folder 62 | }); 63 | this.dialog.show(); 64 | this.setupDialogCleanup(); 65 | } 66 | 67 | handlePrimaryAction() { 68 | const nc_folder = this.select_folder(); 69 | const [doctype, docname] = this.getDocumentInfo(); 70 | if (nc_folder.is_folder) { 71 | frappe.db.set_value("PibiDAV Addon", `pbc_${docname}`, { 72 | "nc_folder": nc_folder.path, 73 | "nc_enable": 1 74 | }); 75 | } else { 76 | frappe.msgprint(__('You have selected a file and not a folder'), nc_folder.file_name); 77 | } 78 | this.dialog.hide(); 79 | this.postSelectionAction(doctype); 80 | } 81 | 82 | getDocumentInfo() { 83 | const dtdn = this.wrapper.ownerDocument.body.getAttribute('data-route').replace('Form/', ''); 84 | const pos = dtdn.lastIndexOf('/'); 85 | const docname = dtdn.substr(pos + 1); 86 | const doctype = dtdn.replace('/' + docname, ''); 87 | return [doctype, docname]; 88 | } 89 | 90 | postSelectionAction(doctype) { 91 | if (doctype === 'Folder Set') { 92 | window.location.reload(); 93 | } else { 94 | document.querySelector('.add-attachment-btn').click(); 95 | } 96 | } 97 | 98 | setupDialogCleanup() { 99 | this.dialog.$wrapper.on('hidden.bs.modal', function() { 100 | $(this).data('bs.modal', null); 101 | $(this).remove(); 102 | }); 103 | } 104 | } 105 | 106 | frappe.provide("frappe.ui"); 107 | frappe.ui.pibiDocs = Browser; 108 | export default Browser; -------------------------------------------------------------------------------- /pibidav/public/image/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | 27 | 28 | 29 | 30 | 32 | 53 | 57 | 64 | 71 | 77 | 83 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /pibidav/fixtures/client_script.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "docstatus": 0, 4 | "doctype": "Client Script", 5 | "dt": "Purchase Invoice", 6 | "enabled": 1, 7 | "modified": "2025-06-15 00:27:09.177410", 8 | "module": "Pibidav", 9 | "name": "Purchase Invoice PDF Fill", 10 | "script": " // Client Script for Purchase Invoice\n // This script automatically fills the pdf_file field ONLY with NextCloud public share links\n\n frappe.ui.form.on('Purchase Invoice', {\n refresh: function(frm) {\n // Only try to fill if the field exists, is empty, and doc is saved\n if (frm.fields_dict.pdf_file && !frm.doc.pdf_file && !frm.doc.__islocal) {\n setTimeout(() => {\n fill_pdf_field_from_nextcloud_only(frm);\n }, 1500);\n }\n },\n\n after_save: function(frm) {\n // Only try to fill if the field exists and is empty\n if (frm.fields_dict.pdf_file && !frm.doc.pdf_file) {\n setTimeout(() => {\n fill_pdf_field_from_nextcloud_only(frm);\n }, 2500);\n }\n }\n });\n\n function fill_pdf_field_from_nextcloud_only(frm) {\n // Double check conditions before proceeding\n if (!frm.fields_dict.pdf_file || frm.doc.pdf_file || frm.doc.__islocal) {\n return;\n }\n\n // Query for PDF files that are uploaded to NextCloud\n frappe.db.get_list('File', {\n filters: {\n attached_to_doctype: frm.doctype,\n attached_to_name: frm.docname,\n file_name: ['like', '%.pdf'],\n uploaded_to_nextcloud: 1 // Only files uploaded to NextCloud\n },\n fields: ['name', 'file_url', 'file_name', 'share_link', 'uploaded_to_nextcloud'],\n order_by: 'creation desc',\n limit: 10\n }).then(files => {\n if (!files || files.length === 0) {\n return;\n }\n\n // Find the first valid NextCloud URL\n for (let file of files) {\n let url_to_use = null;\n\n // Check share_link first (this is the NextCloud public share link)\n if (file.share_link &&\n typeof file.share_link === 'string' &&\n file.share_link.startsWith('http') &&\n !file.share_link.includes('/files/') &&\n !file.share_link.includes('/private/')) {\n url_to_use = file.share_link;\n }\n // Then check file_url as fallback\n else if (file.file_url &&\n typeof file.file_url === 'string' &&\n file.file_url.startsWith('http') &&\n !file.file_url.includes('/files/') &&\n !file.file_url.includes('/private/') &&\n !file.file_url.includes(window.location.hostname)) { // Exclude local domain\n url_to_use = file.file_url;\n }\n\n // If we found a valid NextCloud URL, use it\n if (url_to_use) {\n frm.set_value('pdf_file', url_to_use);\n frappe.show_alert({\n message: __('PDF field updated with NextCloud link'),\n indicator: 'green'\n }, 5);\n return; // Stop after first valid URL\n }\n }\n }).catch(err => {\n console.error(\"Error fetching NextCloud PDFs:\", err);\n });\n }\n\n // Add manual button only\n frappe.ui.form.on('Purchase Invoice', {\n refresh: function(frm) {\n if (frm.fields_dict.pdf_file && !frm.doc.__islocal) {\n frm.add_custom_button(__('Fill PDF from NextCloud'), function() {\n // Clear field if it contains a local path\n if (frm.doc.pdf_file &&\n (frm.doc.pdf_file.includes('/files/') ||\n frm.doc.pdf_file.includes('/private/') ||\n !frm.doc.pdf_file.startsWith('http'))) {\n frm.set_value('pdf_file', '');\n }\n\n // Wait a bit then try to fill\n setTimeout(() => {\n fill_pdf_field_from_nextcloud_only(frm);\n }, 500);\n }, __('Actions'));\n }\n }\n });", 11 | "view": "Form" 12 | } 13 | ] -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Project Overview 6 | 7 | pibiDAV is a Frappe App that integrates WebDAV functionality with NextCloud Server, enabling automatic file synchronization between Frappe and NextCloud. When files are uploaded to Frappe, they are simultaneously uploaded to NextCloud with automatic tagging and folder organization. 8 | 9 | ## Development Commands 10 | 11 | ### Build Commands 12 | ```bash 13 | # Build JavaScript and CSS assets for the pibidav app 14 | bench build --app pibidav 15 | 16 | # Build in production mode (minified) 17 | bench build --app pibidav --production 18 | 19 | # Force rebuild instead of downloading available assets 20 | bench build --app pibidav --force 21 | ``` 22 | 23 | ### Test Commands 24 | ```bash 25 | # Run all tests for pibidav app 26 | bench run-tests --app pibidav 27 | 28 | # Run tests for specific doctype 29 | bench run-tests --app pibidav --doctype "NextCloud Settings" 30 | 31 | # Run tests with coverage 32 | bench run-tests --app pibidav --coverage 33 | 34 | # Run UI tests 35 | bench run-ui-tests 36 | ``` 37 | 38 | ### Database and Migration 39 | ```bash 40 | # Run patches and sync schema after code changes 41 | bench migrate 42 | 43 | # Clear cache after making changes 44 | bench clear-cache 45 | ``` 46 | 47 | ### Installation 48 | ```bash 49 | # Install the app to a site 50 | bench --site site_name install-app pibidav 51 | 52 | # Get the app from repository 53 | bench get-app pibidav --branch version-15 https://github.com/pibico/pibidav.git 54 | ``` 55 | 56 | ## Architecture Overview 57 | 58 | ### Backend Architecture 59 | 60 | 1. **Frappe Integration via hooks.py** 61 | - Dynamically injects JavaScript into configured doctypes (Customer, Sales Invoice, etc.) 62 | - Hooks into File doctype's after_insert event for automatic NextCloud upload 63 | - Scheduled tasks for daily/weekly backups to NextCloud 64 | - Defines app-level JavaScript bundles and icons 65 | 66 | 2. **Core Module Structure** 67 | - `pibidav/nextcloud.py`: Main NextCloud API integration using embedded pyocclient 68 | - `pibidav/custom.py`: Custom functions for file operations and folder management 69 | - DocTypes for configuration and data storage: 70 | - `NextCloud Settings`: Global configuration and credentials 71 | - `PibiDAV Addon`: Extension data for integrated doctypes 72 | - `Folder Set`: Template-based folder structure creation 73 | 74 | 3. **Integration Pattern** 75 | - Each integrated doctype gets a parallel "addon" document (prefixed with 'pbc_') 76 | - Addons store NextCloud-specific data (folder paths, references, attachments) 77 | - File uploads trigger automatic NextCloud synchronization with tagging 78 | 79 | ### Frontend Architecture 80 | 81 | 1. **JavaScript Module System** 82 | - `nc_pibidav.js`: Main form integration that adds NextCloud buttons to doctypes 83 | - `nc_browser.bundle.js`: Vue 3 application for folder browsing and selection 84 | - Lazy-loaded components for optimal performance 85 | 86 | 2. **Vue.js Components** 87 | - `NcBrowser.vue`: Main file browser with tree navigation 88 | - `TreeNode.vue`: Recursive component for folder tree rendering 89 | - Uses Vue 3 composition API with TypeScript support 90 | 91 | 3. **API Integration** 92 | - Server calls via `frappe.call()` for NextCloud operations 93 | - Real-time folder browsing and file listing 94 | - Folder creation with template support 95 | 96 | ### Key Integration Points 97 | 98 | 1. **Authentication Flow** 99 | - SuperUser credentials stored in NextCloud Settings 100 | - Per-user credentials stored in User doctype with "NextCloud User" role 101 | - Automatic credential validation and connection testing 102 | 103 | 2. **File Upload Pipeline** 104 | - File uploaded to Frappe → triggers `after_insert` hook 105 | - Checks if parent doctype is NC-enabled 106 | - Uploads to selected NextCloud folder with automatic tagging 107 | - Creates public share links automatically 108 | 109 | 3. **Folder Structure Management** 110 | - Template-based folder creation using Folder Set doctype 111 | - Recursive folder structure replication in NextCloud 112 | - Automatic folder naming with abbreviations and descriptions 113 | 114 | ## Important Implementation Details 115 | 116 | 1. **SSL Requirements**: Both Frappe and NextCloud must have valid SSL certificates (wildcard certificates not supported) 117 | 118 | 2. **Developer Mode**: Requires `developer_mode = 1` in site_config.json 119 | 120 | 3. **Tagging System**: Files are automatically tagged based on configured doctype fields (e.g., customer name, tax_id) 121 | 122 | 4. **Backup Integration**: Automatic daily/weekly backups to NextCloud with version control 123 | 124 | 5. **Error Handling**: Graceful fallback if NextCloud is unreachable - files still save to Frappe 125 | 126 | 6. **Performance**: Uses lazy loading and caching for optimal performance with large folder structures -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/nextcloud_settings/nextcloud_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "creation": "2022-04-17 10:44:49.736441", 5 | "doctype": "DocType", 6 | "editable_grid": 1, 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "nc_backup_enable", 10 | "nc_backup_url", 11 | "cb_01", 12 | "nc_backup_username", 13 | "nc_backup_token", 14 | "sb_nc_credentials", 15 | "nc_backup_path", 16 | "backup_frequency", 17 | "cb_nc_credentials", 18 | "send_notifications_to", 19 | "send_email_for_successful_backup", 20 | "backup_files", 21 | "sb_doctype_selection", 22 | "nc_doctype_included", 23 | "cb_nc_selection", 24 | "nc_doctype_excluded", 25 | "sb_settings", 26 | "reference_item" 27 | ], 28 | "fields": [ 29 | { 30 | "default": "0", 31 | "fieldname": "nc_backup_enable", 32 | "fieldtype": "Check", 33 | "in_list_view": 1, 34 | "label": "NextCloud Enable" 35 | }, 36 | { 37 | "collapsible": 1, 38 | "depends_on": "eval:doc.nc_backup_enable;", 39 | "fieldname": "sb_nc_credentials", 40 | "fieldtype": "Section Break", 41 | "label": "NextCloud Backup Section" 42 | }, 43 | { 44 | "depends_on": "eval:doc.nc_backup_enable;", 45 | "description": "URL for your NextCloud instance with http:// or https://, with or without port number :nnnn, and without / at the end (i.e. https://nextcloud-instance.com:4443)", 46 | "fieldname": "nc_backup_url", 47 | "fieldtype": "Data", 48 | "in_list_view": 1, 49 | "label": "NextCloud URL", 50 | "options": "URL" 51 | }, 52 | { 53 | "description": "Full Path Route with / at the end where backups will be uploaded. If not provided, by default is /Frappe Backups/", 54 | "fieldname": "nc_backup_path", 55 | "fieldtype": "Data", 56 | "in_list_view": 1, 57 | "label": "NextCloud Backup Dir" 58 | }, 59 | { 60 | "fieldname": "cb_nc_credentials", 61 | "fieldtype": "Column Break" 62 | }, 63 | { 64 | "depends_on": "eval:doc.nc_backup_enable;", 65 | "description": "NextCloud Username with enough privileges for uploading to the selected Backup Dir (case sensitive)", 66 | "fieldname": "nc_backup_username", 67 | "fieldtype": "Data", 68 | "in_list_view": 1, 69 | "label": "NextCloud SuperUser" 70 | }, 71 | { 72 | "depends_on": "eval:doc.nc_backup_enable;", 73 | "fieldname": "nc_backup_token", 74 | "fieldtype": "Password", 75 | "in_list_view": 1, 76 | "label": "NextCloud SuperUser Token" 77 | }, 78 | { 79 | "fieldname": "backup_frequency", 80 | "fieldtype": "Select", 81 | "in_list_view": 1, 82 | "label": "Backup Frequency", 83 | "options": "\nDaily\nWeekly" 84 | }, 85 | { 86 | "default": "1", 87 | "description": "Backup public and private files along with the database and site config", 88 | "fieldname": "backup_files", 89 | "fieldtype": "Check", 90 | "label": "Backup Files" 91 | }, 92 | { 93 | "description": "Only one recipient can be provided", 94 | "fieldname": "send_notifications_to", 95 | "fieldtype": "Data", 96 | "in_list_view": 1, 97 | "label": "Send Notifications To", 98 | "options": "Email" 99 | }, 100 | { 101 | "default": "0", 102 | "fieldname": "send_email_for_successful_backup", 103 | "fieldtype": "Check", 104 | "label": "Send Email for Successful Backup" 105 | }, 106 | { 107 | "collapsible": 1, 108 | "depends_on": "eval:doc.nc_backup_enable;", 109 | "fieldname": "sb_doctype_selection", 110 | "fieldtype": "Section Break", 111 | "label": "DocType Selection" 112 | }, 113 | { 114 | "description": "Exact DocType Name wihout quotes, whithout spaces and separated by comma (i.e. Sales Invoice,Purchase Order)", 115 | "fieldname": "nc_doctype_included", 116 | "fieldtype": "Small Text", 117 | "label": "NC Doctypes Included", 118 | "read_only": 1 119 | }, 120 | { 121 | "fieldname": "cb_nc_selection", 122 | "fieldtype": "Column Break" 123 | }, 124 | { 125 | "default": "NextCloud Settings,File,Folder Set,PibiDAV Addon", 126 | "fieldname": "nc_doctype_excluded", 127 | "fieldtype": "Small Text", 128 | "label": "NC Doctypes Excluded" 129 | }, 130 | { 131 | "depends_on": "eval:doc.nc_backup_enable;", 132 | "fieldname": "sb_settings", 133 | "fieldtype": "Section Break", 134 | "label": "Settings Section" 135 | }, 136 | { 137 | "fieldname": "reference_item", 138 | "fieldtype": "Table", 139 | "label": "Inclusions", 140 | "options": "Reference Item" 141 | }, 142 | { 143 | "fieldname": "cb_01", 144 | "fieldtype": "Column Break" 145 | } 146 | ], 147 | "index_web_pages_for_search": 1, 148 | "issingle": 1, 149 | "links": [], 150 | "modified": "2022-05-11 22:03:17.161609", 151 | "modified_by": "Administrator", 152 | "module": "Pibidav", 153 | "name": "NextCloud Settings", 154 | "owner": "Administrator", 155 | "permissions": [ 156 | { 157 | "create": 1, 158 | "delete": 1, 159 | "email": 1, 160 | "print": 1, 161 | "read": 1, 162 | "role": "System Manager", 163 | "share": 1, 164 | "write": 1 165 | }, 166 | { 167 | "create": 1, 168 | "delete": 1, 169 | "email": 1, 170 | "print": 1, 171 | "read": 1, 172 | "role": "Docs Controller", 173 | "share": 1, 174 | "write": 1 175 | } 176 | ], 177 | "sort_field": "modified", 178 | "sort_order": "DESC", 179 | "track_changes": 1 180 | } -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/folder_set/folder_set_tree.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, pibiCo and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.provide("frappe.treeview_settings"); 5 | frappe.provide('frappe.views'); 6 | 7 | frappe.treeview_settings['Folder Set'] = { 8 | breadcrumbs: "Templates", 9 | title: __('Folder Set'), 10 | get_tree_root: false, 11 | root_label: "", 12 | get_tree_nodes: 'pibidav.pibidav.doctype.folder_set.folder_set.get_children', 13 | filters: [ 14 | { 15 | fieldname: "root_parent", 16 | fieldtype: "Link", 17 | options: "Folder Set", 18 | label: __("Tree"), 19 | default: "BPJ Big Project", 20 | get_query: function() { 21 | var args = [ 22 | ["Folder Set", 'is_group', '=', 1], 23 | ["Folder Set", 'parent_folder_set', '=', ''] 24 | ]; 25 | return {filters: args}; 26 | } 27 | } 28 | ], 29 | post_render: function(treeview) { 30 | treeview.args['is_group'] = 1; 31 | }, 32 | extend_toolbar: false, 33 | toolbar: [ 34 | { 35 | label:__("Edit"), 36 | condition: function(node) { 37 | var me = frappe.views.trees['Folder Set']; 38 | return !node.is_root && me.can_read; 39 | }, 40 | click: function(node) { 41 | var me = frappe.views.trees['Folder Set']; 42 | frappe.set_route("Form", me.doctype, node.label); 43 | } 44 | }, 45 | { 46 | label:__("Add Child"), 47 | condition: function(node) { 48 | var me = frappe.views.trees['Folder Set']; 49 | if (me.can_create && node.expandable && !node.hide_add) { 50 | try { 51 | if (node.parent_node.hide_add) { node.hide_add = true; } 52 | } 53 | catch (e) { } 54 | } 55 | return me.can_create && node.expandable && !node.hide_add; 56 | }, 57 | click: function(node) { 58 | var me = frappe.views.trees['Folder Set']; 59 | me.new_node(); 60 | }//, 61 | //btnClass: "hidden-xs" 62 | }, 63 | { 64 | label:__("Delete"), 65 | async: false, 66 | condition: function(node) { 67 | var allow_delete = true; 68 | var me = frappe.views.trees['Folder Set']; 69 | try { if (node.parent_node.hide_add) { allow_delete = false; } 70 | } catch (e) { } 71 | if (allow_delete && me.can_delete && !node.is_root && !node.hide_add && node.expandable) { 72 | frappe.call({ 73 | method: 'frappe.client.get_value', 74 | args: { 75 | 'doctype': 'Folder Set', 76 | 'filters': {'parent_folder_set' : node.label}, 77 | 'fieldname': [ 'name' ] 78 | }, 79 | async: false, 80 | callback: function(r) { 81 | if (!r.exc) { 82 | if (r.message.name && r.message.name !="" && r.message.name != null) allow_delete = false; 83 | return allow_delete; 84 | } 85 | } 86 | }); 87 | } 88 | return allow_delete && me.can_delete && !node.is_root && !node.hide_add; 89 | }, 90 | click: function(node) { 91 | var me = frappe.views.trees['Folder Set']; 92 | frappe.model.delete_doc(me.doctype, node.label, function() { 93 | node.parent.remove(); 94 | }); 95 | }//, 96 | //btnClass: "hidden-xs" 97 | }, 98 | { 99 | label:__("Recreate"), 100 | condition: function(node) { 101 | var allow_create = true; 102 | var me = frappe.views.trees['Folder Set']; 103 | if (node.is_root) { allow_create = false; } 104 | return allow_create && !node.is_root && !node.hide_add; 105 | }, 106 | click: function(node) { 107 | var upload2nc = true; 108 | frappe.db.get_value('Folder Set', {'name': node.title}, 'nc_folder', (r) => { 109 | let nc_folder = r.nc_folder; 110 | if (nc_folder) { 111 | frappe.call({ 112 | method: "pibidav.pibidav.custom.checkNCuser", 113 | args: { 114 | } 115 | }).then(function(r) { 116 | let isUser = r.message; 117 | if (isUser) { 118 | let d = new frappe.ui.Dialog({ 119 | title: __("Recreate Folders in NC"), 120 | fields: [ 121 | { label: __("Parent Path"), fieldname: "parent_path", fieldtype: "Data", default: nc_folder}, 122 | { label: __("Abbreviation"), fieldname: "abbreviation", fieldtype: "Data", default: "PRJYYABV"}, 123 | { label: __("Root Dir Name"), fieldname: "folder_name", fieldtype: "Data"}, 124 | { label: __("Digits to substitute with abbreviation"), fieldname: "digits", fieldtype: "Int", default: 3} 125 | ], 126 | primary_action_label: __("Create"), 127 | primary_action(values) { 128 | //console.log(values); 129 | frappe.call({ 130 | method: "pibidav.pibidav.custom.create_nc_dirs", 131 | args: { 132 | node_name: node.title, 133 | path: values.parent_path, 134 | abbrv: values.abbreviation, 135 | strmain: values.folder_name, 136 | digits: values.digits 137 | }, 138 | callback: function(r) { 139 | console.log(r.message); 140 | } 141 | }); 142 | d.hide(); 143 | } 144 | }); 145 | d.show(); 146 | } else { 147 | frappe.msgprint(__("Only NextCloud SuperUser can use this method")); 148 | } 149 | }); 150 | } else { 151 | frappe.msgprint(__("First Select your NC Destination Folder")); 152 | } 153 | }); 154 | }//, 155 | //btnClass: "hidden-xs" 156 | } 157 | ] 158 | } 159 | 160 | -------------------------------------------------------------------------------- /pibidav/fixtures/workspace.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "charts": [], 4 | "content": "[{\"id\":\"w6ozBdED5x\",\"type\":\"header\",\"data\":{\"text\":\"Inicio > pibiTools
\",\"col\":12}}]", 5 | "custom_blocks": [], 6 | "docstatus": 0, 7 | "doctype": "Workspace", 8 | "for_user": "", 9 | "hide_custom": 0, 10 | "icon": "pibico", 11 | "indicator_color": "", 12 | "is_hidden": 0, 13 | "label": "pibiTools", 14 | "links": [], 15 | "modified": "2025-05-22 09:31:20.118853", 16 | "module": "Pibidav", 17 | "name": "pibiTools", 18 | "number_cards": [], 19 | "parent_page": "", 20 | "public": 1, 21 | "quick_lists": [], 22 | "restrict_to_domain": null, 23 | "roles": [], 24 | "sequence_id": 11.0, 25 | "shortcuts": [], 26 | "title": "pibiTools" 27 | }, 28 | { 29 | "charts": [], 30 | "content": "[{\"id\":\"azQJesz1jX\",\"type\":\"header\",\"data\":{\"text\":\"Inicio > pibiTools > pibiDAV
\",\"col\":12}},{\"id\":\"XQTScKtokJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Documents\",\"col\":4}},{\"id\":\"OIx7Apgo_M\",\"type\":\"card\",\"data\":{\"card_name\":\"Configuración\",\"col\":4}}]", 31 | "custom_blocks": [], 32 | "docstatus": 0, 33 | "doctype": "Workspace", 34 | "for_user": "", 35 | "hide_custom": 0, 36 | "icon": "pibidav", 37 | "indicator_color": "", 38 | "is_hidden": 0, 39 | "label": "pibiDAV", 40 | "links": [ 41 | { 42 | "dependencies": null, 43 | "description": null, 44 | "hidden": 0, 45 | "icon": null, 46 | "is_query_report": 0, 47 | "label": "Documents", 48 | "link_count": 4, 49 | "link_to": null, 50 | "link_type": "DocType", 51 | "onboard": 0, 52 | "only_for": null, 53 | "parent": "pibiDAV", 54 | "parentfield": "links", 55 | "parenttype": "Workspace", 56 | "report_ref_doctype": null, 57 | "type": "Card Break" 58 | }, 59 | { 60 | "dependencies": null, 61 | "description": null, 62 | "hidden": 0, 63 | "icon": null, 64 | "is_query_report": 0, 65 | "label": "pibiDAV Addon", 66 | "link_count": 0, 67 | "link_to": "PibiDAV Addon", 68 | "link_type": "DocType", 69 | "onboard": 0, 70 | "only_for": null, 71 | "parent": "pibiDAV", 72 | "parentfield": "links", 73 | "parenttype": "Workspace", 74 | "report_ref_doctype": null, 75 | "type": "Link" 76 | }, 77 | { 78 | "dependencies": null, 79 | "description": null, 80 | "hidden": 0, 81 | "icon": null, 82 | "is_query_report": 0, 83 | "label": "Tag", 84 | "link_count": 0, 85 | "link_to": "Tag", 86 | "link_type": "DocType", 87 | "onboard": 0, 88 | "only_for": null, 89 | "parent": "pibiDAV", 90 | "parentfield": "links", 91 | "parenttype": "Workspace", 92 | "report_ref_doctype": null, 93 | "type": "Link" 94 | }, 95 | { 96 | "dependencies": null, 97 | "description": null, 98 | "hidden": 0, 99 | "icon": null, 100 | "is_query_report": 0, 101 | "label": "Contact", 102 | "link_count": 0, 103 | "link_to": "Contact", 104 | "link_type": "DocType", 105 | "onboard": 0, 106 | "only_for": null, 107 | "parent": "pibiDAV", 108 | "parentfield": "links", 109 | "parenttype": "Workspace", 110 | "report_ref_doctype": null, 111 | "type": "Link" 112 | }, 113 | { 114 | "dependencies": null, 115 | "description": null, 116 | "hidden": 0, 117 | "icon": null, 118 | "is_query_report": 0, 119 | "label": "Event", 120 | "link_count": 0, 121 | "link_to": "Event", 122 | "link_type": "DocType", 123 | "onboard": 0, 124 | "only_for": null, 125 | "parent": "pibiDAV", 126 | "parentfield": "links", 127 | "parenttype": "Workspace", 128 | "report_ref_doctype": null, 129 | "type": "Link" 130 | }, 131 | { 132 | "dependencies": null, 133 | "description": null, 134 | "hidden": 0, 135 | "icon": null, 136 | "is_query_report": 0, 137 | "label": "Configuración", 138 | "link_count": 3, 139 | "link_to": null, 140 | "link_type": "DocType", 141 | "onboard": 0, 142 | "only_for": null, 143 | "parent": "pibiDAV", 144 | "parentfield": "links", 145 | "parenttype": "Workspace", 146 | "report_ref_doctype": null, 147 | "type": "Card Break" 148 | }, 149 | { 150 | "dependencies": null, 151 | "description": null, 152 | "hidden": 0, 153 | "icon": null, 154 | "is_query_report": 0, 155 | "label": "File Category", 156 | "link_count": 0, 157 | "link_to": "File Category", 158 | "link_type": "DocType", 159 | "onboard": 0, 160 | "only_for": null, 161 | "parent": "pibiDAV", 162 | "parentfield": "links", 163 | "parenttype": "Workspace", 164 | "report_ref_doctype": null, 165 | "type": "Link" 166 | }, 167 | { 168 | "dependencies": null, 169 | "description": null, 170 | "hidden": 0, 171 | "icon": null, 172 | "is_query_report": 0, 173 | "label": "NextCloud Settings", 174 | "link_count": 0, 175 | "link_to": "NextCloud Settings", 176 | "link_type": "DocType", 177 | "onboard": 0, 178 | "only_for": null, 179 | "parent": "pibiDAV", 180 | "parentfield": "links", 181 | "parenttype": "Workspace", 182 | "report_ref_doctype": null, 183 | "type": "Link" 184 | }, 185 | { 186 | "dependencies": null, 187 | "description": null, 188 | "hidden": 0, 189 | "icon": null, 190 | "is_query_report": 0, 191 | "label": "Folder Set", 192 | "link_count": 0, 193 | "link_to": "Folder Set", 194 | "link_type": "DocType", 195 | "onboard": 0, 196 | "only_for": null, 197 | "parent": "pibiDAV", 198 | "parentfield": "links", 199 | "parenttype": "Workspace", 200 | "report_ref_doctype": null, 201 | "type": "Link" 202 | } 203 | ], 204 | "modified": "2025-05-22 09:29:32.469945", 205 | "module": "Pibidav", 206 | "name": "pibiDAV", 207 | "number_cards": [], 208 | "parent_page": "", 209 | "public": 1, 210 | "quick_lists": [], 211 | "restrict_to_domain": null, 212 | "roles": [ 213 | { 214 | "parent": "pibiDAV", 215 | "parentfield": "roles", 216 | "parenttype": "Workspace", 217 | "role": "Docs Controller" 218 | }, 219 | { 220 | "parent": "pibiDAV", 221 | "parentfield": "roles", 222 | "parenttype": "Workspace", 223 | "role": "Docs User" 224 | }, 225 | { 226 | "parent": "pibiDAV", 227 | "parentfield": "roles", 228 | "parenttype": "Workspace", 229 | "role": "NextCloud User" 230 | } 231 | ], 232 | "sequence_id": 6.0, 233 | "shortcuts": [], 234 | "title": "pibiDAV" 235 | } 236 | ] -------------------------------------------------------------------------------- /pibidav/hooks.py: -------------------------------------------------------------------------------- 1 | from . import __version__ as app_version 2 | 3 | app_name = "pibidav" 4 | app_title = "Pibidav" 5 | app_publisher = "pibiCo" 6 | app_description = "WebDAV, CalDAV and CardDAV Integration between Frappe and NextCloud" 7 | app_icon = "octicon octicon-file-directory" 8 | app_color = "grey" 9 | app_email = "pibico.sl@gmail.com" 10 | app_license = "MIT" 11 | 12 | # Includes in 13 | # ------------------ 14 | 15 | # include js, css files in header of desk.html 16 | # app_include_css = "/assets/pibidav/css/pibidav.css" 17 | app_include_js = "pibidav.bundle.js" 18 | 19 | # include js, css files in header of web template 20 | # web_include_css = "/assets/pibidav/css/pibidav.css" 21 | # web_include_js = "/assets/pibidav/js/pibidav.js" 22 | 23 | # include custom scss in every website theme (without file extension ".scss") 24 | # website_theme_scss = "pibidav/public/scss/website" 25 | 26 | # include js, css files in header of web form 27 | # webform_include_js = {"doctype": "public/js/doctype.js"} 28 | # webform_include_css = {"doctype": "public/css/doctype.css"} 29 | 30 | # include js in page 31 | # page_js = {"page" : "public/js/file.js"} 32 | 33 | app_include_icons = [ 34 | "pibidav/icons/timeless/icons.svg" 35 | ] 36 | 37 | # include js in doctype views 38 | # doctype_js = {"doctype" : "public/js/doctype.js"} 39 | # doctype_list_js = {"doctype" : "public/js/doctype_list.js"} 40 | # doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} 41 | # doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} 42 | 43 | nc_list = [ 44 | "Contact", 45 | "Event", 46 | "Customer", 47 | "Supplier", 48 | "Project", 49 | "Sales Invoice", 50 | "Sales Order", 51 | "Purchase Invoice", 52 | "Quotation", 53 | "Purchase Order", 54 | "Task", 55 | "Timesheet", 56 | "Item", 57 | "Employee" 58 | ] 59 | 60 | doctype_js = {} 61 | for item in nc_list: 62 | doctype_js[item] = "public/js/dist/nc_pibidav.js" 63 | 64 | # Home Pages 65 | # ---------- 66 | 67 | # application home page (will override Website Settings) 68 | # home_page = "login" 69 | 70 | #brand_html = '
pibiDAV
' 71 | 72 | #website_context = { 73 | # "favicon": "/assets/pibidav/image/favicon.svg", 74 | # "splash_image": "/assets/pibidav/image/pibiCo_engine_largo.png" 75 | #} 76 | 77 | # website user home page (by Role) 78 | # role_home_page = { 79 | # "Role": "home_page" 80 | # } 81 | 82 | # Generators 83 | # ---------- 84 | 85 | # automatically create page for each record of this doctype 86 | # website_generators = ["Web Page"] 87 | 88 | # Installation 89 | # ------------ 90 | 91 | # before_install = "pibidav.install.before_install" 92 | # after_install = "pibidav.install.after_install" 93 | 94 | # Uninstallation 95 | # ------------ 96 | 97 | # before_uninstall = "pibidav.uninstall.before_uninstall" 98 | # after_uninstall = "pibidav.uninstall.after_uninstall" 99 | 100 | # Desk Notifications 101 | # ------------------ 102 | # See frappe.core.notifications.get_notification_config 103 | 104 | # notification_config = "pibidav.notifications.get_notification_config" 105 | 106 | # Permissions 107 | # ----------- 108 | # Permissions evaluated in scripted ways 109 | 110 | # permission_query_conditions = { 111 | # "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions", 112 | # } 113 | # 114 | # has_permission = { 115 | # "Event": "frappe.desk.doctype.event.event.has_permission", 116 | # } 117 | 118 | jinja = { 119 | "methods": [ 120 | "pibidav.jinja.timestamp_to_date", 121 | "pibidav.jinja.ts_to_date" 122 | ] 123 | } 124 | 125 | # DocType Class 126 | # --------------- 127 | # Override standard doctype classes 128 | 129 | override_doctype_class = { 130 | "File": "pibidav.pibidav.overrides.file.CustomFile" 131 | } 132 | 133 | # Document Events 134 | # --------------- 135 | # Hook on document methods and events 136 | 137 | doc_events = { 138 | # "*": { 139 | # "after_insert": "pibidav.pibidav.custom.create_nc_folder" 140 | # }, 141 | "File": { 142 | "after_insert": ["pibidav.pibidav.custom.upload_file_to_nc"] 143 | } 144 | # "*": { 145 | # "on_update": "method", 146 | # "on_cancel": "method", 147 | # "on_trash": "method" 148 | # } 149 | } 150 | 151 | # Scheduled Tasks 152 | # --------------- 153 | 154 | scheduler_events = { 155 | # "all": [ 156 | # "pibidav.tasks.all" 157 | # ], 158 | # "cron": { 159 | # "*/1 * * * *": [ 160 | # "pibidav.pibidav.pibical.sync_outside_caldav" 161 | # ] 162 | # }, 163 | "daily": [ 164 | "pibidav.pibidav.doctype.nextcloud_settings.nextcloud_settings.daily_backup" 165 | ], 166 | # "hourly": [ 167 | # "pibidav.tasks.hourly" 168 | # ], 169 | "weekly": [ 170 | "pibidav.pibidav.doctype.nextcloud_settings.nextcloud_settings.weekly_backup" 171 | ] #, 172 | # "monthly": [ 173 | # "pibidav.tasks.monthly" 174 | # ] 175 | } 176 | 177 | # Testing 178 | # ------- 179 | 180 | # before_tests = "pibidav.install.before_tests" 181 | 182 | # Overriding Methods 183 | # ------------------------------ 184 | # 185 | # override_whitelisted_methods = { 186 | # "frappe.desk.doctype.event.event.get_events": "pibidav.event.get_events" 187 | # } 188 | # 189 | # each overriding function accepts a `data` argument; 190 | # generated from the base implementation of the doctype dashboard, 191 | # along with any modifications made in other Frappe apps 192 | # override_doctype_dashboards = { 193 | # "Task": "pibidav.task.get_dashboard_data" 194 | # } 195 | 196 | # exempt linked doctypes from being automatically cancelled 197 | # 198 | # auto_cancel_exempted_doctypes = ["Auto Repeat"] 199 | 200 | 201 | # User Data Protection 202 | # -------------------- 203 | 204 | user_data_fields = [ 205 | { 206 | "doctype": "{doctype_1}", 207 | "filter_by": "{filter_by}", 208 | "redact_fields": ["{field_1}", "{field_2}"], 209 | "partial": 1, 210 | }, 211 | { 212 | "doctype": "{doctype_2}", 213 | "filter_by": "{filter_by}", 214 | "partial": 1, 215 | }, 216 | { 217 | "doctype": "{doctype_3}", 218 | "strict": False, 219 | }, 220 | { 221 | "doctype": "{doctype_4}" 222 | } 223 | ] 224 | 225 | # Authentication and authorization 226 | # -------------------------------- 227 | 228 | # auth_hooks = [ 229 | # "pibidav.auth.validate" 230 | # ] 231 | 232 | fixtures = [ 233 | { 234 | "dt": "Custom Field", 235 | "filters": {"module": ["like", "Pibidav"]} 236 | }, 237 | { 238 | "dt": "Client Script", 239 | "filters": {"module": ["like", "Pibidav"]} 240 | }, 241 | { 242 | "dt": "Workspace", 243 | "filters": {"module": ["like", "Pibidav"]} 244 | } 245 | ] 246 | 247 | treeviews = ['Folder Set'] 248 | 249 | 250 | # Translation 251 | # -------------------------------- 252 | 253 | # Make link fields search translated document names for these DocTypes 254 | # Recommended only for DocTypes which have limited documents with untranslated names 255 | # For example: Role, Gender, etc. 256 | # translated_search_doctypes = [] 257 | -------------------------------------------------------------------------------- /pibidav/pibidav/doctype/nextcloud_settings/nextcloud_settings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, pibiCo and contributors 2 | # For license information, please see license.txt 3 | 4 | import frappe 5 | from frappe.model.document import Document 6 | from frappe import _, msgprint, throw 7 | 8 | from frappe.utils.background_jobs import enqueue 9 | from frappe.utils.backups import new_backup 10 | from frappe.integrations.offsite_backup_utils import send_email, validate_file_size 11 | 12 | import pibidav.pibidav.nextcloud as nextcloud 13 | 14 | from frappe.utils.password import get_decrypted_password 15 | from frappe.utils.file_manager import get_file_path 16 | 17 | import requests, os, datetime 18 | from rq.timeouts import JobTimeoutException 19 | from urllib.parse import urlparse 20 | 21 | class NextCloudSettings(Document): 22 | error_log = [] 23 | failed_uploads = [] 24 | timeout = 1500 25 | 26 | def start_taking_backup(self, retry_count=0, upload_db_backup=True): 27 | try: 28 | if self.nc_backup_enable: 29 | validate_file_size() 30 | self.backup_to_nextcloud(upload_db_backup) 31 | if self.error_log: 32 | raise Exception 33 | if self.send_email_for_successful_backup: 34 | send_email(True, "NextCloud", "NextCloud Settings", "send_notifications_to") 35 | except JobTimeoutException: 36 | if retry_count < 2: 37 | timeout += 1500 38 | args = { 39 | "retry_count": retry_count + 1, 40 | "upload_db_backup": False ## considering till worker timeout db backup is uploaded 41 | } 42 | enqueue(self.start_taking_backup, queue='long', timeout=timeout, **args) ## 43 | except Exception: 44 | if isinstance(self.error_log, str): 45 | error_message = self.error_log + "\n" + frappe.get_traceback() 46 | else: 47 | file_and_error = [" - ".join(f) for f in zip(self.failed_uploads if self.failed_uploads else '', list(set(self.error_log)))] 48 | error_message = ("\n".join(file_and_error) + "\n" + frappe.get_traceback()) 49 | send_email(False, "NextCloud", "NextCloud Settings", "send_notifications_to", error_message) 50 | 51 | def backup_to_nextcloud(self, upload_db_backup=True): 52 | if not frappe.db: 53 | frappe.connect() 54 | if upload_db_backup: 55 | base_url = self.make_baseurl() 56 | if not base_url: 57 | self.error_log.append(_("Wrong NextCloud URL")) 58 | return 59 | self.make_upload_path(base_url) 60 | self.make_session(base_url) 61 | 62 | ## Check if folder exist and upload 63 | self.check_for_upload_folder() 64 | self.process_uploading() 65 | 66 | ## Logout session 67 | self.session.logout() 68 | 69 | def make_baseurl(self): 70 | vurl = urlparse(self.nc_backup_url) 71 | if not vurl.scheme: 72 | return None 73 | if not vurl.netloc: 74 | return None 75 | if not vurl.port: 76 | port = 443 if vurl.scheme == 'https' else 80 77 | else: 78 | port_url = vurl.netloc 79 | nc_url = port_url.replace(":" + str(vurl.port),"") 80 | 81 | base_url = '{0}://{1}:{2}'.format(vurl.scheme, vurl.netloc if not vurl.port else nc_url, vurl.port if vurl.port else port) 82 | if base_url.endswith('/'): 83 | base_url = base_url[:-1] 84 | 85 | return base_url 86 | 87 | def make_upload_path(self, base_url): 88 | """This function checks if path is provided and if not makes default""" 89 | if self.nc_backup_path: 90 | self.upload_path = '{}'.format(self.nc_backup_path) 91 | else: 92 | self.upload_path = '{}'.format('/Frappe Backups/') 93 | 94 | def make_session(self, base_url): 95 | nc_token = get_decrypted_password('NextCloud Settings', 'NextCloud Settings', 'nc_backup_token', True) 96 | session = nextcloud.Client(base_url) 97 | session.login(self.nc_backup_username, nc_token) 98 | self.session = session 99 | 100 | def check_for_upload_folder(self): 101 | """If a path (only the root directory) is provided in NextCloud Settings, this function checks if path exists. 102 | If no path is provided, /Frappe Backups/ dir will be created for backup user""" 103 | try: 104 | ## Check for directory listing if exists 105 | response = self.session.list(self.upload_path, depth=0) 106 | except Exception as e: 107 | ## Error due to non existing directory. We'll create 108 | self.error_log.append(e) 109 | response = self.session.mkdir(self.upload_path) 110 | 111 | def process_uploading(self): 112 | db_backup, site_config, public_file_backup, private_file_backup = self.prepare_backup() 113 | 114 | db_response = self.upload_backup(db_backup) 115 | if db_response == "Failed": 116 | self.failed_uploads.append(db_backup) 117 | self.error_log.append(_('\r\nFailed while uploading DB')) 118 | 119 | site_config_response = self.upload_backup(site_config) 120 | if site_config_response == "Failed": 121 | self.failed_uploads.append(site_config) 122 | self.error_log.append(_('\r\nFailed while uploading Site Config')) 123 | 124 | ## File Backup 125 | if self.backup_files and db_response != "Failed" and site_config_response != "Failed": 126 | self.file_upload(public_file_backup, private_file_backup) 127 | 128 | def file_upload(self, public_file_backup, private_file_backup): 129 | if public_file_backup: 130 | response_public_file = self.upload_backup(public_file_backup) 131 | if response_public_file == "Failed": 132 | self.failed_uploads_append(public_file_backup) 133 | self.error_log.append(_('\r\nFailed while uploading Public Files')) 134 | if private_file_backup: 135 | response_private_file = self.upload_backup(private_file_backup) 136 | if response_private_file == "Failed": 137 | self.failed_uploads_append(private_file_backup) 138 | self.error_log.append(_('\r\nFailed while uploading Private Files')) 139 | 140 | def upload_backup(self, filebackup): 141 | if not os.path.exists(filebackup): 142 | return 143 | local_fileobj = filebackup 144 | fileobj = local_fileobj.split('/') 145 | dir_length = len(fileobj) - 1 146 | ## remove datetime 147 | remote_fileobj = str(datetime.datetime.today().weekday()) + fileobj[dir_length].encode("ascii", "ignore").decode("ascii")[15:] 148 | if self.upload_path.endswith('/'): 149 | remote_path = '{0}{1}'.format(self.upload_path, remote_fileobj) 150 | else: 151 | remote_path = '{0}/{1}'.format(self.upload_path, remote_fileobj) 152 | 153 | try: 154 | response = self.session.put_file(remote_path = remote_path, local_source_file = filebackup) 155 | return response 156 | except Exception as e: 157 | return "Failed" 158 | 159 | def prepare_backup(self): 160 | odb = new_backup(ignore_files=False if self.backup_files else True, force=frappe.flags.create_new_backup) 161 | database, public, private, config = odb.get_recent_backup(older_than=24 * 30) 162 | return database, config, public, private 163 | 164 | @frappe.whitelist() 165 | def take_backup(): 166 | """Enqueue long job for taking backup to NC""" 167 | enqueue("pibidav.pibidav.doctype.nextcloud_settings.nextcloud_settings.start_backup", queue='long', timeout=3000) 168 | frappe.msgprint(_("Queued for Backup. It may take a long time depending on your backup files size. Rest Easy!!!")) 169 | 170 | def daily_backup(): 171 | take_backups_if("Daily") 172 | 173 | def weekly_backup(): 174 | take_backups_if("Weekly") 175 | 176 | def take_backups_if(freq): 177 | if frappe.db.get_single_value("NextCloud Settings", "backup_frequency") == freq: 178 | start_backup() 179 | 180 | def start_backup(): 181 | backup = frappe.get_doc("NextCloud Settings", "NextCloud Settings") 182 | backup.start_taking_backup() 183 | -------------------------------------------------------------------------------- /pibidav/public/js/dist/nc_browser/NcBrowser.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 264 | 265 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## pibiDAV 2 | pibiDAV is a Frappe App to integrate webDAV with a NextCloud Server, used as (DMS), for a copy of Frappe Files uploaded and tagged to NextCloud while uploading files to Frappe. 3 | ## License 4 | MIT# pibiDAV 5 | ## Requirements 6 | Requires a Frappe server instance (refer to https://github.com/frappe/frappe). It also uses a tweaked code of pyocclient but this is embedded already in pibiDAV. 7 | ## Compatibility 8 | PibiDAV branch version-15 is dedicated for Frappe/ERPNext version-15 only. 9 | ## Installation 10 | From the frappe-bench folder, execute 11 | ``` 12 | $ bench get-app pibidav --branch version-15 https://github.com/pibico/pibidav.git 13 | $ bench install-app pibidav 14 | ``` 15 | If you are using a multi-tenant environment, use the following command 16 | ``` 17 | $ bench --site site_name install-app pibidav 18 | ``` 19 | ## Features 20 | Once is installed, be aware that you will need to set **developer_mode = 1** on your site_config.json file. Also it is a must to have SSL active in both servers Frappe and NextCloud with specific certificates (wildcard *.domain.com* certificates are not valid for this integration). Letsencrypt Certificates are valid for both servers. 21 | This integration app is prepared for including specific and custom doctypes to upload its attachments to NextCloud at the same time than to Frappe. There is a new frappe.ui.component dialog based on vue.js and called frappe.ui.pibiDocs where to draw the NextCloud Tree and select the destination NextCloud Directory to upload the files. 22 | ### 1. Credentials for NextCloud SuperUser, Backup and DocTypes to Integrate with NextCloud 23 | PibiCo works on NextCloud making the Main Company Folders Superestructure as a shared folder from this NextCloud SuperUser that should be a System Manager on Frappe also. Let's explain with some pictures. 24 | ![NC_FolderStructure_Shared_from_SuperUser](https://user-images.githubusercontent.com/69711454/165801352-b4a14016-b360-41ea-9a2c-050ea589580f.JPG) 25 | This Folder Superstructure has children at different levels and are shared with different groups or users also at different level, thus giving access to these folders and below (both user internal to Company or External such as Customers or Suppliers). 26 | ![imagen](https://user-images.githubusercontent.com/69711454/165802115-275c6234-77f5-43fa-b2aa-a1f3942e4693.png) 27 | At this point, let's go to Frappe Server and once logged-in as System Manager or Administrator we'll go to module pibiDAV on side menu, and on Settings Card we'll choose NextCloud Settings. 28 | ![pibiDav_NC_SuperUser_Credentials_and_Settings](https://user-images.githubusercontent.com/69711454/165805974-23fcec72-04c6-4f4e-9ff0-eba250862fb5.JPG) 29 | Once there, we'll activate the NextCloud Enable checkbox (valid also for backup) and fill the credentials of the NextCloud SuperUser in the input fields. 30 | ![imagen](https://user-images.githubusercontent.com/69711454/168047648-0c131b17-ba4e-4f77-af4e-8207d0050159.png) 31 | We can check that our credentials are correct clicking on NC commands button on upper right side of the screen. 32 | ![imagen](https://user-images.githubusercontent.com/69711454/165807198-41a41df3-9a6e-447f-96fa-5ee2040190c2.png) 33 | Now, automatic uploading of frappe backup is enabled and will be uploaded to the destination folder as given in the backups details section. Frappe Backups files are renamed on NextCloud to files beginning by a number that is the weekday of the backup files, as shown in the picture. So, we will have always the last backup and also all the ancient backups for this weekday as versions in NextCloud, as shown in the picture 34 | ![imagen](https://user-images.githubusercontent.com/69711454/165808672-33278ca8-1776-4cb4-bd0e-50ad273aa9e0.png) 35 | It's time now of telling to Frappe which doctypes will be integrated to upload its attachments to NextCloud once they are uploaded in Frappe. This is done on the same NextCloud Settings, but on Settings Section, choosing the Frappe Doctype in the table and also giving the DocFields in that DocType that will be used to Tag Automatically the Files on Frappe and On NextCloud as well. We will choose the Sales Invoice Doctype as an example for this configuration. In this case, we will tag the files with the name, the customer and the tax_id from the customer, but whichever field in the doctype, even custom fields are valid. 36 | ![imagen](https://user-images.githubusercontent.com/69711454/168048312-ecb71229-8937-4fe3-bfe2-fdcac6dd5fc1.png) 37 | ### 2. Credentials for each Frappe User to Use his NextCloud Account Credentials 38 | To get the permissions from NextCloud into Frappe we will fill the User NextCloud Credentials on Frappe User Settings. We'll go through the user settings and will select the Role NextCloud User first. 39 | ![imagen](https://user-images.githubusercontent.com/69711454/165817057-d765dd68-ae4f-4ab9-9edf-2fa438a0d012.png) 40 | After that we will go at the bottom of de User Settings Form to provide the NextCloud User Credentials. In this example is the System Manager or Administrator having the SuperUser NextCloud Credentials for having access to the full NextCloud Folder SuperStructure. 41 | ![imagen](https://user-images.githubusercontent.com/69711454/165817406-eeb6fc05-3fa7-4e14-8798-3712c4a2b26c.png) 42 | ### 3. Hooks for NextCloud Integration 43 | Provided we have access to the frappe-bench folder, we must hook the Doctypes included in the integration, by adding the following code to hooks.py of pibidav app: 44 | ``` 45 | # doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} 46 | 47 | nc_list = ["Customer","Project","Sales Invoice","Purchase Invoice","Supplier","Event"] 48 | doctype_js = {} 49 | for item in nc_list: 50 | doctype_js[item] = "public/js/dist/nc_pibidav.js" 51 | 52 | # Home Pages 53 | # ---------- 54 | ``` 55 | In nc_list variable, we will include whichever doctype to integrate with NextCloud. 56 | #### 2.1 PibiDAV Addon DocType 57 | This new DocType is an extension of existing DocTypes to fill with data given by Attchments, Folders, etc. related to NextCloud and based on its referenced doctype. It is somehow a parallel sheet with data only related to NextCloud Functionalities. It is filled automatically from the different actions derived from NC buttons. 58 | ![imagen](https://user-images.githubusercontent.com/69711454/168050089-1f6a7f44-52a8-4d27-ae64-2c94775a17f3.png) 59 | ### 4. The magic of the NextCloud Integration inside Frappe 60 | It's time now to try the integration of the NextCloud Folder Structure from Frappe to choose the NC Destination Folder of our uploaded files (except website urls). 61 | Let's go to a doctype of the NC Integration List, i.e. a Sales Invoice. On this core doctype, we'll have two new buttons, for enabling NC and for getting the browser dialog to choose the NC destination folder. 62 | ![imagen](https://user-images.githubusercontent.com/69711454/168051235-2f762d9e-30a4-476e-83c8-e8345ca2dd4f.png) 63 | First of all, we must enable the NC Integration, clicking on Enable NC button. With this action we will generate the pbc_sales_invoice doctype parallel to our sales invoice (same name with 'pbc_' before it). 64 | ![imagen](https://user-images.githubusercontent.com/69711454/168051867-32eeaeda-0d05-4525-935f-b464a6cb7a06.png) 65 | We can check the created Addon clicking on the Check Addon Button. And return to the sales invoice clicking on the ref_docname on the addon. 66 | ![imagen](https://user-images.githubusercontent.com/69711454/168052040-b623d8ef-7d4c-4135-8497-ba412e730023.png) 67 | After having saved the original Sales Invoice as draft we can select the NextCloud Destination Folder for our attachments by clicking on the NC Commands Button and selecting Select NC Folder to bring the pop-up dialog for browsing and selecting our NC Folder. 68 | ![imagen](https://user-images.githubusercontent.com/69711454/168052743-ac2bec03-915e-47de-82a7-2e62996b1d64.png) 69 | When we select the NextCloud Destination Folder in the dialog, this folder path will be filled in our addon text nc_folder field to remember the destination till it is changed by a new selection. 70 | While we keep this destination folder, all the attachments uploaded to the Sales Invoice will be also uploaded to NextCloud to this folder. Let's create the pdf from the Sales Invoice, signed electronically outside Frappe and uploaded again as attachment in Frappe/ERPNext 71 | ![imagen](https://user-images.githubusercontent.com/69711454/165823914-8dd352e1-69ce-4698-851e-33f53dadb3e2.png) 72 | We have uploaded the attachment to Frappe/ERPNext as shown in the picture 73 | ![imagen](https://user-images.githubusercontent.com/69711454/165824171-d145445b-9c87-4740-b48d-494ef116c26b.png) 74 | Let's Check if it has been also uploaded to NextCloud on the PBC > Customer > Client > Invoices > 2022 as selected. 75 | ![imagen](https://user-images.githubusercontent.com/69711454/165824625-0d650e18-0c94-4c9e-be7e-7577b1d10968.png) 76 | Looking for details we see the pdf file Sales Invoice uploaded in NextCloud Destination, but also it has been created a shared public Link, tagged with customer, tax_id, doctype and name as we defined in the Settings. Voilà, first integration achieved. Let's check the File uploaded to Frappe, it has all metadata from NextCloud as well, and also has some frappe tags also automatically filled on the upload. 77 | ![imagen](https://user-images.githubusercontent.com/69711454/165825969-f883e0d4-b415-4eba-9885-16ed92073276.png) 78 | ### 5. Create folder structures in NextCloud from template Folder Sets in Frappe. 79 | Another possible integration is through a Folder Set Doctype Tree integrated in pibiDav. Folder Set is a Doctype for making Folder Structures taken as templates for recreating them in the NextCloud Instance from a destination folder as root. Let's see in action, once we have the template folder set created in Frappe. We can select the root folder and enable the nc_enable check and select the destination folder in NextCloud where to recreate this structure, renaming the folders in NextCloud upon its creation. 80 | ![imagen](https://user-images.githubusercontent.com/69711454/165839794-602f4e5c-3e7d-4350-9a12-fa16c31bb75b.png) 81 | We select the destination folder in NextCloud browsing in the dialog. 82 | ![imagen](https://user-images.githubusercontent.com/69711454/165840960-bb937ec7-9b2b-491d-94b4-4d9df0374c8e.png) 83 | And once selected the destination folder, on the tree we click on button Recreate to perform the copy of folders from Frappe to NextCloud. 84 | ![imagen](https://user-images.githubusercontent.com/69711454/165841213-d50cd3e1-089e-4106-a997-c9188a2eeef0.png) 85 | The result is seen on the image for a new Client Structure called CUSTOMER as abbreviation and Customer Name Details as Description for the Folder. 86 | ![imagen](https://user-images.githubusercontent.com/69711454/165841463-62829c06-e4f6-4e13-ae3e-49d5bbe67fee.png) 87 | ![imagen](https://user-images.githubusercontent.com/69711454/165841656-f5b458d8-b15d-47ff-8738-bafe8c5bd08a.png) 88 | 89 | There is an automation to make automatic folders upon creation of selected included doctypes, but try to see if you can make it running. This is an Easter Egg for you to discover. 90 | -------------------------------------------------------------------------------- /pibidav/public/icons/timeless/icons.svg: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /pibidav/public/js/dist/nc_pibidav.js: -------------------------------------------------------------------------------- 1 | frappe.ui.form.on(cur_frm.doctype, { 2 | refresh: function (frm) { 3 | if (!frm.doc.__islocal) { 4 | managePibiDAVAddon(frm); 5 | // Set up file upload listener 6 | setupFileUploadListener(frm); 7 | } 8 | }, 9 | after_save: function (frm) { 10 | handleAfterSave(frm); 11 | } 12 | }); 13 | // Function to manage PibiDAV Addon buttons and actions 14 | function managePibiDAVAddon(frm) { 15 | frappe.db.get_value( 16 | "PibiDAV Addon", 17 | { ref_doctype: frm.doc.doctype, ref_docname: frm.doc.name }, 18 | ["nc_enable"] 19 | ).then(r => { 20 | const nc_enable = r.message?.nc_enable || 0; 21 | if (nc_enable !== 1) { 22 | addNextCloudEnableButton(frm); 23 | } else { 24 | addNextCloudButtons(frm); 25 | } 26 | }); 27 | } 28 | // Add Enable NextCloud Button 29 | function addNextCloudEnableButton(frm) { 30 | frm.add_custom_button(frappe.utils.icon('nextcloud', 'md'), function () { 31 | handleEnableNextCloud(frm); 32 | }) 33 | .addClass("btn btn-primary") 34 | .attr('title', __('Enable NextCloud')); 35 | } 36 | // Add Buttons for Upload, Check, and Disable NextCloud 37 | function addNextCloudButtons(frm) { 38 | frm.add_custom_button(__("Upload to NC"), function () { 39 | handleUploadToNextCloud(frm); 40 | }, __("NC")); 41 | 42 | frm.add_custom_button(__("Check Addon"), function () { 43 | handleCheckAddon(frm); 44 | }, __("NC")); 45 | 46 | frm.add_custom_button(__("Refresh Attachments"), function () { 47 | refreshAddonAttachments(frm); 48 | }, __("NC")); 49 | 50 | frm.add_custom_button(frappe.utils.icon('nextcloud', 'md'), function () { 51 | handleDisableNextCloud(frm); 52 | }) 53 | .addClass("btn btn-danger") 54 | .attr('title', __('Disable NextCloud')); 55 | } 56 | // Handle enabling NextCloud 57 | function handleEnableNextCloud(frm) { 58 | frappe.db.get_value( 59 | "PibiDAV Addon", 60 | { ref_doctype: frm.doc.doctype, ref_docname: frm.doc.name }, 61 | ["name", "nc_folder", "nc_enable"] 62 | ).then(r => { 63 | const addon = r.message || {}; 64 | 65 | if (!addon.name) { 66 | // Addon does not exist, create it first 67 | createAddon(frm, (newAddon) => { 68 | processNextCloudEnable(frm, newAddon); 69 | }); 70 | } else { 71 | // Addon exists, proceed to process enabling 72 | processNextCloudEnable(frm, addon); 73 | } 74 | }); 75 | } 76 | // Process enabling NextCloud 77 | function processNextCloudEnable(frm, addon) { 78 | const nc_parent_folder = frm.doc.nc_parent_folder; 79 | 80 | if (nc_parent_folder) { 81 | frappe.db.set_value("PibiDAV Addon", addon.name, { 82 | nc_enable: 1, 83 | nc_folder_internal_link: nc_parent_folder 84 | }).then(() => { 85 | fetchAndSetFolderPath(nc_parent_folder, addon.name, frm); 86 | }); 87 | } else { 88 | frappe.db.set_value("PibiDAV Addon", addon.name, { nc_enable: 1 }) 89 | .then(() => { 90 | frm.reload_doc(); 91 | setTimeout(() => { 92 | managePibiDAVAddon(frm); 93 | }, 500); 94 | }); 95 | } 96 | } 97 | // Handle uploading to NextCloud 98 | function handleUploadToNextCloud(frm) { 99 | frappe.db.get_value( 100 | "PibiDAV Addon", 101 | { ref_doctype: frm.doc.doctype, ref_docname: frm.doc.name }, 102 | ["name", "nc_folder", "nc_folder_internal_link"] 103 | ).then(r => { 104 | const addon = r.message || {}; 105 | if (addon.name) { 106 | // Check if nc_folder already has a valid path (not just "/") 107 | if (addon.nc_folder && addon.nc_folder !== "/" && addon.nc_folder.length > 1) { 108 | openNextCloudBrowser(addon); 109 | } 110 | // If nc_folder is just "/" but we have nc_folder_internal_link, fetch the actual path 111 | else if (addon.nc_folder === "/" && addon.nc_folder_internal_link) { 112 | const fileId = extractFileId(addon.nc_folder_internal_link); 113 | if (fileId) { 114 | frappe.call({ 115 | method: 'pibidav.pibidav.custom.get_folder_path_from_link', 116 | args: { fileid: fileId }, 117 | callback: function(result) { 118 | if (!result.exc && result.message && result.message !== '/') { 119 | addon.nc_folder = result.message; 120 | // Update the addon's nc_folder field for future use 121 | frappe.db.set_value("PibiDAV Addon", addon.name, "nc_folder", result.message); 122 | } else { 123 | // Try to extract path from the internal link 124 | if (frm && frm.doc.nc_parent_folder) { 125 | // Use the nc_parent_folder from the document 126 | frappe.call({ 127 | method: 'pibidav.pibidav.custom.get_folder_path_from_link', 128 | args: { fileid: extractFileId(frm.doc.nc_parent_folder) }, 129 | callback: function(r2) { 130 | if (!r2.exc && r2.message && r2.message !== '/') { 131 | addon.nc_folder = r2.message; 132 | frappe.db.set_value("PibiDAV Addon", addon.name, "nc_folder", r2.message); 133 | } 134 | openNextCloudBrowser(addon); 135 | } 136 | }); 137 | return; 138 | } 139 | } 140 | openNextCloudBrowser(addon); 141 | } 142 | }); 143 | } else { 144 | openNextCloudBrowser(addon); 145 | } 146 | } else { 147 | openNextCloudBrowser(addon); 148 | } 149 | } else { 150 | createAddon(frm, () => openNextCloudBrowser({})); 151 | } 152 | }); 153 | } 154 | // Handle checking the Addon 155 | function handleCheckAddon(frm) { 156 | frappe.db.get_value( 157 | "PibiDAV Addon", 158 | { ref_doctype: frm.doc.doctype, ref_docname: frm.doc.name }, 159 | ["name"] 160 | ).then(r => { 161 | const addon = r.message || {}; 162 | if (addon.name) { 163 | frappe.set_route("Form", "PibiDAV Addon", addon.name); 164 | } else { 165 | createAddon(frm, pibidav => { 166 | frappe.set_route("Form", "PibiDAV Addon", pibidav.name); 167 | }); 168 | } 169 | }); 170 | } 171 | // Handle disabling NextCloud 172 | function handleDisableNextCloud(frm) { 173 | frappe.db.set_value("PibiDAV Addon", `pbc_${frm.doc.name}`, { nc_enable: 0 }) 174 | .then(() => { 175 | frm.reload_doc(); 176 | setTimeout(() => { 177 | managePibiDAVAddon(frm); 178 | }, 500); 179 | }); 180 | } 181 | // Create or Update Addon with Folder 182 | function createOrUpdateAddonWithFolder(frm, addon) { 183 | const docname = frm.doc.name; 184 | const nc_parent_folder = frm.doc.nc_parent_folder; 185 | const addonName = addon.name || `pbc_${docname}`; 186 | 187 | frappe.db.set_value("PibiDAV Addon", addonName, { 188 | nc_enable: 1, 189 | nc_folder_internal_link: nc_parent_folder 190 | }).then(() => { 191 | fetchAndSetFolderPath(nc_parent_folder, addonName, frm); 192 | }); 193 | } 194 | // Create or Update Addon without Folder 195 | function createOrUpdateAddonWithoutFolder(frm, addon) { 196 | const docname = frm.doc.name; 197 | const addonName = addon.name || `pbc_${docname}`; 198 | 199 | frappe.db.set_value("PibiDAV Addon", addonName, { nc_enable: 1 }) 200 | .then(() => { 201 | frm.reload_doc(); 202 | setTimeout(() => { 203 | managePibiDAVAddon(frm); 204 | }, 500); 205 | }); 206 | } 207 | // Fetch and Set Folder Path 208 | function fetchAndSetFolderPath(folderLink, addonName, frm) { 209 | frappe.db.get_value("PibiDAV Addon", addonName, "nc_folder").then(result => { 210 | const currentFolder = result.message.nc_folder; 211 | 212 | // Check if nc_folder is already filled 213 | if (currentFolder && currentFolder.trim() !== "") { 214 | frm.reload_doc(); 215 | setTimeout(() => { 216 | managePibiDAVAddon(frm); 217 | }, 500); 218 | } else { 219 | // If not filled, proceed to fetch and set the folder path 220 | frappe.call({ 221 | method: 'pibidav.pibidav.custom.get_folder_path_from_link', 222 | args: { fileid: extractFileId(folderLink) }, 223 | callback: function (r) { 224 | if (!r.exc) { 225 | frappe.db.set_value("PibiDAV Addon", addonName, { 226 | nc_folder: r.message 227 | }).then(() => { 228 | frm.reload_doc(); 229 | setTimeout(() => { 230 | managePibiDAVAddon(frm); 231 | }, 500); 232 | }); 233 | } else { 234 | frappe.msgprint(__('Failed to fetch folder path.')); 235 | frm.reload_doc(); 236 | setTimeout(() => { 237 | managePibiDAVAddon(frm); 238 | }, 500); 239 | } 240 | } 241 | }); 242 | } 243 | }); 244 | } 245 | // Open NextCloud Browser 246 | function openNextCloudBrowser(addon) { 247 | const targetFolder = addon.nc_folder || '/'; // Use addon's nc_folder which should be populated from nc_parent_folder 248 | 249 | new frappe.ui.pibiDocs({ 250 | targetFolder: targetFolder 251 | }); 252 | } 253 | // Create Addon 254 | function createAddon(frm, callback) { 255 | frappe.db.insert({ 256 | doctype: "PibiDAV Addon", 257 | ref_doctype: frm.doc.doctype, 258 | ref_docname: frm.doc.name, 259 | nc_enable: 1 260 | }).then(callback); 261 | } 262 | // Handle After Save 263 | function handleAfterSave(frm) { 264 | frappe.db.get_list("PibiDAV Addon", { 265 | filters: { ref_doctype: frm.doc.doctype, ref_docname: frm.doc.name }, 266 | fields: ["nc_enable", "nc_folder_internal_link"] 267 | }).then(res => { 268 | const addon = res[0] || {}; 269 | if (!addon.nc_folder_internal_link && addon.nc_enable) { 270 | createFolderDialog(frm); 271 | } 272 | }); 273 | } 274 | // Create Folder Dialog 275 | function createFolderDialog(frm) { 276 | frappe.call({ 277 | method: "pibidav.pibidav.custom.doCreateFolder", 278 | args: { doctype: frm.doc.doctype } 279 | }).then(r => { 280 | if (r.message) { 281 | openFolderCreationDialog(frm, r.message); 282 | } 283 | }); 284 | } 285 | // Open Folder Creation Dialog 286 | function openFolderCreationDialog(frm, doCreate) { 287 | const defaultData = getDefaultFolderData(frm); 288 | if (doCreate) { 289 | const d = new frappe.ui.Dialog({ 290 | title: __('Create NC Folder'), 291 | fields: getFolderDialogFields(defaultData), 292 | primary_action_label: 'Create', 293 | primary_action(values) { 294 | if (!values.abbreviation || !values.strmain || !values.folder_set) { 295 | frappe.throw(__('Complete all data: Abbreviation, Folder Name, and Folder Set')); 296 | } 297 | createNextCloudFolder(frm, values); 298 | d.hide(); 299 | } 300 | }); 301 | d.show(); 302 | } 303 | } 304 | // Utility Functions 305 | function extractFileId(htmlString) { 306 | const regex = /\/f\/(\d+)/; 307 | const match = htmlString.match(regex); 308 | return match ? match[1] : null; 309 | } 310 | // 311 | function getDefaultFolderData(frm) { 312 | if (frm.doc.doctype === 'Customer') { 313 | return { 314 | folder_name: frm.doc.customer_name, 315 | abbreviation: frm.doc.pb_abbreviation, 316 | folder_set: '(CLI) Plantilla Clientes' 317 | }; 318 | } else if (frm.doc.doctype === 'Supplier') { 319 | return { 320 | folder_name: frm.doc.supplier_name, 321 | abbreviation: frm.doc.pb_abbreviation, 322 | folder_set: '(PRO) Plantilla Proveedores' 323 | }; 324 | } 325 | return {}; 326 | } 327 | // 328 | function getFolderDialogFields(defaultData) { 329 | return [ 330 | { label: __('Enter Abbreviation'), fieldname: 'abbreviation', fieldtype: 'Data', default: defaultData.abbreviation }, 331 | { label: __('Enter Folder Name'), fieldname: 'strmain', fieldtype: 'Data', default: defaultData.folder_name }, 332 | { label: __('Select Folder Set'), fieldname: 'folder_set', fieldtype: 'Link', options: 'Folder Set', filters: { parent_folder_set: '' }, default: defaultData.folder_set }, 333 | { label: __('Sharing Option'), fieldname: 'sharing_option', fieldtype: 'Select', options: ['', '4-Upload Only', '17-Read Only', '31-Upload and Edit'] }, 334 | { label: __('Sharing Password'), fieldname: 'secret', fieldtype: 'Data' } 335 | ]; 336 | } 337 | // 338 | function createNextCloudFolder(frm, values) { 339 | frappe.call({ 340 | method: "pibidav.pibidav.custom.create_nc_folder", 341 | args: { 342 | dt: frm.doc.doctype, 343 | dn: frm.doc.name, 344 | abbr: values.abbreviation, 345 | strmain: values.strmain, 346 | folder_set: values.folder_set, 347 | sharing_option: values.sharing_option, 348 | secret: values.secret || "" 349 | } 350 | }).then(r => { 351 | frappe.msgprint(r.message); 352 | }); 353 | } 354 | 355 | // Setup file upload listener 356 | function setupFileUploadListener(frm) { 357 | // Remove any existing listeners to prevent duplicates 358 | frm.attachments && frm.attachments.remove_file_listeners && frm.attachments.remove_file_listeners(); 359 | 360 | // Add listener for file uploads 361 | if (frm.attachments) { 362 | frm.attachments.on_finish_upload = function() { 363 | // Wait a moment for the file to be processed in backend 364 | setTimeout(() => { 365 | refreshAddonAttachments(frm); 366 | }, 2000); 367 | }; 368 | } 369 | } 370 | 371 | // Refresh addon attachment items 372 | function refreshAddonAttachments(frm) { 373 | frappe.call({ 374 | method: "pibidav.pibidav.custom.refresh_addon_attachments", 375 | args: { 376 | dt: frm.doc.doctype, 377 | dn: frm.doc.name 378 | }, 379 | callback: function(r) { 380 | if (r.message && r.message.status === 'success') { 381 | frappe.show_alert({ 382 | message: __('NextCloud attachments refreshed'), 383 | indicator: 'green' 384 | }, 3); 385 | 386 | // If addon form is open, refresh it 387 | if (frappe.get_route_str().includes('PibiDAV Addon')) { 388 | frappe.model.clear_doc('PibiDAV Addon', `pbc_${frm.doc.name}`); 389 | frappe.set_route('Form', 'PibiDAV Addon', `pbc_${frm.doc.name}`); 390 | } 391 | } 392 | } 393 | }); 394 | } -------------------------------------------------------------------------------- /pibidav/translations/es.csv: -------------------------------------------------------------------------------- 1 | 17-Read Only,17-Solo Lectura 2 | 31-Upload and Edit,31-Subida y Edición 3 | 4-Upload Only,4-Subida Unicamente 4 | Abbreviation,Abreviatura 5 | About,Acerca de 6 | About Us Settings,Ajustes Página Acerca de 7 | Accounting,Financiero 8 | "Accounts, Invoices, Taxation, and more.","Cuentas, Facturas, Tasas y más." 9 | Action If Same Rate is Not Maintained,Acción si no se mantiene la misma Tasa 10 | Active,Activo 11 | Activity Type,Identificación Actividad 12 | Add / Remove Columns,Añade / Elimina Columnas 13 | Add a Filter,Añade Filtro 14 | Add Blog Category,Añadir Categoria de Blog 15 | Add Tags,Añade Etiquetas 16 | AI Extract,Extraído IA 17 | Alert Channels,Canales de Alerta 18 | Alert Date,Fecha de Alerta 19 | Alert Gap,Intervalo sin Alerta 20 | Alert Log,Registro de Alertas 21 | Alert Threshold,Umbrales de Alerta 22 | Alerts Active,Alertas Activas 23 | Alerts Received,Alertas Recibidas 24 | Answered,Respondido a Cliente 25 | Apply Filters,Aplicar Filtros 26 | Assets,Bienes/Activos 27 | "Assets, Depreciations, Repairs, and more.","Activos, Depreciaciones, Reparaciones y más." 28 | Assigned,Asignado 29 | Assistant Section,Asistente 30 | Attachment Files,Ficheros Adjuntos 31 | Attachment Item,Ficheros Controlados 32 | Attended,Asistió 33 | Backup Now,Respaldo Ahora 34 | Backup public and private files along with the database and site config,"Respaldo con base de datos, archivos públicos, privados y site_config.json" 35 | Bank Reconciliation Tool,Herramienta de Reconciliación Bancaria 36 | Bill for Rejected Quantity in Purchase Invoice,Factura por cantidad rechazada en la Factura de Compra 37 | Birthdays,Cumpleaños 38 | Blanket Order,Pedido Marco 39 | Blogger,Bloguero 40 | "Blogs, Website View Tracking, and more.","Blogs, Seguimiento de Vistas Web y más." 41 | BOM,LdM 42 | Box,Caja 43 | Build,Desarrollador 44 | by Email,Por Email 45 | by SMS,por SMS 46 | CalDav Calendar,Calendario CalDav 47 | CalDav ID Calendar,Calendario CalDav 48 | CalDav ID URL,ID URL CalDav 49 | Calendar,Calendario 50 | Can Submit,Puede Validar 51 | Cash Flow Statement,Estado de Tesorería 52 | Chairperson,Presidente 53 | Change Abbreviation,Cambiar Abreviatura 54 | Channels,Canales 55 | Chart of Accounts,Plan Contable 56 | Check Addon,Extensión NC 57 | Check Credentials,Comprueba Credenciales 58 | Check Stock Projected Qty,Comprobar la Cantidad de Estocaje Proyectado 59 | Child Data,Datos de Tabla 60 | Child Selector,Selector de Tablas 61 | Column Width,Ancho de Columna 62 | Commented,Comentado por Cliente 63 | Comments Closed,Comentarios Cerrados 64 | Committed Date,Fecha Contrato Proveedor 65 | Communication Date,Fecha Comunicación 66 | Company Fax,Fax Empresa 67 | Company Phone,Teléfono Empresa 68 | Company Website,Web Empresa 69 | "Complete all data Abbreviation, Folder Name and Folder Set","Completa la abreviatura, el nombre de carpeta y la plantilla" 70 | Complete by,Completar el 71 | Configure Account Settings,Configura los Ajustes de Finanzas 72 | Configure Buying Settings.,Configurar los Ajustes de Compras. 73 | Configure Columns,Configurar Columnas 74 | Configure Selling Settings.,Configurar los Ajustes de Ventas. 75 | Configure the action to stop the transaction or just warn if the same rate is not maintained.,Configurar la transacción de paro o sólo advertir de que no se mantiene la misma tasa 76 | Confirm action,Confirma la acción 77 | Connections,Conexiones 78 | Consequent Event,Evento Consecuente 79 | Content Phase,Fase de Creación de Contenido 80 | Contract Number,Numero Contrato 81 | Copy to Clipboard,Copiar al Portapapeles 82 | Create,Crear 83 | Create a Customer,Crear un Cliente 84 | Create a Fixed Asset Item,Crear un Artículo de Activo Fijo 85 | Create a Material Request,Crear una Solicitud de Material 86 | Create a Material Transfer Entry,Crear una Entrada de Transferencia de Material 87 | Create a Product,Crear un Producto 88 | Create a Quotation,Crear una Oferta 89 | Create a Supplier,Crear un Proveedor 90 | Create an Asset,Crear un Activo 91 | Create an Asset Category,Crear una Categoría de Activos 92 | Create and Send Quotation,Crear y Enviar Oferta 93 | Create Blogger,Crear Bloguero 94 | Create Department,Crear Departamento 95 | Create Designation,Crear Puesto 96 | Create Entry,Crear Entrada 97 | Create Holiday List,Crear Listado de Vacaciones 98 | Create in NC,Crear en NC 99 | Create Income Tax Slab,Crear Tabla IRPF 100 | Create Lead,Crear Iniciativa 101 | Create Leave Allocation,Crear Solicitud de Ausencia 102 | Create Leave Application,Crear Peticion de Ausencia 103 | Create Leave Type,Crear Tipo de Ausencia 104 | Create NC Folder,Crear Carpeta NC 105 | Create NC Group,Crear Grupo NC 106 | Create Opportunity,Crear Oportunidad 107 | Create Payroll Period,Crear Período de Nómina 108 | Create Salary Component,Crear Componente Salarial 109 | Create Salary Structure,Crear Estructura Salarial 110 | Create Your First Purchase Invoice ,Crear la primera Factura de Venta 111 | Create your first Purchase Order,Crear tu primera Orden de Compra 112 | Create Your First Sales Invoice ,Crear la primera Factura de Venta 113 | Create your first Sales Order,Crear tu primer Pedido de Venta 114 | Customer Comments,Comentarios Cliente 115 | Customer Date,Fecha Contractual Cliente 116 | Customer name,Denominación Cliente 117 | Customer Number,Numero Cliente 118 | Customer Penalty,Penalizado Cliente 119 | Customer Submission,Fecha Entrega Cliente 120 | Customer Updated,Fecha Actualizada Cliente 121 | Customer Weight,Peso Cliente 122 | Dashboard,Panel Control 123 | Data Import,Importación de Datos 124 | Default In-Transit Warehouse,Almacén en Tránsito por Defecto 125 | Deleted Event in CalDav,Borrado Evento en CalDav 126 | Deleted Event in CalDav Calendar,Borrado el Evento en Calendario CalDav 127 | Deliver To,Transmitir A 128 | Deliverable,Entregable 129 | Deliverable Type,Tipo Entregable 130 | Deliverables,Entregables 131 | Deliverables & Settings,Entregables 132 | Delivery Item,Documentos Entrega 133 | Delivery Note,Albaran 134 | Delivery Purpose,Propósito de Envío 135 | Digits to substitute with abbreviation,Digitos Plantilla a sustituir con Abreviatura 136 | Disable NC,Desactiva NC 137 | Disable NextCloud,Desactiva NextCloud 138 | Discipline,Especialidad 139 | Dispatch Address Name,Dirección de Entrega 140 | Distribution,Distribución 141 | Do you want to save empty doc to prompt?,Quieres gravar primero para ejecutar prompts? 142 | "DocField Names selected for tagging, comma separated without spaces","Nombre de campo elegido para etiquetado, separado por comas y sin espacios" 143 | DocFields to use from selected DocType separated by commas without spaces,Cadena de DocFields del DocType elegido a usar separados por comas y sin espacios 144 | Docs Controller,Control Documentacion 145 | Docs User,Usuario Documentacion 146 | DocType Selection,Selección de DocTypes 147 | DocType Selector,Selector DocTypes 148 | Document Number,Numero Documento 149 | Document Responsible,Responsable del Documento 150 | Document Submission,Transmisión de Documento 151 | Documentation,Documentación 152 | Documents,Documentos 153 | Drag and drop files here or upload from,Arrastra y suelta los archivos aquí o súbelos desde 154 | Due to MicroTemplating never use ' simple quotes in the html code.,Debido al uso de MicroTemplates nunca use comillas simples en el código html 155 | Edit Filters,Editar Filtros 156 | Edition,Edición 157 | Email alert,Alerta por Email 158 | Email Recipients,Receptores Email 159 | Employee Grievance,Queja de Empleado 160 | Employee Onboarding,Oferta de Empleo 161 | Employee Records to be created by,Los registros de Empleados se crean por 162 | "Employee, Leaves, and more.","Empleado, Ausencias y más" 163 | Employees Working on a Holiday,Empleados trabajando en festivo 164 | Enable NC,Activa NC 165 | Enable Website Tracking,Activar Seguimiento Web 166 | Enable NextCloud,Activa NextCloud 167 | Enter Abbreviation,Introduce la Abreviatura 168 | Enter Folder Name,Introduce el Nombre de Carpeta 169 | Error Creating NC Group,Error creando Grupo en NC 170 | Event created successfully.,Evento creado satisfactoriamente. 171 | Event removed successfully.,Evento eliminado correctamente. 172 | Event Stamp,Sello Evento 173 | Event UID,UID Evento 174 | Event updated successfully.,Evento actualizado correctamente. 175 | "Exact DocType Name wihout quotes, whithout spaces and separated by comma (i.e. Sales Invoice,Purchase Order)",Nombre de DocType Exacto sin espacios y separado por comas 176 | Feed Data,Datos a Alimentar 177 | Feeding Section,Sección Asistencia 178 | Fields to Tag,Campos para Etiquetado 179 | File Category,Categoría Archivado 180 | File List,Lista Archivos 181 | File Structure,Estructuras Carpetas 182 | Filename,Nombre Archivo 183 | filter,filtro 184 | Finally Approved,Aprobado Cliente 185 | First Select your NC Destination Folder,Primero Selecciona tu Carpeta Destino NC 186 | Fixed Asset Defaults,Configuración de Activos 187 | Folder Path,Ruta de Carpeta 188 | Folder Set,Plantillas Carpetas 189 | Follow Up Process,Seguimiento 190 | Free,Libre 191 | From Opportunity,Desde Oportunidad 192 | Generate Vcard Book,Generar Libro de Contactos 193 | Go to Page,Ir a la Página 194 | GPT Response,Respuesta GPT 195 | Grievance Type,Tipo de Queja 196 | Hero with Right Image,Heroe con Imagen a la Derecha 197 | Hour,Hora 198 | HR Settings,Configuración de RRHH 199 | HTML Code,Código HTML 200 | ID Number,Número ID 201 | "If checked, Rejected Quantity will be included while making Purchase Invoice from Purchase Receipt.","Si se marca, la cantidad rechazada será incluida al hacerse la factura de Compra desde el Albarán de Compra." 202 | Import .eml,Importar .eml 203 | Import .msg,Importar .msg 204 | Import vCard,Importar vCard 205 | Importing Contacts,Importando Contactos 206 | Inclusions,Inclusiones 207 | Integrate vCard Book,Integrar libro vCard 208 | Internal Comments,Comentarios Internos 209 | Introduction to Assets,Introducción a los Activos 210 | Introduction to Buying,Introducción a Compras 211 | Introduction to CRM,Introducción al CRM 212 | Introduction to Selling,Introducción a Ventas 213 | Introduction to Website,Introducción al Sitio Web 214 | "Inventory, Warehouses, Analysis, and more.","Inventario, Almacenamientos, Análisis y Más" 215 | Invitation Accepted,Invitación Aceptada 216 | Invitations,Invitaciones 217 | is Group,es Carpeta 218 | Is HTML,es HTML 219 | Is SuperStructure,Es Superestructura 220 | Is Translation,Es Traducción? 221 | Issuer Native,Edición Nativa 222 | Item,Producto 223 | Item Name,Nombre del Producto 224 | Language,Idioma 225 | Last Seen,Ultima Actividad 226 | "Lead, Opportunity, Customer, and more.","Iniciativa, Oportunidad, Cliente y más." 227 | Leaderboard,Cuadro de Honor 228 | Learn about Web Pages,Aprender sobre Páginas Web 229 | Leave Application,Petición de Ausencia 230 | Let's Set Up the Assets Module.,Configurar el Módulo de Activos. 231 | Let's Set Up the Buying Module.,Configuremos el Módulo de Compras 232 | Let's Set Up the Human Resource Module.,Configuremos el módulo de RRHH 233 | Let's Set Up the Payroll Module.,Configurar el Módulo de Nómina 234 | Let's Set Up the Selling Module.,Configuremos el Módulo de Ventas. 235 | Let's Set Up the Stock Module.,Configuremos el Módulo de Productos 236 | Let's Set Up Your Accounts and Taxes.,Configuremos Cuentas e Impuestos 237 | Let's Set Up Your CRM.,Configurar el CRM 238 | Let's Set Up Your Website.,Configuremos su Sitio Web 239 | Let’s create a stock opening entry,Creemos una entrada de apertura de stock 240 | Let’s create your first warehouse ,Creemos tu primer almacen 241 | Lets create a Tax Template for Sales ,Crear una Plantilla de Impuestos en Ventas 242 | Library,Biblioteca 243 | LifeCycle Status,Ciclo de Vida 244 | Like,Contiene 245 | List,Lista 246 | List of DocFields separated by commas without spaces to produce automatic file tagging in FP and NC,Lista de DocField separados por comas y sin espacios para asignar etiquetas automáticas en FP y NC a los archivos 247 | List View,Vista Lista 248 | Lists,Listados 249 | Location,Localización 250 | Lower Value,Valor Inferior 251 | Mail Signature,Firma de Correo 252 | Make a Sales Tax Template,Crear una plantilla de Impuestos en Ventas 253 | Manage eMails,Gestión eMails 254 | Manage Stock Movements,Gestionar movimientos de Artículos 255 | Masters,Maestros 256 | Minute of Meeting,Acta de Reunión 257 | Minutes Section,Registros/Actas 258 | Modules,Módulos 259 | Month,Mes 260 | MQTT Settings,Ajustes MQTT 261 | My Device,Mi Equipo 262 | My Profile,Mi Perfil 263 | My Settings,Mis Ajustes 264 | Name,ID 265 | name,ID 266 | Native,Original Nativo 267 | Native Files,Archivos Originales 268 | Native multipart,Multiparte nativo 269 | Native pdf,pdf Nativo 270 | NC Commands,Ordenes NC 271 | NC Doctypes Excluded,DocTypes Excluidos de NC 272 | NC Doctypes Included,DocTypes incluidos para NC 273 | NC Enable,Activa NC 274 | NC File List,Lista Archivos NC 275 | NC File Upload,Subir Archivos a NC 276 | NC Folder,Carpeta NC 277 | NC Folder Creation,Creación de Carpetas en NC 278 | NC Folder Internal Link,Enlace Interno Carpeta NC 279 | NC Folder Share Link,Enlace Compartido Carpeta NC 280 | NC Group Already Exists,Grupo NC ya Existe 281 | NC Group Created,Grupo NC Creado 282 | NC Path,Ruta NC 283 | NC Private Link,Enlace privado NC 284 | NC Share Link,Enlace Compartido NC 285 | New Chart,Nuevo Gráfico 286 | New Shortcut,Nuevo Atajo 287 | NextCloud Backup Credentials,Credenciales NextCloud Backup 288 | NextCloud Backup Enable,Activa NextCloud Backup 289 | NextCloud Credentials,Credenciales NextCloud 290 | NextCloud Credentials are correct,Credenciales NC correctas 291 | NextCloud Settings,Ajustes NextCloud 292 | No contacts selected.,No hay contactos seleccionados. 293 | No message associated,No hay asociado ningún mensaje 294 | Non Participant,No Participante 295 | OCR Extract,Extraído OCR 296 | OCR Extraction in progress,Extracción OCR en ejecución 297 | Off From Time,Fuera Desde 298 | Off To Time,Fuera Hasta 299 | Only one recipient can be provided,Solo se puede proporcionar un destinatario 300 | Optional,Opcional 301 | Overview,Información General 302 | Page Title,Título de Página 303 | Parent File Structure,Nodo Padre 304 | Parent Folder,Carpeta Padre 305 | Parent Path,Ruta Padre 306 | Parties,Participantes 307 | Pending Close,Comentarios pendientes cierre 308 | Phase,Fase 309 | Please provide a proper prompt,Por favor proporciona un prompt adecuado 310 | PO Name,Denominación Orden Compra 311 | prepare eMail,Preparar eMail 312 | Print Language,Idioma de Impresión 313 | Production Phase,Fase de Producción 314 | Production Unit,Unidad Producida 315 | "Products, Purchases, Analysis, and more.","Productos, Compras, Análisis y más" 316 | Profile,Perfil 317 | Published,Publicado 318 | Purchase an Asset Item,Comprar un Activo 319 | Purchase Receipt,Albarán de Compra 320 | QR Key,Llavero RFID/QR 321 | QR Keys,Llaveros RFID 322 | QRKey,Llave RFID/QR 323 | Qualification Stamp,Sello Calificación 324 | Queued for Backup. It may take a long time depending on your backup files size. Rest Easy!!!,Copia de Respaldo en cola. Puede llevar mucho tiempo dependiendo del tamaño de los archivos 325 | Quick Access,Acceso Rápido 326 | Quotation,Oferta 327 | Rate per Minute,Tasa por minuto 328 | Raw File,Archivo Fuente 329 | Rebuild Tree,Reconstruir Árbol 330 | Recipient,Destinatario 331 | Recreate,Recrea 332 | Recreate Folders in NC,Recrea las carpetas en NC 333 | Refresh Files List,Refrescar Indice NC 334 | Reminders,Recordatorios 335 | Rendered HTML,HTML 336 | Report,Informe 337 | Report an Issue,Informar de una Incidencia 338 | Report View,Vista Informe 339 | Reports & Masters,Informes y Maestros 340 | Request for Quotation,Petición de Oferta 341 | Required,Requerido 342 | Reset Customizations,Eliminar Personalizaciones 343 | Reset to default,Resetear por Defecto 344 | Restrict Backdated Leave Application ,Restringir Solicitudes de Vacaciones Retroactivas 345 | Retail,Al por menor 346 | Retirement Age,Edad de Jubilación 347 | Review Chart of Accounts,Revisar Plan Contable 348 | Review Process,Proceso de Revision 349 | Review Stock Settings,Revisar los Ajustes de Productos 350 | RFI Answer,Respuesta Solicitud Información 351 | RFI Answered,Solicitud Información Respondida 352 | RFI Closed,Solicitud de Información Cerrada 353 | RFI Pending,Solicitud Informacion Pendiente 354 | RFI Request,Solicitud Informacion 355 | RFID Tag,Control Llaves Tag 356 | Role Allowed to Override Stop Action,Rol permitido para sobreescribir Acción de Paro 357 | Root Dir Name,Nombre Directorio Raiz 358 | "Salary, Compensation, and more.","Salrios, Compensaciones y más" 359 | Sales Order,Pedido de Venta 360 | Save Customizations,Guardar Personalizaciones 361 | Secret,Clave 362 | Seed,Germen 363 | Select NC Folder,Elige Carpeta NC 364 | Select NextCloud Destination Folder,Elige la carpeta de destino en NextCloud 365 | Select NextCloud Folder,Elige la carpeta de destino en NextCloud 366 | Send Email,Enviar Correo 367 | Select Folder Set,Elige la Plantilla de Carpeta 368 | Send Invitation,Enviar Invitación 369 | Send Leave Notification,Enviar Notificación de Ausente 370 | Sensor Log,Registro de Sensores 371 | Sensor Readings,Lecturas de Sensor 372 | Sent Or Received,Enviado o Recibido 373 | Serial,N.Serie 374 | Set all private,Establecer todos privados 375 | Set all public,Establecer todos públicos 376 | Set the frequency for holiday reminders,Establecer la frecuencia de los recordatorios de Vacaciones 377 | Set up your Warehouse,Configurar tu Almacenamiento 378 | Settings Section,Sección de Ajustes 379 | Setup a Warehouse,Configurar un Almacenamiento 380 | Share Link,Enlace Compartido 381 | Share URL,Comparte URL 382 | Sharing Option,Opción Compartir 383 | Sharing Password,Clave Compartición 384 | Sharing password must be greater than 10 chars long and not usual,La clave de compartición debe ser mayor de 10 caracteres y no comun 385 | Shipping Address Name,Dirección de Despacho 386 | Shortener,Recorte 387 | Show Form Tour,Mostrar un Tour en Formulario 388 | Show Preview,Previsualización 389 | Show Saved,Mostrar Guardados 390 | Signature Item,Firmas de Correo 391 | Signature Picture,Imagen Firma 392 | SMS alert,Alerta por SMS 393 | SMS Recipients,Receptores SMS 394 | Source Lang,Idioma Origen 395 | Spain,España 396 | Standard with Numbers,Estándar con Numeración 397 | Standard Working Hours,Horas de Trabajo Normales 398 | Start and End Dates,Control Actividad 399 | Stock,Productos 400 | Submitted,Transmitido 401 | Summarization,Sumarización 402 | Superseeded,Superado 403 | Supplier Attachments,Archivos de Proveedor 404 | Supplier Delivery,Fecha Entrega Proveedor 405 | Supplier Number,Numero Proveedor 406 | Supplier Penalty,Penalizado Proveedor 407 | Supplier Weight,Peso Proveedor 408 | Sure you want to ask chatGPT?,Seguro de preguntar a chatGPT 409 | Sync with CalDav,Sincronizar con CalDav 410 | Synchronization successful.,Sincronización correcta. 411 | System User,Usuario del Sistema 412 | Take a walk through Stock Settings,Dar un paseo por los Ajustes de Productos 413 | Target Lang,Idioma Destino 414 | Team Members Subtitle,Subtítulos Miembros del Equipo 415 | This is based on Alerts received from this Sensor,Mapa basado en Alertas recibidas desde este Sensor 416 | Timesheet,Parte de Trabajo 417 | to Customer,Envio a Cliente 418 | To Customer,A Cliente 419 | to External,Envio a Terceros 420 | To ReIssue,A reeditar 421 | To Review,A Revisar 422 | Toggle Full Width,Alterna Vista Completa 423 | Toggle Theme,Ajustar Aspecto 424 | Tools,Herramientas 425 | Transaction Feed,Alimentar Transacción 426 | Transcription Language,Idioma Destino 427 | Tree,Árbol 428 | Unit of Measure (UOM),Unidad de Medida (UdM) 429 | UOM,UdM 430 | Update Settings,Actualizar Ajustes 431 | Update Stock Opening Balance,Actualizar el Balance de Apertura de Estocaje 432 | Updated Date,Fecha actualizada Proveedor 433 | Updated/Created Event in Calendar,Actualizado/Creado un Evento de Calendario 434 | Upload to NC,Subir a NC 435 | Upload to NextCloud,Subir a NextCloud 436 | Uploaded To NC,Subido a NC 437 | Uploaded To NextCloud,Subido a NextCloud 438 | Upper Value,Valor Superior 439 | User Forum,Foro de Usuarios 440 | User Prompt,Prompt de Usuario 441 | Validated,Validado 442 | vCard Create,Crear vCard 443 | vCard Download,Descargar vCard 444 | vCard Integration,Integración vCard 445 | vCard QR Code,Código QR vCard 446 | vCard Text,Texto vCard 447 | Vendor,Proveedor 448 | Watch Tutorial,Ver el Tutorial 449 | Watch Video,Mirar el Video 450 | Web Page,Página Web 451 | Work Anniversaries ,Aniversarios de Trabajo 452 | Work Place,Area de Control 453 | WorkCenter,Area de Control 454 | WorkCenter Occupancy,Ocupacion Area 455 | WorkCenter Schedule,Horario Area 456 | Workship,Area de Control 457 | Workship Occupancy,Ocupación de Área 458 | Workship Schedule,Horario Area 459 | You can only reply to one message at a time,Sólo puedes responder a un mensaje de cada vez 460 | You have selected a file and not a folder,Se ha seleccionado un archivo y no una carpeta 461 | You must select one message,Debes elegir un mensaje 462 | NC Folder Link,Enlace Carpeta NC 463 | Fetch NC Folder Link,Obtener Enlace Carpeta NC 464 | -------------------------------------------------------------------------------- /pibidav/fixtures/custom_field.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "allow_in_quick_entry": 0, 4 | "allow_on_submit": 0, 5 | "bold": 0, 6 | "collapsible": 0, 7 | "collapsible_depends_on": null, 8 | "columns": 0, 9 | "default": null, 10 | "depends_on": null, 11 | "description": null, 12 | "docstatus": 0, 13 | "doctype": "Custom Field", 14 | "dt": "Tag", 15 | "fetch_from": null, 16 | "fetch_if_empty": 0, 17 | "fieldname": "uploaded_to_nextcloud", 18 | "fieldtype": "Check", 19 | "hidden": 0, 20 | "hide_border": 0, 21 | "hide_days": 0, 22 | "hide_seconds": 0, 23 | "ignore_user_permissions": 0, 24 | "ignore_xss_filter": 0, 25 | "in_global_search": 0, 26 | "in_list_view": 0, 27 | "in_preview": 0, 28 | "in_standard_filter": 0, 29 | "insert_after": "description", 30 | "is_system_generated": 0, 31 | "is_virtual": 0, 32 | "label": "Uploaded To NextCloud", 33 | "length": 0, 34 | "link_filters": null, 35 | "mandatory_depends_on": null, 36 | "modified": "2022-04-27 09:01:18.884151", 37 | "module": "Pibidav", 38 | "name": "Tag-uploaded_to_nextcloud", 39 | "no_copy": 0, 40 | "non_negative": 0, 41 | "options": null, 42 | "permlevel": 0, 43 | "placeholder": null, 44 | "precision": "", 45 | "print_hide": 0, 46 | "print_hide_if_no_value": 0, 47 | "print_width": null, 48 | "read_only": 1, 49 | "read_only_depends_on": null, 50 | "report_hide": 0, 51 | "reqd": 0, 52 | "search_index": 0, 53 | "show_dashboard": 0, 54 | "sort_options": 0, 55 | "translatable": 0, 56 | "unique": 0, 57 | "width": null 58 | }, 59 | { 60 | "allow_in_quick_entry": 0, 61 | "allow_on_submit": 0, 62 | "bold": 0, 63 | "collapsible": 0, 64 | "collapsible_depends_on": null, 65 | "columns": 0, 66 | "default": null, 67 | "depends_on": null, 68 | "description": null, 69 | "docstatus": 0, 70 | "doctype": "Custom Field", 71 | "dt": "Tag", 72 | "fetch_from": null, 73 | "fetch_if_empty": 0, 74 | "fieldname": "tagid", 75 | "fieldtype": "Data", 76 | "hidden": 0, 77 | "hide_border": 0, 78 | "hide_days": 0, 79 | "hide_seconds": 0, 80 | "ignore_user_permissions": 0, 81 | "ignore_xss_filter": 0, 82 | "in_global_search": 0, 83 | "in_list_view": 0, 84 | "in_preview": 0, 85 | "in_standard_filter": 0, 86 | "insert_after": "uploaded_to_nextcloud", 87 | "is_system_generated": 0, 88 | "is_virtual": 0, 89 | "label": "TagId", 90 | "length": 0, 91 | "link_filters": null, 92 | "mandatory_depends_on": null, 93 | "modified": "2022-04-27 09:01:08.998367", 94 | "module": "Pibidav", 95 | "name": "Tag-tagid", 96 | "no_copy": 0, 97 | "non_negative": 0, 98 | "options": null, 99 | "permlevel": 0, 100 | "placeholder": null, 101 | "precision": "", 102 | "print_hide": 0, 103 | "print_hide_if_no_value": 0, 104 | "print_width": null, 105 | "read_only": 1, 106 | "read_only_depends_on": null, 107 | "report_hide": 0, 108 | "reqd": 0, 109 | "search_index": 0, 110 | "show_dashboard": 0, 111 | "sort_options": 0, 112 | "translatable": 0, 113 | "unique": 0, 114 | "width": null 115 | }, 116 | { 117 | "allow_in_quick_entry": 0, 118 | "allow_on_submit": 0, 119 | "bold": 0, 120 | "collapsible": 0, 121 | "collapsible_depends_on": null, 122 | "columns": 0, 123 | "default": null, 124 | "depends_on": null, 125 | "description": null, 126 | "docstatus": 0, 127 | "doctype": "Custom Field", 128 | "dt": "Supplier", 129 | "fetch_from": null, 130 | "fetch_if_empty": 0, 131 | "fieldname": "nextcloud", 132 | "fieldtype": "Link", 133 | "hidden": 0, 134 | "hide_border": 0, 135 | "hide_days": 0, 136 | "hide_seconds": 0, 137 | "ignore_user_permissions": 0, 138 | "ignore_xss_filter": 0, 139 | "in_global_search": 0, 140 | "in_list_view": 0, 141 | "in_preview": 0, 142 | "in_standard_filter": 0, 143 | "insert_after": "pb_abbreviation", 144 | "is_system_generated": 0, 145 | "is_virtual": 0, 146 | "label": "Nextcloud", 147 | "length": 0, 148 | "link_filters": null, 149 | "mandatory_depends_on": null, 150 | "modified": "2024-12-06 21:59:37.115047", 151 | "module": "Pibidav", 152 | "name": "Supplier-custom_pd_nextcloud", 153 | "no_copy": 0, 154 | "non_negative": 0, 155 | "options": "PibiDAV Addon", 156 | "permlevel": 0, 157 | "placeholder": null, 158 | "precision": "", 159 | "print_hide": 0, 160 | "print_hide_if_no_value": 0, 161 | "print_width": null, 162 | "read_only": 0, 163 | "read_only_depends_on": null, 164 | "report_hide": 0, 165 | "reqd": 0, 166 | "search_index": 0, 167 | "show_dashboard": 0, 168 | "sort_options": 0, 169 | "translatable": 0, 170 | "unique": 0, 171 | "width": null 172 | }, 173 | { 174 | "allow_in_quick_entry": 0, 175 | "allow_on_submit": 1, 176 | "bold": 0, 177 | "collapsible": 0, 178 | "collapsible_depends_on": null, 179 | "columns": 0, 180 | "default": null, 181 | "depends_on": null, 182 | "description": null, 183 | "docstatus": 0, 184 | "doctype": "Custom Field", 185 | "dt": "Customer", 186 | "fetch_from": null, 187 | "fetch_if_empty": 0, 188 | "fieldname": "pb_abbreviation", 189 | "fieldtype": "Data", 190 | "hidden": 0, 191 | "hide_border": 0, 192 | "hide_days": 0, 193 | "hide_seconds": 0, 194 | "ignore_user_permissions": 0, 195 | "ignore_xss_filter": 0, 196 | "in_global_search": 1, 197 | "in_list_view": 1, 198 | "in_preview": 0, 199 | "in_standard_filter": 1, 200 | "insert_after": "customer_group", 201 | "is_system_generated": 0, 202 | "is_virtual": 0, 203 | "label": "Abbreviation", 204 | "length": 0, 205 | "link_filters": null, 206 | "mandatory_depends_on": null, 207 | "modified": "2024-12-06 17:30:57.212078", 208 | "module": "Pibidav", 209 | "name": "Customer-custom_pb_abbreviation", 210 | "no_copy": 0, 211 | "non_negative": 0, 212 | "options": null, 213 | "permlevel": 0, 214 | "placeholder": null, 215 | "precision": "", 216 | "print_hide": 0, 217 | "print_hide_if_no_value": 0, 218 | "print_width": null, 219 | "read_only": 0, 220 | "read_only_depends_on": null, 221 | "report_hide": 0, 222 | "reqd": 1, 223 | "search_index": 0, 224 | "show_dashboard": 0, 225 | "sort_options": 0, 226 | "translatable": 0, 227 | "unique": 1, 228 | "width": null 229 | }, 230 | { 231 | "allow_in_quick_entry": 0, 232 | "allow_on_submit": 0, 233 | "bold": 0, 234 | "collapsible": 0, 235 | "collapsible_depends_on": null, 236 | "columns": 0, 237 | "default": null, 238 | "depends_on": null, 239 | "description": null, 240 | "docstatus": 0, 241 | "doctype": "Custom Field", 242 | "dt": "Customer", 243 | "fetch_from": null, 244 | "fetch_if_empty": 0, 245 | "fieldname": "nextcloud", 246 | "fieldtype": "Link", 247 | "hidden": 0, 248 | "hide_border": 0, 249 | "hide_days": 0, 250 | "hide_seconds": 0, 251 | "ignore_user_permissions": 0, 252 | "ignore_xss_filter": 0, 253 | "in_global_search": 0, 254 | "in_list_view": 0, 255 | "in_preview": 0, 256 | "in_standard_filter": 0, 257 | "insert_after": "pb_abbreviation", 258 | "is_system_generated": 0, 259 | "is_virtual": 0, 260 | "label": "Nextcloud", 261 | "length": 0, 262 | "link_filters": null, 263 | "mandatory_depends_on": null, 264 | "modified": "2024-12-08 01:12:24.325454", 265 | "module": "Pibidav", 266 | "name": "Customer-custom_pd_nextcloud", 267 | "no_copy": 0, 268 | "non_negative": 0, 269 | "options": "PibiDAV Addon", 270 | "permlevel": 0, 271 | "placeholder": null, 272 | "precision": "", 273 | "print_hide": 0, 274 | "print_hide_if_no_value": 0, 275 | "print_width": null, 276 | "read_only": 0, 277 | "read_only_depends_on": null, 278 | "report_hide": 0, 279 | "reqd": 0, 280 | "search_index": 0, 281 | "show_dashboard": 0, 282 | "sort_options": 0, 283 | "translatable": 0, 284 | "unique": 0, 285 | "width": null 286 | }, 287 | { 288 | "allow_in_quick_entry": 0, 289 | "allow_on_submit": 0, 290 | "bold": 0, 291 | "collapsible": 0, 292 | "collapsible_depends_on": null, 293 | "columns": 0, 294 | "default": null, 295 | "depends_on": null, 296 | "description": null, 297 | "docstatus": 0, 298 | "doctype": "Custom Field", 299 | "dt": "Supplier", 300 | "fetch_from": "nextcloud.nc_folder_internal_link", 301 | "fetch_if_empty": 0, 302 | "fieldname": "nc_folder_link", 303 | "fieldtype": "Read Only", 304 | "hidden": 0, 305 | "hide_border": 0, 306 | "hide_days": 0, 307 | "hide_seconds": 0, 308 | "ignore_user_permissions": 0, 309 | "ignore_xss_filter": 0, 310 | "in_global_search": 0, 311 | "in_list_view": 0, 312 | "in_preview": 0, 313 | "in_standard_filter": 0, 314 | "insert_after": "is_transporter", 315 | "is_system_generated": 0, 316 | "is_virtual": 0, 317 | "label": "NC Folder Link", 318 | "length": 0, 319 | "link_filters": null, 320 | "mandatory_depends_on": null, 321 | "modified": "2024-12-08 12:07:55.697978", 322 | "module": "Pibidav", 323 | "name": "Supplier-custom_pd_nc_folder_link", 324 | "no_copy": 0, 325 | "non_negative": 0, 326 | "options": null, 327 | "permlevel": 0, 328 | "placeholder": null, 329 | "precision": "", 330 | "print_hide": 0, 331 | "print_hide_if_no_value": 0, 332 | "print_width": null, 333 | "read_only": 0, 334 | "read_only_depends_on": null, 335 | "report_hide": 0, 336 | "reqd": 0, 337 | "search_index": 0, 338 | "show_dashboard": 0, 339 | "sort_options": 0, 340 | "translatable": 0, 341 | "unique": 0, 342 | "width": null 343 | }, 344 | { 345 | "allow_in_quick_entry": 0, 346 | "allow_on_submit": 0, 347 | "bold": 0, 348 | "collapsible": 0, 349 | "collapsible_depends_on": null, 350 | "columns": 0, 351 | "default": null, 352 | "depends_on": null, 353 | "description": null, 354 | "docstatus": 0, 355 | "doctype": "Custom Field", 356 | "dt": "Customer", 357 | "fetch_from": "nextcloud.nc_folder_internal_link", 358 | "fetch_if_empty": 0, 359 | "fieldname": "nc_folder_link", 360 | "fieldtype": "Read Only", 361 | "hidden": 0, 362 | "hide_border": 0, 363 | "hide_days": 0, 364 | "hide_seconds": 0, 365 | "ignore_user_permissions": 0, 366 | "ignore_xss_filter": 0, 367 | "in_global_search": 0, 368 | "in_list_view": 0, 369 | "in_preview": 0, 370 | "in_standard_filter": 0, 371 | "insert_after": "account_manager", 372 | "is_system_generated": 0, 373 | "is_virtual": 0, 374 | "label": "NC Folder Link", 375 | "length": 0, 376 | "link_filters": null, 377 | "mandatory_depends_on": null, 378 | "modified": "2024-12-08 12:11:23.806930", 379 | "module": "Pibidav", 380 | "name": "Customer-custom_pd_nc_folder_link", 381 | "no_copy": 0, 382 | "non_negative": 0, 383 | "options": null, 384 | "permlevel": 0, 385 | "placeholder": null, 386 | "precision": "", 387 | "print_hide": 0, 388 | "print_hide_if_no_value": 0, 389 | "print_width": null, 390 | "read_only": 0, 391 | "read_only_depends_on": null, 392 | "report_hide": 0, 393 | "reqd": 0, 394 | "search_index": 0, 395 | "show_dashboard": 0, 396 | "sort_options": 0, 397 | "translatable": 0, 398 | "unique": 0, 399 | "width": null 400 | }, 401 | { 402 | "allow_in_quick_entry": 0, 403 | "allow_on_submit": 0, 404 | "bold": 0, 405 | "collapsible": 0, 406 | "collapsible_depends_on": null, 407 | "columns": 0, 408 | "default": null, 409 | "depends_on": null, 410 | "description": null, 411 | "docstatus": 0, 412 | "doctype": "Custom Field", 413 | "dt": "File", 414 | "fetch_from": null, 415 | "fetch_if_empty": 0, 416 | "fieldname": "uploaded_to_nextcloud", 417 | "fieldtype": "Check", 418 | "hidden": 0, 419 | "hide_border": 0, 420 | "hide_days": 0, 421 | "hide_seconds": 0, 422 | "ignore_user_permissions": 0, 423 | "ignore_xss_filter": 0, 424 | "in_global_search": 0, 425 | "in_list_view": 0, 426 | "in_preview": 0, 427 | "in_standard_filter": 0, 428 | "insert_after": "uploaded_to_google_drive", 429 | "is_system_generated": 0, 430 | "is_virtual": 0, 431 | "label": "Uploaded To NextCloud", 432 | "length": 0, 433 | "link_filters": null, 434 | "mandatory_depends_on": null, 435 | "modified": "2022-04-14 18:42:39.635285", 436 | "module": "Pibidav", 437 | "name": "File-uploaded_to_nextcloud", 438 | "no_copy": 0, 439 | "non_negative": 0, 440 | "options": null, 441 | "permlevel": 0, 442 | "placeholder": null, 443 | "precision": "", 444 | "print_hide": 0, 445 | "print_hide_if_no_value": 0, 446 | "print_width": null, 447 | "read_only": 1, 448 | "read_only_depends_on": null, 449 | "report_hide": 0, 450 | "reqd": 0, 451 | "search_index": 0, 452 | "show_dashboard": 0, 453 | "sort_options": 0, 454 | "translatable": 0, 455 | "unique": 0, 456 | "width": null 457 | }, 458 | { 459 | "allow_in_quick_entry": 0, 460 | "allow_on_submit": 0, 461 | "bold": 0, 462 | "collapsible": 0, 463 | "collapsible_depends_on": null, 464 | "columns": 0, 465 | "default": null, 466 | "depends_on": null, 467 | "description": null, 468 | "docstatus": 0, 469 | "doctype": "Custom Field", 470 | "dt": "File", 471 | "fetch_from": null, 472 | "fetch_if_empty": 0, 473 | "fieldname": "folder_path", 474 | "fieldtype": "Text", 475 | "hidden": 0, 476 | "hide_border": 0, 477 | "hide_days": 0, 478 | "hide_seconds": 0, 479 | "ignore_user_permissions": 0, 480 | "ignore_xss_filter": 0, 481 | "in_global_search": 0, 482 | "in_list_view": 0, 483 | "in_preview": 0, 484 | "in_standard_filter": 0, 485 | "insert_after": "uploaded_to_nextcloud", 486 | "is_system_generated": 0, 487 | "is_virtual": 0, 488 | "label": "Folder Path", 489 | "length": 0, 490 | "link_filters": null, 491 | "mandatory_depends_on": null, 492 | "modified": "2022-04-14 22:39:14.270549", 493 | "module": "Pibidav", 494 | "name": "File-folder_path", 495 | "no_copy": 0, 496 | "non_negative": 0, 497 | "options": null, 498 | "permlevel": 0, 499 | "placeholder": null, 500 | "precision": "", 501 | "print_hide": 0, 502 | "print_hide_if_no_value": 0, 503 | "print_width": null, 504 | "read_only": 1, 505 | "read_only_depends_on": null, 506 | "report_hide": 0, 507 | "reqd": 0, 508 | "search_index": 0, 509 | "show_dashboard": 0, 510 | "sort_options": 0, 511 | "translatable": 0, 512 | "unique": 0, 513 | "width": null 514 | }, 515 | { 516 | "allow_in_quick_entry": 0, 517 | "allow_on_submit": 0, 518 | "bold": 0, 519 | "collapsible": 0, 520 | "collapsible_depends_on": null, 521 | "columns": 0, 522 | "default": null, 523 | "depends_on": null, 524 | "description": null, 525 | "docstatus": 0, 526 | "doctype": "Custom Field", 527 | "dt": "File", 528 | "fetch_from": null, 529 | "fetch_if_empty": 0, 530 | "fieldname": "share_link", 531 | "fieldtype": "Data", 532 | "hidden": 0, 533 | "hide_border": 0, 534 | "hide_days": 0, 535 | "hide_seconds": 0, 536 | "ignore_user_permissions": 0, 537 | "ignore_xss_filter": 0, 538 | "in_global_search": 0, 539 | "in_list_view": 0, 540 | "in_preview": 0, 541 | "in_standard_filter": 0, 542 | "insert_after": "folder_path", 543 | "is_system_generated": 0, 544 | "is_virtual": 0, 545 | "label": "Share Link", 546 | "length": 0, 547 | "link_filters": null, 548 | "mandatory_depends_on": null, 549 | "modified": "2022-04-17 09:27:40.367685", 550 | "module": "Pibidav", 551 | "name": "File-share_link", 552 | "no_copy": 0, 553 | "non_negative": 0, 554 | "options": null, 555 | "permlevel": 0, 556 | "placeholder": null, 557 | "precision": "", 558 | "print_hide": 0, 559 | "print_hide_if_no_value": 0, 560 | "print_width": null, 561 | "read_only": 1, 562 | "read_only_depends_on": null, 563 | "report_hide": 0, 564 | "reqd": 0, 565 | "search_index": 0, 566 | "show_dashboard": 0, 567 | "sort_options": 0, 568 | "translatable": 0, 569 | "unique": 0, 570 | "width": null 571 | }, 572 | { 573 | "allow_in_quick_entry": 0, 574 | "allow_on_submit": 0, 575 | "bold": 0, 576 | "collapsible": 0, 577 | "collapsible_depends_on": null, 578 | "columns": 0, 579 | "default": null, 580 | "depends_on": null, 581 | "description": null, 582 | "docstatus": 0, 583 | "doctype": "Custom Field", 584 | "dt": "File", 585 | "fetch_from": null, 586 | "fetch_if_empty": 0, 587 | "fieldname": "fileid", 588 | "fieldtype": "Data", 589 | "hidden": 0, 590 | "hide_border": 0, 591 | "hide_days": 0, 592 | "hide_seconds": 0, 593 | "ignore_user_permissions": 0, 594 | "ignore_xss_filter": 0, 595 | "in_global_search": 0, 596 | "in_list_view": 0, 597 | "in_preview": 0, 598 | "in_standard_filter": 0, 599 | "insert_after": "share_link", 600 | "is_system_generated": 0, 601 | "is_virtual": 0, 602 | "label": "FileID", 603 | "length": 0, 604 | "link_filters": null, 605 | "mandatory_depends_on": null, 606 | "modified": "2022-04-26 13:24:09.671191", 607 | "module": "Pibidav", 608 | "name": "File-fileid", 609 | "no_copy": 0, 610 | "non_negative": 0, 611 | "options": null, 612 | "permlevel": 0, 613 | "placeholder": null, 614 | "precision": "", 615 | "print_hide": 0, 616 | "print_hide_if_no_value": 0, 617 | "print_width": null, 618 | "read_only": 1, 619 | "read_only_depends_on": null, 620 | "report_hide": 0, 621 | "reqd": 0, 622 | "search_index": 0, 623 | "show_dashboard": 0, 624 | "sort_options": 0, 625 | "translatable": 0, 626 | "unique": 0, 627 | "width": null 628 | }, 629 | { 630 | "allow_in_quick_entry": 0, 631 | "allow_on_submit": 0, 632 | "bold": 0, 633 | "collapsible": 1, 634 | "collapsible_depends_on": null, 635 | "columns": 0, 636 | "default": null, 637 | "depends_on": null, 638 | "description": null, 639 | "docstatus": 0, 640 | "doctype": "Custom Field", 641 | "dt": "User", 642 | "fetch_from": null, 643 | "fetch_if_empty": 0, 644 | "fieldname": "nextcloud_credentials", 645 | "fieldtype": "Section Break", 646 | "hidden": 0, 647 | "hide_border": 0, 648 | "hide_days": 0, 649 | "hide_seconds": 0, 650 | "ignore_user_permissions": 1, 651 | "ignore_xss_filter": 0, 652 | "in_global_search": 0, 653 | "in_list_view": 0, 654 | "in_preview": 0, 655 | "in_standard_filter": 0, 656 | "insert_after": "generate_keys", 657 | "is_system_generated": 0, 658 | "is_virtual": 0, 659 | "label": "NextCloud Credentials", 660 | "length": 0, 661 | "link_filters": null, 662 | "mandatory_depends_on": null, 663 | "modified": "2022-04-01 19:14:50.269324", 664 | "module": "Pibidav", 665 | "name": "User-nextcloud_credentials", 666 | "no_copy": 0, 667 | "non_negative": 0, 668 | "options": null, 669 | "permlevel": 0, 670 | "placeholder": null, 671 | "precision": "", 672 | "print_hide": 0, 673 | "print_hide_if_no_value": 0, 674 | "print_width": null, 675 | "read_only": 0, 676 | "read_only_depends_on": null, 677 | "report_hide": 0, 678 | "reqd": 0, 679 | "search_index": 0, 680 | "show_dashboard": 0, 681 | "sort_options": 0, 682 | "translatable": 0, 683 | "unique": 0, 684 | "width": null 685 | }, 686 | { 687 | "allow_in_quick_entry": 1, 688 | "allow_on_submit": 0, 689 | "bold": 0, 690 | "collapsible": 0, 691 | "collapsible_depends_on": null, 692 | "columns": 0, 693 | "default": "0", 694 | "depends_on": null, 695 | "description": null, 696 | "docstatus": 0, 697 | "doctype": "Custom Field", 698 | "dt": "User", 699 | "fetch_from": null, 700 | "fetch_if_empty": 0, 701 | "fieldname": "nextcloud_enable", 702 | "fieldtype": "Check", 703 | "hidden": 0, 704 | "hide_border": 0, 705 | "hide_days": 0, 706 | "hide_seconds": 0, 707 | "ignore_user_permissions": 1, 708 | "ignore_xss_filter": 0, 709 | "in_global_search": 0, 710 | "in_list_view": 0, 711 | "in_preview": 0, 712 | "in_standard_filter": 0, 713 | "insert_after": "nextcloud_credentials", 714 | "is_system_generated": 0, 715 | "is_virtual": 0, 716 | "label": "Enable NextCloud", 717 | "length": 0, 718 | "link_filters": null, 719 | "mandatory_depends_on": null, 720 | "modified": "2022-04-01 19:14:50.269324", 721 | "module": "Pibidav", 722 | "name": "User-nextcloud_enable", 723 | "no_copy": 0, 724 | "non_negative": 0, 725 | "options": null, 726 | "permlevel": 0, 727 | "placeholder": null, 728 | "precision": "", 729 | "print_hide": 0, 730 | "print_hide_if_no_value": 0, 731 | "print_width": null, 732 | "read_only": 0, 733 | "read_only_depends_on": null, 734 | "report_hide": 0, 735 | "reqd": 0, 736 | "search_index": 0, 737 | "show_dashboard": 0, 738 | "sort_options": 0, 739 | "translatable": 0, 740 | "unique": 0, 741 | "width": null 742 | }, 743 | { 744 | "allow_in_quick_entry": 1, 745 | "allow_on_submit": 0, 746 | "bold": 0, 747 | "collapsible": 0, 748 | "collapsible_depends_on": null, 749 | "columns": 0, 750 | "default": null, 751 | "depends_on": null, 752 | "description": "NextCloud URL as https://domain.com without / at the end", 753 | "docstatus": 0, 754 | "doctype": "Custom Field", 755 | "dt": "User", 756 | "fetch_from": null, 757 | "fetch_if_empty": 0, 758 | "fieldname": "nextcloud_url", 759 | "fieldtype": "Data", 760 | "hidden": 0, 761 | "hide_border": 0, 762 | "hide_days": 0, 763 | "hide_seconds": 0, 764 | "ignore_user_permissions": 1, 765 | "ignore_xss_filter": 0, 766 | "in_global_search": 0, 767 | "in_list_view": 0, 768 | "in_preview": 0, 769 | "in_standard_filter": 0, 770 | "insert_after": "nextcloud_enable", 771 | "is_system_generated": 0, 772 | "is_virtual": 0, 773 | "label": "NextCloud URL", 774 | "length": 0, 775 | "link_filters": null, 776 | "mandatory_depends_on": null, 777 | "modified": "2022-04-01 19:14:50.979439", 778 | "module": "Pibidav", 779 | "name": "User-nextcloud_url", 780 | "no_copy": 0, 781 | "non_negative": 0, 782 | "options": null, 783 | "permlevel": 0, 784 | "placeholder": null, 785 | "precision": "", 786 | "print_hide": 0, 787 | "print_hide_if_no_value": 0, 788 | "print_width": null, 789 | "read_only": 0, 790 | "read_only_depends_on": null, 791 | "report_hide": 0, 792 | "reqd": 0, 793 | "search_index": 0, 794 | "show_dashboard": 0, 795 | "sort_options": 0, 796 | "translatable": 0, 797 | "unique": 0, 798 | "width": null 799 | }, 800 | { 801 | "allow_in_quick_entry": 1, 802 | "allow_on_submit": 0, 803 | "bold": 0, 804 | "collapsible": 0, 805 | "collapsible_depends_on": null, 806 | "columns": 0, 807 | "default": null, 808 | "depends_on": null, 809 | "description": null, 810 | "docstatus": 0, 811 | "doctype": "Custom Field", 812 | "dt": "User", 813 | "fetch_from": null, 814 | "fetch_if_empty": 0, 815 | "fieldname": "nextcloud_username", 816 | "fieldtype": "Data", 817 | "hidden": 0, 818 | "hide_border": 0, 819 | "hide_days": 0, 820 | "hide_seconds": 0, 821 | "ignore_user_permissions": 1, 822 | "ignore_xss_filter": 0, 823 | "in_global_search": 0, 824 | "in_list_view": 0, 825 | "in_preview": 0, 826 | "in_standard_filter": 0, 827 | "insert_after": "nextcloud_url", 828 | "is_system_generated": 0, 829 | "is_virtual": 0, 830 | "label": "Nextcloud Username", 831 | "length": 0, 832 | "link_filters": null, 833 | "mandatory_depends_on": null, 834 | "modified": "2022-04-01 19:14:51.480230", 835 | "module": "Pibidav", 836 | "name": "User-nextcloud_username", 837 | "no_copy": 0, 838 | "non_negative": 0, 839 | "options": null, 840 | "permlevel": 0, 841 | "placeholder": null, 842 | "precision": "", 843 | "print_hide": 0, 844 | "print_hide_if_no_value": 0, 845 | "print_width": null, 846 | "read_only": 0, 847 | "read_only_depends_on": null, 848 | "report_hide": 0, 849 | "reqd": 0, 850 | "search_index": 0, 851 | "show_dashboard": 0, 852 | "sort_options": 0, 853 | "translatable": 0, 854 | "unique": 0, 855 | "width": null 856 | }, 857 | { 858 | "allow_in_quick_entry": 1, 859 | "allow_on_submit": 0, 860 | "bold": 0, 861 | "collapsible": 0, 862 | "collapsible_depends_on": null, 863 | "columns": 0, 864 | "default": null, 865 | "depends_on": null, 866 | "description": null, 867 | "docstatus": 0, 868 | "doctype": "Custom Field", 869 | "dt": "User", 870 | "fetch_from": null, 871 | "fetch_if_empty": 0, 872 | "fieldname": "nextcloud_token", 873 | "fieldtype": "Password", 874 | "hidden": 0, 875 | "hide_border": 0, 876 | "hide_days": 0, 877 | "hide_seconds": 0, 878 | "ignore_user_permissions": 1, 879 | "ignore_xss_filter": 0, 880 | "in_global_search": 0, 881 | "in_list_view": 0, 882 | "in_preview": 0, 883 | "in_standard_filter": 0, 884 | "insert_after": "nextcloud_username", 885 | "is_system_generated": 0, 886 | "is_virtual": 0, 887 | "label": "NextCloud Token", 888 | "length": 0, 889 | "link_filters": null, 890 | "mandatory_depends_on": null, 891 | "modified": "2022-04-01 19:14:51.997615", 892 | "module": "Pibidav", 893 | "name": "User-nextcloud_token", 894 | "no_copy": 0, 895 | "non_negative": 0, 896 | "options": null, 897 | "permlevel": 0, 898 | "placeholder": null, 899 | "precision": "", 900 | "print_hide": 0, 901 | "print_hide_if_no_value": 0, 902 | "print_width": null, 903 | "read_only": 0, 904 | "read_only_depends_on": null, 905 | "report_hide": 0, 906 | "reqd": 0, 907 | "search_index": 0, 908 | "show_dashboard": 0, 909 | "sort_options": 0, 910 | "translatable": 0, 911 | "unique": 0, 912 | "width": null 913 | } 914 | ] -------------------------------------------------------------------------------- /pibidav/pibidav/custom.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2022, PibiCo and contributors 3 | # For license information, please see license.txt 4 | from __future__ import unicode_literals 5 | import frappe 6 | from frappe import _, msgprint, throw, enqueue 7 | from frappe.utils import cint, cstr 8 | 9 | import json, time, requests, sys, hashlib, re, os, io 10 | 11 | from datetime import date, datetime, timedelta 12 | 13 | import pibidav.pibidav.nextcloud as nextcloud 14 | 15 | from frappe.utils.password import get_decrypted_password 16 | from frappe.utils.file_manager import get_file_path 17 | 18 | import frappe.desk.doctype.tag.tag as tag 19 | 20 | @frappe.whitelist() 21 | def checkNCuser(): 22 | ncuser = frappe.get_value("NextCloud Settings", "NextCloud Settings", "nc_backup_username") 23 | loggeduser = frappe.get_value("User", frappe.session.user, "nextcloud_username") 24 | if ncuser == loggeduser: 25 | return True 26 | else: 27 | return False 28 | 29 | @frappe.whitelist() 30 | def doCreateFolder(doctype): 31 | data = frappe.db.get_value("Reference Item", {"parent": "NextCloud Settings", "reference_doctype": doctype},['create_nc_folder','nc_enable'], as_dict = 1) 32 | if data is None: 33 | return False 34 | if data.create_nc_folder and data.nc_enable: 35 | return True 36 | else: 37 | return False 38 | 39 | @frappe.whitelist() 40 | def create_nc_folder(dt, dn, abbr, strmain, folder_set, sharing_option=None, secret=None): 41 | doc = frappe.get_doc(doctype=dt, docname=dn) 42 | ## Check docs excluded and included in the NC Integration 43 | docs_excluded = frappe.get_value("NextCloud Settings", "NextCloud Settings", "nc_doctype_excluded") 44 | docs_included = frappe.get_value("NextCloud Settings", "NextCloud Settings", "nc_doctype_included") 45 | nc_url = frappe.get_value("NextCloud Settings", "NextCloud Settings", "nc_backup_url") 46 | if nc_url[-1] != '/': nc_url += '/' 47 | 48 | if dt in docs_excluded or not dt in docs_included: 49 | return "DocType not in NC integration {}".format(docs_included) 50 | 51 | data = frappe.db.get_value("Reference Item", {"parent": "NextCloud Settings", "reference_doctype": dt},['nc_folder','create_nc_folder', 'nc_enable'], as_dict = 1) 52 | node_name = folder_set.strip() 53 | path = data.nc_folder 54 | 55 | if path is None: 56 | return "Failed. Not defined the Default destination Folder in NC Settings" 57 | 58 | if not data.nc_enable: 59 | return "Failed. Not enabled NextCloud Integration in Addon" 60 | 61 | if data.create_nc_folder and data.nc_folder != '' and data.folder_set != '': 62 | pibidav = check_addon(dt,dn) 63 | 64 | ## Get default data from NextCloud Settings 65 | ## Assign data to variables for creating folders in NC 66 | strmain = strmain.strip() 67 | abbreviation = abbr.strip() 68 | digits = 3 69 | if path[-1] != '/': 70 | path += '/' 71 | if path[0] != '/': 72 | return frappe.throw(_("{} Root Destination Folder must start with /. Correct in your NextCloud Settings and retry".format(path))) 73 | 74 | root_path = "{}{} {}/".format(path, abbreviation, strmain) 75 | 76 | ## Create Folders if needed data are filled in logged in as superuser in NC 77 | if node_name and path and abbreviation and strmain: 78 | create_nc_dirs(node_name, path, abbreviation, strmain, digits) 79 | nclog = make_nc_session() 80 | ## Get data fileid from dir 81 | fileinfo = nclog.file_info(root_path, properties=['{http://owncloud.org/ns}fileid']) 82 | if fileinfo: 83 | fileid = fileinfo.attributes['{http://owncloud.org/ns}fileid'] 84 | ## Create internal Link 85 | intlink = nc_url + 'f/' + fileid 86 | nc_folder_internal_link = '' + intlink + '' 88 | pibidav.nc_folder_internal_link = nc_folder_internal_link 89 | 90 | pibidav.nc_folder = root_path 91 | pibidav.nc_folder_internal_link = nc_folder_internal_link 92 | 93 | ## Create shared Link 94 | if secret is not None or sharing_option is not None: 95 | if sharing_option is None: 96 | pibidav.save() 97 | return 98 | share_option = sharing_option.split('-') 99 | share_option = int(share_option[0]) 100 | share_link = nclog.share_file_with_link(path=root_path) 101 | if share_link: 102 | ## Create public link 103 | publink = share_link.get_link() 104 | nc_folder_share_link = '' + publink + '' 106 | pibidav.nc_folder_share_link = nc_folder_share_link 107 | intlink = share_link.get_id() 108 | ## Change perms from read to update 109 | #oth = {'perms': sharing_option, 'secret': secret} 110 | oth = {} 111 | if share_option: 112 | oth = { 'perms': share_option } 113 | pibidav.sharing_option = sharing_option 114 | if secret is not None and secret != '': 115 | oth['password'] = str(secret) 116 | pibidav.secret = secret 117 | nclog.update_share(intlink, **oth) 118 | 119 | pibidav.save() 120 | nclog.logout() 121 | 122 | @frappe.whitelist() 123 | def get_native(parent, filetype): 124 | native_list = frappe.db.get_list('Deliverable Item', 125 | filters={ 126 | 'docstatus': ['<', 2], 127 | 'parent': parent, 128 | 'filetype': filetype 129 | }, 130 | fields=['attachment','delivery_date'], 131 | order_by='delivery_date desc' 132 | ) 133 | 134 | return native_list 135 | 136 | @frappe.whitelist() 137 | def check_nextcloud(): 138 | doc = frappe.get_doc("NextCloud Settings", "NextCloud Settings") 139 | nc_url = doc.nc_backup_url 140 | nc_username = doc.nc_backup_username 141 | nc_token = get_decrypted_password('NextCloud Settings', doc.name, 'nc_backup_token', True) 142 | try: 143 | nc = nextcloud.Client(nc_url) 144 | nc.login(nc_username, nc_token) 145 | frappe.msgprint(_("NextCloud Credentials are correct")) 146 | nc.logout() 147 | except: 148 | frappe.throw(_("NextCloud Credentials not correct, please rectify")) 149 | 150 | def make_nc_session_user(user): 151 | if user.nextcloud_enable: 152 | if user.nextcloud_url and user.nextcloud_username and user.nextcloud_token: 153 | nc_token = get_decrypted_password('User', user.name, 'nextcloud_token', False) 154 | try: 155 | _session = nextcloud.Client(user.nextcloud_url) 156 | _session.login(user.nextcloud_username, nc_token) 157 | return _session 158 | except Exception as err: 159 | frappe.throw(_("Error in login in NC: " + str(err))) 160 | return "Failed" 161 | else: 162 | frappe.msgprint(_("User has errors in credentials for NC")) 163 | return "Failed" 164 | else: 165 | frappe.msgprint(_("User has not enabled NC")) 166 | return "Failed" 167 | 168 | @frappe.whitelist() 169 | def get_nc_nodes(path): 170 | nc_user = frappe.get_doc("User", frappe.session.user) 171 | nc = make_nc_session_user(nc_user) 172 | 173 | if nc == "Failed": 174 | return [{"title": "Check NextCloud User Settings"}] 175 | 176 | ## Get DAV List from NC Server 177 | """nc_nodes = nc.list( 178 | path, 179 | depth=1, 180 | properties=[ 181 | '{DAV:}getetag', 182 | '{DAV:}getlastmodified', 183 | '{http://owncloud.org/ns}fileid' 184 | ] 185 | )""" 186 | nc_nodes = nc.list(path, depth=1, properties=['{http://owncloud.org/ns}fileid']) 187 | 188 | nodes = [] 189 | for row in nc_nodes: 190 | node = {} 191 | if row.file_type == 'dir': 192 | folder = row.path[:-1] 193 | pos = folder.rfind("/") 194 | node['folder'] = 1 195 | node['title'] = folder[pos+1:] 196 | else: 197 | folder = row.path 198 | pos = folder.rfind("/") 199 | node['title'] = folder[pos+1:] 200 | node['path'] = row.path 201 | node['fileid'] = row.attributes['{http://owncloud.org/ns}fileid'] 202 | nodes.append(node) 203 | 204 | #nc.logout() 205 | 206 | return nodes 207 | 208 | def tag_file_fp(doc, method=None): 209 | if doc.attached_to_doctype and doc.attached_to_name: 210 | dctype = doc.attached_to_doctype 211 | dcname = doc.attached_to_name 212 | ## Get DocType attached to File 213 | dt = frappe.get_doc(dctype, dcname) 214 | ## Assign default tag to DocType name to the tag list 215 | tag_list = [dctype] 216 | ## Add user tags to tag list if any 217 | if hasattr(dt, "_user_tags"): 218 | if dt._user_tags is not None: 219 | if len(dt._user_tags) > 0: 220 | user_tags = dt._user_tags.split(',') 221 | for lbl in user_tags: 222 | if lbl is not None and lbl not in tag_list and lbl != "": tag_list.append(lbl) 223 | ## Get all docfields for tagging the DocType from NextCloud Settings 224 | _fields_to_tag = frappe.db.get_value("Reference Item", {"parent": "NextCloud Settings", "reference_doctype": dctype}, "reference_docfield") 225 | ## Assign tags to DocType in Frappe (limited length 60 chars due to NextCloud Limits) 226 | if _fields_to_tag: 227 | fields_to_tag = _fields_to_tag.split(',') 228 | for item in fields_to_tag: 229 | try: 230 | if not ',' in dt.get(item): 231 | fp_tag = dt.get(item) #frappe.db.get_value(dctype, dcname, item) 232 | if fp_tag is not None and fp_tag not in tag_list and fp_tag != "": tag_list.append(fp_tag) 233 | for el in tag_list: 234 | if el != '' and el is not None and len(el) > 0 and len(el) <= 60: tag.add_tag(el, "File", doc.name) 235 | except: 236 | pass 237 | else: 238 | tag.add_tag(dctype, "File", doc.name) 239 | else: 240 | tag_list = [] 241 | 242 | return tag_list 243 | 244 | def get_tagid(session, tag): 245 | taglist = session.list_tags() 246 | for item in taglist: 247 | if item.attributes['{http://owncloud.org/ns}display-name'] == tag: 248 | tag_id = item.attributes['{http://owncloud.org/ns}id'] 249 | return tag_id 250 | 251 | def tag_file_nc(nc_session, filepath, tags): 252 | nc_tags = [] 253 | ## Get fileid fron nc uploaded file 254 | remotefile = nc_session.file_info(filepath, properties=['{http://owncloud.org/ns}fileid']) 255 | if remotefile: 256 | fileid = remotefile.attributes['{http://owncloud.org/ns}fileid'] 257 | ## Write tags to uploaded file 258 | try: 259 | for tag in tags: 260 | res = nc_session.add_tag(tag) 261 | if res == 409: 262 | tagid = get_tagid(nc_session, tag) 263 | else: 264 | tag_res = res.split('/') 265 | tagid = tag_res[len(tag_res)-1] 266 | if not tagid in nc_tags: 267 | nc_tags.append({"display-name": tag, "tagid": tagid}) 268 | ## Add tag to file 269 | nc_session.assign_tag_to_file(fileid, tagid) 270 | except Exception as err: 271 | #return err 272 | nc_tags = [] 273 | pass 274 | return fileid, nc_tags 275 | 276 | @frappe.whitelist() 277 | def upload_file_to_nc(doc, method=None): 278 | ## Tag File in Frappe 279 | _tag_list = tag_file_fp(doc) 280 | ## Method only valid for Files, not WebSites 281 | if 'http' in doc.file_url: 282 | return 283 | ## Check docs excluded and included in the NC Integration 284 | docs_excluded = frappe.get_value("NextCloud Settings", "NextCloud Settings", "nc_doctype_excluded") 285 | docs_included = frappe.get_value("NextCloud Settings", "NextCloud Settings", "nc_doctype_included") 286 | nc_url = frappe.get_value("NextCloud Settings", "NextCloud Settings", "nc_backup_url") 287 | if not nc_url: 288 | return 289 | if not nc_url[-1] == '/': nc_url += '/' 290 | ## Check the file attached to parent docType and its inclusion in the list 291 | dt = doc.attached_to_doctype 292 | if docs_excluded and dt: 293 | if dt in docs_excluded: 294 | return 295 | ## Get name of file attached to doctype and file_name and local server path 296 | dn = doc.attached_to_name 297 | file_name = doc.file_name 298 | local_path = get_file_path(file_name) 299 | ## Method only valid for docTypes not being Files 300 | if dt != 'File' and dn: 301 | ## check whether document has NC addon extension active 302 | document = check_addon(dt,dn) 303 | 304 | #document = frappe.get_doc(dt, dn) 305 | ## Check whether doctype has NextCloud Integration active 306 | if not hasattr(document, "nc_enable"): 307 | return 308 | ## Continues if NC Integration is enabled and NC Destination Folder given 309 | if document.nc_enable and document.nc_folder: 310 | ## Get file storage mode from settings 311 | file_storage_mode = frappe.db.get_value("Reference Item", 312 | {"parent": "NextCloud Settings", "reference_doctype": dt}, 313 | "file_storage_mode") 314 | if not file_storage_mode: 315 | file_storage_mode = "keep_in_frappe" 316 | 317 | local_site = frappe.utils.get_url() 318 | ## Get current logged user and makes session in NC 319 | nc_user = frappe.get_doc("User", frappe.session.user) 320 | nc = make_nc_session_user(nc_user) 321 | if nc == "Failed": 322 | return frappe.msgprint(_("Error in NC Login")) 323 | ## Continues to upload files to NC 324 | nc_path = document.nc_folder + file_name 325 | nc_args = { 326 | 'remote_path': nc_path, 327 | 'local_source_file': local_path 328 | } 329 | ## Upload file to NC 330 | enqueue(method=nc.put_file,queue='short',timeout=300,now=True,**nc_args) 331 | ## Get Shared Link from NC 332 | share = enqueue(method=nc.share_file_with_link, queue='short', timeout=600, now=True, path=nc_path) 333 | ## Update and save File in frappe updated with NC data 334 | doc.uploaded_to_nextcloud = 1 335 | doc.folder_path = document.nc_folder 336 | doc.share_link = share.get_link() 337 | 338 | fileid, nctags = tag_file_nc(nc, nc_path, _tag_list) 339 | doc.fileid = fileid 340 | ## Register nc tagids into frappe tags 341 | if len(nctags) > 0: 342 | for strtag in nctags: 343 | fptag = frappe.get_doc("Tag", strtag['display-name']) 344 | if fptag: 345 | fptag.uploaded_to_nextcloud = 1 346 | fptag.tagid = strtag['tagid'] 347 | fptag.save() 348 | 349 | ## Handle file storage mode 350 | if file_storage_mode == "replace_with_link": 351 | # Delete the physical file in Frappe and replace with NC public share link 352 | if os.path.exists(local_path): 353 | os.remove(local_path) 354 | # Update file URL to point to NextCloud public share link for external access 355 | doc.file_url = doc.share_link # Use the public share link 356 | # Note: is_remote_file is automatically set by Frappe when file_url contains http/https 357 | 358 | doc.save() 359 | nc.logout() 360 | 361 | # Update addon attachment items after upload 362 | update_attachment_item(dt, dn) 363 | 364 | return {'fname': doc, 'local_path': local_path, 'local_site': share.get_link()} 365 | else: 366 | return 367 | 368 | @frappe.whitelist() 369 | def get_nc_settings(doctype): 370 | use_default = frappe.get_list("Reference Item", {"parent": "NextCloud Settings", "reference_doctype": doctype}, "use_default_folder") 371 | if use_default: 372 | if use_default[0]['use_default_folder']: 373 | path = frappe.get_list("Reference Item", {"parent": "NextCloud Settings", "reference_doctype": doctype}, "nc_folder") 374 | nc_folder = path[0]['nc_folder'] 375 | if nc_folder[-1] != '/': nc_folder + '/' 376 | return nc_folder 377 | 378 | @frappe.whitelist() 379 | def update_attachment_item(dt, dn): 380 | nc_url = frappe.get_value("NextCloud Settings", "NextCloud Settings", "nc_backup_url") 381 | if not nc_url[-1] == '/': nc_url += '/' 382 | ## Get a list of all files attached to doctype and uploaded to NC 383 | attached_to_doctype = frappe.get_list( 384 | doctype = "File", 385 | fields = ["*"], 386 | filters = [ 387 | ["attached_to_doctype", "=", dt], 388 | ["attached_to_name", "=", dn], 389 | ["uploaded_to_nextcloud", "=", 1], 390 | ["docstatus", "<", 1] 391 | ] 392 | ) 393 | 394 | doc = frappe.get_doc("PibiDAV Addon", "pbc_" + dn) 395 | if doc is None: 396 | return 397 | 398 | if len(attached_to_doctype) > 0 and hasattr(doc, "nc_enable"): 399 | if hasattr(doc, "attachment_item"): 400 | attachment_item = doc.attachment_item 401 | if len(attachment_item) > 0: 402 | ## Update attachments uploaded to NC 403 | ## Get all files attached to docname 404 | _attachment_item = [] 405 | for item in attachment_item: 406 | if item.attachment not in _attachment_item: 407 | _attachment_item.append(item.attachment) 408 | for row in attached_to_doctype: 409 | if row.name not in _attachment_item: 410 | nc_link = 'NextCloud' 413 | json_item = { 414 | "attachment": row.name, 415 | "filename": row.file_name, 416 | "uploaded_to_nc": row.uploaded_to_nextcloud, 417 | "nc_path": row.folder_path, 418 | "nc_link": row.share_link, 419 | "nc_private": nc_url + 'apps/files/?fileid=' + row.fileid, 420 | "nc_url": nc_link 421 | } 422 | doc.append("attachment_item", json_item) 423 | doc.save() 424 | frappe.db.commit() 425 | else: 426 | ## Create new attachment item 427 | for row in attached_to_doctype: 428 | nc_link = 'NextCloud' 431 | json_item = { 432 | "attachment": row.name, 433 | "filename": row.file_name, 434 | "uploaded_to_nc": row.uploaded_to_nextcloud, 435 | "nc_path": row.folder_path, 436 | "nc_link": row.share_link, 437 | "nc_private": nc_url + 'apps/files/?fileid=' + row.fileid, 438 | "nc_url": nc_link 439 | } 440 | doc.append("attachment_item", json_item) 441 | doc.save() 442 | frappe.db.commit() 443 | 444 | return doc 445 | 446 | @frappe.whitelist() 447 | def upload_nc_file(remote_path, local_file): 448 | ## Get bench path 449 | fname = frappe.get_doc('File', local_file) 450 | ## Only applicable if File is not attached to any doctype 451 | if not fname.attached_to_doctype == 'File' or 'http' in fname.file_url: 452 | return 453 | 454 | local_site = frappe.utils.get_url() 455 | local_path = get_file_path(local_file) 456 | nc_user = frappe.get_doc("User", frappe.session.user) 457 | nc = make_nc_session_user(nc_user) 458 | 459 | if nc == "Failed": 460 | return frappe.msgprint(_("Error in NC Login")) 461 | 462 | nc_args = { 463 | 'remote_path': remote_path, 464 | 'local_source_file': local_path 465 | } 466 | enqueue(method=nc.put_file,queue='short',timeout=300,now=True,**nc_args) 467 | 468 | fname.uploaded_to_nextcloud = 1 469 | fname.folder_path = remote_path.replace(fname.file_name, '') 470 | fname.save() 471 | 472 | nc.logout() 473 | 474 | return {'fname': fname, 'local_path': local_path, 'local_site': local_site} 475 | 476 | @frappe.whitelist() 477 | def get_nc_files_in_folder(folder, start=0, page_length=20): 478 | start = cint(start) 479 | page_length = cint(page_length) 480 | if folder == 0: folder = "/" 481 | 482 | nc_user = frappe.get_doc("User", frappe.session.user) 483 | 484 | nc = make_nc_session_user(nc_user) 485 | 486 | if nc == "Failed": 487 | return {"files": ["Enable NextCloud on User Settings"], 488 | "has_more": 0 489 | } 490 | 491 | ## Get DAV List from NC Server 492 | #nc_nodes = nc.list(path, depth=1, properties=['{DAV:}getetag','{DAV:}getlastmodified','{http://owncloud.org/ns}fileid']) 493 | nc_nodes = nc.list(folder, depth=1, properties=['{http://owncloud.org/ns}fileid', '{DAV:}getlastmodified']) 494 | 495 | nodes = [] 496 | for row in nc_nodes: 497 | node = {} 498 | if row.file_type == 'dir': 499 | folder = row.path[:-1] 500 | pos = folder.rfind("/") 501 | node['is_folder'] = 1 502 | node['file_name'] = folder[pos+1:] 503 | else: 504 | folder = row.path 505 | pos = folder.rfind("/") 506 | node['is_folder'] = 0 507 | node['file_name'] = folder[pos+1:] 508 | node['name'] = row.path 509 | node['file_url'] = row.attributes['{http://owncloud.org/ns}fileid'] 510 | node['modified'] = row.attributes['{DAV:}getlastmodified'] 511 | nodes.append(node) 512 | start += 1 513 | if start == page_length: 514 | break 515 | 516 | #nc.logout() 517 | 518 | return { 519 | 'files': nodes[:page_length], 520 | 'has_more': len(nodes) > page_length 521 | } 522 | 523 | @frappe.whitelist() 524 | def make_nc_session(): 525 | nc_settings = frappe.get_doc('NextCloud Settings', 'NextCloud Settings') 526 | if nc_settings.nc_backup_enable: 527 | nc_token = get_decrypted_password('NextCloud Settings', 'NextCloud Settings', 'nc_backup_token', True) 528 | args = { 529 | "verify_certs": False 530 | } 531 | session = nextcloud.Client(nc_settings.nc_backup_url, **args) 532 | session.login(nc_settings.nc_backup_username, nc_token) 533 | return session 534 | else: 535 | frappe.msgprint(_("NextCloud Integration not Enabled")) 536 | 537 | @frappe.whitelist() 538 | def create_nc_group(alias): 539 | ## Login in NC as superuser 540 | nclient = make_nc_session() 541 | ## Create NC Group with name alias 542 | ## Check if group exists first 543 | doGroup = nclient.group_exists(alias.upper()) 544 | if doGroup: 545 | frappe.msgprint(_("NC Group Already Exists")) 546 | else: 547 | res = nclient.create_group(alias.upper()) 548 | if res: 549 | frappe.msgprint(_("NC Group Created")) 550 | else: 551 | frappe.msgprint(_("Error Creating NC Group")) 552 | 553 | @frappe.whitelist() 554 | def create_nc_dirs(node_name, path, abbrv, strmain, digits): 555 | """ Create folders in NC Instance on path 556 | node_name = '(BPJ) Project' 557 | path = "/Projects/" 558 | abbrv = "PRJ22PBC" 559 | ref_doctype = "pb_Project" ?? 560 | strmain = "NC Integration" """ 561 | 562 | ## Include tags as Project Name, abbreviation, Customer 563 | ## ---- 564 | 565 | ## Login in NC as superuser 566 | nclient = make_nc_session() 567 | ## Create root path and recursively the rest as per origin structure in selected node in folder set 568 | root_node = node_name 569 | n = int(digits) 570 | """ 571 | ## Check if selected node is root or not 572 | root_parent = frappe.db.get_value("Folder Set", {"name": node_name}, "root_parent") 573 | if root_parent == root_node: 574 | ## Change title depending on ref_doctype 575 | root_path = path + abbrv + " " + strmain + "/" 576 | else: 577 | root_path = path + node_name[n+1:] + "/" 578 | """ 579 | ## Root path is different from the rest of nodes 580 | root_path = "{}{} {}/".format(path, abbrv, strmain) 581 | ## Create first node in NC 582 | folder = nclient.mkdir(root_path) 583 | ## If dir is created in NC, continues recursively with its children 584 | if folder: 585 | children = get_children(nclient, root_node, root_path, abbrv, n) 586 | else: 587 | frappe.msgprint(_("Error creating root folder in NC")) 588 | 589 | return frappe.msgprint(_("Successfully Created Folders in NC")) 590 | 591 | def get_children(session, parent_node, parent_path, abr, n): 592 | children_list = [] 593 | ## Get all children folders only for given node 594 | children = frappe.get_list( 595 | doctype = "Folder Set", 596 | filters = { 597 | "is_group": 1, 598 | "parent_folder_set": parent_node 599 | }, 600 | fields = ["name", "parent_folder_set", "is_group"], 601 | order_by = "creation asc" 602 | ) 603 | ## Get recursively the rest of children an create folders in NC 604 | if len(children) > 0: 605 | for child in children: 606 | if child.name not in children_list: children_list.append(child.name) 607 | parent_path_child = "{}{} {}/".format(parent_path, abr, child.name[n+1:]) 608 | ## Create folder in NC 609 | folder = session.mkdir(parent_path_child) 610 | ## If folder is created continues recursively 611 | if folder: 612 | get_children(session, child.name, parent_path_child, abr, n) 613 | else: 614 | frappe.msgprint(_("Error creating child folder in NC")) 615 | 616 | return children_list 617 | 618 | def check_addon(dt, dn): 619 | ## First we'll check and create pibiDAV Addon doctype related 620 | addon_list = frappe.db.get_list('PibiDAV Addon', 621 | filters={ 622 | 'docstatus': ['<', 2], 623 | 'name': "pbc_{}".format(dn) 624 | }, 625 | fields=['*'] 626 | ) 627 | if len(addon_list) == 0: 628 | ## Create pibiDAV Addon doctype 629 | pibidav = frappe.new_doc("PibiDAV Addon") 630 | pibidav.ref_doctype = dt 631 | pibidav.ref_docname = dn 632 | ## Get default data from NextCloud Settings 633 | settings = frappe.db.get_value("Reference Item", {"parent": "NextCloud Settings", "reference_doctype": dt},['nc_folder'], as_dict = 1) 634 | if settings is not None: 635 | if settings.use_default_folder and settings.nc_folder: 636 | pibidav.nc_folder = settings.nc_folder 637 | pibidav.insert() 638 | frappe.db.commit() 639 | else: 640 | pibidav = frappe.get_doc("PibiDAV Addon", "pbc_{}".format(dn)) 641 | 642 | return pibidav 643 | 644 | @frappe.whitelist() 645 | def fetch_nc_folder_internal_link_from_addon(addon_name): 646 | pibidav = frappe.get_doc("PibiDAV Addon", addon_name) 647 | if not pibidav.nc_folder: 648 | frappe.throw(_("No NC Folder defined in the Addon.")) 649 | 650 | session = make_nc_session() # Authenticate as superuser 651 | fileinfo = session.file_info(pibidav.nc_folder, properties=['{http://owncloud.org/ns}fileid']) 652 | if fileinfo: 653 | fileid = fileinfo.attributes['{http://owncloud.org/ns}fileid'] 654 | nc_url = frappe.get_value("NextCloud Settings", "NextCloud Settings", "nc_backup_url") 655 | if nc_url[-1] != '/': 656 | nc_url += '/' 657 | intlink = f'{nc_url}f/{fileid}' 658 | pibidav.nc_folder_internal_link = intlink 659 | pibidav.save() 660 | return intlink 661 | else: 662 | frappe.throw(_("Failed to fetch folder metadata from NextCloud.")) 663 | 664 | @frappe.whitelist() 665 | def get_folder_path_from_link(fileid): 666 | """ 667 | Retrieves the full folder path using the fileid. 668 | 669 | :param fileid: File ID from the NextCloud folder link 670 | :return: Full path of the folder or an error message 671 | """ 672 | if not fileid: 673 | frappe.log_error("No fileid provided to get_folder_path_from_link.") 674 | return "/" 675 | 676 | try: 677 | # Create a NextCloud session 678 | nc_session = make_nc_session() 679 | if not nc_session or nc_session == "Failed": 680 | frappe.log_error("Failed to establish session with NextCloud.") 681 | return "/" 682 | 683 | # Log the fileid for debugging 684 | frappe.logger().info(f"Fetching directory for fileid: {fileid}") 685 | 686 | # Use the NextCloud session to fetch the folder path 687 | folder_path = nc_session.get_path_from_fileid(fileid) 688 | 689 | if folder_path: 690 | frappe.logger().info(f"Retrieved folder path for fileid {fileid}: {folder_path}") 691 | return folder_path 692 | else: 693 | frappe.log_error(f"Folder path not found for fileid: {fileid}") 694 | return "/" 695 | 696 | except Exception as e: 697 | frappe.log_error(message=f"Error retrieving folder path for fileid {fileid}: {e}", title="NextCloud Path Retrieval Error") 698 | return "/" 699 | 700 | @frappe.whitelist() 701 | def create_nc_subfolder(parent_folder, folder_name): 702 | try: 703 | session = make_nc_session() 704 | if session == "Failed": 705 | return frappe.throw(_("Error in NC Login")) 706 | 707 | # Ensure parent folder starts with / 708 | if not parent_folder.startswith('/'): 709 | parent_folder = '/' + parent_folder 710 | 711 | # Create full path 712 | new_folder_path = f"{parent_folder}/{folder_name}" 713 | if new_folder_path.startswith('//'): 714 | new_folder_path = new_folder_path[1:] 715 | 716 | # Create folder using NextCloud client 717 | session.mkdir(new_folder_path) 718 | session.logout() 719 | 720 | return new_folder_path 721 | 722 | except Exception as e: 723 | frappe.log_error(message=f"Error creating NextCloud subfolder: {str(e)}", title="NextCloud Folder Creation Error") 724 | return frappe.throw(_("Failed to create folder in NextCloud: {0}").format(str(e))) 725 | 726 | @frappe.whitelist() 727 | def refresh_addon_attachments(dt, dn): 728 | """ 729 | Refresh the addon attachment items to sync with current file status 730 | """ 731 | try: 732 | addon_doc = update_attachment_item(dt, dn) 733 | if addon_doc: 734 | # Return the updated attachment items 735 | return { 736 | 'status': 'success', 737 | 'attachment_items': [item.as_dict() for item in addon_doc.attachment_item] 738 | } 739 | else: 740 | return { 741 | 'status': 'error', 742 | 'message': 'Addon not found' 743 | } 744 | except Exception as e: 745 | frappe.log_error(message=f"Error refreshing addon attachments: {str(e)}", title="Addon Refresh Error") 746 | return { 747 | 'status': 'error', 748 | 'message': str(e) 749 | } 750 | 751 | 752 | @frappe.whitelist() 753 | def get_file_content(doc, method=None): 754 | """ 755 | Hook method to get file content, handling NextCloud share links properly. 756 | Returns the actual file content for NextCloud shared files. 757 | """ 758 | # Check if this is a URL-based file (NextCloud share link or other URL) 759 | if doc.file_url and doc.file_url.startswith('http'): 760 | # Check if this is a NextCloud share link 761 | if _is_nextcloud_share_link(doc.file_url): 762 | # Download from NextCloud share link 763 | content_info = _download_nextcloud_share(doc) 764 | if content_info: 765 | # Update the file name if we got the real filename 766 | if content_info.get('filename') and doc.file_name != content_info['filename']: 767 | # Store original file_name 768 | original_name = doc.file_name 769 | doc.file_name = content_info['filename'] 770 | # Also update in database if needed 771 | if original_name == os.path.basename(doc.file_url): 772 | # Only update if the file_name was just the share ID 773 | frappe.db.set_value('File', doc.name, 'file_name', content_info['filename']) 774 | return content_info['content'] 775 | 776 | # Check if this is a NextCloud file with folder path (WebDAV access) 777 | elif hasattr(doc, 'uploaded_to_nextcloud') and doc.uploaded_to_nextcloud and hasattr(doc, 'folder_path') and doc.folder_path: 778 | # Download using WebDAV 779 | return _download_nextcloud_webdav(doc) 780 | 781 | # For other URLs, try direct download 782 | else: 783 | try: 784 | response = requests.get(doc.file_url, timeout=30, verify=False, allow_redirects=True) 785 | if response.status_code == 200: 786 | # Try to get filename from headers 787 | content_disposition = response.headers.get('Content-Disposition', '') 788 | if 'filename=' in content_disposition: 789 | import re 790 | match = re.search(r'filename[^;=\n]*=(([\'"]).*?\2|[^;\n]*)', content_disposition) 791 | if match: 792 | suggested_filename = match.group(1).strip('"\'') 793 | if suggested_filename and doc.file_name != suggested_filename: 794 | doc.file_name = suggested_filename 795 | return response.content 796 | except Exception as e: 797 | frappe.log_error(f"Error downloading file from URL: {str(e)}", 798 | "PibiDAV get_file_content") 799 | 800 | # For normal files, return None to let Frappe handle it normally 801 | return None 802 | 803 | 804 | def _is_nextcloud_share_link(url): 805 | """Check if URL is a NextCloud public share link""" 806 | # NextCloud share links typically have patterns like: 807 | # https://cloud.example.com/s/SHAREID 808 | # https://cloud.example.com/index.php/s/SHAREID 809 | return '/s/' in url or '/public.php' in url 810 | 811 | 812 | def _download_nextcloud_share(doc): 813 | """Download file from NextCloud public share link""" 814 | try: 815 | # Convert share link to download link 816 | download_url = _convert_share_to_download_url(doc.file_url) 817 | 818 | # Make request with proper headers 819 | headers = { 820 | 'User-Agent': 'Mozilla/5.0 (compatible; Frappe/pibiDAV)', 821 | 'Accept': '*/*' 822 | } 823 | 824 | response = requests.get(download_url, headers=headers, timeout=60, verify=False, allow_redirects=True) 825 | 826 | if response.status_code == 200: 827 | # Extract filename from Content-Disposition header 828 | content_disposition = response.headers.get('Content-Disposition', '') 829 | filename = None 830 | 831 | if 'filename=' in content_disposition: 832 | import re 833 | # Handle different filename encoding formats 834 | match = re.search(r'filename\*?=([^;]+)', content_disposition) 835 | if match: 836 | filename_part = match.group(1).strip() 837 | # Handle RFC 5987 encoded filenames (UTF-8) 838 | if "UTF-8''" in filename_part: 839 | filename = filename_part.split("UTF-8''")[1] 840 | # URL decode the filename 841 | from urllib.parse import unquote 842 | filename = unquote(filename) 843 | else: 844 | # Regular filename 845 | filename = filename_part.strip('"\'') 846 | 847 | # If no filename in header, try to get from NextCloud API 848 | if not filename and doc.file_name == os.path.basename(doc.file_url): 849 | # File name is just the share ID, we need the real name 850 | # Try to get file info from NextCloud 851 | info_url = doc.file_url.rstrip('/') + '/download' 852 | filename = 'document.pdf' # Default fallback 853 | 854 | # Use existing filename if we couldn't determine a better one 855 | if not filename: 856 | filename = doc.file_name 857 | 858 | return { 859 | 'filename': filename, 860 | 'content': response.content 861 | } 862 | else: 863 | frappe.log_error(f"Failed to download NextCloud share. Status: {response.status_code}, URL: {doc.file_url}", 864 | "PibiDAV NextCloud Share") 865 | return None 866 | 867 | except Exception as e: 868 | frappe.log_error(f"Error downloading NextCloud share link: {str(e)}", 869 | "PibiDAV NextCloud Share") 870 | return None 871 | 872 | 873 | def _convert_share_to_download_url(share_url): 874 | """Convert NextCloud share URL to direct download URL""" 875 | from urllib.parse import urlparse, parse_qs 876 | 877 | # Parse the URL 878 | parsed_url = urlparse(share_url) 879 | 880 | # Extract share ID from various URL formats 881 | share_id = None 882 | if '/s/' in parsed_url.path: 883 | # Format: /s/SHAREID or /index.php/s/SHAREID 884 | parts = parsed_url.path.split('/s/') 885 | if len(parts) > 1: 886 | share_id = parts[1].split('/')[0].split('?')[0] 887 | 888 | if not share_id: 889 | # Try query parameters 890 | query_params = parse_qs(parsed_url.query) 891 | if 'share' in query_params: 892 | share_id = query_params['share'][0] 893 | 894 | if share_id: 895 | # Construct download URL 896 | # NextCloud download format: /s/SHAREID/download 897 | base_url = f"{parsed_url.scheme}://{parsed_url.netloc}" 898 | 899 | # Handle different NextCloud URL structures 900 | if '/index.php' in parsed_url.path: 901 | download_url = f"{base_url}/index.php/s/{share_id}/download" 902 | else: 903 | download_url = f"{base_url}/s/{share_id}/download" 904 | 905 | return download_url 906 | 907 | # If we can't parse it, try appending /download 908 | if share_url.endswith('/'): 909 | return share_url + 'download' 910 | else: 911 | return share_url + '/download' 912 | 913 | 914 | def _download_nextcloud_webdav(doc): 915 | """Download file from NextCloud using WebDAV (for non-public files)""" 916 | try: 917 | # Get NextCloud session 918 | nc_session = make_nc_session() 919 | if not nc_session: 920 | frappe.log_error("Could not establish NextCloud session", 921 | "PibiDAV WebDAV Download") 922 | return None 923 | 924 | # Construct remote path 925 | remote_path = doc.folder_path 926 | if not remote_path.endswith('/'): 927 | remote_path += '/' 928 | remote_path += doc.file_name 929 | 930 | # Get file content from NextCloud 931 | file_content = nc_session.get_file_contents(remote_path) 932 | nc_session.logout() 933 | 934 | return file_content 935 | 936 | except Exception as e: 937 | frappe.log_error(f"Error downloading NextCloud file via WebDAV {doc.file_name}: {str(e)}", 938 | "PibiDAV WebDAV Download") 939 | return None --------------------------------------------------------------------------------