diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..f87390934
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,20 @@
+# Root editor config file
+root = true
+
+# Common settings
+[*]
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+charset = utf-8
+
+# pythonindentation settings
+[{*.py}]
+indent_style = space
+indent_size = 4
+max_line_length = 120
+
+[{*.js,*.tsx,*.jsx,*.vue,*.css,*.scss,*.html}]
+indent_style = space
+indent_size = 2
+max_line_length = 120
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 000000000..4e3aff128
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,10 @@
+{
+ "parserOptions": {
+ "ecmaVersion": "latest",
+ "sourceType": "module"
+ },
+
+ "env": {
+ "es6": true
+ }
+ }
diff --git a/.flake8 b/.flake8
new file mode 100644
index 000000000..3f68b22b9
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,72 @@
+[flake8]
+ignore =
+ B007,
+ B950,
+ E101,
+ E111,
+ E114,
+ E116,
+ E117,
+ E121,
+ E122,
+ E123,
+ E124,
+ E125,
+ E126,
+ E127,
+ E128,
+ E131,
+ E201,
+ E202,
+ E203,
+ E211,
+ E221,
+ E222,
+ E223,
+ E224,
+ E225,
+ E226,
+ E228,
+ E231,
+ E241,
+ E242,
+ E251,
+ E261,
+ E262,
+ E265,
+ E266,
+ E271,
+ E272,
+ E273,
+ E274,
+ E301,
+ E302,
+ E303,
+ E305,
+ E306,
+ E401,
+ E402,
+ E501,
+ E502,
+ E701,
+ E702,
+ E703,
+ E741,
+ F403,
+ W191,
+ W291,
+ W292,
+ W293,
+ W391,
+ W503,
+ W504,
+ I001,
+ I005,
+ I004,
+ I003
+
+per-file-ignores =
+ # syntax: [comma-separated path/to/file: comma-separated ERROR CODES]
+ __init__.py, hooks.py: F401
+
+max-line-length = 200
diff --git a/.github/logo.png b/.github/logo.png
index 6bf9bd93e..919f2bc65 100644
Binary files a/.github/logo.png and b/.github/logo.png differ
diff --git a/.github/screenshots/CallLogs.png b/.github/screenshots/CallLogs.png
deleted file mode 100644
index 0eab791a5..000000000
Binary files a/.github/screenshots/CallLogs.png and /dev/null differ
diff --git a/.github/screenshots/CallUI.png b/.github/screenshots/CallUI.png
deleted file mode 100644
index 7ebddd018..000000000
Binary files a/.github/screenshots/CallUI.png and /dev/null differ
diff --git a/.github/screenshots/DealsList.png b/.github/screenshots/DealsList.png
deleted file mode 100644
index c2911d703..000000000
Binary files a/.github/screenshots/DealsList.png and /dev/null differ
diff --git a/.github/screenshots/Emailtemplates.png b/.github/screenshots/Emailtemplates.png
deleted file mode 100644
index a30f08a89..000000000
Binary files a/.github/screenshots/Emailtemplates.png and /dev/null differ
diff --git a/.github/screenshots/LeadPage.png b/.github/screenshots/LeadPage.png
deleted file mode 100644
index 854035e45..000000000
Binary files a/.github/screenshots/LeadPage.png and /dev/null differ
diff --git a/.github/screenshots/MainDealPage.png b/.github/screenshots/MainDealPage.png
deleted file mode 100644
index fe32d4ec4..000000000
Binary files a/.github/screenshots/MainDealPage.png and /dev/null differ
diff --git a/.github/screenshots/OpportunityPage.jpeg b/.github/screenshots/OpportunityPage.jpeg
new file mode 100644
index 000000000..48eaf5953
Binary files /dev/null and b/.github/screenshots/OpportunityPage.jpeg differ
diff --git a/.gitignore b/.gitignore
index 206a011d4..56403dc46 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,7 @@ __pycache__
dev-dist
tags
node_modules
-crm/public/frontend
+next_crm/public/frontend
frontend/yarn.lock
-crm/www/crm.html
-build
+next_crm/www/next-crm/index.html
+build
\ No newline at end of file
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 000000000..a796939d5
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,80 @@
+exclude: "node_modules|.git"
+default_stages: [commit]
+fail_fast: false
+
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.5.0
+ hooks:
+ - id: end-of-file-fixer
+ - id: trailing-whitespace
+ files: '^next_crm/.*\.py'
+ exclude: ".*json$|.*txt$|.*csv|.*md|.*svg"
+ - id: check-yaml
+ - id: check-merge-conflict
+ - id: check-ast
+ - id: check-json
+ - id: check-toml
+ - id: check-yaml
+ - id: debug-statements
+ files: '^next_crm/.*\.py'
+
+ - repo: https://github.com/asottile/pyupgrade
+ rev: v3.15.0
+ hooks:
+ - id: pyupgrade
+ args: ["--py310-plus"]
+
+ - repo: https://github.com/psf/black
+ rev: 24.2.0
+ hooks:
+ - id: black
+ files: '^next_crm/.*\.py'
+
+ - repo: https://github.com/pre-commit/mirrors-prettier
+ rev: v3.1.0
+ hooks:
+ - id: prettier
+ files: '^next_crm/.*\.js|jsx|ts|tsx'
+ # Ignore any files that might contain jinja / bundles
+ exclude: |
+ (?x)^(
+ next_crm/public/dist/.*|
+ .*node_modules.*|
+ .*boilerplate.*|
+ next_crm/www/website_script.js|
+ next_crm/templates/includes/.*|
+ next_crm/public/js/lib/.*|
+ next_crm/website/doctype/website_theme/website_theme_template.scss
+ )$
+
+ - repo: https://github.com/pre-commit/mirrors-eslint
+ rev: v8.56.0
+ hooks:
+ - id: eslint
+ files: '^next_crm/.*\.js|jsx|ts|tsx'
+ args: ["--quiet"]
+ # Ignore any files that might contain jinja / bundles
+ exclude: |
+ (?x)^(
+ next_crm/public/dist/.*|
+ cypress/.*|
+ .*node_modules.*|
+ .*boilerplate.*|
+ next_crm/www/website_script.js|
+ next_crm/templates/includes/.*|
+ next_crm/public/js/lib/.*
+ )$
+
+ - repo: https://github.com/PyCQA/isort
+ rev: 5.13.2
+ hooks:
+ - id: isort
+ args: ["--profile", "black"]
+ files: '^next_crm/.*\.py'
+
+ - repo: https://github.com/PyCQA/flake8
+ rev: 7.0.0
+ hooks:
+ - id: flake8
+ additional_dependencies: ["flake8-isort", "flake8-bugbear"]
diff --git a/.releaserc b/.releaserc
index 0c43b999a..ffcda2ea9 100644
--- a/.releaserc
+++ b/.releaserc
@@ -7,12 +7,12 @@
"@semantic-release/release-notes-generator",
[
"@semantic-release/exec", {
- "prepareCmd": 'sed -ir "s/[0-9]*\.[0-9]*\.[0-9]*/${nextRelease.version}/" crm/__init__.py'
+ "prepareCmd": 'sed -ir "s/[0-9]*\.[0-9]*\.[0-9]*/${nextRelease.version}/" next_crm/__init__.py'
}
],
[
"@semantic-release/git", {
- "assets": ["crm/__init__.py"],
+ "assets": ["next_crm/__init__.py"],
"message": "chore(release): Bumped to Version ${nextRelease.version}"
}
],
diff --git a/.semgrepignore b/.semgrepignore
new file mode 100644
index 000000000..65dc151d3
--- /dev/null
+++ b/.semgrepignore
@@ -0,0 +1,30 @@
+# Common large paths
+node_modules/
+build/
+dist/
+vendor/
+.env/
+.venv/
+.tox/
+*.min.js
+.npm/
+.yarn/
+
+# Common test paths
+test/
+tests/
+testsuite/
+*_test.go
+test*.py
+
+# Semgrep rules folder
+.frappe-semgrep
+
+# Semgrep-action log folder
+.semgrep_logs/
+
+# Github Actions
+.github/
+
+# Markdown files
+*.md
diff --git a/README.md b/README.md
index ed962a2a1..9603f31bd 100644
--- a/README.md
+++ b/README.md
@@ -1,126 +1,65 @@
+
+
+
Next CRM
+
+
-
-
-
-
- Show more screenshots
-
-
-
-
-
-
-
## Key Features
- **Views:** Create custom views which is a combination of filters, sort and columns.
- - **Pinned View:** Pin important leads and deals in the sidebar.
+ - **Pinned View:** Pin important leads and opportunities in the sidebar.
- **Public View:** Share views with all users.
- **Saved View:** Save views for later use.
-- **Email Communication:** Send and receive emails directly from the Lead/Deal Page.
+- **Email Communication:** Send and receive emails directly from the Lead/Opportunity Page.
- **Email Templates:** Create and use email templates for faster communication.
-- **Comments:** Add comments to leads and deals to keep track of the conversation.
+- **Comments:** Add comments to leads and opportunities to keep track of the conversation.
- **Notifications:** Get notified when someone mentions you in a comment.
-- **Service Level Agreement:** Set SLA for leads and deals and get notified when the SLA is breached.
-- **Assignment Rule:** Automatically assign leads and deals to users based on the criteria.
-- **Tasks:** Create tasks for leads and deals.
-- **Notes:** Add notes to leads and deals.
+- **Service Level Agreement:** Set SLA for leads and opportunities and get notified when the SLA is breached.
+- **Assignment Rule:** Automatically assign leads and opportunities to users based on the criteria.
+- **ToDos:** Create todos for leads and opportunity.
+- **Notes:** Add notes to leads and opportunity.
- **Call Logs:** See the call logs with call details and recordings.
-## Integrations
-
-- **Twilio:** Integrate Twilio to make and receive calls from the CRM. You can also record calls. It is a built-in integration.
-- **WhatsApp:** Integrate WhatsApp to send and receive messages from the CRM. [Frappe WhatsApp](https://github.com/shridarpatil/frappe_whatsapp) is used for this integration.
-
## Getting Started
-### Managed Hosting
-
-Get started with your personal or business site with a few clicks on [Frappe Cloud](https://frappecloud.com/marketplace/apps/crm).
-
-### Docker (Recommended)
-
-The quickest way to set up Frappe CRM and take it for a test ride.
-
-Frappe framework is multi-tenant and supports multiple apps by default. This docker compose is just a standalone version with Frappe CRM pre-installed. Just put it behind your desired reverse-proxy if needed, and you're good to go.
-
-If you wish to use multiple Frappe apps or need multi-tenancy. Take a look at our production ready self-hosted workflow, or join us on Frappe Cloud to get first party support and hassle-free hosting.
-
-**Step 1**: Setup folder and download the required files
-
- mkdir frappe-crm
- cd frappe-crm
-
-**Step 2**: Download the required files
-
-Docker Compose File:
-
- wget -O docker-compose.yml https://raw.githubusercontent.com/frappe/crm/develop/docker/docker-compose.yml
-
-Frappe CRM bench setup script
-
- wget -O init.sh https://raw.githubusercontent.com/frappe/crm/develop/docker/init.sh
-
-**Step 3**: Run the container and daemonize it
-
- docker compose up -d
-
-**Step 4**: The site [http://crm.localhost](http://crm.localhost) should now be available. The default credentials are:
-
-> username: administrator
-> password: admin
-
-### Self-hosting
-
-If you prefer self-hosting, follow the official [Frappe Bench Installation](https://github.com/frappe/bench#installation) instructions.
-
-## Want to Just Try Out or Contribute?
-
-### Codespaces
-
-1. Open [this link](https://github.com/codespaces/new?hide_repo_select=true&ref=master&repo=668199241&skip_quickstart=true&machine=standardLinux32gb&devcontainer_path=.devcontainer%2Fdevcontainer.json&geo=SoutheastAsia) and click on "Create Codespace".
-2. Wait for initialization (~15 mins).
-3. Run `bench start` from the terminal tab.
-4. Click on the link beside "8000" port under "Ports" tab.
-5. Log in with "Administrator" as the username and "admin" as the password.
-6. Go to `.github.dev/crm` to access the crm interface.
-
### Local Setup
1. [Install Bench](https://github.com/frappe/bench).
-2. Install Frappe CRM app:
+2. [Install ERPNext](https://github.com/frappe/erpnext)
+2. Get the Next CRM app:
```sh
- $ bench get-app crm
+ $ bench get-app https://github.com/rtCamp/next-crm --branch next-develop
```
3. Create a site with the crm app:
```sh
- $ bench --site sitename.localhost install-app crm
+ $ bench --site sitename.localhost install-app next_crm
```
4. Open the site in the browser:
```sh
$ bench browse sitename.localhost --user Administrator
```
-5. Access the crm page at `sitename.localhost:8000/crm` in your web browser.
-
-## Need help?
-
-Join our [telegram group](https://t.me/frappecrm) for instant help.
+5. Access the crm page at `sitename.localhost:8000/next-crm` in your web browser.
-## Documentation
+### Changes other than DocType
-Check out the [official documentation](https://docs.frappe.io/crm) for more details.
+1. App renamed to Next CRM
+2. URL changed from /crm to /next-crm
+3. Lead is compulsory to create Opportunity (being reconsidered)
+4. ERPNext integration enabled by default
-## License
+### Removed Features
+1. CRM Invitation – Permissions from the ERPNext CRM module are used directly.
+2. Ability to link to ERPNext on a different site – Not required as this is tightly integrated with the ERPNext CRM module.
-[GNU Affero General Public License v3.0](LICENSE)
+We’d love your feedback! Please check it out and share your thoughts on Discuss Forum .
diff --git a/crm/__init__.py b/crm/__init__.py
deleted file mode 100644
index 3af1df468..000000000
--- a/crm/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-
-__version__ = "2.0.0-dev"
-__title__ = "Frappe CRM"
-
diff --git a/crm/api/__init__.py b/crm/api/__init__.py
deleted file mode 100644
index 58739de0d..000000000
--- a/crm/api/__init__.py
+++ /dev/null
@@ -1,127 +0,0 @@
-from bs4 import BeautifulSoup
-import frappe
-from frappe.translate import get_all_translations
-from frappe.utils import validate_email_address, split_emails, cstr
-from frappe.utils.telemetry import POSTHOG_HOST_FIELD, POSTHOG_PROJECT_FIELD
-from frappe.core.api.file import get_max_file_size
-
-
-@frappe.whitelist(allow_guest=True)
-def get_translations():
- if frappe.session.user != "Guest":
- language = frappe.db.get_value("User", frappe.session.user, "language")
- else:
- language = frappe.db.get_single_value("System Settings", "language")
-
- return get_all_translations(language)
-
-
-@frappe.whitelist()
-def get_user_signature():
- user = frappe.session.user
- user_email_signature = (
- frappe.db.get_value(
- "User",
- user,
- "email_signature",
- )
- if user
- else None
- )
-
- signature = user_email_signature or frappe.db.get_value(
- "Email Account",
- {"default_outgoing": 1, "add_signature": 1},
- "signature",
- )
-
- if not signature:
- return
-
- soup = BeautifulSoup(signature, "html.parser")
- html_signature = soup.find("div", {"class": "ql-editor read-mode"})
- _signature = None
- if html_signature:
- _signature = html_signature.renderContents()
- content = ""
- if (cstr(_signature) or signature):
- content = f'{signature}
'
- return content
-
-
-@frappe.whitelist()
-def get_posthog_settings():
- return {
- "posthog_project_id": frappe.conf.get(POSTHOG_PROJECT_FIELD),
- "posthog_host": frappe.conf.get(POSTHOG_HOST_FIELD),
- "enable_telemetry": frappe.get_system_settings("enable_telemetry"),
- "telemetry_site_age": frappe.utils.telemetry.site_age(),
- }
-
-
-def check_app_permission():
- if frappe.session.user == "Administrator":
- return True
-
- roles = frappe.get_roles()
- if any(role in ["System Manager", "Sales User", "Sales Manager", "Sales Master Manager"] for role in roles):
- return True
-
- return False
-
-
-@frappe.whitelist(allow_guest=True)
-def accept_invitation(key: str = None):
- if not key:
- frappe.throw("Invalid or expired key")
-
- result = frappe.db.get_all("CRM Invitation", filters={"key": key}, pluck="name")
- if not result:
- frappe.throw("Invalid or expired key")
-
- invitation = frappe.get_doc("CRM Invitation", result[0])
- invitation.accept()
- invitation.reload()
-
- if invitation.status == "Accepted":
- frappe.local.login_manager.login_as(invitation.email)
- frappe.local.response["type"] = "redirect"
- frappe.local.response["location"] = "/crm"
-
-
-@frappe.whitelist()
-def invite_by_email(emails: str, role: str):
- if not emails:
- return
- email_string = validate_email_address(emails, throw=False)
- email_list = split_emails(email_string)
- if not email_list:
- return
- existing_members = frappe.db.get_all("User", filters={"email": ["in", email_list]}, pluck="email")
- existing_invites = frappe.db.get_all(
- "CRM Invitation",
- filters={"email": ["in", email_list], "role": ["in", ["Sales Manager", "Sales User"]]},
- pluck="email",
- )
-
- to_invite = list(set(email_list) - set(existing_members) - set(existing_invites))
-
- for email in to_invite:
- frappe.get_doc(doctype="CRM Invitation", email=email, role=role).insert(ignore_permissions=True)
-
-
-@frappe.whitelist()
-def get_file_uploader_defaults(doctype: str):
- max_number_of_files = None
- make_attachments_public = False
- if doctype:
- meta = frappe.get_meta(doctype)
- max_number_of_files = meta.get("max_attachments")
- make_attachments_public = meta.get("make_attachments_public")
-
- return {
- 'allowed_file_types': frappe.get_system_settings("allowed_file_extensions"),
- 'max_file_size': get_max_file_size(),
- 'max_number_of_files': max_number_of_files,
- 'make_attachments_public': bool(make_attachments_public),
- }
\ No newline at end of file
diff --git a/crm/api/activities.py b/crm/api/activities.py
deleted file mode 100644
index 8cc714ebc..000000000
--- a/crm/api/activities.py
+++ /dev/null
@@ -1,393 +0,0 @@
-import json
-
-from bs4 import BeautifulSoup
-import frappe
-from frappe import _
-from frappe.utils.caching import redis_cache
-from frappe.desk.form.load import get_docinfo
-
-@frappe.whitelist()
-def get_activities(name):
- if frappe.db.exists("CRM Deal", name):
- return get_deal_activities(name)
- elif frappe.db.exists("CRM Lead", name):
- return get_lead_activities(name)
- else:
- frappe.throw(_("Document not found"), frappe.DoesNotExistError)
-
-def get_deal_activities(name):
- get_docinfo('', "CRM Deal", name)
- docinfo = frappe.response["docinfo"]
- deal_meta = frappe.get_meta("CRM Deal")
- deal_fields = {field.fieldname: {"label": field.label, "options": field.options} for field in deal_meta.fields}
- avoid_fields = [
- "lead",
- "response_by",
- "sla_creation",
- "sla",
- "first_response_time",
- "first_responded_on",
- ]
-
- doc = frappe.db.get_values("CRM Deal", name, ["creation", "owner", "lead"])[0]
- lead = doc[2]
-
- activities = []
- calls = []
- notes = []
- tasks = []
- attachments = []
- creation_text = "created this deal"
-
- if lead:
- activities, calls, notes, tasks, attachments = get_lead_activities(lead)
- creation_text = "converted the lead to this deal"
-
- activities.append({
- "activity_type": "creation",
- "creation": doc[0],
- "owner": doc[1],
- "data": creation_text,
- "is_lead": False,
- })
-
- docinfo.versions.reverse()
-
- for version in docinfo.versions:
- data = json.loads(version.data)
- if not data.get("changed"):
- continue
-
- if change := data.get("changed")[0]:
- field = deal_fields.get(change[0], None)
-
- if not field or change[0] in avoid_fields or (not change[1] and not change[2]):
- continue
-
- field_label = field.get("label") or change[0]
- field_option = field.get("options") or None
-
- activity_type = "changed"
- data = {
- "field": change[0],
- "field_label": field_label,
- "old_value": change[1],
- "value": change[2],
- }
-
- if not change[1] and change[2]:
- activity_type = "added"
- data = {
- "field": change[0],
- "field_label": field_label,
- "value": change[2],
- }
- elif change[1] and not change[2]:
- activity_type = "removed"
- data = {
- "field": change[0],
- "field_label": field_label,
- "value": change[1],
- }
-
- activity = {
- "activity_type": activity_type,
- "creation": version.creation,
- "owner": version.owner,
- "data": data,
- "is_lead": False,
- "options": field_option,
- }
- activities.append(activity)
-
- for comment in docinfo.comments:
- activity = {
- "name": comment.name,
- "activity_type": "comment",
- "creation": comment.creation,
- "owner": comment.owner,
- "content": comment.content,
- "attachments": get_attachments('Comment', comment.name),
- "is_lead": False,
- }
- activities.append(activity)
-
- for communication in docinfo.communications + docinfo.automated_messages:
- activity = {
- "activity_type": "communication",
- "communication_type": communication.communication_type,
- "creation": communication.creation,
- "data": {
- "subject": communication.subject,
- "content": communication.content,
- "sender_full_name": communication.sender_full_name,
- "sender": communication.sender,
- "recipients": communication.recipients,
- "cc": communication.cc,
- "bcc": communication.bcc,
- "attachments": get_attachments('Communication', communication.name),
- "read_by_recipient": communication.read_by_recipient,
- "delivery_status": communication.delivery_status,
- },
- "is_lead": False,
- }
- activities.append(activity)
-
- for attachment_log in docinfo.attachment_logs:
- activity = {
- "name": attachment_log.name,
- "activity_type": "attachment_log",
- "creation": attachment_log.creation,
- "owner": attachment_log.owner,
- "data": parse_attachment_log(attachment_log.content, attachment_log.comment_type),
- "is_lead": False,
- }
- activities.append(activity)
-
- calls = calls + get_linked_calls(name)
- notes = notes + get_linked_notes(name)
- tasks = tasks + get_linked_tasks(name)
- attachments = attachments + get_attachments('CRM Deal', name)
-
- activities.sort(key=lambda x: x["creation"], reverse=True)
- activities = handle_multiple_versions(activities)
-
- return activities, calls, notes, tasks, attachments
-
-def get_lead_activities(name):
- get_docinfo('', "CRM Lead", name)
- docinfo = frappe.response["docinfo"]
- lead_meta = frappe.get_meta("CRM Lead")
- lead_fields = {field.fieldname: {"label": field.label, "options": field.options} for field in lead_meta.fields}
- avoid_fields = [
- "converted",
- "response_by",
- "sla_creation",
- "sla",
- "first_response_time",
- "first_responded_on",
- ]
-
- doc = frappe.db.get_values("CRM Lead", name, ["creation", "owner"])[0]
- activities = [{
- "activity_type": "creation",
- "creation": doc[0],
- "owner": doc[1],
- "data": "created this lead",
- "is_lead": True,
- }]
-
- docinfo.versions.reverse()
-
- for version in docinfo.versions:
- data = json.loads(version.data)
- if not data.get("changed"):
- continue
-
- if change := data.get("changed")[0]:
- field = lead_fields.get(change[0], None)
-
- if not field or change[0] in avoid_fields or (not change[1] and not change[2]):
- continue
-
- field_label = field.get("label") or change[0]
- field_option = field.get("options") or None
-
- activity_type = "changed"
- data = {
- "field": change[0],
- "field_label": field_label,
- "old_value": change[1],
- "value": change[2],
- }
-
- if not change[1] and change[2]:
- activity_type = "added"
- data = {
- "field": change[0],
- "field_label": field_label,
- "value": change[2],
- }
- elif change[1] and not change[2]:
- activity_type = "removed"
- data = {
- "field": change[0],
- "field_label": field_label,
- "value": change[1],
- }
-
- activity = {
- "activity_type": activity_type,
- "creation": version.creation,
- "owner": version.owner,
- "data": data,
- "is_lead": True,
- "options": field_option,
- }
- activities.append(activity)
-
- for comment in docinfo.comments:
- activity = {
- "name": comment.name,
- "activity_type": "comment",
- "creation": comment.creation,
- "owner": comment.owner,
- "content": comment.content,
- "attachments": get_attachments('Comment', comment.name),
- "is_lead": True,
- }
- activities.append(activity)
-
- for communication in docinfo.communications + docinfo.automated_messages:
- activity = {
- "activity_type": "communication",
- "communication_type": communication.communication_type,
- "creation": communication.creation,
- "data": {
- "subject": communication.subject,
- "content": communication.content,
- "sender_full_name": communication.sender_full_name,
- "sender": communication.sender,
- "recipients": communication.recipients,
- "cc": communication.cc,
- "bcc": communication.bcc,
- "attachments": get_attachments('Communication', communication.name),
- "read_by_recipient": communication.read_by_recipient,
- "delivery_status": communication.delivery_status,
- },
- "is_lead": True,
- }
- activities.append(activity)
-
- for attachment_log in docinfo.attachment_logs:
- activity = {
- "name": attachment_log.name,
- "activity_type": "attachment_log",
- "creation": attachment_log.creation,
- "owner": attachment_log.owner,
- "data": parse_attachment_log(attachment_log.content, attachment_log.comment_type),
- "is_lead": True,
- }
- activities.append(activity)
-
- calls = get_linked_calls(name)
- notes = get_linked_notes(name)
- tasks = get_linked_tasks(name)
- attachments = get_attachments('CRM Lead', name)
-
- activities.sort(key=lambda x: x["creation"], reverse=True)
- activities = handle_multiple_versions(activities)
-
- return activities, calls, notes, tasks, attachments
-
-
-def get_attachments(doctype, name):
- return frappe.db.get_all(
- "File",
- filters={"attached_to_doctype": doctype, "attached_to_name": name},
- fields=["name", "file_name", "file_type", "file_url", "file_size", "is_private", "creation", "owner"],
- ) or []
-
-def handle_multiple_versions(versions):
- activities = []
- grouped_versions = []
- old_version = None
- for version in versions:
- is_version = version["activity_type"] in ["changed", "added", "removed"]
- if not is_version:
- activities.append(version)
- if not old_version:
- old_version = version
- if is_version: grouped_versions.append(version)
- continue
- if is_version and old_version.get("owner") and version["owner"] == old_version["owner"]:
- grouped_versions.append(version)
- else:
- if grouped_versions:
- activities.append(parse_grouped_versions(grouped_versions))
- grouped_versions = []
- if is_version: grouped_versions.append(version)
- old_version = version
- if version == versions[-1] and grouped_versions:
- activities.append(parse_grouped_versions(grouped_versions))
-
- return activities
-
-def parse_grouped_versions(versions):
- version = versions[0]
- if len(versions) == 1:
- return version
- other_versions = versions[1:]
- version["other_versions"] = other_versions
- return version
-
-def get_linked_calls(name):
- calls = frappe.db.get_all(
- "CRM Call Log",
- filters={"reference_docname": name},
- fields=[
- "name",
- "caller",
- "receiver",
- "from",
- "to",
- "duration",
- "start_time",
- "end_time",
- "status",
- "type",
- "recording_url",
- "creation",
- "note",
- ],
- )
- return calls or []
-
-def get_linked_notes(name):
- notes = frappe.db.get_all(
- "FCRM Note",
- filters={"reference_docname": name},
- fields=['name', 'title', 'content', 'owner', 'modified'],
- )
- return notes or []
-
-def get_linked_tasks(name):
- tasks = frappe.db.get_all(
- "CRM Task",
- filters={"reference_docname": name},
- fields=[
- "name",
- "title",
- "description",
- "assigned_to",
- "assigned_to",
- "due_date",
- "priority",
- "status",
- "modified",
- ],
- )
- return tasks or []
-
-def parse_attachment_log(html, type):
- soup = BeautifulSoup(html, "html.parser")
- a_tag = soup.find("a")
- type = "added" if type == "Attachment" else "removed"
- if not a_tag:
- return {
- "type": type,
- "file_name": html.replace("Removed ", ""),
- "file_url": "",
- "is_private": False,
- }
-
- is_private = False
- if "private/files" in a_tag["href"]:
- is_private = True
-
- return {
- "type": type,
- "file_name": a_tag.text,
- "file_url": a_tag["href"],
- "is_private": is_private,
- }
\ No newline at end of file
diff --git a/crm/api/auth.py b/crm/api/auth.py
deleted file mode 100644
index d485ccc12..000000000
--- a/crm/api/auth.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import frappe
-
-@frappe.whitelist(allow_guest=True)
-def oauth_providers():
- from frappe.utils.html_utils import get_icon_html
- from frappe.utils.password import get_decrypted_password
- from frappe.utils.oauth import get_oauth2_authorize_url, get_oauth_keys
-
- out = []
- providers = frappe.get_all(
- "Social Login Key",
- filters={"enable_social_login": 1},
- fields=["name", "client_id", "base_url", "provider_name", "icon"],
- order_by="name",
- )
-
- for provider in providers:
- client_secret = get_decrypted_password("Social Login Key", provider.name, "client_secret")
- if not client_secret:
- continue
-
- icon = None
- if provider.icon:
- if provider.provider_name == "Custom":
- icon = get_icon_html(provider.icon, small=True)
- else:
- icon = f" "
-
- if provider.client_id and provider.base_url and get_oauth_keys(provider.name):
- out.append(
- {
- "name": provider.name,
- "provider_name": provider.provider_name,
- "auth_url": get_oauth2_authorize_url(provider.name, "/crm"),
- "icon": icon,
- }
- )
- return out
\ No newline at end of file
diff --git a/crm/api/comment.py b/crm/api/comment.py
deleted file mode 100644
index 7d78f0d4e..000000000
--- a/crm/api/comment.py
+++ /dev/null
@@ -1,94 +0,0 @@
-from collections.abc import Iterable
-
-import frappe
-from frappe import _
-from bs4 import BeautifulSoup
-from crm.fcrm.doctype.crm_notification.crm_notification import notify_user
-
-def on_update(self, method):
- notify_mentions(self)
-
-
-def notify_mentions(doc):
- """
- Extract mentions from `content`, and notify.
- `content` must have `HTML` content.
- """
- content = getattr(doc, "content", None)
- if not content:
- return
- mentions = extract_mentions(content)
- reference_doc = frappe.get_doc(doc.reference_doctype, doc.reference_name)
- for mention in mentions:
- owner = frappe.get_cached_value("User", doc.owner, "full_name")
- doctype = doc.reference_doctype
- if doctype.startswith("CRM "):
- doctype = doctype[4:].lower()
- name = reference_doc.lead_name or name if doctype == "lead" else reference_doc.organization or reference_doc.lead_name or name
- notification_text = f"""
-
- { owner }
- { _('mentioned you in {0}').format(doctype) }
- { name }
-
- """
- notify_user({
- "owner": doc.owner,
- "assigned_to": mention.email,
- "notification_type": "Mention",
- "message": doc.content,
- "notification_text": notification_text,
- "reference_doctype": "Comment",
- "reference_docname": doc.name,
- "redirect_to_doctype": doc.reference_doctype,
- "redirect_to_docname": doc.reference_name,
- })
-
-
-def extract_mentions(html):
- if not html:
- return []
- soup = BeautifulSoup(html, "html.parser")
- mentions = []
- for d in soup.find_all("span", attrs={"data-type": "mention"}):
- mentions.append(
- frappe._dict(full_name=d.get("data-label"), email=d.get("data-id"))
- )
- return mentions
-
-@frappe.whitelist()
-def add_attachments(name: str, attachments: Iterable[str | dict]) -> None:
- """Add attachments to the given Comment
-
- :param name: Comment name
- :param attachments: File names or dicts with keys "fname" and "fcontent"
- """
- # loop through attachments
- for a in attachments:
- if isinstance(a, str):
- attach = frappe.db.get_value("File", {"name": a}, ["file_url", "is_private"], as_dict=1)
- file_args = {
- "file_url": attach.file_url,
- "is_private": attach.is_private,
- }
- elif isinstance(a, dict) and "fcontent" in a and "fname" in a:
- # dict returned by frappe.attach_print()
- file_args = {
- "file_name": a["fname"],
- "content": a["fcontent"],
- "is_private": 1,
- }
- else:
- continue
-
- file_args.update(
- {
- "attached_to_doctype": "Comment",
- "attached_to_name": name,
- "folder": "Home/Attachments",
- }
- )
-
- _file = frappe.new_doc("File")
- _file.update(file_args)
- _file.save(ignore_permissions=True)
\ No newline at end of file
diff --git a/crm/api/contact.py b/crm/api/contact.py
deleted file mode 100644
index 65bc06609..000000000
--- a/crm/api/contact.py
+++ /dev/null
@@ -1,183 +0,0 @@
-import frappe
-from frappe import _
-
-
-def validate(doc, method):
- set_primary_email(doc)
- set_primary_mobile_no(doc)
- doc.set_primary_email()
- doc.set_primary("mobile_no")
- update_deals_email_mobile_no(doc)
-
-
-def set_primary_email(doc):
- if not doc.email_ids:
- return
-
- if len(doc.email_ids) == 1:
- doc.email_ids[0].is_primary = 1
-
-
-def set_primary_mobile_no(doc):
- if not doc.phone_nos:
- return
-
- if len(doc.phone_nos) == 1:
- doc.phone_nos[0].is_primary_mobile_no = 1
-
-
-def update_deals_email_mobile_no(doc):
- linked_deals = frappe.get_all(
- "CRM Contacts",
- filters={"contact": doc.name, "is_primary": 1},
- fields=["parent"],
- )
-
- for linked_deal in linked_deals:
- deal = frappe.get_cached_doc("CRM Deal", linked_deal.parent)
- if deal.email != doc.email_id or deal.mobile_no != doc.mobile_no:
- deal.email = doc.email_id
- deal.mobile_no = doc.mobile_no
- deal.save(ignore_permissions=True)
-
-
-@frappe.whitelist()
-def get_contact(name):
- Contact = frappe.qb.DocType("Contact")
-
- query = (
- frappe.qb.from_(Contact)
- .select("*")
- .where(Contact.name == name)
- .limit(1)
- )
-
- contact = query.run(as_dict=True)
- if not len(contact):
- frappe.throw(_("Contact not found"), frappe.DoesNotExistError)
- contact = contact.pop()
-
- contact["doctype"] = "Contact"
- contact["email_ids"] = frappe.get_all(
- "Contact Email", filters={"parent": name}, fields=["name", "email_id", "is_primary"]
- )
- contact["phone_nos"] = frappe.get_all(
- "Contact Phone", filters={"parent": name}, fields=["name", "phone", "is_primary_mobile_no"]
- )
- return contact
-
-@frappe.whitelist()
-def get_linked_deals(contact):
- """Get linked deals for a contact"""
-
- if not frappe.has_permission("Contact", "read", contact):
- frappe.throw("Not permitted", frappe.PermissionError)
-
- deal_names = frappe.get_all(
- "CRM Contacts",
- filters={"contact": contact, "parenttype": "CRM Deal"},
- fields=["parent"],
- distinct=True,
- )
-
- # get deals data
- deals = []
- for d in deal_names:
- deal = frappe.get_cached_doc(
- "CRM Deal",
- d.parent,
- fields=[
- "name",
- "organization",
- "currency",
- "annual_revenue",
- "status",
- "email",
- "mobile_no",
- "deal_owner",
- "modified",
- ],
- )
- deals.append(deal.as_dict())
-
- return deals
-
-
-@frappe.whitelist()
-def create_new(contact, field, value):
- """Create new email or phone for a contact"""
- if not frappe.has_permission("Contact", "write", contact):
- frappe.throw("Not permitted", frappe.PermissionError)
-
- contact = frappe.get_doc("Contact", contact)
-
- if field == "email":
- contact.append("email_ids", {"email_id": value})
- elif field in ("mobile_no", "phone"):
- contact.append("phone_nos", {"phone": value})
- else:
- frappe.throw("Invalid field")
-
- contact.save()
- return True
-
-
-@frappe.whitelist()
-def set_as_primary(contact, field, value):
- """Set email or phone as primary for a contact"""
- if not frappe.has_permission("Contact", "write", contact):
- frappe.throw("Not permitted", frappe.PermissionError)
-
- contact = frappe.get_doc("Contact", contact)
-
- if field == "email":
- for email in contact.email_ids:
- if email.email_id == value:
- email.is_primary = 1
- else:
- email.is_primary = 0
- elif field in ("mobile_no", "phone"):
- name = "is_primary_mobile_no" if field == "mobile_no" else "is_primary_phone"
- for phone in contact.phone_nos:
- if phone.phone == value:
- phone.set(name, 1)
- else:
- phone.set(name, 0)
- else:
- frappe.throw("Invalid field")
-
- contact.save()
- return True
-
-
-@frappe.whitelist()
-def search_emails(txt: str):
- doctype = "Contact"
- meta = frappe.get_meta(doctype)
- filters = [["Contact", "email_id", "is", "set"]]
-
- if meta.get("fields", {"fieldname": "enabled", "fieldtype": "Check"}):
- filters.append([doctype, "enabled", "=", 1])
- if meta.get("fields", {"fieldname": "disabled", "fieldtype": "Check"}):
- filters.append([doctype, "disabled", "!=", 1])
-
- or_filters = []
- search_fields = ["full_name", "email_id", "name"]
- if txt:
- for f in search_fields:
- or_filters.append([doctype, f.strip(), "like", f"%{txt}%"])
-
- results = frappe.get_list(
- doctype,
- filters=filters,
- fields=search_fields,
- or_filters=or_filters,
- limit_start=0,
- limit_page_length=20,
- order_by='email_id, full_name, name',
- ignore_permissions=False,
- as_list=True,
- strict=False,
- )
-
- return results
\ No newline at end of file
diff --git a/crm/api/demo.py b/crm/api/demo.py
deleted file mode 100644
index 1ac27b7ff..000000000
--- a/crm/api/demo.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import frappe
-from frappe import _
-from frappe.auth import LoginManager
-
-
-@frappe.whitelist(allow_guest=True)
-def login():
- if not frappe.conf.demo_username or not frappe.conf.demo_password:
- return
- frappe.local.response["redirect_to"] = "/crm"
- login_manager = LoginManager()
- login_manager.authenticate(frappe.conf.demo_username, frappe.conf.demo_password)
- login_manager.post_login()
- frappe.local.response["type"] = "redirect"
- frappe.local.response["location"] = frappe.local.response["redirect_to"]
-
-
-def validate_reset_password(user):
- if frappe.conf.demo_username and frappe.session.user == frappe.conf.demo_username:
- frappe.throw(
- _("Password cannot be reset by Demo User {}").format(
- frappe.bold(frappe.conf.demo_username)
- ),
- frappe.PermissionError,
- )
-
-
-def validate_user(doc, event):
- if frappe.conf.demo_username and frappe.session.user == frappe.conf.demo_username and doc.new_password:
- frappe.throw(
- _("Password cannot be reset by Demo User {}").format(
- frappe.bold(frappe.conf.demo_username)
- ),
- frappe.PermissionError,
- )
-
diff --git a/crm/api/doc.py b/crm/api/doc.py
deleted file mode 100644
index 576127a1a..000000000
--- a/crm/api/doc.py
+++ /dev/null
@@ -1,702 +0,0 @@
-import frappe
-import json
-from frappe import _
-from frappe.model.document import get_controller
-from frappe.model import no_value_fields
-from pypika import Criterion
-from frappe.utils import make_filter_tuple
-
-from crm.api.views import get_views
-from crm.fcrm.doctype.crm_form_script.crm_form_script import get_form_script
-
-
-@frappe.whitelist()
-def sort_options(doctype: str):
- fields = frappe.get_meta(doctype).fields
- fields = [field for field in fields if field.fieldtype not in no_value_fields]
- fields = [
- {
- "label": _(field.label),
- "value": field.fieldname,
- }
- for field in fields
- if field.label and field.fieldname
- ]
-
- standard_fields = [
- {"label": "Name", "value": "name"},
- {"label": "Created On", "value": "creation"},
- {"label": "Last Modified", "value": "modified"},
- {"label": "Modified By", "value": "modified_by"},
- {"label": "Owner", "value": "owner"},
- ]
-
- for field in standard_fields:
- field["label"] = _(field["label"])
- fields.append(field)
-
- return fields
-
-
-@frappe.whitelist()
-def get_filterable_fields(doctype: str):
- allowed_fieldtypes = [
- "Check",
- "Data",
- "Float",
- "Int",
- "Currency",
- "Dynamic Link",
- "Link",
- "Long Text",
- "Select",
- "Small Text",
- "Text Editor",
- "Text",
- "Duration",
- "Date",
- "Datetime",
- ]
-
- c = get_controller(doctype)
- restricted_fields = []
- if hasattr(c, "get_non_filterable_fields"):
- restricted_fields = c.get_non_filterable_fields()
-
- res = []
-
- # append DocFields
- DocField = frappe.qb.DocType("DocField")
- doc_fields = get_doctype_fields_meta(DocField, doctype, allowed_fieldtypes, restricted_fields)
- res.extend(doc_fields)
-
- # append Custom Fields
- CustomField = frappe.qb.DocType("Custom Field")
- custom_fields = get_doctype_fields_meta(CustomField, doctype, allowed_fieldtypes, restricted_fields)
- res.extend(custom_fields)
-
- # append standard fields (getting error when using frappe.model.std_fields)
- standard_fields = [
- {"fieldname": "name", "fieldtype": "Link", "label": "ID", "options": doctype},
- {
- "fieldname": "owner",
- "fieldtype": "Link",
- "label": "Created By",
- "options": "User"
- },
- {
- "fieldname": "modified_by",
- "fieldtype": "Link",
- "label": "Last Updated By",
- "options": "User",
- },
- {"fieldname": "_user_tags", "fieldtype": "Data", "label": "Tags"},
- {"fieldname": "_liked_by", "fieldtype": "Data", "label": "Like"},
- {"fieldname": "_comments", "fieldtype": "Text", "label": "Comments"},
- {"fieldname": "_assign", "fieldtype": "Text", "label": "Assigned To"},
- {"fieldname": "creation", "fieldtype": "Datetime", "label": "Created On"},
- {"fieldname": "modified", "fieldtype": "Datetime", "label": "Last Updated On"},
- ]
- for field in standard_fields:
- if (
- field.get("fieldname") not in restricted_fields and
- field.get("fieldtype") in allowed_fieldtypes
- ):
- field["name"] = field.get("fieldname")
- res.append(field)
-
- for field in res:
- field["label"] = _(field.get("label"))
-
- return res
-
-
-@frappe.whitelist()
-def get_group_by_fields(doctype: str):
- allowed_fieldtypes = [
- "Check",
- "Data",
- "Float",
- "Int",
- "Currency",
- "Dynamic Link",
- "Link",
- "Select",
- "Duration",
- "Date",
- "Datetime",
- ]
-
- fields = frappe.get_meta(doctype).fields
- fields = [field for field in fields if field.fieldtype not in no_value_fields and field.fieldtype in allowed_fieldtypes]
- fields = [
- {
- "label": _(field.label),
- "value": field.fieldname,
- }
- for field in fields
- if field.label and field.fieldname
- ]
-
- standard_fields = [
- {"label": "Name", "value": "name"},
- {"label": "Created On", "value": "creation"},
- {"label": "Last Modified", "value": "modified"},
- {"label": "Modified By", "value": "modified_by"},
- {"label": "Owner", "value": "owner"},
- {"label": "Liked By", "value": "_liked_by"},
- {"label": "Assigned To", "value": "_assign"},
- {"label": "Comments", "value": "_comments"},
- {"label": "Created On", "value": "creation"},
- {"label": "Modified On", "value": "modified"},
- ]
-
- for field in standard_fields:
- field["label"] = _(field["label"])
- fields.append(field)
-
- return fields
-
-
-def get_doctype_fields_meta(DocField, doctype, allowed_fieldtypes, restricted_fields):
- parent = "parent" if DocField._table_name == "tabDocField" else "dt"
- return (
- frappe.qb.from_(DocField)
- .select(
- DocField.fieldname,
- DocField.fieldtype,
- DocField.label,
- DocField.name,
- DocField.options,
- )
- .where(DocField[parent] == doctype)
- .where(DocField.hidden == False)
- .where(Criterion.any([DocField.fieldtype == i for i in allowed_fieldtypes]))
- .where(Criterion.all([DocField.fieldname != i for i in restricted_fields]))
- .run(as_dict=True)
- )
-
-@frappe.whitelist()
-def get_quick_filters(doctype: str):
- meta = frappe.get_meta(doctype)
- fields = [field for field in meta.fields if field.in_standard_filter]
- quick_filters = []
-
- for field in fields:
-
- if field.fieldtype == "Select":
- field.options = field.options.split("\n")
- field.options = [{"label": option, "value": option} for option in field.options]
- field.options.insert(0, {"label": "", "value": ""})
- quick_filters.append({
- "label": _(field.label),
- "name": field.fieldname,
- "type": field.fieldtype,
- "options": field.options,
- })
-
- if doctype == "CRM Lead":
- quick_filters = [filter for filter in quick_filters if filter.get("name") != "converted"]
-
- return quick_filters
-
-@frappe.whitelist()
-def get_data(
- doctype: str,
- filters: dict,
- order_by: str,
- page_length=20,
- page_length_count=20,
- column_field=None,
- title_field=None,
- columns=[],
- rows=[],
- kanban_columns=[],
- kanban_fields=[],
- view=None,
- default_filters=None,
-):
- custom_view = False
- filters = frappe._dict(filters)
- rows = frappe.parse_json(rows or "[]")
- columns = frappe.parse_json(columns or "[]")
- kanban_fields = frappe.parse_json(kanban_fields or "[]")
- kanban_columns = frappe.parse_json(kanban_columns or "[]")
-
- custom_view_name = view.get('custom_view_name') if view else None
- view_type = view.get('view_type') if view else None
- group_by_field = view.get('group_by_field') if view else None
-
- for key in filters:
- value = filters[key]
- if isinstance(value, list):
- if "@me" in value:
- value[value.index("@me")] = frappe.session.user
- elif "%@me%" in value:
- index = [i for i, v in enumerate(value) if v == "%@me%"]
- for i in index:
- value[i] = "%" + frappe.session.user + "%"
- elif value == "@me":
- filters[key] = frappe.session.user
-
- if default_filters:
- default_filters = frappe.parse_json(default_filters)
- filters.update(default_filters)
-
- is_default = True
- data = []
- _list = get_controller(doctype)
- default_rows = []
- if hasattr(_list, "default_list_data"):
- default_rows = _list.default_list_data().get("rows")
-
- if view_type != "kanban":
- if columns or rows:
- custom_view = True
- is_default = False
- columns = frappe.parse_json(columns)
- rows = frappe.parse_json(rows)
-
- if not columns:
- columns = [
- {"label": "Name", "type": "Data", "key": "name", "width": "16rem"},
- {"label": "Last Modified", "type": "Datetime", "key": "modified", "width": "8rem"},
- ]
-
- if not rows:
- rows = ["name"]
-
- default_view_filters = {
- "dt": doctype,
- "type": view_type or 'list',
- "is_default": 1,
- "user": frappe.session.user,
- }
-
- if not custom_view and frappe.db.exists("CRM View Settings", default_view_filters):
- list_view_settings = frappe.get_doc("CRM View Settings", default_view_filters)
- columns = frappe.parse_json(list_view_settings.columns)
- rows = frappe.parse_json(list_view_settings.rows)
- is_default = False
- elif not custom_view or is_default and hasattr(_list, "default_list_data"):
- rows = default_rows
- columns = _list.default_list_data().get("columns")
-
- # check if rows has all keys from columns if not add them
- for column in columns:
- if column.get("key") not in rows:
- rows.append(column.get("key"))
- column["label"] = _(column.get("label"))
-
- if column.get("key") == "_liked_by" and column.get("width") == "10rem":
- column["width"] = "50px"
-
- # check if rows has group_by_field if not add it
- if group_by_field and group_by_field not in rows:
- rows.append(group_by_field)
-
- data = frappe.get_list(
- doctype,
- fields=rows,
- filters=filters,
- order_by=order_by,
- page_length=page_length,
- ) or []
-
- if view_type == "kanban":
- if not rows:
- rows = default_rows
-
- if not kanban_columns and column_field:
- field_meta = frappe.get_meta(doctype).get_field(column_field)
- if field_meta.fieldtype == "Link":
- kanban_columns = frappe.get_all(
- field_meta.options,
- fields=["name"],
- order_by="modified asc",
- )
- elif field_meta.fieldtype == "Select":
- kanban_columns = [{"name": option} for option in field_meta.options.split("\n")]
-
- if not title_field:
- title_field = "name"
- if hasattr(_list, "default_kanban_settings"):
- title_field = _list.default_kanban_settings().get("title_field")
-
- if title_field not in rows:
- rows.append(title_field)
-
- if not kanban_fields:
- kanban_fields = ["name"]
- if hasattr(_list, "default_kanban_settings"):
- kanban_fields = json.loads(_list.default_kanban_settings().get("kanban_fields"))
-
- for field in kanban_fields:
- if field not in rows:
- rows.append(field)
-
- for kc in kanban_columns:
- column_filters = { column_field: kc.get('name') }
- order = kc.get("order")
- if column_field in filters and filters.get(column_field) != kc.name or kc.get('delete'):
- column_data = []
- else:
- column_filters.update(filters.copy())
- page_length = 20
-
- if kc.get("page_length"):
- page_length = kc.get("page_length")
-
- if order:
- column_data = get_records_based_on_order(doctype, rows, column_filters, page_length, order)
- else:
- column_data = frappe.get_list(
- doctype,
- fields=rows,
- filters=convert_filter_to_tuple(doctype, column_filters),
- order_by=order_by,
- page_length=page_length,
- )
-
- new_filters = filters.copy()
- new_filters.update({ column_field: kc.get('name') })
-
- all_count = len(frappe.get_list(doctype, filters=convert_filter_to_tuple(doctype, new_filters)))
-
- kc["all_count"] = all_count
- kc["count"] = len(column_data)
-
- for d in column_data:
- getCounts(d, doctype)
-
- if order:
- column_data = sorted(
- column_data, key=lambda x: order.index(x.get("name"))
- if x.get("name") in order else len(order)
- )
-
- data.append({"column": kc, "fields": kanban_fields, "data": column_data})
-
- fields = frappe.get_meta(doctype).fields
- fields = [field for field in fields if field.fieldtype not in no_value_fields]
- fields = [
- {
- "label": _(field.label),
- "type": field.fieldtype,
- "value": field.fieldname,
- "options": field.options,
- }
- for field in fields
- if field.label and field.fieldname
- ]
-
- std_fields = [
- {"label": "Name", "type": "Data", "value": "name"},
- {"label": "Created On", "type": "Datetime", "value": "creation"},
- {"label": "Last Modified", "type": "Datetime", "value": "modified"},
- {
- "label": "Modified By",
- "type": "Link",
- "value": "modified_by",
- "options": "User",
- },
- {"label": "Assigned To", "type": "Text", "value": "_assign"},
- {"label": "Owner", "type": "Link", "value": "owner", "options": "User"},
- {"label": "Like", "type": "Data", "value": "_liked_by"},
- ]
-
- for field in std_fields:
- if field.get('value') not in rows:
- rows.append(field.get('value'))
- if field not in fields:
- field["label"] = _(field["label"])
- fields.append(field)
-
- if not is_default and custom_view_name:
- is_default = frappe.db.get_value("CRM View Settings", custom_view_name, "load_default_columns")
-
- if group_by_field and view_type == "group_by":
- def get_options(type, options):
- if type == "Select":
- return [option for option in options.split("\n")]
- else:
- has_empty_values = any([not d.get(group_by_field) for d in data])
- options = list(set([d.get(group_by_field) for d in data]))
- options = [u for u in options if u]
- if has_empty_values:
- options.append("")
-
- if order_by and group_by_field in order_by:
- order_by_fields = order_by.split(",")
- order_by_fields = [(field.split(" ")[0], field.split(" ")[1]) for field in order_by_fields]
- if (group_by_field, "asc") in order_by_fields:
- options.sort()
- elif (group_by_field, "desc") in order_by_fields:
- options.sort(reverse=True)
- else:
- options.sort()
- return options
-
- for field in fields:
- if field.get("value") == group_by_field:
- group_by_field = {
- "label": field.get("label"),
- "name": field.get("value"),
- "type": field.get("type"),
- "options": get_options(field.get("type"), field.get("options")),
- }
-
- return {
- "data": data,
- "columns": columns,
- "rows": rows,
- "fields": fields,
- "column_field": column_field,
- "title_field": title_field,
- "kanban_columns": kanban_columns,
- "kanban_fields": kanban_fields,
- "group_by_field": group_by_field,
- "page_length": page_length,
- "page_length_count": page_length_count,
- "is_default": is_default,
- "views": get_views(doctype),
- "total_count": len(frappe.get_list(doctype, filters=filters)),
- "row_count": len(data),
- "form_script": get_form_script(doctype),
- "list_script": get_form_script(doctype, "List"),
- "view_type": view_type,
- }
-
-def convert_filter_to_tuple(doctype, filters):
- if isinstance(filters, dict):
- filters_items = filters.items()
- filters = []
- for key, value in filters_items:
- filters.append(make_filter_tuple(doctype, key, value))
- return filters
-
-
-def get_records_based_on_order(doctype, rows, filters, page_length, order):
- records = []
- filters = convert_filter_to_tuple(doctype, filters)
- in_filters = filters.copy()
- in_filters.append([doctype, "name", "in", order[:page_length]])
- records = frappe.get_list(
- doctype,
- fields=rows,
- filters=in_filters,
- order_by="creation desc",
- page_length=page_length,
- )
-
- if len(records) < page_length:
- not_in_filters = filters.copy()
- not_in_filters.append([doctype, "name", "not in", order])
- remaining_records = frappe.get_list(
- doctype,
- fields=rows,
- filters=not_in_filters,
- order_by="creation desc",
- page_length=page_length - len(records),
- )
- for record in remaining_records:
- records.append(record)
-
- return records
-
-@frappe.whitelist()
-def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False):
- not_allowed_fieldtypes = [
- "Tab Break",
- "Section Break",
- "Column Break",
- ]
-
- if restricted_fieldtypes:
- restricted_fieldtypes = frappe.parse_json(restricted_fieldtypes)
- not_allowed_fieldtypes += restricted_fieldtypes
-
- fields = frappe.get_meta(doctype).fields
- fields = [field for field in fields if field.fieldtype not in not_allowed_fieldtypes]
-
- standard_fields = [
- {"fieldname": "name", "fieldtype": "Link", "label": "ID", "options": doctype},
- {
- "fieldname": "owner",
- "fieldtype": "Link",
- "label": "Created By",
- "options": "User"
- },
- {
- "fieldname": "modified_by",
- "fieldtype": "Link",
- "label": "Last Updated By",
- "options": "User",
- },
- {"fieldname": "_user_tags", "fieldtype": "Data", "label": "Tags"},
- {"fieldname": "_liked_by", "fieldtype": "Data", "label": "Like"},
- {"fieldname": "_comments", "fieldtype": "Text", "label": "Comments"},
- {"fieldname": "_assign", "fieldtype": "Text", "label": "Assigned To"},
- {"fieldname": "creation", "fieldtype": "Datetime", "label": "Created On"},
- {"fieldname": "modified", "fieldtype": "Datetime", "label": "Last Updated On"},
- ]
-
- for field in standard_fields:
- if not restricted_fieldtypes or field.get('fieldtype') not in restricted_fieldtypes:
- fields.append(field)
-
- if as_array:
- return fields
-
- fields_meta = {}
- for field in fields:
- fields_meta[field.get('fieldname')] = field
-
- return fields_meta
-
-@frappe.whitelist()
-def get_sidebar_fields(doctype, name):
- if not frappe.db.exists("CRM Fields Layout", {"dt": doctype, "type": "Side Panel"}):
- return []
- layout = frappe.get_doc("CRM Fields Layout", {"dt": doctype, "type": "Side Panel"}).layout
-
- if not layout:
- return []
-
- layout = json.loads(layout)
-
- not_allowed_fieldtypes = [
- "Tab Break",
- "Section Break",
- "Column Break",
- ]
-
- fields = frappe.get_meta(doctype).fields
- fields = [field for field in fields if field.fieldtype not in not_allowed_fieldtypes]
-
- doc = frappe.get_cached_doc(doctype, name)
- has_high_permlevel_fields = any(df.permlevel > 0 for df in fields)
- if has_high_permlevel_fields:
- has_read_access_to_permlevels = doc.get_permlevel_access("read")
- has_write_access_to_permlevels = doc.get_permlevel_access("write")
-
- for section in layout:
- section["name"] = section.get("name") or section.get("label")
- for field in section.get("fields") if section.get("fields") else []:
- field_obj = next((f for f in fields if f.fieldname == field), None)
- if field_obj:
- if field_obj.permlevel > 0:
- field_has_write_access = field_obj.permlevel in has_write_access_to_permlevels
- field_has_read_access = field_obj.permlevel in has_read_access_to_permlevels
- if not field_has_write_access and field_has_read_access:
- field_obj.read_only = 1
- if not field_has_read_access and not field_has_write_access:
- field_obj.hidden = 1
- section["fields"][section.get("fields").index(field)] = get_field_obj(field_obj)
-
- fields_meta = {}
- for field in fields:
- fields_meta[field.fieldname] = field
-
- return layout
-
-def get_field_obj(field):
- obj = {
- "label": field.label,
- "type": get_type(field),
- "name": field.fieldname,
- "hidden": field.hidden,
- "reqd": field.reqd,
- "read_only": field.read_only,
- "all_properties": field,
- }
-
- obj["placeholder"] = field.get("placeholder") or "Add " + field.label + "..."
-
- if field.fieldtype == "Link":
- obj["placeholder"] = field.get("placeholder") or "Select " + field.label + "..."
- obj["doctype"] = field.options
- elif field.fieldtype == "Select" and field.options:
- obj["placeholder"] = field.get("placeholder") or "Select " + field.label + "..."
- obj["options"] = [{"label": option, "value": option} for option in field.options.split("\n")]
-
- if field.read_only:
- obj["tooltip"] = "This field is read only and cannot be edited."
-
- return obj
-
-
-def get_type(field):
- if field.fieldtype == "Data" and field.options == "Phone":
- return "phone"
- elif field.fieldtype == "Data" and field.options == "Email":
- return "email"
- elif field.fieldtype == "Check":
- return "checkbox"
- elif field.fieldtype == "Int":
- return "number"
- elif field.fieldtype in ["Small Text", "Text", "Long Text"]:
- return "textarea"
- elif field.read_only:
- return "read_only"
- return field.fieldtype.lower()
-
-def get_assigned_users(doctype, name, default_assigned_to=None):
- assigned_users = frappe.get_all(
- "ToDo",
- fields=["allocated_to"],
- filters={
- "reference_type": doctype,
- "reference_name": name,
- "status": ("!=", "Cancelled"),
- },
- pluck="allocated_to",
- )
-
- users = list(set(assigned_users))
-
- # if users is empty, add default_assigned_to
- if not users and default_assigned_to:
- users = [default_assigned_to]
- return users
-
-
-@frappe.whitelist()
-def get_fields(doctype: str, allow_all_fieldtypes: bool = False):
- not_allowed_fieldtypes = list(frappe.model.no_value_fields) + ["Read Only"]
- if allow_all_fieldtypes:
- not_allowed_fieldtypes = []
- fields = frappe.get_meta(doctype).fields
-
- _fields = []
-
- for field in fields:
- if (
- field.fieldtype not in not_allowed_fieldtypes
- and field.fieldname
- ):
- _fields.append({
- "label": field.label,
- "type": field.fieldtype,
- "value": field.fieldname,
- "options": field.options,
- "mandatory": field.reqd,
- "read_only": field.read_only,
- "hidden": field.hidden,
- "depends_on": field.depends_on,
- "mandatory_depends_on": field.mandatory_depends_on,
- "read_only_depends_on": field.read_only_depends_on,
- "link_filters": field.get("link_filters"),
- "placeholder": field.get("placeholder"),
- })
-
- return _fields
-
-
-def getCounts(d, doctype):
- d["_email_count"] = frappe.db.count("Communication", filters={"reference_doctype": doctype, "reference_name": d.get("name"), "communication_type": "Communication"}) or 0
- d["_email_count"] = d["_email_count"] + frappe.db.count("Communication", filters={"reference_doctype": doctype, "reference_name": d.get("name"), "communication_type": "Automated Message"})
- d["_comment_count"] = frappe.db.count("Comment", filters={"reference_doctype": doctype, "reference_name": d.get("name"), "comment_type": "Comment"})
- d["_task_count"] = frappe.db.count("CRM Task", filters={"reference_doctype": doctype, "reference_docname": d.get("name")})
- d["_note_count"] = frappe.db.count("FCRM Note", filters={"reference_doctype": doctype, "reference_docname": d.get("name")})
- return d
\ No newline at end of file
diff --git a/crm/api/session.py b/crm/api/session.py
deleted file mode 100644
index 2e2b93758..000000000
--- a/crm/api/session.py
+++ /dev/null
@@ -1,89 +0,0 @@
-import frappe
-
-
-@frappe.whitelist()
-def get_users():
- users = frappe.qb.get_query(
- "User",
- fields=["name", "email", "enabled", "user_image", "first_name", "last_name", "full_name", "user_type"],
- order_by="full_name asc",
- distinct=True,
- ).run(as_dict=1)
-
- for user in users:
- if frappe.session.user == user.name:
- user.session_user = True
-
- user.is_manager = (
- "Sales Manager" in frappe.get_roles(user.name) or user.name == "Administrator"
- )
- return users
-
-@frappe.whitelist()
-def get_contacts():
- contacts = frappe.get_all(
- "Contact",
- fields=[
- "name",
- "salutation",
- "first_name",
- "last_name",
- "full_name",
- "gender",
- "address",
- "designation",
- "image",
- "email_id",
- "mobile_no",
- "phone",
- "company_name",
- "modified"
- ],
- order_by="first_name asc",
- distinct=True,
- )
-
- for contact in contacts:
- contact["email_ids"] = frappe.get_all(
- "Contact Email",
- filters={"parenttype": "Contact", "parent": contact.name},
- fields=["name", "email_id", "is_primary"],
- )
-
- contact["phone_nos"] = frappe.get_all(
- "Contact Phone",
- filters={"parenttype": "Contact", "parent": contact.name},
- fields=["name", "phone", "is_primary_phone", "is_primary_mobile_no"],
- )
-
- return contacts
-
-@frappe.whitelist()
-def get_lead_contacts():
- lead_contacts = frappe.get_all(
- "CRM Lead",
- fields=[
- "name",
- "lead_name",
- "mobile_no",
- "phone",
- "image",
- "modified"
- ],
- filters={"converted": 0},
- order_by="lead_name asc",
- distinct=True,
- )
-
- return lead_contacts
-
-@frappe.whitelist()
-def get_organizations():
- organizations = frappe.qb.get_query(
- "CRM Organization",
- fields=['*'],
- order_by="name asc",
- distinct=True,
- ).run(as_dict=1)
-
- return organizations
diff --git a/crm/api/todo.py b/crm/api/todo.py
deleted file mode 100644
index f30e19f41..000000000
--- a/crm/api/todo.py
+++ /dev/null
@@ -1,103 +0,0 @@
-import frappe
-from frappe import _
-from crm.fcrm.doctype.crm_notification.crm_notification import notify_user
-
-def after_insert(doc, method):
- if doc.reference_type in ["CRM Lead", "CRM Deal"] and doc.reference_name and doc.allocated_to:
- fieldname = "lead_owner" if doc.reference_type == "CRM Lead" else "deal_owner"
- lead_owner = frappe.db.get_value(doc.reference_type, doc.reference_name, fieldname)
- if not lead_owner:
- frappe.db.set_value(doc.reference_type, doc.reference_name, fieldname, doc.allocated_to)
-
- if doc.reference_type in ["CRM Lead", "CRM Deal", "CRM Task"] and doc.reference_name and doc.allocated_to:
- notify_assigned_user(doc)
-
-def on_update(doc, method):
- if doc.has_value_changed("status") and doc.status == "Cancelled" and doc.reference_type in ["CRM Lead", "CRM Deal", "CRM Task"] and doc.reference_name and doc.allocated_to:
- notify_assigned_user(doc, is_cancelled=True)
-
-def notify_assigned_user(doc, is_cancelled=False):
- _doc = frappe.get_doc(doc.reference_type, doc.reference_name)
- owner = frappe.get_cached_value("User", frappe.session.user, "full_name")
- notification_text = get_notification_text(owner, doc, _doc, is_cancelled)
-
- message = _("Your assignment on {0} {1} has been removed by {2}").format(
- doc.reference_type,
- doc.reference_name,
- owner
- ) if is_cancelled else _("{0} assigned a {1} {2} to you").format(
- owner,
- doc.reference_type,
- doc.reference_name
- )
-
- redirect_to_doctype, redirect_to_name = get_redirect_to_doc(doc)
-
- notify_user({
- "owner": frappe.session.user,
- "assigned_to": doc.allocated_to,
- "notification_type": "Assignment",
- "message": message,
- "notification_text": notification_text,
- "reference_doctype": doc.reference_type,
- "reference_docname": doc.reference_name,
- "redirect_to_doctype": redirect_to_doctype,
- "redirect_to_docname": redirect_to_name,
- })
-
-def get_notification_text(owner, doc, reference_doc, is_cancelled=False):
- name = doc.reference_name
- doctype = doc.reference_type
-
- if doctype.startswith("CRM "):
- doctype = doctype[4:].lower()
-
- if doctype in ["lead", "deal"]:
- name = reference_doc.lead_name or name if doctype == "lead" else reference_doc.organization or reference_doc.lead_name or name
-
- if is_cancelled:
- return f"""
-
- { _('Your assignment on {0} {1} has been removed by {2}').format(
- doctype,
- f'{ name } ',
- f'{ owner } '
- ) }
-
- """
-
- return f"""
-
- { owner }
- { _('assigned a {0} {1} to you').format(
- doctype,
- f'{ name } '
- ) }
-
- """
-
- if doctype == "task":
- if is_cancelled:
- return f"""
-
- { _('Your assignment on task {0} has been removed by {1}').format(
- f'{ reference_doc.title } ',
- f'{ owner } '
- ) }
-
- """
- return f"""
-
- { owner }
- { _('assigned a new task {0} to you').format(
- f'{ reference_doc.title } '
- ) }
-
- """
-
-def get_redirect_to_doc(doc):
- if doc.reference_type == "CRM Task":
- reference_doc = frappe.get_doc(doc.reference_type, doc.reference_name)
- return reference_doc.reference_doctype, reference_doc.reference_docname
-
- return doc.reference_type, doc.reference_name
diff --git a/crm/api/views.py b/crm/api/views.py
deleted file mode 100644
index f70246e1d..000000000
--- a/crm/api/views.py
+++ /dev/null
@@ -1,16 +0,0 @@
-import frappe
-from pypika import Criterion
-
-
-@frappe.whitelist()
-def get_views(doctype):
- View = frappe.qb.DocType("CRM View Settings")
- query = (
- frappe.qb.from_(View)
- .select("*")
- .where(Criterion.any([View.user == '', View.user == frappe.session.user]))
- )
- if doctype:
- query = query.where(View.dt == doctype)
- views = query.run(as_dict=True)
- return views
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_call_log/crm_call_log.py b/crm/fcrm/doctype/crm_call_log/crm_call_log.py
deleted file mode 100644
index 42752c6ac..000000000
--- a/crm/fcrm/doctype/crm_call_log/crm_call_log.py
+++ /dev/null
@@ -1,99 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-import frappe
-from frappe.model.document import Document
-
-
-class CRMCallLog(Document):
- @staticmethod
- def default_list_data():
- columns = [
- {
- 'label': 'From',
- 'type': 'Link',
- 'key': 'caller',
- 'options': 'User',
- 'width': '9rem',
- },
- {
- 'label': 'To',
- 'type': 'Link',
- 'key': 'receiver',
- 'options': 'User',
- 'width': '9rem',
- },
- {
- 'label': 'Type',
- 'type': 'Select',
- 'key': 'type',
- 'width': '9rem',
- },
- {
- 'label': 'Status',
- 'type': 'Select',
- 'key': 'status',
- 'width': '9rem',
- },
- {
- 'label': 'Duration',
- 'type': 'Duration',
- 'key': 'duration',
- 'width': '6rem',
- },
- {
- 'label': 'From (number)',
- 'type': 'Data',
- 'key': 'from',
- 'width': '9rem',
- },
- {
- 'label': 'To (number)',
- 'type': 'Data',
- 'key': 'to',
- 'width': '9rem',
- },
- {
- 'label': 'Created On',
- 'type': 'Datetime',
- 'key': 'creation',
- 'width': '8rem',
- },
- ]
- rows = [
- "name",
- "caller",
- "receiver",
- "type",
- "status",
- "duration",
- "from",
- "to",
- "note",
- "recording_url",
- "reference_doctype",
- "reference_docname",
- "creation",
- ]
- return {'columns': columns, 'rows': rows}
-
-@frappe.whitelist()
-def create_lead_from_call_log(call_log):
- lead = frappe.new_doc("CRM Lead")
- lead.first_name = "Lead from call " + call_log.get("from")
- lead.mobile_no = call_log.get("from")
- lead.lead_owner = frappe.session.user
- lead.save(ignore_permissions=True)
-
- frappe.db.set_value("CRM Call Log", call_log.get("name"), {
- "reference_doctype": "CRM Lead",
- "reference_docname": lead.name
- })
-
- if call_log.get("note"):
- frappe.db.set_value("FCRM Note", call_log.get("note"), {
- "reference_doctype": "CRM Lead",
- "reference_docname": lead.name
- })
-
- return lead.name
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_deal/api.py b/crm/fcrm/doctype/crm_deal/api.py
deleted file mode 100644
index 76a764aed..000000000
--- a/crm/fcrm/doctype/crm_deal/api.py
+++ /dev/null
@@ -1,66 +0,0 @@
-import frappe
-from frappe import _
-
-from crm.api.doc import get_fields_meta, get_assigned_users
-from crm.fcrm.doctype.crm_form_script.crm_form_script import get_form_script
-
-@frappe.whitelist()
-def get_deal(name):
- Deal = frappe.qb.DocType("CRM Deal")
-
- query = (
- frappe.qb.from_(Deal)
- .select("*")
- .where(Deal.name == name)
- .limit(1)
- )
-
- deal = query.run(as_dict=True)
- if not len(deal):
- frappe.throw(_("Deal not found"), frappe.DoesNotExistError)
- deal = deal.pop()
-
-
- deal["contacts"] = frappe.get_all(
- "CRM Contacts",
- filters={"parenttype": "CRM Deal", "parent": deal.name},
- fields=["contact", "is_primary"],
- )
-
- deal["doctype"] = "CRM Deal"
- deal["fields_meta"] = get_fields_meta("CRM Deal")
- deal["_form_script"] = get_form_script('CRM Deal')
- deal["_assign"] = get_assigned_users("CRM Deal", deal.name, deal.owner)
- return deal
-
-@frappe.whitelist()
-def get_deal_contacts(name):
- contacts = frappe.get_all(
- "CRM Contacts",
- filters={"parenttype": "CRM Deal", "parent": name},
- fields=["contact", "is_primary"],
- )
- deal_contacts = []
- for contact in contacts:
- is_primary = contact.is_primary
- contact = frappe.get_doc("Contact", contact.contact).as_dict()
- def get_primary_email(contact):
- for email in contact.email_ids:
- if email.is_primary:
- return email.email_id
- return contact.email_ids[0].email_id if contact.email_ids else ""
- def get_primary_mobile_no(contact):
- for phone in contact.phone_nos:
- if phone.is_primary:
- return phone.phone
- return contact.phone_nos[0].phone if contact.phone_nos else ""
- _contact = {
- "name": contact.name,
- "image": contact.image,
- "full_name": contact.full_name,
- "email": get_primary_email(contact),
- "mobile_no": get_primary_mobile_no(contact),
- "is_primary": is_primary,
- }
- deal_contacts.append(_contact)
- return deal_contacts
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.js b/crm/fcrm/doctype/crm_deal/crm_deal.js
deleted file mode 100644
index 944ac6d4e..000000000
--- a/crm/fcrm/doctype/crm_deal/crm_deal.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on("CRM Deal", {
- refresh(frm) {
- frm.add_web_link(`/crm/deals/${frm.doc.name}`, __("Open in Portal"));
- },
-});
diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.json b/crm/fcrm/doctype/crm_deal/crm_deal.json
deleted file mode 100644
index 34a22e35a..000000000
--- a/crm/fcrm/doctype/crm_deal/crm_deal.json
+++ /dev/null
@@ -1,379 +0,0 @@
-{
- "actions": [],
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "naming_series:",
- "creation": "2023-11-06 17:56:25.210449",
- "doctype": "DocType",
- "engine": "InnoDB",
- "field_order": [
- "organization_tab",
- "naming_series",
- "organization",
- "next_step",
- "probability",
- "column_break_ijan",
- "status",
- "close_date",
- "deal_owner",
- "contacts_tab",
- "contacts",
- "contact",
- "lead_details_tab",
- "lead",
- "source",
- "column_break_wsde",
- "lead_name",
- "organization_details_section",
- "organization_name",
- "website",
- "no_of_employees",
- "job_title",
- "column_break_xbyf",
- "territory",
- "currency",
- "annual_revenue",
- "industry",
- "person_section",
- "salutation",
- "first_name",
- "last_name",
- "column_break_xjmy",
- "email",
- "mobile_no",
- "phone",
- "gender",
- "sla_tab",
- "sla",
- "sla_creation",
- "column_break_pfvq",
- "sla_status",
- "communication_status",
- "response_details_section",
- "response_by",
- "column_break_hpvj",
- "first_response_time",
- "first_responded_on",
- "log_tab",
- "status_change_log"
- ],
- "fields": [
- {
- "fieldname": "organization",
- "fieldtype": "Link",
- "label": "Organization",
- "options": "CRM Organization"
- },
- {
- "fieldname": "probability",
- "fieldtype": "Percent",
- "label": "Probability"
- },
- {
- "fetch_from": ".annual_revenue",
- "fieldname": "annual_revenue",
- "fieldtype": "Currency",
- "label": "Amount",
- "options": "currency"
- },
- {
- "fetch_from": ".website",
- "fieldname": "website",
- "fieldtype": "Data",
- "label": "Website"
- },
- {
- "fieldname": "close_date",
- "fieldtype": "Date",
- "label": "Close Date"
- },
- {
- "fieldname": "next_step",
- "fieldtype": "Data",
- "label": "Next Step"
- },
- {
- "fieldname": "lead",
- "fieldtype": "Link",
- "label": "Lead",
- "options": "CRM Lead"
- },
- {
- "fieldname": "deal_owner",
- "fieldtype": "Link",
- "label": "Deal Owner",
- "options": "User"
- },
- {
- "default": "CRM-DEAL-.YYYY.-",
- "fieldname": "naming_series",
- "fieldtype": "Select",
- "label": "Naming Series",
- "options": "CRM-DEAL-.YYYY.-"
- },
- {
- "fieldname": "contacts_tab",
- "fieldtype": "Tab Break",
- "label": "Contacts"
- },
- {
- "fieldname": "email",
- "fieldtype": "Data",
- "label": "Email",
- "options": "Email"
- },
- {
- "fieldname": "mobile_no",
- "fieldtype": "Data",
- "label": "Mobile No",
- "options": "Phone"
- },
- {
- "default": "Qualification",
- "fieldname": "status",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Status",
- "options": "CRM Deal Status",
- "reqd": 1,
- "search_index": 1
- },
- {
- "fieldname": "contacts",
- "fieldtype": "Table",
- "label": "Contacts",
- "options": "CRM Contacts"
- },
- {
- "fieldname": "organization_tab",
- "fieldtype": "Tab Break",
- "label": "Organization"
- },
- {
- "fieldname": "sla_tab",
- "fieldtype": "Tab Break",
- "label": "SLA",
- "read_only": 1
- },
- {
- "fieldname": "sla",
- "fieldtype": "Link",
- "label": "SLA",
- "options": "CRM Service Level Agreement"
- },
- {
- "fieldname": "response_by",
- "fieldtype": "Datetime",
- "label": "Response By",
- "read_only": 1
- },
- {
- "fieldname": "column_break_pfvq",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "sla_status",
- "fieldtype": "Select",
- "label": "SLA Status",
- "options": "\nFirst Response Due\nFailed\nFulfilled",
- "read_only": 1
- },
- {
- "fieldname": "sla_creation",
- "fieldtype": "Datetime",
- "label": "SLA Creation",
- "read_only": 1
- },
- {
- "fieldname": "response_details_section",
- "fieldtype": "Section Break",
- "label": "Response Details"
- },
- {
- "fieldname": "column_break_hpvj",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "first_response_time",
- "fieldtype": "Duration",
- "label": "First Response Time",
- "read_only": 1
- },
- {
- "fieldname": "first_responded_on",
- "fieldtype": "Datetime",
- "label": "First Responded On",
- "read_only": 1
- },
- {
- "default": "Open",
- "fieldname": "communication_status",
- "fieldtype": "Link",
- "label": "Communication Status",
- "options": "CRM Communication Status"
- },
- {
- "fetch_from": ".territory",
- "fieldname": "territory",
- "fieldtype": "Link",
- "label": "Territory",
- "options": "CRM Territory"
- },
- {
- "fieldname": "source",
- "fieldtype": "Link",
- "label": "Source",
- "options": "CRM Lead Source"
- },
- {
- "fieldname": "no_of_employees",
- "fieldtype": "Select",
- "label": "No. of Employees",
- "options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+"
- },
- {
- "fieldname": "job_title",
- "fieldtype": "Data",
- "label": "Job Title"
- },
- {
- "fieldname": "phone",
- "fieldtype": "Data",
- "label": "Phone",
- "options": "Phone"
- },
- {
- "fieldname": "log_tab",
- "fieldtype": "Tab Break",
- "label": "Log",
- "read_only": 1
- },
- {
- "fieldname": "status_change_log",
- "fieldtype": "Table",
- "label": "Status Change Log",
- "options": "CRM Status Change Log"
- },
- {
- "fieldname": "lead_name",
- "fieldtype": "Data",
- "label": "Lead Name"
- },
- {
- "fieldname": "column_break_ijan",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "lead_details_tab",
- "fieldtype": "Tab Break",
- "label": "Lead Details"
- },
- {
- "fieldname": "column_break_wsde",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "organization_details_section",
- "fieldtype": "Section Break",
- "label": "Organization Details"
- },
- {
- "fieldname": "organization_name",
- "fieldtype": "Data",
- "label": "Organization Name"
- },
- {
- "fieldname": "column_break_xbyf",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "industry",
- "fieldtype": "Link",
- "label": "Industry",
- "options": "CRM Industry"
- },
- {
- "fieldname": "person_section",
- "fieldtype": "Section Break",
- "label": "Person"
- },
- {
- "fieldname": "salutation",
- "fieldtype": "Link",
- "label": "Salutation",
- "options": "Salutation"
- },
- {
- "fieldname": "first_name",
- "fieldtype": "Data",
- "label": "First Name"
- },
- {
- "fieldname": "last_name",
- "fieldtype": "Data",
- "label": "Last Name"
- },
- {
- "fieldname": "column_break_xjmy",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "gender",
- "fieldtype": "Link",
- "label": "Gender",
- "options": "Gender"
- },
- {
- "fieldname": "contact",
- "fieldtype": "Link",
- "label": "Contact",
- "options": "Contact"
- },
- {
- "fieldname": "currency",
- "fieldtype": "Link",
- "label": "Currency",
- "options": "Currency"
- }
- ],
- "index_web_pages_for_search": 1,
- "links": [],
- "modified": "2024-09-17 18:34:15.873610",
- "modified_by": "Administrator",
- "module": "FCRM",
- "name": "CRM Deal",
- "naming_rule": "By \"Naming Series\" field",
- "owner": "Administrator",
- "permissions": [
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales User",
- "share": 1,
- "write": 1
- },
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales Manager",
- "share": 1,
- "write": 1
- }
- ],
- "show_title_field_in_link": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "states": [],
- "title_field": "organization",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.py b/crm/fcrm/doctype/crm_deal/crm_deal.py
deleted file mode 100644
index dc18c1331..000000000
--- a/crm/fcrm/doctype/crm_deal/crm_deal.py
+++ /dev/null
@@ -1,308 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-import json
-
-import frappe
-from frappe import _
-from frappe.desk.form.assign_to import add as assign
-from frappe.model.document import Document
-
-from crm.fcrm.doctype.crm_service_level_agreement.utils import get_sla
-from crm.fcrm.doctype.crm_status_change_log.crm_status_change_log import add_status_change_log
-
-
-class CRMDeal(Document):
- def before_validate(self):
- self.set_sla()
-
- def validate(self):
- self.set_primary_contact()
- self.set_primary_email_mobile_no()
- if not self.is_new() and self.has_value_changed("deal_owner") and self.deal_owner:
- self.share_with_agent(self.deal_owner)
- self.assign_agent(self.deal_owner)
- if self.has_value_changed("status"):
- add_status_change_log(self)
-
- def after_insert(self):
- if self.deal_owner:
- self.assign_agent(self.deal_owner)
-
- def before_save(self):
- self.apply_sla()
-
- def set_primary_contact(self, contact=None):
- if not self.contacts:
- return
-
- if not contact and len(self.contacts) == 1:
- self.contacts[0].is_primary = 1
- elif contact:
- for d in self.contacts:
- if d.contact == contact:
- d.is_primary = 1
- else:
- d.is_primary = 0
-
- def set_primary_email_mobile_no(self):
- if not self.contacts:
- self.email = ""
- self.mobile_no = ""
- self.phone = ""
- return
-
- if len([contact for contact in self.contacts if contact.is_primary]) > 1:
- frappe.throw(_("Only one {0} can be set as primary.").format(frappe.bold("Contact")))
-
- primary_contact_exists = False
- for d in self.contacts:
- if d.is_primary == 1:
- primary_contact_exists = True
- self.email = d.email.strip() if d.email else ""
- self.mobile_no = d.mobile_no.strip() if d.mobile_no else ""
- self.phone = d.phone.strip() if d.phone else ""
- break
-
- if not primary_contact_exists:
- self.email = ""
- self.mobile_no = ""
- self.phone = ""
-
- def assign_agent(self, agent):
- if not agent:
- return
-
- assignees = self.get_assigned_users()
- if assignees:
- for assignee in assignees:
- if agent == assignee:
- # the agent is already set as an assignee
- return
-
- assign({"assign_to": [agent], "doctype": "CRM Deal", "name": self.name})
-
- def share_with_agent(self, agent):
- if not agent:
- return
-
- docshares = frappe.get_all(
- "DocShare",
- filters={"share_name": self.name, "share_doctype": self.doctype},
- fields=["name", "user"],
- )
-
- shared_with = [d.user for d in docshares] + [agent]
-
- for user in shared_with:
- if user == agent and not frappe.db.exists("DocShare", {"user": agent, "share_name": self.name, "share_doctype": self.doctype}):
- frappe.share.add_docshare(
- self.doctype, self.name, agent, write=1, flags={"ignore_share_permission": True}
- )
- elif user != agent:
- frappe.share.remove(self.doctype, self.name, user)
-
-
- def set_sla(self):
- """
- Find an SLA to apply to the deal.
- """
- if self.sla: return
-
- sla = get_sla(self)
- if not sla:
- self.first_responded_on = None
- self.first_response_time = None
- return
- self.sla = sla.name
-
- def apply_sla(self):
- """
- Apply SLA if set.
- """
- if not self.sla:
- return
- sla = frappe.get_last_doc("CRM Service Level Agreement", {"name": self.sla})
- if sla:
- sla.apply(self)
-
- @staticmethod
- def default_list_data():
- columns = [
- {
- 'label': 'Organization',
- 'type': 'Link',
- 'key': 'organization',
- 'options': 'CRM Organization',
- 'width': '11rem',
- },
- {
- 'label': 'Amount',
- 'type': 'Currency',
- 'key': 'annual_revenue',
- 'width': '9rem',
- },
- {
- 'label': 'Status',
- 'type': 'Select',
- 'key': 'status',
- 'width': '10rem',
- },
- {
- 'label': 'Email',
- 'type': 'Data',
- 'key': 'email',
- 'width': '12rem',
- },
- {
- 'label': 'Mobile No',
- 'type': 'Data',
- 'key': 'mobile_no',
- 'width': '11rem',
- },
- {
- 'label': 'Assigned To',
- 'type': 'Text',
- 'key': '_assign',
- 'width': '10rem',
- },
- {
- 'label': 'Last Modified',
- 'type': 'Datetime',
- 'key': 'modified',
- 'width': '8rem',
- },
- ]
- rows = [
- "name",
- "organization",
- "annual_revenue",
- "status",
- "email",
- "currency",
- "mobile_no",
- "deal_owner",
- "sla_status",
- "response_by",
- "first_response_time",
- "first_responded_on",
- "modified",
- "_assign",
- ]
- return {'columns': columns, 'rows': rows}
-
- @staticmethod
- def default_kanban_settings():
- return {
- "column_field": "status",
- "title_field": "organization",
- "kanban_fields": '["annual_revenue", "email", "mobile_no", "_assign", "modified"]'
- }
-
-@frappe.whitelist()
-def add_contact(deal, contact):
- if not frappe.has_permission("CRM Deal", "write", deal):
- frappe.throw(_("Not allowed to add contact to Deal"), frappe.PermissionError)
-
- deal = frappe.get_cached_doc("CRM Deal", deal)
- deal.append("contacts", {"contact": contact})
- deal.save()
- return True
-
-@frappe.whitelist()
-def remove_contact(deal, contact):
- if not frappe.has_permission("CRM Deal", "write", deal):
- frappe.throw(_("Not allowed to remove contact from Deal"), frappe.PermissionError)
-
- deal = frappe.get_cached_doc("CRM Deal", deal)
- deal.contacts = [d for d in deal.contacts if d.contact != contact]
- deal.save()
- return True
-
-@frappe.whitelist()
-def set_primary_contact(deal, contact):
- if not frappe.has_permission("CRM Deal", "write", deal):
- frappe.throw(_("Not allowed to set primary contact for Deal"), frappe.PermissionError)
-
- deal = frappe.get_cached_doc("CRM Deal", deal)
- deal.set_primary_contact(contact)
- deal.save()
- return True
-
-def create_organization(doc):
- if not doc.get("organization_name"):
- return
-
- existing_organization = frappe.db.exists("CRM Organization", {"organization_name": doc.get("organization_name")})
- if existing_organization:
- return existing_organization
-
- organization = frappe.new_doc("CRM Organization")
- organization.update(
- {
- "organization_name": doc.get("organization_name"),
- "website": doc.get("website"),
- "territory": doc.get("territory"),
- "industry": doc.get("industry"),
- "annual_revenue": doc.get("annual_revenue"),
- }
- )
- organization.insert(ignore_permissions=True)
- return organization.name
-
-def contact_exists(doc):
- email_exist = frappe.db.exists("Contact Email", {"email_id": doc.get("email")})
- mobile_exist = frappe.db.exists("Contact Phone", {"phone": doc.get("mobile_no")})
-
- doctype = "Contact Email" if email_exist else "Contact Phone"
- name = email_exist or mobile_exist
-
- if name:
- return frappe.db.get_value(doctype, name, "parent")
-
- return False
-
-def create_contact(doc):
- existing_contact = contact_exists(doc)
- if existing_contact:
- return existing_contact
-
- contact = frappe.new_doc("Contact")
- contact.update(
- {
- "first_name": doc.get("first_name"),
- "last_name": doc.get("last_name"),
- "salutation": doc.get("salutation"),
- "company_name": doc.get("organization") or doc.get("organization_name"),
- }
- )
-
- if doc.get("email"):
- contact.append("email_ids", {"email_id": doc.get("email"), "is_primary": 1})
-
- if doc.get("mobile_no"):
- contact.append("phone_nos", {"phone": doc.get("mobile_no"), "is_primary_mobile_no": 1})
-
- contact.insert(ignore_permissions=True)
- contact.reload() # load changes by hooks on contact
-
- return contact.name
-
-@frappe.whitelist()
-def create_deal(args):
- deal = frappe.new_doc("CRM Deal")
-
- contact = args.get("contact")
- if not contact and (args.get("first_name") or args.get("last_name") or args.get("email") or args.get("mobile_no")):
- contact = create_contact(args)
-
- deal.update({
- "organization": args.get("organization") or create_organization(args),
- "contacts": [{"contact": contact, "is_primary": 1}] if contact else [],
- })
-
- args.pop("organization", None)
-
- deal.update(args)
-
- deal.insert(ignore_permissions=True)
- return deal.name
diff --git a/crm/fcrm/doctype/crm_deal/test_crm_deal.py b/crm/fcrm/doctype/crm_deal/test_crm_deal.py
deleted file mode 100644
index 85ef93310..000000000
--- a/crm/fcrm/doctype/crm_deal/test_crm_deal.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-# import frappe
-from frappe.tests import UnitTestCase
-
-
-class TestCRMDeal(UnitTestCase):
- pass
diff --git a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py
deleted file mode 100644
index 6926d0b2b..000000000
--- a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-import json
-import frappe
-from frappe import _
-from frappe.model.document import Document
-
-
-class CRMFieldsLayout(Document):
- pass
-
-@frappe.whitelist()
-def get_fields_layout(doctype: str, type: str):
- sections = []
- if frappe.db.exists("CRM Fields Layout", {"dt": doctype, "type": type}):
- layout = frappe.get_doc("CRM Fields Layout", {"dt": doctype, "type": type})
- else:
- return []
-
- if layout.layout:
- sections = json.loads(layout.layout)
-
- allowed_fields = []
- for section in sections:
- if not section.get("fields"):
- continue
- allowed_fields.extend(section.get("fields"))
-
- fields = frappe.get_meta(doctype).fields
- fields = [field for field in fields if field.fieldname in allowed_fields]
-
- for section in sections:
- for field in section.get("fields") if section.get("fields") else []:
- field = next((f for f in fields if f.fieldname == field), None)
- if field:
- if field.fieldtype == "Select" and field.options:
- field.options = field.options.split("\n")
- field.options = [{"label": _(option), "value": option} for option in field.options]
- field.options.insert(0, {"label": "", "value": ""})
- field = {
- "label": _(field.label),
- "name": field.fieldname,
- "type": field.fieldtype,
- "options": field.options,
- "mandatory": field.reqd,
- "placeholder": field.get("placeholder"),
- "filters": field.get("link_filters")
- }
- section["fields"][section.get("fields").index(field["name"])] = field
-
- return sections or []
-
-
-@frappe.whitelist()
-def save_fields_layout(doctype: str, type: str, layout: str):
- if frappe.db.exists("CRM Fields Layout", {"dt": doctype, "type": type}):
- doc = frappe.get_doc("CRM Fields Layout", {"dt": doctype, "type": type})
- else:
- doc = frappe.new_doc("CRM Fields Layout")
-
- doc.update({
- "dt": doctype,
- "type": type,
- "layout": layout,
- })
- doc.save(ignore_permissions=True)
-
- return doc.layout
diff --git a/crm/fcrm/doctype/crm_form_script/crm_form_script.js b/crm/fcrm/doctype/crm_form_script/crm_form_script.js
deleted file mode 100644
index 0fbea0bb7..000000000
--- a/crm/fcrm/doctype/crm_form_script/crm_form_script.js
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on("CRM Form Script", {
- refresh(frm) {
- frm.set_query("dt", {
- filters: {
- istable: 0,
- },
- });
-
- if (frm.doc.is_standard && !frappe.boot.developer_mode) {
- frm.disable_form();
- frappe.show_alert(
- __(
- "Standard Form Scripts can not be modified, duplicate the Form Script instead."
- )
- );
- }
-
- if (!frappe.boot.developer_mode) {
- frm.toggle_enable("is_standard", 0);
- }
-
- frm.trigger("add_enable_button");
- },
-
- add_enable_button(frm) {
- frm.add_custom_button(
- frm.doc.enabled ? __("Disable") : __("Enable"),
- () => {
- frm.set_value("enabled", !frm.doc.enabled);
- frm.save();
- }
- );
- },
-
- view(frm) {
- let has_form_boilerplate = frm.doc.script.includes(
- "function setupForm("
- );
- let has_list_boilerplate = frm.doc.script.includes(
- "function setupList("
- );
-
- if (frm.doc.view == "Form" && !has_form_boilerplate) {
- frm.doc.script = `
-function setupForm({ doc }) {
- return {
- actions: [],
- statuses: [],
- }
-}`.trim();
- }
- if (frm.doc.view == "List" && !has_list_boilerplate) {
- frm.doc.script = `
-function setupList({ list }) {
- return {
- actions: [],
- bulk_actions: [],
- }
-}`.trim();
- }
- },
-});
diff --git a/crm/fcrm/doctype/crm_form_script/crm_form_script.py b/crm/fcrm/doctype/crm_form_script/crm_form_script.py
deleted file mode 100644
index bb35c851c..000000000
--- a/crm/fcrm/doctype/crm_form_script/crm_form_script.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-import frappe
-from frappe import _
-from frappe.model.document import Document
-
-
-class CRMFormScript(Document):
- def validate(self):
- in_user_env = not (
- frappe.flags.in_install
- or frappe.flags.in_patch
- or frappe.flags.in_test
- or frappe.flags.in_fixtures
- )
- if in_user_env and not self.is_new() and self.is_standard and not frappe.conf.developer_mode:
- # only enabled can be changed for standard form scripts
- if self.has_value_changed("enabled"):
- enabled_value = self.enabled
- self.reload()
- self.enabled = enabled_value
- else:
- frappe.throw(_("You need to be in developer mode to edit a Standard Form Script"))
-
-def get_form_script(dt, view="Form"):
- """Returns the form script for the given doctype"""
- FormScript = frappe.qb.DocType("CRM Form Script")
- query = (
- frappe.qb.from_(FormScript)
- .select("script")
- .where(FormScript.dt == dt)
- .where(FormScript.view == view)
- .where(FormScript.enabled == 1)
- )
-
- doc = query.run(as_dict=True)
- if doc:
- return [d.script for d in doc] if len(doc) > 1 else doc[0].script
- else:
- return None
diff --git a/crm/fcrm/doctype/crm_holiday/crm_holiday.json b/crm/fcrm/doctype/crm_holiday/crm_holiday.json
deleted file mode 100644
index fc0f24f39..000000000
--- a/crm/fcrm/doctype/crm_holiday/crm_holiday.json
+++ /dev/null
@@ -1,57 +0,0 @@
-{
- "actions": [],
- "allow_rename": 1,
- "creation": "2023-12-14 11:16:15.476366",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
- "date",
- "column_break_xzyo",
- "weekly_off",
- "section_break_zenz",
- "description"
- ],
- "fields": [
- {
- "fieldname": "date",
- "fieldtype": "Date",
- "in_list_view": 1,
- "label": "Date",
- "reqd": 1
- },
- {
- "fieldname": "column_break_xzyo",
- "fieldtype": "Column Break"
- },
- {
- "default": "0",
- "fieldname": "weekly_off",
- "fieldtype": "Check",
- "label": "Weekly Off"
- },
- {
- "fieldname": "section_break_zenz",
- "fieldtype": "Section Break"
- },
- {
- "fieldname": "description",
- "fieldtype": "Text Editor",
- "in_list_view": 1,
- "label": "Description",
- "reqd": 1
- }
- ],
- "index_web_pages_for_search": 1,
- "istable": 1,
- "links": [],
- "modified": "2023-12-14 11:17:41.745419",
- "modified_by": "Administrator",
- "module": "FCRM",
- "name": "CRM Holiday",
- "owner": "Administrator",
- "permissions": [],
- "sort_field": "modified",
- "sort_order": "DESC",
- "states": []
-}
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_holiday/crm_holiday.py b/crm/fcrm/doctype/crm_holiday/crm_holiday.py
deleted file mode 100644
index d77b735dc..000000000
--- a/crm/fcrm/doctype/crm_holiday/crm_holiday.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-# import frappe
-from frappe.model.document import Document
-
-
-class CRMHoliday(Document):
- pass
diff --git a/crm/fcrm/doctype/crm_holiday_list/crm_holiday_list.js b/crm/fcrm/doctype/crm_holiday_list/crm_holiday_list.js
deleted file mode 100644
index 914d43de6..000000000
--- a/crm/fcrm/doctype/crm_holiday_list/crm_holiday_list.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-// frappe.ui.form.on("CRM Holiday List", {
-// refresh(frm) {
-
-// },
-// });
diff --git a/crm/fcrm/doctype/crm_holiday_list/crm_holiday_list.json b/crm/fcrm/doctype/crm_holiday_list/crm_holiday_list.json
deleted file mode 100644
index 066f12eed..000000000
--- a/crm/fcrm/doctype/crm_holiday_list/crm_holiday_list.json
+++ /dev/null
@@ -1,123 +0,0 @@
-{
- "actions": [],
- "allow_rename": 1,
- "autoname": "field:holiday_list_name",
- "creation": "2023-12-14 11:09:12.876640",
- "doctype": "DocType",
- "engine": "InnoDB",
- "field_order": [
- "holiday_list_name",
- "from_date",
- "to_date",
- "column_break_qwqc",
- "total_holidays",
- "add_weekly_holidays_section",
- "weekly_off",
- "add_to_holidays",
- "holidays_section",
- "holidays",
- "clear_table"
- ],
- "fields": [
- {
- "fieldname": "holiday_list_name",
- "fieldtype": "Data",
- "in_list_view": 1,
- "label": "Holiday List Name",
- "reqd": 1,
- "unique": 1
- },
- {
- "fieldname": "from_date",
- "fieldtype": "Date",
- "in_list_view": 1,
- "label": "From Date",
- "reqd": 1
- },
- {
- "fieldname": "to_date",
- "fieldtype": "Date",
- "in_list_view": 1,
- "label": "To Date",
- "reqd": 1
- },
- {
- "fieldname": "column_break_qwqc",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "total_holidays",
- "fieldtype": "Int",
- "label": "Total Holidays"
- },
- {
- "fieldname": "add_weekly_holidays_section",
- "fieldtype": "Section Break",
- "label": "Add Weekly Holidays"
- },
- {
- "fieldname": "weekly_off",
- "fieldtype": "Select",
- "label": "Weekly Off",
- "options": "\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday"
- },
- {
- "fieldname": "add_to_holidays",
- "fieldtype": "Button",
- "label": "Add to Holidays"
- },
- {
- "fieldname": "holidays_section",
- "fieldtype": "Section Break",
- "label": "Holidays"
- },
- {
- "fieldname": "clear_table",
- "fieldtype": "Button",
- "label": "Clear Table"
- },
- {
- "fieldname": "holidays",
- "fieldtype": "Table",
- "label": "Holidays",
- "options": "CRM Holiday"
- }
- ],
- "index_web_pages_for_search": 1,
- "links": [],
- "modified": "2024-01-19 21:54:54.809445",
- "modified_by": "Administrator",
- "module": "FCRM",
- "name": "CRM Holiday List",
- "naming_rule": "By fieldname",
- "owner": "Administrator",
- "permissions": [
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales User",
- "share": 1,
- "write": 1
- },
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales Manager",
- "share": 1,
- "write": 1
- }
- ],
- "sort_field": "modified",
- "sort_order": "DESC",
- "states": []
-}
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_holiday_list/crm_holiday_list.py b/crm/fcrm/doctype/crm_holiday_list/crm_holiday_list.py
deleted file mode 100644
index 45c8b1015..000000000
--- a/crm/fcrm/doctype/crm_holiday_list/crm_holiday_list.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-# import frappe
-from frappe.model.document import Document
-
-
-class CRMHolidayList(Document):
- pass
diff --git a/crm/fcrm/doctype/crm_holiday_list/test_crm_holiday_list.py b/crm/fcrm/doctype/crm_holiday_list/test_crm_holiday_list.py
deleted file mode 100644
index ee90dc8a4..000000000
--- a/crm/fcrm/doctype/crm_holiday_list/test_crm_holiday_list.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-# import frappe
-from frappe.tests import UnitTestCase
-
-
-class TestCRMHolidayList(UnitTestCase):
- pass
diff --git a/crm/fcrm/doctype/crm_industry/crm_industry.js b/crm/fcrm/doctype/crm_industry/crm_industry.js
deleted file mode 100644
index 1489970c6..000000000
--- a/crm/fcrm/doctype/crm_industry/crm_industry.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-// frappe.ui.form.on("CRM Industry", {
-// refresh(frm) {
-
-// },
-// });
diff --git a/crm/fcrm/doctype/crm_industry/crm_industry.json b/crm/fcrm/doctype/crm_industry/crm_industry.json
deleted file mode 100644
index d3d15a711..000000000
--- a/crm/fcrm/doctype/crm_industry/crm_industry.json
+++ /dev/null
@@ -1,60 +0,0 @@
-{
- "actions": [],
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "field:industry",
- "creation": "2023-07-24 19:40:31.980882",
- "default_view": "List",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
- "industry"
- ],
- "fields": [
- {
- "fieldname": "industry",
- "fieldtype": "Data",
- "label": "Industry",
- "unique": 1
- }
- ],
- "index_web_pages_for_search": 1,
- "links": [],
- "modified": "2024-01-19 21:57:02.025918",
- "modified_by": "Administrator",
- "module": "FCRM",
- "name": "CRM Industry",
- "naming_rule": "By fieldname",
- "owner": "Administrator",
- "permissions": [
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales User",
- "share": 1,
- "write": 1
- },
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales Manager",
- "share": 1,
- "write": 1
- }
- ],
- "quick_entry": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "states": []
-}
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_industry/crm_industry.py b/crm/fcrm/doctype/crm_industry/crm_industry.py
deleted file mode 100644
index cbafacd94..000000000
--- a/crm/fcrm/doctype/crm_industry/crm_industry.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-# import frappe
-from frappe.model.document import Document
-
-
-class CRMIndustry(Document):
- pass
diff --git a/crm/fcrm/doctype/crm_industry/test_crm_industry.py b/crm/fcrm/doctype/crm_industry/test_crm_industry.py
deleted file mode 100644
index 16d87b134..000000000
--- a/crm/fcrm/doctype/crm_industry/test_crm_industry.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-# import frappe
-from frappe.tests import UnitTestCase
-
-
-class TestCRMIndustry(UnitTestCase):
- pass
diff --git a/crm/fcrm/doctype/crm_invitation/crm_invitation.js b/crm/fcrm/doctype/crm_invitation/crm_invitation.js
deleted file mode 100644
index 6e0485d84..000000000
--- a/crm/fcrm/doctype/crm_invitation/crm_invitation.js
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on("CRM Invitation", {
- refresh(frm) {
- if (frm.doc.status != "Accepted") {
- frm.add_custom_button(__("Accept Invitation"), () => {
- return frm.call("accept_invitation");
- });
- }
- },
-});
diff --git a/crm/fcrm/doctype/crm_invitation/crm_invitation.json b/crm/fcrm/doctype/crm_invitation/crm_invitation.json
deleted file mode 100644
index f5902d6ea..000000000
--- a/crm/fcrm/doctype/crm_invitation/crm_invitation.json
+++ /dev/null
@@ -1,112 +0,0 @@
-{
- "actions": [],
- "allow_rename": 1,
- "creation": "2024-09-03 12:19:18.933810",
- "doctype": "DocType",
- "engine": "InnoDB",
- "field_order": [
- "email",
- "role",
- "key",
- "invited_by",
- "column_break_dsuz",
- "status",
- "email_sent_at",
- "accepted_at"
- ],
- "fields": [
- {
- "fieldname": "email",
- "fieldtype": "Data",
- "in_list_view": 1,
- "label": "Email",
- "reqd": 1
- },
- {
- "fieldname": "role",
- "fieldtype": "Select",
- "in_list_view": 1,
- "label": "Role",
- "options": "\nSales User\nSales Manager",
- "reqd": 1
- },
- {
- "fieldname": "key",
- "fieldtype": "Data",
- "label": "Key"
- },
- {
- "fieldname": "invited_by",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Invited By",
- "options": "User"
- },
- {
- "fieldname": "column_break_dsuz",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "status",
- "fieldtype": "Select",
- "in_list_view": 1,
- "label": "Status",
- "options": "\nPending\nAccepted\nExpired"
- },
- {
- "fieldname": "email_sent_at",
- "fieldtype": "Datetime",
- "label": "Email Sent At"
- },
- {
- "fieldname": "accepted_at",
- "fieldtype": "Datetime",
- "label": "Accepted At"
- }
- ],
- "index_web_pages_for_search": 1,
- "links": [],
- "modified": "2024-09-03 14:59:29.450018",
- "modified_by": "Administrator",
- "module": "FCRM",
- "name": "CRM Invitation",
- "owner": "Administrator",
- "permissions": [
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "share": 1,
- "write": 1
- },
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales Manager",
- "share": 1,
- "write": 1
- },
- {
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales User",
- "share": 1
- }
- ],
- "sort_field": "creation",
- "sort_order": "DESC",
- "states": []
-}
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_invitation/crm_invitation.py b/crm/fcrm/doctype/crm_invitation/crm_invitation.py
deleted file mode 100644
index f4c9a8f9c..000000000
--- a/crm/fcrm/doctype/crm_invitation/crm_invitation.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-import frappe
-from frappe.model.document import Document
-
-
-class CRMInvitation(Document):
- def before_insert(self):
- frappe.utils.validate_email_address(self.email, True)
-
- self.key = frappe.generate_hash(length=12)
- self.invited_by = frappe.session.user
- self.status = "Pending"
-
- def after_insert(self):
- self.invite_via_email()
-
- def invite_via_email(self):
- invite_link = frappe.utils.get_url(f"/api/method/crm.api.accept_invitation?key={self.key}")
- if frappe.local.dev_server:
- print(f"Invite link for {self.email}: {invite_link}")
-
- title = f"Frappe CRM"
- template = "crm_invitation"
-
- frappe.sendmail(
- recipients=self.email,
- subject=f"You have been invited to join {title}",
- template=template,
- args={"title": title, "invite_link": invite_link},
- now=True,
- )
- self.db_set("email_sent_at", frappe.utils.now())
-
- @frappe.whitelist()
- def accept_invitation(self):
- frappe.only_for("System Manager")
- self.accept()
-
- def accept(self):
- if self.status == "Expired":
- frappe.throw("Invalid or expired key")
-
- user = self.create_user_if_not_exists()
- user.append_roles(self.role)
- user.save(ignore_permissions=True)
-
- self.status = "Accepted"
- self.accepted_at = frappe.utils.now()
- self.save(ignore_permissions=True)
-
- def create_user_if_not_exists(self):
- if not frappe.db.exists("User", self.email):
- first_name = self.email.split("@")[0].title()
- user = frappe.get_doc(
- doctype="User",
- user_type="System User",
- email=self.email,
- send_welcome_email=0,
- first_name=first_name,
- ).insert(ignore_permissions=True)
- else:
- user = frappe.get_doc("User", self.email)
- return user
-
-
-def expire_invitations():
- """expire invitations after 3 days"""
- from frappe.utils import add_days, now
-
- days = 3
- invitations_to_expire = frappe.db.get_all(
- "CRM Invitation", filters={"status": "Pending", "creation": ["<", add_days(now(), -days)]}
- )
- for invitation in invitations_to_expire:
- invitation = frappe.get_doc("CRM Invitation", invitation.name)
- invitation.status = "Expired"
- invitation.save(ignore_permissions=True)
diff --git a/crm/fcrm/doctype/crm_invitation/test_crm_invitation.py b/crm/fcrm/doctype/crm_invitation/test_crm_invitation.py
deleted file mode 100644
index ddee0e24f..000000000
--- a/crm/fcrm/doctype/crm_invitation/test_crm_invitation.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-# import frappe
-from frappe.tests import UnitTestCase
-
-
-class TestCRMInvitation(UnitTestCase):
- pass
diff --git a/crm/fcrm/doctype/crm_lead/api.py b/crm/fcrm/doctype/crm_lead/api.py
deleted file mode 100644
index e1bb4a4f5..000000000
--- a/crm/fcrm/doctype/crm_lead/api.py
+++ /dev/null
@@ -1,22 +0,0 @@
-import frappe
-from frappe import _
-
-from crm.api.doc import get_fields_meta, get_assigned_users
-from crm.fcrm.doctype.crm_form_script.crm_form_script import get_form_script
-
-@frappe.whitelist()
-def get_lead(name):
- Lead = frappe.qb.DocType("CRM Lead")
-
- query = frappe.qb.from_(Lead).select("*").where(Lead.name == name).limit(1)
-
- lead = query.run(as_dict=True)
- if not len(lead):
- frappe.throw(_("Lead not found"), frappe.DoesNotExistError)
- lead = lead.pop()
-
- lead["doctype"] = "CRM Lead"
- lead["fields_meta"] = get_fields_meta("CRM Lead")
- lead["_form_script"] = get_form_script('CRM Lead')
- lead["_assign"] = get_assigned_users("CRM Lead", lead.name, lead.owner)
- return lead
diff --git a/crm/fcrm/doctype/crm_lead/crm_lead.js b/crm/fcrm/doctype/crm_lead/crm_lead.js
deleted file mode 100644
index 0a9d57e1f..000000000
--- a/crm/fcrm/doctype/crm_lead/crm_lead.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on("CRM Lead", {
- refresh(frm) {
- frm.add_web_link(`/crm/leads/${frm.doc.name}`, __("Open in Portal"));
- },
-});
diff --git a/crm/fcrm/doctype/crm_lead/crm_lead.json b/crm/fcrm/doctype/crm_lead/crm_lead.json
deleted file mode 100644
index 786c03a53..000000000
--- a/crm/fcrm/doctype/crm_lead/crm_lead.json
+++ /dev/null
@@ -1,333 +0,0 @@
-{
- "actions": [],
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "naming_series:",
- "creation": "2023-07-24 12:19:39.616298",
- "default_view": "List",
- "doctype": "DocType",
- "editable_grid": 1,
- "email_append_to": 1,
- "engine": "InnoDB",
- "field_order": [
- "details",
- "organization",
- "website",
- "territory",
- "industry",
- "job_title",
- "source",
- "lead_owner",
- "person_tab",
- "salutation",
- "first_name",
- "last_name",
- "email",
- "mobile_no",
- "organization_tab",
- "section_break_uixv",
- "naming_series",
- "lead_name",
- "middle_name",
- "gender",
- "phone",
- "column_break_dbsv",
- "status",
- "no_of_employees",
- "annual_revenue",
- "image",
- "converted",
- "sla_tab",
- "sla",
- "sla_creation",
- "column_break_ffnp",
- "sla_status",
- "communication_status",
- "response_details_section",
- "response_by",
- "column_break_pweh",
- "first_response_time",
- "first_responded_on",
- "log_tab",
- "status_change_log"
- ],
- "fields": [
- {
- "default": "CRM-LEAD-.YYYY.-",
- "fieldname": "naming_series",
- "fieldtype": "Select",
- "label": "Series",
- "options": "CRM-LEAD-.YYYY.-"
- },
- {
- "fieldname": "salutation",
- "fieldtype": "Link",
- "label": "Salutation",
- "options": "Salutation"
- },
- {
- "fieldname": "first_name",
- "fieldtype": "Data",
- "label": "First Name",
- "reqd": 1
- },
- {
- "fieldname": "middle_name",
- "fieldtype": "Data",
- "label": "Middle Name"
- },
- {
- "fieldname": "last_name",
- "fieldtype": "Data",
- "label": "Last Name"
- },
- {
- "fieldname": "gender",
- "fieldtype": "Link",
- "label": "Gender",
- "options": "Gender"
- },
- {
- "default": "New",
- "fieldname": "status",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Status",
- "options": "CRM Lead Status",
- "reqd": 1,
- "search_index": 1
- },
- {
- "fieldname": "email",
- "fieldtype": "Data",
- "label": "Email",
- "options": "Email",
- "search_index": 1
- },
- {
- "fieldname": "website",
- "fieldtype": "Data",
- "label": "Website"
- },
- {
- "fieldname": "mobile_no",
- "fieldtype": "Data",
- "label": "Mobile No",
- "options": "Phone"
- },
- {
- "fieldname": "phone",
- "fieldtype": "Data",
- "label": "Phone",
- "options": "Phone"
- },
- {
- "fieldname": "section_break_uixv",
- "fieldtype": "Section Break"
- },
- {
- "fieldname": "column_break_dbsv",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "no_of_employees",
- "fieldtype": "Select",
- "label": "No. of Employees",
- "options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+"
- },
- {
- "fieldname": "annual_revenue",
- "fieldtype": "Currency",
- "label": "Annual Revenue"
- },
- {
- "fieldname": "lead_owner",
- "fieldtype": "Link",
- "label": "Lead Owner",
- "options": "User"
- },
- {
- "fieldname": "source",
- "fieldtype": "Link",
- "label": "Source",
- "options": "CRM Lead Source"
- },
- {
- "fieldname": "industry",
- "fieldtype": "Link",
- "label": "Industry",
- "options": "CRM Industry"
- },
- {
- "fieldname": "image",
- "fieldtype": "Attach Image",
- "hidden": 1,
- "label": "Image",
- "print_hide": 1
- },
- {
- "fieldname": "lead_name",
- "fieldtype": "Data",
- "label": "Full Name",
- "search_index": 1
- },
- {
- "fieldname": "job_title",
- "fieldtype": "Data",
- "label": "Job Title"
- },
- {
- "fieldname": "organization_tab",
- "fieldtype": "Tab Break",
- "label": "Others",
- "read_only": 1
- },
- {
- "fieldname": "organization",
- "fieldtype": "Data",
- "label": "Organization"
- },
- {
- "default": "0",
- "fieldname": "converted",
- "fieldtype": "Check",
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Converted"
- },
- {
- "fieldname": "person_tab",
- "fieldtype": "Tab Break",
- "label": "Person"
- },
- {
- "fieldname": "details",
- "fieldtype": "Tab Break",
- "label": "Details"
- },
- {
- "fieldname": "sla_tab",
- "fieldtype": "Tab Break",
- "label": "SLA",
- "read_only": 1
- },
- {
- "fieldname": "sla",
- "fieldtype": "Link",
- "label": "SLA",
- "options": "CRM Service Level Agreement"
- },
- {
- "fieldname": "sla_creation",
- "fieldtype": "Datetime",
- "label": "SLA Creation",
- "read_only": 1
- },
- {
- "fieldname": "column_break_ffnp",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "sla_status",
- "fieldtype": "Select",
- "label": "SLA Status",
- "options": "\nFirst Response Due\nFailed\nFulfilled",
- "read_only": 1
- },
- {
- "fieldname": "response_details_section",
- "fieldtype": "Section Break",
- "label": "Response Details"
- },
- {
- "fieldname": "response_by",
- "fieldtype": "Datetime",
- "label": "Response By",
- "read_only": 1
- },
- {
- "fieldname": "column_break_pweh",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "first_response_time",
- "fieldtype": "Duration",
- "label": "First Response Time",
- "read_only": 1
- },
- {
- "fieldname": "first_responded_on",
- "fieldtype": "Datetime",
- "label": "First Responded On",
- "read_only": 1
- },
- {
- "default": "Open",
- "fieldname": "communication_status",
- "fieldtype": "Link",
- "label": "Communication Status",
- "options": "CRM Communication Status"
- },
- {
- "fieldname": "territory",
- "fieldtype": "Link",
- "label": "Territory",
- "options": "CRM Territory"
- },
- {
- "fieldname": "log_tab",
- "fieldtype": "Tab Break",
- "label": "Log",
- "read_only": 1
- },
- {
- "fieldname": "status_change_log",
- "fieldtype": "Table",
- "label": "Status Change Log",
- "options": "CRM Status Change Log"
- }
- ],
- "image_field": "image",
- "index_web_pages_for_search": 1,
- "links": [],
- "modified": "2024-09-17 18:36:57.289897",
- "modified_by": "Administrator",
- "module": "FCRM",
- "name": "CRM Lead",
- "naming_rule": "By \"Naming Series\" field",
- "owner": "Administrator",
- "permissions": [
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales User",
- "share": 1,
- "write": 1
- },
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales Manager",
- "share": 1,
- "write": 1
- }
- ],
- "sender_field": "email",
- "sender_name_field": "first_name",
- "show_title_field_in_link": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "states": [],
- "title_field": "lead_name",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_lead/crm_lead.py b/crm/fcrm/doctype/crm_lead/crm_lead.py
deleted file mode 100644
index 632080a90..000000000
--- a/crm/fcrm/doctype/crm_lead/crm_lead.py
+++ /dev/null
@@ -1,352 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-import json
-
-import frappe
-from frappe import _
-from frappe.desk.form.assign_to import add as assign
-from frappe.model.document import Document
-
-from frappe.utils import has_gravatar, validate_email_address
-from crm.fcrm.doctype.crm_service_level_agreement.utils import get_sla
-from crm.fcrm.doctype.crm_status_change_log.crm_status_change_log import add_status_change_log
-
-
-class CRMLead(Document):
- def before_validate(self):
- self.set_sla()
-
- def validate(self):
- self.set_full_name()
- self.set_lead_name()
- self.set_title()
- self.validate_email()
- if not self.is_new() and self.has_value_changed("lead_owner") and self.lead_owner:
- self.share_with_agent(self.lead_owner)
- self.assign_agent(self.lead_owner)
- if self.has_value_changed("status"):
- add_status_change_log(self)
-
- def after_insert(self):
- if self.lead_owner:
- self.assign_agent(self.lead_owner)
-
- def before_save(self):
- self.apply_sla()
-
- def set_full_name(self):
- if self.first_name:
- self.lead_name = " ".join(
- filter(None, [self.salutation, self.first_name, self.middle_name, self.last_name])
- )
-
- def set_lead_name(self):
- if not self.lead_name:
- # Check for leads being created through data import
- if not self.organization and not self.email and not self.flags.ignore_mandatory:
- frappe.throw(_("A Lead requires either a person's name or an organization's name"))
- elif self.organization:
- self.lead_name = self.organization
- elif self.email:
- self.lead_name = self.email.split("@")[0]
- else:
- self.lead_name = "Unnamed Lead"
-
- def set_title(self):
- self.title = self.organization or self.lead_name
-
- def validate_email(self):
- if self.email:
- if not self.flags.ignore_email_validation:
- validate_email_address(self.email, throw=True)
-
- if self.email == self.lead_owner:
- frappe.throw(_("Lead Owner cannot be same as the Lead Email Address"))
-
- if self.is_new() or not self.image:
- self.image = has_gravatar(self.email)
-
- def assign_agent(self, agent):
- if not agent:
- return
-
- assignees = self.get_assigned_users()
- if assignees:
- for assignee in assignees:
- if agent == assignee:
- # the agent is already set as an assignee
- return
-
- assign({"assign_to": [agent], "doctype": "CRM Lead", "name": self.name})
-
- def share_with_agent(self, agent):
- if not agent:
- return
-
- docshares = frappe.get_all(
- "DocShare",
- filters={"share_name": self.name, "share_doctype": self.doctype},
- fields=["name", "user"],
- )
-
- shared_with = [d.user for d in docshares] + [agent]
-
- for user in shared_with:
- if user == agent and not frappe.db.exists("DocShare", {"user": agent, "share_name": self.name, "share_doctype": self.doctype}):
- frappe.share.add_docshare(
- self.doctype, self.name, agent, write=1, flags={"ignore_share_permission": True}
- )
- elif user != agent:
- frappe.share.remove(self.doctype, self.name, user)
-
- def create_contact(self, throw=True):
- if not self.lead_name:
- self.set_full_name()
- self.set_lead_name()
-
- existing_contact = self.contact_exists(throw)
- if existing_contact:
- return existing_contact
-
- contact = frappe.new_doc("Contact")
- contact.update(
- {
- "first_name": self.first_name or self.lead_name,
- "last_name": self.last_name,
- "salutation": self.salutation,
- "gender": self.gender,
- "designation": self.job_title,
- "company_name": self.organization,
- "image": self.image or "",
- }
- )
-
- if self.email:
- contact.append("email_ids", {"email_id": self.email, "is_primary": 1})
-
- if self.phone:
- contact.append("phone_nos", {"phone": self.phone, "is_primary_phone": 1})
-
- if self.mobile_no:
- contact.append("phone_nos", {"phone": self.mobile_no, "is_primary_mobile_no": 1})
-
- contact.insert(ignore_permissions=True)
- contact.reload() # load changes by hooks on contact
-
- return contact.name
-
- def create_organization(self):
- if not self.organization:
- return
-
- existing_organization = frappe.db.exists("CRM Organization", {"organization_name": self.organization})
- if existing_organization:
- return existing_organization
-
- organization = frappe.new_doc("CRM Organization")
- organization.update(
- {
- "organization_name": self.organization,
- "website": self.website,
- "territory": self.territory,
- "industry": self.industry,
- "annual_revenue": self.annual_revenue,
- }
- )
- organization.insert(ignore_permissions=True)
- return organization.name
-
- def contact_exists(self, throw=True):
- email_exist = frappe.db.exists("Contact Email", {"email_id": self.email})
- phone_exist = frappe.db.exists("Contact Phone", {"phone": self.phone})
- mobile_exist = frappe.db.exists("Contact Phone", {"phone": self.mobile_no})
-
- doctype = "Contact Email" if email_exist else "Contact Phone"
- name = email_exist or phone_exist or mobile_exist
-
- if name:
- text = "Email" if email_exist else "Phone" if phone_exist else "Mobile No"
- data = self.email if email_exist else self.phone if phone_exist else self.mobile_no
-
- value = "{0}: {1}".format(text, data)
-
- contact = frappe.db.get_value(doctype, name, "parent")
-
- if throw:
- frappe.throw(
- _("Contact already exists with {0}").format(value),
- title=_("Contact Already Exists"),
- )
- return contact
-
- return False
-
- def create_deal(self, contact, organization):
- deal = frappe.new_doc("CRM Deal")
-
- lead_deal_map = {
- "lead_owner": "deal_owner",
- }
-
- restricted_fieldtypes = ["Tab Break", "Section Break", "Column Break", "HTML", "Button", "Attach", "Table"]
- restricted_map_fields = ["name", "naming_series", "creation", "owner", "modified", "modified_by", "idx", "docstatus", "status", "email", "mobile_no", "phone", "sla", "sla_status", "response_by", "first_response_time", "first_responded_on", "communication_status", "sla_creation"]
-
- for field in self.meta.fields:
- if field.fieldtype in restricted_fieldtypes:
- continue
- if field.fieldname in restricted_map_fields:
- continue
-
- fieldname = field.fieldname
- if field.fieldname in lead_deal_map:
- fieldname = lead_deal_map[field.fieldname]
-
- if hasattr(deal, fieldname):
- if fieldname == "organization":
- deal.update({fieldname: organization})
- else:
- deal.update({fieldname: self.get(field.fieldname)})
-
- deal.update(
- {
- "lead": self.name,
- "contacts": [{"contact": contact}],
- }
- )
-
- if self.first_responded_on:
- deal.update(
- {
- "sla_creation": self.sla_creation,
- "response_by": self.response_by,
- "sla_status": self.sla_status,
- "communication_status": self.communication_status,
- "first_response_time": self.first_response_time,
- "first_responded_on": self.first_responded_on
- }
- )
-
- deal.insert(ignore_permissions=True)
- return deal.name
-
- def set_sla(self):
- """
- Find an SLA to apply to the lead.
- """
- if self.sla: return
-
- sla = get_sla(self)
- if not sla:
- self.first_responded_on = None
- self.first_response_time = None
- return
- self.sla = sla.name
-
- def apply_sla(self):
- """
- Apply SLA if set.
- """
- if not self.sla:
- return
- sla = frappe.get_last_doc("CRM Service Level Agreement", {"name": self.sla})
- if sla:
- sla.apply(self)
-
- def convert_to_deal(self):
- return convert_to_deal(lead=self.name, doc=self)
-
- @staticmethod
- def get_non_filterable_fields():
- return ["converted"]
-
- @staticmethod
- def default_list_data():
- columns = [
- {
- 'label': 'Name',
- 'type': 'Data',
- 'key': 'lead_name',
- 'width': '12rem',
- },
- {
- 'label': 'Organization',
- 'type': 'Link',
- 'key': 'organization',
- 'options': 'CRM Organization',
- 'width': '10rem',
- },
- {
- 'label': 'Status',
- 'type': 'Select',
- 'key': 'status',
- 'width': '8rem',
- },
- {
- 'label': 'Email',
- 'type': 'Data',
- 'key': 'email',
- 'width': '12rem',
- },
- {
- 'label': 'Mobile No',
- 'type': 'Data',
- 'key': 'mobile_no',
- 'width': '11rem',
- },
- {
- 'label': 'Assigned To',
- 'type': 'Text',
- 'key': '_assign',
- 'width': '10rem',
- },
- {
- 'label': 'Last Modified',
- 'type': 'Datetime',
- 'key': 'modified',
- 'width': '8rem',
- },
- ]
- rows = [
- "name",
- "lead_name",
- "organization",
- "status",
- "email",
- "mobile_no",
- "lead_owner",
- "first_name",
- "sla_status",
- "response_by",
- "first_response_time",
- "first_responded_on",
- "modified",
- "_assign",
- "image",
- ]
- return {'columns': columns, 'rows': rows}
-
- @staticmethod
- def default_kanban_settings():
- return {
- "column_field": "status",
- "title_field": "lead_name",
- "kanban_fields": '["organization", "email", "mobile_no", "_assign", "modified"]'
- }
-
-
-@frappe.whitelist()
-def convert_to_deal(lead, doc=None):
- if not (doc and doc.flags.get("ignore_permissions")) and not frappe.has_permission("CRM Lead", "write", lead):
- frappe.throw(_("Not allowed to convert Lead to Deal"), frappe.PermissionError)
-
- lead = frappe.get_cached_doc("CRM Lead", lead)
- if frappe.db.exists("CRM Lead Status", "Qualified"):
- lead.status = "Qualified"
- lead.converted = 1
- if lead.sla and frappe.db.exists("CRM Communication Status", "Replied"):
- lead.communication_status = "Replied"
- lead.save(ignore_permissions=True)
- contact = lead.create_contact(False)
- organization = lead.create_organization()
- deal = lead.create_deal(contact, organization)
- return deal
diff --git a/crm/fcrm/doctype/crm_lead/test_crm_lead.py b/crm/fcrm/doctype/crm_lead/test_crm_lead.py
deleted file mode 100644
index e730792ea..000000000
--- a/crm/fcrm/doctype/crm_lead/test_crm_lead.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-# import frappe
-from frappe.tests import UnitTestCase
-
-
-class TestCRMLead(UnitTestCase):
- pass
diff --git a/crm/fcrm/doctype/crm_lead_source/crm_lead_source.js b/crm/fcrm/doctype/crm_lead_source/crm_lead_source.js
deleted file mode 100644
index 2330b7835..000000000
--- a/crm/fcrm/doctype/crm_lead_source/crm_lead_source.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-// frappe.ui.form.on("CRM Lead Source", {
-// refresh(frm) {
-
-// },
-// });
diff --git a/crm/fcrm/doctype/crm_lead_source/crm_lead_source.json b/crm/fcrm/doctype/crm_lead_source/crm_lead_source.json
deleted file mode 100644
index 23aea6f62..000000000
--- a/crm/fcrm/doctype/crm_lead_source/crm_lead_source.json
+++ /dev/null
@@ -1,68 +0,0 @@
-{
- "actions": [],
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "field:source_name",
- "creation": "2023-07-24 19:47:01.063203",
- "default_view": "List",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
- "source_name",
- "details"
- ],
- "fields": [
- {
- "fieldname": "source_name",
- "fieldtype": "Data",
- "in_list_view": 1,
- "label": "Source Name",
- "reqd": 1,
- "unique": 1
- },
- {
- "fieldname": "details",
- "fieldtype": "Text Editor",
- "label": "Details"
- }
- ],
- "index_web_pages_for_search": 1,
- "links": [],
- "modified": "2024-01-19 21:56:04.702254",
- "modified_by": "Administrator",
- "module": "FCRM",
- "name": "CRM Lead Source",
- "naming_rule": "By fieldname",
- "owner": "Administrator",
- "permissions": [
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales User",
- "share": 1,
- "write": 1
- },
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales Manager",
- "share": 1,
- "write": 1
- }
- ],
- "quick_entry": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "states": []
-}
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_lead_source/crm_lead_source.py b/crm/fcrm/doctype/crm_lead_source/crm_lead_source.py
deleted file mode 100644
index 48487b406..000000000
--- a/crm/fcrm/doctype/crm_lead_source/crm_lead_source.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-# import frappe
-from frappe.model.document import Document
-
-
-class CRMLeadSource(Document):
- pass
diff --git a/crm/fcrm/doctype/crm_lead_source/test_crm_lead_source.py b/crm/fcrm/doctype/crm_lead_source/test_crm_lead_source.py
deleted file mode 100644
index 31d379179..000000000
--- a/crm/fcrm/doctype/crm_lead_source/test_crm_lead_source.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-# import frappe
-from frappe.tests import UnitTestCase
-
-
-class TestCRMLeadSource(UnitTestCase):
- pass
diff --git a/crm/fcrm/doctype/crm_notification/crm_notification.py b/crm/fcrm/doctype/crm_notification/crm_notification.py
deleted file mode 100644
index 69aa127d3..000000000
--- a/crm/fcrm/doctype/crm_notification/crm_notification.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-import frappe
-from frappe import _
-from frappe.model.document import Document
-
-
-class CRMNotification(Document):
- def on_update(self):
- frappe.publish_realtime("crm_notification")
-
-def notify_user(args):
- """
- Notify the assigned user
- """
- args = frappe._dict(args)
- if args.owner == args.assigned_to:
- return
-
- values = frappe._dict(
- doctype="CRM Notification",
- from_user=args.owner,
- to_user=args.assigned_to,
- type=args.notification_type,
- message=args.message,
- notification_text=args.notification_text,
- notification_type_doctype=args.reference_doctype,
- notification_type_doc=args.reference_docname,
- reference_doctype=args.redirect_to_doctype,
- reference_name=args.redirect_to_docname,
- )
-
- if frappe.db.exists("CRM Notification", values):
- return
- frappe.get_doc(values).insert(ignore_permissions=True)
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_organization/crm_organization.js b/crm/fcrm/doctype/crm_organization/crm_organization.js
deleted file mode 100644
index 763952d1f..000000000
--- a/crm/fcrm/doctype/crm_organization/crm_organization.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-// frappe.ui.form.on("CRM Organization", {
-// refresh(frm) {
-
-// },
-// });
diff --git a/crm/fcrm/doctype/crm_organization/crm_organization.json b/crm/fcrm/doctype/crm_organization/crm_organization.json
deleted file mode 100644
index 34252d1c6..000000000
--- a/crm/fcrm/doctype/crm_organization/crm_organization.json
+++ /dev/null
@@ -1,117 +0,0 @@
-{
- "actions": [],
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "field:organization_name",
- "creation": "2023-11-03 16:23:59.341751",
- "doctype": "DocType",
- "engine": "InnoDB",
- "field_order": [
- "organization_name",
- "no_of_employees",
- "currency",
- "annual_revenue",
- "organization_logo",
- "column_break_pnpp",
- "website",
- "territory",
- "industry",
- "address"
- ],
- "fields": [
- {
- "fieldname": "organization_name",
- "fieldtype": "Data",
- "label": "Organization Name",
- "unique": 1
- },
- {
- "fieldname": "website",
- "fieldtype": "Data",
- "label": "Website"
- },
- {
- "fieldname": "organization_logo",
- "fieldtype": "Attach Image",
- "label": "Organization Logo"
- },
- {
- "fieldname": "no_of_employees",
- "fieldtype": "Select",
- "label": "No. of Employees",
- "options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+"
- },
- {
- "fieldname": "column_break_pnpp",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "annual_revenue",
- "fieldtype": "Currency",
- "label": "Annual Revenue",
- "options": "currency"
- },
- {
- "fieldname": "industry",
- "fieldtype": "Link",
- "label": "Industry",
- "options": "CRM Industry"
- },
- {
- "fieldname": "territory",
- "fieldtype": "Link",
- "label": "Territory",
- "options": "CRM Territory"
- },
- {
- "fieldname": "currency",
- "fieldtype": "Link",
- "label": "Currency",
- "options": "Currency"
- },
- {
- "fieldname": "address",
- "fieldtype": "Link",
- "label": "Address",
- "options": "Address"
- }
- ],
- "image_field": "organization_logo",
- "index_web_pages_for_search": 1,
- "links": [],
- "modified": "2024-09-17 18:37:10.341062",
- "modified_by": "Administrator",
- "module": "FCRM",
- "name": "CRM Organization",
- "naming_rule": "By fieldname",
- "owner": "Administrator",
- "permissions": [
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales User",
- "share": 1,
- "write": 1
- },
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales Manager",
- "share": 1,
- "write": 1
- }
- ],
- "sort_field": "modified",
- "sort_order": "DESC",
- "states": []
-}
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_organization/crm_organization.py b/crm/fcrm/doctype/crm_organization/crm_organization.py
deleted file mode 100644
index 471d0f512..000000000
--- a/crm/fcrm/doctype/crm_organization/crm_organization.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-import frappe
-from frappe.model.document import Document
-
-
-class CRMOrganization(Document):
- @staticmethod
- def default_list_data():
- columns = [
- {
- 'label': 'Organization',
- 'type': 'Data',
- 'key': 'organization_name',
- 'width': '16rem',
- },
- {
- 'label': 'Website',
- 'type': 'Data',
- 'key': 'website',
- 'width': '14rem',
- },
- {
- 'label': 'Industry',
- 'type': 'Link',
- 'key': 'industry',
- 'options': 'CRM Industry',
- 'width': '14rem',
- },
- {
- 'label': 'Annual Revenue',
- 'type': 'Currency',
- 'key': 'annual_revenue',
- 'width': '14rem',
- },
- {
- 'label': 'Last Modified',
- 'type': 'Datetime',
- 'key': 'modified',
- 'width': '8rem',
- },
- ]
- rows = [
- "name",
- "organization_name",
- "organization_logo",
- "website",
- "industry",
- "currency",
- "annual_revenue",
- "modified",
- ]
- return {'columns': columns, 'rows': rows}
diff --git a/crm/fcrm/doctype/crm_organization/test_crm_organization.py b/crm/fcrm/doctype/crm_organization/test_crm_organization.py
deleted file mode 100644
index e80667548..000000000
--- a/crm/fcrm/doctype/crm_organization/test_crm_organization.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-# import frappe
-from frappe.tests import UnitTestCase
-
-
-class TestCRMOrganization(UnitTestCase):
- pass
diff --git a/crm/fcrm/doctype/crm_service_day/crm_service_day.json b/crm/fcrm/doctype/crm_service_day/crm_service_day.json
deleted file mode 100644
index ddca98836..000000000
--- a/crm/fcrm/doctype/crm_service_day/crm_service_day.json
+++ /dev/null
@@ -1,59 +0,0 @@
-{
- "actions": [],
- "allow_rename": 1,
- "creation": "2023-12-04 16:07:20.400084",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
- "workday",
- "section_break_uegc",
- "start_time",
- "column_break_maie",
- "end_time"
- ],
- "fields": [
- {
- "fieldname": "workday",
- "fieldtype": "Select",
- "in_list_view": 1,
- "label": "Workday",
- "options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday",
- "reqd": 1
- },
- {
- "fieldname": "section_break_uegc",
- "fieldtype": "Section Break"
- },
- {
- "fieldname": "start_time",
- "fieldtype": "Time",
- "in_list_view": 1,
- "label": "Start Time",
- "reqd": 1
- },
- {
- "fieldname": "column_break_maie",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "end_time",
- "fieldtype": "Time",
- "in_list_view": 1,
- "label": "End Time",
- "reqd": 1
- }
- ],
- "index_web_pages_for_search": 1,
- "istable": 1,
- "links": [],
- "modified": "2023-12-04 16:09:22.928308",
- "modified_by": "Administrator",
- "module": "FCRM",
- "name": "CRM Service Day",
- "owner": "Administrator",
- "permissions": [],
- "sort_field": "modified",
- "sort_order": "DESC",
- "states": []
-}
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_service_day/crm_service_day.py b/crm/fcrm/doctype/crm_service_day/crm_service_day.py
deleted file mode 100644
index 724a720db..000000000
--- a/crm/fcrm/doctype/crm_service_day/crm_service_day.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-# import frappe
-from frappe.model.document import Document
-
-
-class CRMServiceDay(Document):
- pass
diff --git a/crm/fcrm/doctype/crm_service_level_agreement/crm_service_level_agreement.js b/crm/fcrm/doctype/crm_service_level_agreement/crm_service_level_agreement.js
deleted file mode 100644
index 4c08f7bf3..000000000
--- a/crm/fcrm/doctype/crm_service_level_agreement/crm_service_level_agreement.js
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on("CRM Service Level Agreement", {
- validate(frm) {
- let default_priority_count = 0;
- frm.doc.priorities.forEach(function (row) {
- if (row.default_priority) {
- default_priority_count++;
- }
- });
- if (default_priority_count > 1) {
- frappe.throw(
- __("There can only be one default priority in Priorities table")
- );
- }
- },
-});
diff --git a/crm/fcrm/doctype/crm_service_level_agreement/crm_service_level_agreement.py b/crm/fcrm/doctype/crm_service_level_agreement/crm_service_level_agreement.py
deleted file mode 100644
index d8f3f6698..000000000
--- a/crm/fcrm/doctype/crm_service_level_agreement/crm_service_level_agreement.py
+++ /dev/null
@@ -1,225 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-import frappe
-from frappe import _
-from datetime import timedelta
-from frappe.model.document import Document
-from frappe.utils import (
- add_to_date,
- get_datetime,
- get_weekdays,
- getdate,
- now_datetime,
- time_diff_in_seconds,
-)
-from crm.fcrm.doctype.crm_service_level_agreement.utils import get_context
-
-
-class CRMServiceLevelAgreement(Document):
- def validate(self):
- self.validate_default()
- self.validate_condition()
-
- def validate_default(self):
- if self.default:
- other_slas = frappe.get_all(
- "CRM Service Level Agreement",
- filters={"apply_on": self.apply_on, "default": True},
- fields=["name"],
- )
- if other_slas:
- frappe.throw(
- _(
- "Default Service Level Agreement already exists for {0}"
- ).format(self.apply_on)
- )
-
- def validate_condition(self):
- if not self.condition:
- return
- try:
- temp_doc = frappe.new_doc(self.apply_on)
- frappe.safe_eval(self.condition, None, get_context(temp_doc))
- except Exception as e:
- frappe.throw(
- _("The Condition '{0}' is invalid: {1}").format(self.condition, str(e))
- )
-
- def apply(self, doc: Document):
- self.handle_creation(doc)
- self.handle_communication_status(doc)
- self.handle_targets(doc)
- self.handle_sla_status(doc)
-
- def handle_creation(self, doc: Document):
- doc.sla_creation = doc.sla_creation or now_datetime()
-
- def handle_communication_status(self, doc: Document):
- if doc.is_new() or not doc.has_value_changed("communication_status"):
- return
- self.set_first_responded_on(doc)
- self.set_first_response_time(doc)
-
- def set_first_responded_on(self, doc: Document):
- if doc.communication_status != self.get_default_priority():
- doc.first_responded_on = (
- doc.first_responded_on or now_datetime()
- )
-
- def set_first_response_time(self, doc: Document):
- start_at = doc.sla_creation
- end_at = doc.first_responded_on
- if not start_at or not end_at:
- return
- doc.first_response_time = self.calc_elapsed_time(start_at, end_at)
-
- def handle_targets(self, doc: Document):
- self.set_response_by(doc)
-
- def set_response_by(self, doc: Document):
- start_time = doc.sla_creation
- communication_status = doc.communication_status
-
- priorities = self.get_priorities()
- priority = priorities.get(communication_status)
- if not priority or doc.response_by:
- return
-
- first_response_time = priority.get("first_response_time", 0)
- end_time = self.calc_time(start_time, first_response_time)
- if end_time:
- doc.response_by = end_time
-
- def handle_sla_status(self, doc: Document):
- is_failed = self.is_first_response_failed(doc)
- options = {
- "Fulfilled": True,
- "First Response Due": not doc.first_responded_on,
- "Failed": is_failed,
- }
- for status in options:
- if options[status]:
- doc.sla_status = status
-
- def is_first_response_failed(self, doc: Document):
- if not doc.first_responded_on:
- return get_datetime(doc.response_by) < now_datetime()
- return get_datetime(doc.response_by) < get_datetime(doc.first_responded_on)
-
- def calc_time(
- self,
- start_at: str,
- duration_seconds: int,
- ):
- res = get_datetime(start_at)
- time_needed = duration_seconds
- holidays = self.get_holidays()
- weekdays = get_weekdays()
- workdays = self.get_workdays()
- while time_needed:
- today = res
- today_day = getdate(today)
- today_weekday = weekdays[today.weekday()]
- is_workday = today_weekday in workdays
- is_holiday = today_day in holidays
- if is_holiday or not is_workday:
- res = add_to_date(res, days=1, as_datetime=True)
- continue
- today_workday = workdays[today_weekday]
- now_in_seconds = time_diff_in_seconds(today, today_day)
- start_time = max(today_workday.start_time.total_seconds(), now_in_seconds)
- till_start_time = max(start_time - now_in_seconds, 0)
- end_time = max(today_workday.end_time.total_seconds(), now_in_seconds)
- time_left = max(end_time - start_time, 0)
- if not time_left:
- res = getdate(add_to_date(res, days=1, as_datetime=True))
- continue
- time_taken = min(time_needed, time_left)
- time_needed -= time_taken
- time_required = till_start_time + time_taken
- res = add_to_date(res, seconds=time_required, as_datetime=True)
- return res
-
- def calc_elapsed_time(self, start_time, end_time) -> float:
- """
- Get took from start to end, excluding non-working hours
-
- :param start_at: Date at which calculation starts
- :param end_at: Date at which calculation ends
- :return: Number of seconds
- """
- start_time = get_datetime(start_time)
- end_time = get_datetime(end_time)
- holiday_list = []
- working_day_list = self.get_working_days()
- working_hours = self.get_working_hours()
-
- total_seconds = 0
- current_time = start_time
-
- while current_time < end_time:
- in_holiday_list = current_time.date() in holiday_list
- not_in_working_day_list = get_weekdays()[current_time.weekday()] not in working_day_list
- if in_holiday_list or not_in_working_day_list or not self.is_working_time(current_time, working_hours):
- current_time += timedelta(seconds=1)
- continue
- total_seconds += 1
- current_time += timedelta(seconds=1)
-
- return total_seconds
-
- def get_priorities(self):
- """
- Return priorities related info as a dict. With `priority` as key
- """
- res = {}
- for row in self.priorities:
- res[row.priority] = row
- return res
-
- def get_default_priority(self):
- """
- Return default priority
- """
- for row in self.priorities:
- if row.default_priority:
- return row.priority
-
- return self.priorities[0].priority
-
- def get_workdays(self) -> dict[str, dict]:
- """
- Return workdays related info as a dict. With `workday` as key
- """
- res = {}
- for row in self.working_hours:
- res[row.workday] = row
- return res
-
- def get_working_days(self) -> dict[str, dict]:
- workdays = []
- for row in self.working_hours:
- workdays.append(row.workday)
- return workdays
-
- def get_working_hours(self) -> dict[str, dict]:
- res = {}
- for row in self.working_hours:
- res[row.workday] = (row.start_time, row.end_time)
- return res
-
- def is_working_time(self, date_time, working_hours):
- day_of_week = get_weekdays()[date_time.weekday()]
- start_time, end_time = working_hours.get(day_of_week, (0, 0))
- date_time = timedelta(hours=date_time.hour, minutes=date_time.minute, seconds=date_time.second)
- return start_time <= date_time < end_time
-
- def get_holidays(self):
- res = []
- if not self.holiday_list:
- return res
- holiday_list = frappe.get_doc("CRM Holiday List", self.holiday_list)
- for row in holiday_list.holidays:
- res.append(row.date)
- return res
diff --git a/crm/fcrm/doctype/crm_service_level_agreement/utils.py b/crm/fcrm/doctype/crm_service_level_agreement/utils.py
deleted file mode 100644
index 809dcafe6..000000000
--- a/crm/fcrm/doctype/crm_service_level_agreement/utils.py
+++ /dev/null
@@ -1,61 +0,0 @@
-import frappe
-from frappe.model.document import Document
-from frappe.query_builder import JoinType
-from frappe.utils.safe_exec import get_safe_globals
-from frappe.utils import now_datetime
-from pypika import Criterion
-
-def get_sla(doc: Document) -> Document:
- """
- Get Service Level Agreement for `doc`
-
- :param doc: Lead/Deal to use
- :return: Applicable SLA
- """
- SLA = frappe.qb.DocType("CRM Service Level Agreement")
- Priority = frappe.qb.DocType("CRM Service Level Priority")
- now = now_datetime()
- priority = doc.communication_status
- q = (
- frappe.qb.from_(SLA)
- .select(SLA.name, SLA.condition)
- .where(SLA.apply_on == doc.doctype)
- .where(SLA.enabled == True)
- .where(Criterion.any([SLA.start_date.isnull(), SLA.start_date <= now]))
- .where(Criterion.any([SLA.end_date.isnull(), SLA.end_date >= now]))
- )
- if priority:
- q = (
- q.join(Priority, JoinType.inner)
- .on(Priority.parent == SLA.name)
- .where(Priority.priority == priority)
- )
- sla_list = q.run(as_dict=True)
- res = None
-
- # move default sla to the end of the list
- for sla in sla_list:
- if sla.get("default") == True:
- sla_list.remove(sla)
- sla_list.append(sla)
- break
-
- for sla in sla_list:
- cond = sla.get("condition")
- if not cond or frappe.safe_eval(cond, None, get_context(doc)):
- res = sla
- break
- return res
-
-def get_context(d: Document) -> dict:
- """
- Get safe context for `safe_eval`
-
- :param doc: `Document` to add in context
- :return: Context with `doc` and safe variables
- """
- utils = get_safe_globals().get("frappe").get("utils")
- return {
- "doc": d.as_dict(),
- "frappe": frappe._dict(utils=utils),
- }
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_service_level_priority/crm_service_level_priority.js b/crm/fcrm/doctype/crm_service_level_priority/crm_service_level_priority.js
deleted file mode 100644
index ef808235f..000000000
--- a/crm/fcrm/doctype/crm_service_level_priority/crm_service_level_priority.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-// frappe.ui.form.on("CRM Service Level Priority", {
-// refresh(frm) {
-
-// },
-// });
diff --git a/crm/fcrm/doctype/crm_service_level_priority/crm_service_level_priority.json b/crm/fcrm/doctype/crm_service_level_priority/crm_service_level_priority.json
deleted file mode 100644
index ede5c8c0e..000000000
--- a/crm/fcrm/doctype/crm_service_level_priority/crm_service_level_priority.json
+++ /dev/null
@@ -1,64 +0,0 @@
-{
- "actions": [],
- "allow_rename": 1,
- "creation": "2023-12-04 13:18:58.028384",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
- "default_priority",
- "column_break_grod",
- "priority",
- "section_break_anyl",
- "first_response_time",
- "column_break_bwgs"
- ],
- "fields": [
- {
- "default": "0",
- "fieldname": "default_priority",
- "fieldtype": "Check",
- "in_list_view": 1,
- "label": "Default Priority"
- },
- {
- "fieldname": "priority",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Priority",
- "options": "CRM Communication Status",
- "reqd": 1
- },
- {
- "fieldname": "first_response_time",
- "fieldtype": "Duration",
- "in_list_view": 1,
- "label": "First Response TIme",
- "reqd": 1
- },
- {
- "fieldname": "column_break_grod",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "section_break_anyl",
- "fieldtype": "Section Break"
- },
- {
- "fieldname": "column_break_bwgs",
- "fieldtype": "Column Break"
- }
- ],
- "index_web_pages_for_search": 1,
- "istable": 1,
- "links": [],
- "modified": "2023-12-15 11:49:54.424029",
- "modified_by": "Administrator",
- "module": "FCRM",
- "name": "CRM Service Level Priority",
- "owner": "Administrator",
- "permissions": [],
- "sort_field": "modified",
- "sort_order": "DESC",
- "states": []
-}
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_service_level_priority/crm_service_level_priority.py b/crm/fcrm/doctype/crm_service_level_priority/crm_service_level_priority.py
deleted file mode 100644
index a7210a827..000000000
--- a/crm/fcrm/doctype/crm_service_level_priority/crm_service_level_priority.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-# import frappe
-from frappe.model.document import Document
-
-
-class CRMServiceLevelPriority(Document):
- pass
diff --git a/crm/fcrm/doctype/crm_service_level_priority/test_crm_service_level_priority.py b/crm/fcrm/doctype/crm_service_level_priority/test_crm_service_level_priority.py
deleted file mode 100644
index 89f9c4e39..000000000
--- a/crm/fcrm/doctype/crm_service_level_priority/test_crm_service_level_priority.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-# import frappe
-from frappe.tests import UnitTestCase
-
-
-class TestCRMServiceLevelPriority(UnitTestCase):
- pass
diff --git a/crm/fcrm/doctype/crm_status_change_log/crm_status_change_log.py b/crm/fcrm/doctype/crm_status_change_log/crm_status_change_log.py
deleted file mode 100644
index 2a3691e48..000000000
--- a/crm/fcrm/doctype/crm_status_change_log/crm_status_change_log.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-import frappe
-from datetime import datetime
-from frappe.utils import add_to_date, get_datetime
-from frappe.model.document import Document
-
-
-class CRMStatusChangeLog(Document):
- pass
-
-def get_duration(from_date, to_date):
- if not isinstance(from_date, datetime):
- from_date = get_datetime(from_date)
- if not isinstance(to_date, datetime):
- to_date = get_datetime(to_date)
- duration = to_date - from_date
- return duration.total_seconds()
-
-def add_status_change_log(doc):
- if not doc.is_new():
- previous_status = doc.get_doc_before_save().status if doc.get_doc_before_save() else None
- if not doc.status_change_log and previous_status:
- now_minus_one_minute = add_to_date(datetime.now(), minutes=-1)
- doc.append("status_change_log", {
- "from": previous_status,
- "to": "",
- "from_date": now_minus_one_minute,
- "to_date": "",
- "log_owner": frappe.session.user,
- })
- last_status_change = doc.status_change_log[-1]
- last_status_change.to = doc.status
- last_status_change.to_date = datetime.now()
- last_status_change.log_owner = frappe.session.user
- last_status_change.duration = get_duration(last_status_change.from_date, last_status_change.to_date)
-
- doc.append("status_change_log", {
- "from": doc.status,
- "to": "",
- "from_date": datetime.now(),
- "to_date": "",
- "log_owner": frappe.session.user,
- })
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_task/crm_task.json b/crm/fcrm/doctype/crm_task/crm_task.json
deleted file mode 100644
index cdb95c616..000000000
--- a/crm/fcrm/doctype/crm_task/crm_task.json
+++ /dev/null
@@ -1,125 +0,0 @@
-{
- "actions": [],
- "allow_import": 1,
- "autoname": "autoincrement",
- "creation": "2023-09-28 15:04:28.084159",
- "default_view": "List",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
- "title",
- "priority",
- "start_date",
- "reference_doctype",
- "reference_docname",
- "column_break_cqua",
- "assigned_to",
- "status",
- "due_date",
- "section_break_bzhd",
- "description"
- ],
- "fields": [
- {
- "fieldname": "title",
- "fieldtype": "Data",
- "in_list_view": 1,
- "label": "Title",
- "reqd": 1
- },
- {
- "fieldname": "priority",
- "fieldtype": "Select",
- "label": "Priority",
- "options": "Low\nMedium\nHigh"
- },
- {
- "fieldname": "start_date",
- "fieldtype": "Date",
- "label": "Start Date"
- },
- {
- "fieldname": "column_break_cqua",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "assigned_to",
- "fieldtype": "Link",
- "label": "Assigned To",
- "options": "User"
- },
- {
- "fieldname": "status",
- "fieldtype": "Select",
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Status",
- "options": "Backlog\nTodo\nIn Progress\nDone\nCanceled"
- },
- {
- "fieldname": "due_date",
- "fieldtype": "Datetime",
- "label": "Due Date"
- },
- {
- "fieldname": "section_break_bzhd",
- "fieldtype": "Section Break"
- },
- {
- "fieldname": "description",
- "fieldtype": "Text Editor",
- "label": "Description"
- },
- {
- "default": "CRM Lead",
- "fieldname": "reference_doctype",
- "fieldtype": "Link",
- "label": "Reference Document Type",
- "options": "DocType"
- },
- {
- "fieldname": "reference_docname",
- "fieldtype": "Dynamic Link",
- "label": "Reference Doc",
- "options": "reference_doctype"
- }
- ],
- "index_web_pages_for_search": 1,
- "links": [],
- "modified": "2024-02-08 12:04:00.955984",
- "modified_by": "Administrator",
- "module": "FCRM",
- "name": "CRM Task",
- "naming_rule": "Autoincrement",
- "owner": "Administrator",
- "permissions": [
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales User",
- "share": 1,
- "write": 1
- },
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales Manager",
- "share": 1,
- "write": 1
- }
- ],
- "sort_field": "modified",
- "sort_order": "DESC",
- "states": []
-}
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_task/crm_task.py b/crm/fcrm/doctype/crm_task/crm_task.py
deleted file mode 100644
index 06b50c4de..000000000
--- a/crm/fcrm/doctype/crm_task/crm_task.py
+++ /dev/null
@@ -1,97 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-import frappe
-from frappe import _
-from frappe.model.document import Document
-from frappe.desk.form.assign_to import add as assign, remove as unassign
-from crm.fcrm.doctype.crm_notification.crm_notification import notify_user
-
-
-class CRMTask(Document):
- def after_insert(self):
- self.assign_to()
-
- def validate(self):
- if self.is_new() or not self.assigned_to:
- return
-
- if self.get_doc_before_save().assigned_to != self.assigned_to:
- self.unassign_from_previous_user(self.get_doc_before_save().assigned_to)
- self.assign_to()
-
- def unassign_from_previous_user(self, user):
- unassign(self.doctype, self.name, user)
-
- def assign_to(self):
- if self.assigned_to:
- assign({
- "assign_to": [self.assigned_to],
- "doctype": self.doctype,
- "name": self.name,
- "description": self.title or self.description,
- })
-
-
- @staticmethod
- def default_list_data():
- columns = [
- {
- 'label': 'Title',
- 'type': 'Data',
- 'key': 'title',
- 'width': '16rem',
- },
- {
- 'label': 'Status',
- 'type': 'Select',
- 'key': 'status',
- 'width': '8rem',
- },
- {
- 'label': 'Priority',
- 'type': 'Select',
- 'key': 'priority',
- 'width': '8rem',
- },
- {
- 'label': 'Due Date',
- 'type': 'Date',
- 'key': 'due_date',
- 'width': '8rem',
- },
- {
- 'label': 'Assigned To',
- 'type': 'Link',
- 'key': 'assigned_to',
- 'width': '10rem',
- },
- {
- 'label': 'Last Modified',
- 'type': 'Datetime',
- 'key': 'modified',
- 'width': '8rem',
- },
- ]
-
- rows = [
- "name",
- "title",
- "description",
- "assigned_to",
- "due_date",
- "status",
- "priority",
- "reference_doctype",
- "reference_docname",
- "modified",
- ]
- return {'columns': columns, 'rows': rows}
-
- @staticmethod
- def default_kanban_settings():
- return {
- "column_field": "status",
- "title_field": "title",
- "kanban_fields": '["description", "priority", "creation"]'
- }
diff --git a/crm/fcrm/doctype/crm_task/test_crm_task.py b/crm/fcrm/doctype/crm_task/test_crm_task.py
deleted file mode 100644
index d632cebe2..000000000
--- a/crm/fcrm/doctype/crm_task/test_crm_task.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-# import frappe
-from frappe.tests import UnitTestCase
-
-
-class TestCRMTask(UnitTestCase):
- pass
diff --git a/crm/fcrm/doctype/crm_territory/__init__.py b/crm/fcrm/doctype/crm_territory/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/crm/fcrm/doctype/crm_territory/crm_territory.js b/crm/fcrm/doctype/crm_territory/crm_territory.js
deleted file mode 100644
index 568e9c4c7..000000000
--- a/crm/fcrm/doctype/crm_territory/crm_territory.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-// frappe.ui.form.on("CRM Territory", {
-// refresh(frm) {
-
-// },
-// });
diff --git a/crm/fcrm/doctype/crm_territory/crm_territory.json b/crm/fcrm/doctype/crm_territory/crm_territory.json
deleted file mode 100644
index 91e3b09cb..000000000
--- a/crm/fcrm/doctype/crm_territory/crm_territory.json
+++ /dev/null
@@ -1,129 +0,0 @@
-{
- "actions": [],
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "field:territory_name",
- "creation": "2024-01-04 18:52:58.872535",
- "doctype": "DocType",
- "engine": "InnoDB",
- "field_order": [
- "territory_name",
- "column_break_mckp",
- "territory_manager",
- "section_break_qhaf",
- "old_parent",
- "parent_crm_territory",
- "column_break_pypy",
- "lft",
- "rgt",
- "is_group"
- ],
- "fields": [
- {
- "fieldname": "territory_name",
- "fieldtype": "Data",
- "in_list_view": 1,
- "label": "Territory Name",
- "reqd": 1,
- "unique": 1
- },
- {
- "default": "0",
- "fieldname": "is_group",
- "fieldtype": "Check",
- "label": "Is Group"
- },
- {
- "fieldname": "column_break_pypy",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "territory_manager",
- "fieldtype": "Link",
- "label": "Territory Manager",
- "options": "User"
- },
- {
- "fieldname": "lft",
- "fieldtype": "Int",
- "hidden": 1,
- "label": "Left",
- "no_copy": 1,
- "read_only": 1
- },
- {
- "fieldname": "rgt",
- "fieldtype": "Int",
- "hidden": 1,
- "label": "Right",
- "no_copy": 1,
- "read_only": 1
- },
- {
- "default": "0",
- "fieldname": "is_group",
- "fieldtype": "Check",
- "label": "Is Group"
- },
- {
- "fieldname": "old_parent",
- "fieldtype": "Link",
- "label": "Old Parent",
- "options": "CRM Territory"
- },
- {
- "fieldname": "parent_crm_territory",
- "fieldtype": "Link",
- "ignore_user_permissions": 1,
- "label": "Parent CRM Territory",
- "options": "CRM Territory"
- },
- {
- "fieldname": "column_break_mckp",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "section_break_qhaf",
- "fieldtype": "Section Break"
- }
- ],
- "index_web_pages_for_search": 1,
- "is_tree": 1,
- "links": [],
- "modified": "2024-01-19 21:53:53.451891",
- "modified_by": "Administrator",
- "module": "FCRM",
- "name": "CRM Territory",
- "naming_rule": "By fieldname",
- "nsm_parent_field": "parent_crm_territory",
- "owner": "Administrator",
- "permissions": [
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales User",
- "share": 1,
- "write": 1
- },
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales Manager",
- "share": 1,
- "write": 1
- }
- ],
- "sort_field": "modified",
- "sort_order": "DESC",
- "states": []
-}
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_territory/crm_territory.py b/crm/fcrm/doctype/crm_territory/crm_territory.py
deleted file mode 100644
index d08f3bee4..000000000
--- a/crm/fcrm/doctype/crm_territory/crm_territory.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-# import frappe
-from frappe.model.document import Document
-
-
-class CRMTerritory(Document):
- pass
diff --git a/crm/fcrm/doctype/crm_view_settings/__init__.py b/crm/fcrm/doctype/crm_view_settings/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py
deleted file mode 100644
index e0cf53d5e..000000000
--- a/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py
+++ /dev/null
@@ -1,203 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-import json
-import frappe
-from frappe.model.document import Document, get_controller
-from frappe.utils import parse_json
-
-
-class CRMViewSettings(Document):
- pass
-
-@frappe.whitelist()
-def create(view):
- view = frappe._dict(view)
-
- view.filters = parse_json(view.filters) or {}
- view.columns = parse_json(view.columns or '[]')
- view.rows = parse_json(view.rows or '[]')
- view.kanban_columns = parse_json(view.kanban_columns or '[]')
- view.kanban_fields = parse_json(view.kanban_fields or '[]')
-
- default_rows = sync_default_rows(view.doctype)
- view.rows = view.rows + default_rows if default_rows else view.rows
- view.rows = remove_duplicates(view.rows)
-
- if not view.kanban_columns and view.type == "kanban":
- view.kanban_columns = sync_default_columns(view)
- elif not view.columns:
- view.columns = sync_default_columns(view)
-
- doc = frappe.new_doc("CRM View Settings")
- doc.name = view.label
- doc.label = view.label
- doc.type = view.type or 'list'
- doc.icon = view.icon
- doc.dt = view.doctype
- doc.user = frappe.session.user
- doc.route_name = view.route_name or ""
- doc.load_default_columns = view.load_default_columns or False
- doc.filters = json.dumps(view.filters)
- doc.order_by = view.order_by
- doc.group_by_field = view.group_by_field
- doc.column_field = view.column_field
- doc.title_field = view.title_field
- doc.kanban_columns = json.dumps(view.kanban_columns)
- doc.kanban_fields = json.dumps(view.kanban_fields)
- doc.columns = json.dumps(view.columns)
- doc.rows = json.dumps(view.rows)
- doc.insert()
- return doc
-
-@frappe.whitelist()
-def update(view):
- view = frappe._dict(view)
-
- filters = parse_json(view.filters or {})
- columns = parse_json(view.columns or [])
- rows = parse_json(view.rows or [])
- kanban_columns = parse_json(view.kanban_columns or [])
- kanban_fields = parse_json(view.kanban_fields or [])
-
- default_rows = sync_default_rows(view.doctype)
- rows = rows + default_rows if default_rows else rows
- rows = remove_duplicates(rows)
-
- doc = frappe.get_doc("CRM View Settings", view.name)
- doc.label = view.label
- doc.type = view.type or 'list'
- doc.icon = view.icon
- doc.route_name = view.route_name or ""
- doc.load_default_columns = view.load_default_columns or False
- doc.filters = json.dumps(filters)
- doc.order_by = view.order_by
- doc.group_by_field = view.group_by_field
- doc.column_field = view.column_field
- doc.title_field = view.title_field
- doc.kanban_columns = json.dumps(kanban_columns)
- doc.kanban_fields = json.dumps(kanban_fields)
- doc.columns = json.dumps(columns)
- doc.rows = json.dumps(rows)
- doc.save()
- return doc
-
-@frappe.whitelist()
-def delete(name):
- if frappe.db.exists("CRM View Settings", name):
- frappe.delete_doc("CRM View Settings", name)
-
-@frappe.whitelist()
-def public(name, value):
- if frappe.session.user != "Administrator" and "Sales Manager" not in frappe.get_roles():
- frappe.throw("Not permitted", frappe.PermissionError)
-
- doc = frappe.get_doc("CRM View Settings", name)
- if doc.pinned:
- doc.pinned = False
- doc.public = value
- doc.user = "" if value else frappe.session.user
- doc.save()
-
-@frappe.whitelist()
-def pin(name, value):
- doc = frappe.get_doc("CRM View Settings", name)
- doc.pinned = value
- doc.save()
-
-def remove_duplicates(l):
- return list(dict.fromkeys(l))
-
-def sync_default_rows(doctype, type="list"):
- list = get_controller(doctype)
- rows = []
-
- if hasattr(list, "default_list_data"):
- rows = list.default_list_data().get("rows")
-
- return rows
-
-def sync_default_columns(view):
- list = get_controller(view.doctype)
- columns = []
-
- if view.type == "kanban" and view.column_field:
- field_meta = frappe.get_meta(view.doctype).get_field(view.column_field)
- if field_meta.fieldtype == "Link":
- columns = frappe.get_all(
- field_meta.options,
- fields=["name"],
- order_by="modified asc",
- )
- elif field_meta.fieldtype == "Select":
- columns = [{"name": option} for option in field_meta.options.split("\n")]
- elif hasattr(list, "default_list_data"):
- columns = list.default_list_data().get("columns")
-
- return columns
-
-
-@frappe.whitelist()
-def create_or_update_default_view(view):
- view = frappe._dict(view)
-
- filters = parse_json(view.filters) or {}
- columns = parse_json(view.columns or '[]')
- rows = parse_json(view.rows or '[]')
- kanban_columns = parse_json(view.kanban_columns or '[]')
- kanban_fields = parse_json(view.kanban_fields or '[]')
-
- default_rows = sync_default_rows(view.doctype, view.type)
- rows = rows + default_rows if default_rows else rows
- rows = remove_duplicates(rows)
-
- if not kanban_columns and view.type == "kanban":
- kanban_columns = sync_default_columns(view)
- elif not columns:
- columns = sync_default_columns(view)
-
- doc = frappe.db.exists(
- "CRM View Settings",
- {
- "dt": view.doctype,
- "type": view.type or 'list',
- "is_default": True,
- "user": frappe.session.user
- },
- )
- if doc:
- doc = frappe.get_doc("CRM View Settings", doc)
- doc.label = view.label
- doc.type = view.type or 'list'
- doc.route_name = view.route_name or ""
- doc.load_default_columns = view.load_default_columns or False
- doc.filters = json.dumps(filters)
- doc.order_by = view.order_by
- doc.group_by_field = view.group_by_field
- doc.column_field = view.column_field
- doc.title_field = view.title_field
- doc.kanban_columns = json.dumps(kanban_columns)
- doc.kanban_fields = json.dumps(kanban_fields)
- doc.columns = json.dumps(columns)
- doc.rows = json.dumps(rows)
- doc.save()
- else:
- doc = frappe.new_doc("CRM View Settings")
- label = 'Group By View' if view.type == 'group_by' else 'List View'
- doc.name = view.label or label
- doc.label = view.label or label
- doc.type = view.type or 'list'
- doc.dt = view.doctype
- doc.user = frappe.session.user
- doc.route_name = view.route_name or ""
- doc.load_default_columns = view.load_default_columns or False
- doc.filters = json.dumps(filters)
- doc.order_by = view.order_by
- doc.group_by_field = view.group_by_field
- doc.column_field = view.column_field
- doc.title_field = view.title_field
- doc.kanban_columns = json.dumps(kanban_columns)
- doc.kanban_fields = json.dumps(kanban_fields)
- doc.columns = json.dumps(columns)
- doc.rows = json.dumps(rows)
- doc.is_default = True
- doc.insert()
\ No newline at end of file
diff --git a/crm/fcrm/doctype/erpnext_crm_settings/__init__.py b/crm/fcrm/doctype/erpnext_crm_settings/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.js b/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.js
deleted file mode 100644
index 535e83fbd..000000000
--- a/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.js
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on("ERPNext CRM Settings", {
- refresh(frm) {
- if (!frm.doc.enabled) return;
- frm.add_custom_button(__("Reset ERPNext Form Script"), () => {
- frappe.confirm(
- __(
- "Are you sure you want to reset 'Create Quotation from CRM Deal' Form Script?"
- ),
- () => frm.trigger("reset_erpnext_form_script")
- );
- });
- },
- async reset_erpnext_form_script(frm) {
- let script = await frm.call("reset_erpnext_form_script");
- script.message &&
- frappe.msgprint(__("Form Script updated successfully"));
- },
-});
diff --git a/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.json b/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.json
deleted file mode 100644
index 9a6f0f75e..000000000
--- a/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.json
+++ /dev/null
@@ -1,124 +0,0 @@
-{
- "actions": [],
- "allow_rename": 1,
- "creation": "2024-07-02 15:23:17.022214",
- "doctype": "DocType",
- "engine": "InnoDB",
- "field_order": [
- "enabled",
- "is_erpnext_in_different_site",
- "column_break_vfru",
- "erpnext_company",
- "section_break_oubd",
- "erpnext_site_url",
- "column_break_fllx",
- "api_key",
- "api_secret",
- "section_break_jnbn",
- "create_customer_on_status_change",
- "column_break_kbhw",
- "deal_status"
- ],
- "fields": [
- {
- "depends_on": "eval:doc.enabled && doc.is_erpnext_in_different_site",
- "fieldname": "api_key",
- "fieldtype": "Data",
- "label": "API Key",
- "mandatory_depends_on": "is_erpnext_in_different_site"
- },
- {
- "depends_on": "eval:doc.enabled && doc.is_erpnext_in_different_site",
- "fieldname": "api_secret",
- "fieldtype": "Password",
- "label": "API Secret",
- "mandatory_depends_on": "is_erpnext_in_different_site"
- },
- {
- "depends_on": "enabled",
- "fieldname": "section_break_oubd",
- "fieldtype": "Section Break"
- },
- {
- "fieldname": "column_break_fllx",
- "fieldtype": "Column Break"
- },
- {
- "depends_on": "eval:doc.enabled && doc.is_erpnext_in_different_site",
- "fieldname": "erpnext_site_url",
- "fieldtype": "Data",
- "label": "ERPNext Site URL",
- "mandatory_depends_on": "is_erpnext_in_different_site"
- },
- {
- "depends_on": "enabled",
- "fieldname": "erpnext_company",
- "fieldtype": "Data",
- "label": "Company in ERPNext Site",
- "mandatory_depends_on": "enabled"
- },
- {
- "fieldname": "column_break_vfru",
- "fieldtype": "Column Break"
- },
- {
- "default": "0",
- "fieldname": "enabled",
- "fieldtype": "Check",
- "label": "Enabled"
- },
- {
- "default": "0",
- "depends_on": "enabled",
- "fieldname": "is_erpnext_in_different_site",
- "fieldtype": "Check",
- "label": "Is ERPNext installed on a different site?"
- },
- {
- "fieldname": "section_break_jnbn",
- "fieldtype": "Section Break"
- },
- {
- "default": "0",
- "depends_on": "enabled",
- "fieldname": "create_customer_on_status_change",
- "fieldtype": "Check",
- "label": "Create customer on status change"
- },
- {
- "fieldname": "column_break_kbhw",
- "fieldtype": "Column Break"
- },
- {
- "depends_on": "eval:doc.enabled && doc.create_customer_on_status_change",
- "fieldname": "deal_status",
- "fieldtype": "Link",
- "label": "Deal Status",
- "mandatory_depends_on": "create_customer_on_status_change",
- "options": "CRM Deal Status"
- }
- ],
- "index_web_pages_for_search": 1,
- "issingle": 1,
- "links": [],
- "modified": "2024-09-17 19:21:11.060901",
- "modified_by": "Administrator",
- "module": "FCRM",
- "name": "ERPNext CRM Settings",
- "owner": "Administrator",
- "permissions": [
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "print": 1,
- "read": 1,
- "role": "System Manager",
- "share": 1,
- "write": 1
- }
- ],
- "sort_field": "creation",
- "sort_order": "DESC",
- "states": []
-}
\ No newline at end of file
diff --git a/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.py b/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.py
deleted file mode 100644
index 2e15a0118..000000000
--- a/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.py
+++ /dev/null
@@ -1,272 +0,0 @@
-# Copyright (c) 2024, Frappe and contributors
-# For license information, please see license.txt
-
-import frappe
-from frappe import _
-from frappe.custom.doctype.property_setter.property_setter import make_property_setter
-from frappe.model.document import Document
-from frappe.frappeclient import FrappeClient
-from frappe.utils import get_url_to_form, get_url_to_list
-import json
-
-class ERPNextCRMSettings(Document):
- def validate(self):
- if self.enabled:
- self.validate_if_erpnext_installed()
- self.add_quotation_to_option()
- self.create_custom_fields()
- self.create_crm_form_script()
-
- def validate_if_erpnext_installed(self):
- if not self.is_erpnext_in_different_site:
- if "erpnext" not in frappe.get_installed_apps():
- frappe.throw(_("ERPNext is not installed in the current site"))
-
- def add_quotation_to_option(self):
- if not self.is_erpnext_in_different_site:
- if not frappe.db.exists("Property Setter", {"name": "Quotation-quotation_to-link_filters"}):
- make_property_setter(
- doctype="Quotation",
- fieldname="quotation_to",
- property="link_filters",
- value='[["DocType","name","in", ["Customer", "Lead", "Prospect", "CRM Deal"]]]',
- property_type="JSON",
- validate_fields_for_doctype=False,
- )
-
- def create_custom_fields(self):
- if not self.is_erpnext_in_different_site:
- from erpnext.crm.frappe_crm_api import create_custom_fields_for_frappe_crm
- create_custom_fields_for_frappe_crm()
- else:
- self.create_custom_fields_in_remote_site()
-
- def create_custom_fields_in_remote_site(self):
- client = get_erpnext_site_client(self)
- try:
- client.post_api("erpnext.crm.frappe_crm_api.create_custom_fields_for_frappe_crm")
- except Exception:
- frappe.log_error(
- frappe.get_traceback(),
- f"Error while creating custom field in the remote erpnext site: {self.erpnext_site_url}"
- )
- frappe.throw("Error while creating custom field in ERPNext, check error log for more details")
-
- def create_crm_form_script(self):
- if not frappe.db.exists("CRM Form Script", "Create Quotation from CRM Deal"):
- script = get_crm_form_script()
- frappe.get_doc({
- "doctype": "CRM Form Script",
- "name": "Create Quotation from CRM Deal",
- "dt": "CRM Deal",
- "view": "Form",
- "script": script,
- "enabled": 1,
- "is_standard": 1
- }).insert()
-
- @frappe.whitelist()
- def reset_erpnext_form_script(self):
- try:
- if frappe.db.exists("CRM Form Script", "Create Quotation from CRM Deal"):
- script = get_crm_form_script()
- frappe.db.set_value("CRM Form Script", "Create Quotation from CRM Deal", "script", script)
- return True
- return False
- except Exception:
- frappe.log_error(frappe.get_traceback(), "Error while resetting form script")
- return False
-
-def get_erpnext_site_client(erpnext_crm_settings):
- site_url = erpnext_crm_settings.erpnext_site_url
- api_key = erpnext_crm_settings.api_key
- api_secret = erpnext_crm_settings.get_password("api_secret", raise_exception=False)
-
- return FrappeClient(
- site_url, api_key=api_key, api_secret=api_secret
- )
-
-@frappe.whitelist()
-def get_customer_link(crm_deal):
- erpnext_crm_settings = frappe.get_single("ERPNext CRM Settings")
- if not erpnext_crm_settings.enabled:
- frappe.throw(_("ERPNext is not integrated with the CRM"))
-
- if not erpnext_crm_settings.is_erpnext_in_different_site:
- customer = frappe.db.exists("Customer", {"crm_deal": crm_deal})
- return get_url_to_form("Customer", customer) if customer else ""
- else:
- client = get_erpnext_site_client(erpnext_crm_settings)
- try:
- customer = client.get_list("Customer", {"crm_deal": crm_deal})
- customer = customer[0].get("name") if len(customer) else None
- if customer:
- return f"{erpnext_crm_settings.erpnext_site_url}/app/customer/{customer}"
- else:
- return ""
- except Exception:
- frappe.log_error(
- frappe.get_traceback(),
- f"Error while fetching customer in remote site: {erpnext_crm_settings.erpnext_site_url}"
- )
- frappe.throw(_("Error while fetching customer in ERPNext, check error log for more details"))
-
-
-@frappe.whitelist()
-def get_quotation_url(crm_deal, organization):
- erpnext_crm_settings = frappe.get_single("ERPNext CRM Settings")
- if not erpnext_crm_settings.enabled:
- frappe.throw(_("ERPNext is not integrated with the CRM"))
-
- if not erpnext_crm_settings.is_erpnext_in_different_site:
- quotation_url = get_url_to_list("Quotation")
- return f"{quotation_url}/new?quotation_to=CRM Deal&crm_deal={crm_deal}&party_name={crm_deal}&company={erpnext_crm_settings.erpnext_company}"
- else:
- site_url = erpnext_crm_settings.get("erpnext_site_url")
- quotation_url = f"{site_url}/app/quotation"
-
- prospect = create_prospect_in_remote_site(crm_deal, erpnext_crm_settings)
- return f"{quotation_url}/new?quotation_to=Prospect&crm_deal={crm_deal}&party_name={prospect}&company={erpnext_crm_settings.erpnext_company}"
-
-def create_prospect_in_remote_site(crm_deal, erpnext_crm_settings):
- try:
- client = get_erpnext_site_client(erpnext_crm_settings)
- doc = frappe.get_doc("CRM Deal", crm_deal)
- contacts = get_contacts(doc)
- address = get_organization_address(doc.organization)
- return client.post_api("erpnext.crm.frappe_crm_api.create_prospect_against_crm_deal",
- {
- "organization": doc.organization,
- "lead_name": doc.lead_name,
- "no_of_employees": doc.no_of_employees,
- "deal_owner": doc.deal_owner,
- "crm_deal": doc.name,
- "territory": doc.territory,
- "industry": doc.industry,
- "website": doc.website,
- "annual_revenue": doc.annual_revenue,
- "contacts": json.dumps(contacts),
- "erpnext_company": erpnext_crm_settings.erpnext_company,
- "address": address.as_dict() if address else None
- },
- )
- except Exception:
- frappe.log_error(
- frappe.get_traceback(),
- f"Error while creating prospect in remote site: {erpnext_crm_settings.erpnext_site_url}"
- )
- frappe.throw(_("Error while creating prospect in ERPNext, check error log for more details"))
-
-def get_contacts(doc):
- contacts = []
- for c in doc.contacts:
- contacts.append({
- "contact": c.contact,
- "full_name": c.full_name,
- "email": c.email,
- "mobile_no": c.mobile_no,
- "gender": c.gender,
- "is_primary": c.is_primary,
- })
- return contacts
-
-def get_organization_address(organization):
- address = frappe.db.get_value("CRM Organization", organization, "address")
- address = frappe.get_doc("Address", address) if address else None
- if not address:
- return None
- return {
- "name": address.name,
- "address_title": address.address_title,
- "address_type": address.address_type,
- "address_line1": address.address_line1,
- "address_line2": address.address_line2,
- "city": address.city,
- "county": address.county,
- "state": address.state,
- "country": address.country,
- "pincode": address.pincode,
- }
-
-def create_customer_in_erpnext(doc, method):
- erpnext_crm_settings = frappe.get_single("ERPNext CRM Settings")
- if (
- not erpnext_crm_settings.enabled
- or not erpnext_crm_settings.create_customer_on_status_change
- or doc.status != erpnext_crm_settings.deal_status
- ):
- return
-
- contacts = get_contacts(doc)
- address = get_organization_address(doc.organization)
- customer = {
- "customer_name": doc.organization,
- "customer_group": "All Customer Groups",
- "customer_type": "Company",
- "territory": doc.territory,
- "default_currency": doc.currency,
- "industry": doc.industry,
- "website": doc.website,
- "crm_deal": doc.name,
- "contacts": json.dumps(contacts),
- "address": json.dumps(address) if address else None,
- }
- if not erpnext_crm_settings.is_erpnext_in_different_site:
- from erpnext.crm.frappe_crm_api import create_customer
- create_customer(customer)
- else:
- create_customer_in_remote_site(customer, erpnext_crm_settings)
-
- frappe.publish_realtime("crm_customer_created")
-
-def create_customer_in_remote_site(customer, erpnext_crm_settings):
- client = get_erpnext_site_client(erpnext_crm_settings)
- try:
- client.post_api("erpnext.crm.frappe_crm_api.create_customer", customer)
- except Exception:
- frappe.log_error(
- frappe.get_traceback(),
- "Error while creating customer in remote site"
- )
- frappe.throw(_("Error while creating customer in ERPNext, check error log for more details"))
-
-@frappe.whitelist()
-def get_crm_form_script():
- return """
-async function setupForm({ doc, call, $dialog, updateField, createToast }) {
- let actions = [];
- let is_erpnext_integration_enabled = await call("frappe.client.get_single_value", {doctype: "ERPNext CRM Settings", field: "enabled"});
- if (!["Lost", "Won"].includes(doc?.status) && is_erpnext_integration_enabled) {
- actions.push({
- label: __("Create Quotation"),
- onClick: async () => {
- let quotation_url = await call(
- "crm.fcrm.doctype.erpnext_crm_settings.erpnext_crm_settings.get_quotation_url",
- {
- crm_deal: doc.name,
- organization: doc.organization
- }
- );
-
- if (quotation_url) {
- window.open(quotation_url, '_blank');
- }
- }
- })
- }
- if (is_erpnext_integration_enabled) {
- let customer_url = await call("crm.fcrm.doctype.erpnext_crm_settings.erpnext_crm_settings.get_customer_link", {
- crm_deal: doc.name
- });
- if (customer_url) {
- actions.push({
- label: __("View Customer"),
- onClick: () => window.open(customer_url, '_blank')
- });
- }
- }
- return {
- actions: actions,
- };
-}
-"""
diff --git a/crm/fcrm/doctype/fcrm_note/__init__.py b/crm/fcrm/doctype/fcrm_note/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/crm/fcrm/doctype/fcrm_note/fcrm_note.js b/crm/fcrm/doctype/fcrm_note/fcrm_note.js
deleted file mode 100644
index 843e8f734..000000000
--- a/crm/fcrm/doctype/fcrm_note/fcrm_note.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-// frappe.ui.form.on("FCRM Note", {
-// refresh(frm) {
-
-// },
-// });
diff --git a/crm/fcrm/doctype/fcrm_note/fcrm_note.py b/crm/fcrm/doctype/fcrm_note/fcrm_note.py
deleted file mode 100644
index c81538d8c..000000000
--- a/crm/fcrm/doctype/fcrm_note/fcrm_note.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-# import frappe
-from frappe.model.document import Document
-
-
-class FCRMNote(Document):
- @staticmethod
- def default_list_data():
- rows = [
- "name",
- "title",
- "content",
- "reference_doctype",
- "reference_docname",
- "owner",
- "modified",
- ]
- return {'columns': [], 'rows': rows}
diff --git a/crm/fcrm/doctype/fcrm_settings/__init__.py b/crm/fcrm/doctype/fcrm_settings/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/crm/fcrm/doctype/fcrm_settings/fcrm_settings.js b/crm/fcrm/doctype/fcrm_settings/fcrm_settings.js
deleted file mode 100644
index 4264130fc..000000000
--- a/crm/fcrm/doctype/fcrm_settings/fcrm_settings.js
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on("FCRM Settings", {
- // refresh(frm) {
-
- // },
- restore_defaults: function (frm) {
- let message = __(
- "This will restore (if not exist) all the default statuses, custom fields and layouts. Delete & Restore will delete default layouts and then restore them."
- );
- let d = new frappe.ui.Dialog({
- title: __("Restore Defaults"),
- primary_action_label: __("Restore"),
- primary_action: () => {
- frm.call("restore_defaults", { force: false }, () => {
- frappe.show_alert({
- message: __(
- "Default statuses, custom fields and layouts restored successfully."
- ),
- indicator: "green",
- });
- });
- d.hide();
- },
- secondary_action_label: __("Delete & Restore"),
- secondary_action: () => {
- frm.call("restore_defaults", { force: true }, () => {
- frappe.show_alert({
- message: __(
- "Default statuses, custom fields and layouts restored successfully."
- ),
- indicator: "green",
- });
- });
- d.hide();
- },
- });
- d.show();
- d.set_message(message);
- },
-});
diff --git a/crm/fcrm/doctype/fcrm_settings/test_fcrm_settings.py b/crm/fcrm/doctype/fcrm_settings/test_fcrm_settings.py
deleted file mode 100644
index 8e626f3e5..000000000
--- a/crm/fcrm/doctype/fcrm_settings/test_fcrm_settings.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-# import frappe
-from frappe.tests import UnitTestCase
-
-
-class TestFCRMSettings(UnitTestCase):
- pass
diff --git a/crm/fcrm/doctype/twilio_agents/__init__.py b/crm/fcrm/doctype/twilio_agents/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/crm/fcrm/doctype/twilio_settings/__init__.py b/crm/fcrm/doctype/twilio_settings/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/crm/fcrm/doctype/twilio_settings/twilio_settings.py b/crm/fcrm/doctype/twilio_settings/twilio_settings.py
deleted file mode 100644
index 1d3a20b63..000000000
--- a/crm/fcrm/doctype/twilio_settings/twilio_settings.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-import frappe
-from frappe.model.document import Document
-from frappe import _
-
-from twilio.rest import Client
-
-class TwilioSettings(Document):
- friendly_resource_name = "Frappe CRM" # System creates TwiML app & API keys with this name.
-
- def validate(self):
- self.validate_twilio_account()
-
- def on_update(self):
- # Single doctype records are created in DB at time of installation and those field values are set as null.
- # This condition make sure that we handle null.
- if not self.account_sid:
- return
-
- twilio = Client(self.account_sid, self.get_password("auth_token"))
- self.set_api_credentials(twilio)
- self.set_application_credentials(twilio)
- self.reload()
-
- def validate_twilio_account(self):
- try:
- twilio = Client(self.account_sid, self.get_password("auth_token"))
- twilio.api.accounts(self.account_sid).fetch()
- return twilio
- except Exception:
- frappe.throw(_("Invalid Account SID or Auth Token."))
-
- def set_api_credentials(self, twilio):
- """Generate Twilio API credentials if not exist and update them.
- """
- if self.api_key and self.api_secret:
- return
- new_key = self.create_api_key(twilio)
- self.api_key = new_key.sid
- self.api_secret = new_key.secret
- frappe.db.set_value('Twilio Settings', 'Twilio Settings', {
- 'api_key': self.api_key,
- 'api_secret': self.api_secret
- })
-
- def set_application_credentials(self, twilio):
- """Generate TwiML app credentials if not exist and update them.
- """
- credentials = self.get_application(twilio) or self.create_application(twilio)
- self.twiml_sid = credentials.sid
- frappe.db.set_value('Twilio Settings', 'Twilio Settings', 'twiml_sid', self.twiml_sid)
-
- def create_api_key(self, twilio):
- """Create API keys in twilio account.
- """
- try:
- return twilio.new_keys.create(friendly_name=self.friendly_resource_name)
- except Exception:
- frappe.log_error(title=_("Twilio API credential creation error."))
- frappe.throw(_("Twilio API credential creation error."))
-
- def get_twilio_voice_url(self):
- url_path = "/api/method/crm.integrations.twilio.api.voice"
- return get_public_url(url_path)
-
- def get_application(self, twilio, friendly_name=None):
- """Get TwiML App from twilio account if exists.
- """
- friendly_name = friendly_name or self.friendly_resource_name
- applications = twilio.applications.list(friendly_name)
- return applications and applications[0]
-
- def create_application(self, twilio, friendly_name=None):
- """Create TwilML App in twilio account.
- """
- friendly_name = friendly_name or self.friendly_resource_name
- application = twilio.applications.create(
- voice_method='POST',
- voice_url=self.get_twilio_voice_url(),
- friendly_name=friendly_name
- )
- return application
-
-def get_public_url(path: str=None):
- from frappe.utils import get_url
- return get_url().split(":8", 1)[0] + path
\ No newline at end of file
diff --git a/crm/hooks.py b/crm/hooks.py
deleted file mode 100644
index dfc4c9d75..000000000
--- a/crm/hooks.py
+++ /dev/null
@@ -1,253 +0,0 @@
-app_name = "crm"
-app_title = "Frappe CRM"
-app_publisher = "Frappe Technologies Pvt. Ltd."
-app_description = "Kick-ass Open Source CRM"
-app_email = "shariq@frappe.io"
-app_license = "AGPLv3"
-app_icon_url = "/assets/crm/images/logo.svg"
-app_icon_title = "CRM"
-app_icon_route = "/crm"
-
-# Apps
-# ------------------
-
-# required_apps = []
-add_to_apps_screen = [
- {
- "name": "crm",
- "logo": "/assets/crm/images/logo.svg",
- "title": "CRM",
- "route": "/crm",
- "has_permission": "crm.api.check_app_permission",
- }
-]
-
-# Includes in
-# ------------------
-
-# include js, css files in header of desk.html
-# app_include_css = "/assets/crm/css/crm.css"
-# app_include_js = "/assets/crm/js/crm.js"
-
-# include js, css files in header of web template
-# web_include_css = "/assets/crm/css/crm.css"
-# web_include_js = "/assets/crm/js/crm.js"
-
-# include custom scss in every website theme (without file extension ".scss")
-# website_theme_scss = "crm/public/scss/website"
-
-# include js, css files in header of web form
-# webform_include_js = {"doctype": "public/js/doctype.js"}
-# webform_include_css = {"doctype": "public/css/doctype.css"}
-
-# include js in page
-# page_js = {"page" : "public/js/file.js"}
-
-# include js in doctype views
-# doctype_js = {"doctype" : "public/js/doctype.js"}
-# doctype_list_js = {"doctype" : "public/js/doctype_list.js"}
-# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"}
-# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"}
-
-# Home Pages
-# ----------
-
-# application home page (will override Website Settings)
-# home_page = "login"
-
-# website user home page (by Role)
-# role_home_page = {
-# "Role": "home_page"
-# }
-
-website_route_rules = [
- {"from_route": "/crm/", "to_route": "crm"},
-]
-
-# Generators
-# ----------
-
-# automatically create page for each record of this doctype
-# website_generators = ["Web Page"]
-
-# Jinja
-# ----------
-
-# add methods and filters to jinja environment
-# jinja = {
-# "methods": "crm.utils.jinja_methods",
-# "filters": "crm.utils.jinja_filters"
-# }
-
-# Installation
-# ------------
-
-before_install = "crm.install.before_install"
-after_install = "crm.install.after_install"
-
-# Uninstallation
-# ------------
-
-before_uninstall = "crm.uninstall.before_uninstall"
-# after_uninstall = "crm.uninstall.after_uninstall"
-
-# Integration Setup
-# ------------------
-# To set up dependencies/integrations with other apps
-# Name of the app being installed is passed as an argument
-
-# before_app_install = "crm.utils.before_app_install"
-# after_app_install = "crm.utils.after_app_install"
-
-# Integration Cleanup
-# -------------------
-# To clean up dependencies/integrations with other apps
-# Name of the app being uninstalled is passed as an argument
-
-# before_app_uninstall = "crm.utils.before_app_uninstall"
-# after_app_uninstall = "crm.utils.after_app_uninstall"
-
-# Desk Notifications
-# ------------------
-# See frappe.core.notifications.get_notification_config
-
-# notification_config = "crm.notifications.get_notification_config"
-
-# Permissions
-# -----------
-# Permissions evaluated in scripted ways
-
-# permission_query_conditions = {
-# "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions",
-# }
-#
-# has_permission = {
-# "Event": "frappe.desk.doctype.event.event.has_permission",
-# }
-
-# DocType Class
-# ---------------
-# Override standard doctype classes
-
-override_doctype_class = {
- "Contact": "crm.overrides.contact.CustomContact",
- "Email Template": "crm.overrides.email_template.CustomEmailTemplate",
- "User": "crm.overrides.user.CustomUser",
-}
-
-# Document Events
-# ---------------
-# Hook on document methods and events
-
-doc_events = {
- "Contact": {
- "validate": ["crm.api.contact.validate"],
- },
- "ToDo": {
- "after_insert": ["crm.api.todo.after_insert"],
- "on_update": ["crm.api.todo.on_update"],
- },
- "Comment": {
- "on_update": ["crm.api.comment.on_update"],
- },
- "WhatsApp Message": {
- "validate": ["crm.api.whatsapp.validate"],
- "on_update": ["crm.api.whatsapp.on_update"],
- },
- "CRM Deal": {
- "on_update": ["crm.fcrm.doctype.erpnext_crm_settings.erpnext_crm_settings.create_customer_in_erpnext"],
- },
- "User": {
- "before_validate": ["crm.api.demo.validate_user"],
- }
-}
-
-# Scheduled Tasks
-# ---------------
-
-# scheduler_events = {
-# "all": [
-# "crm.tasks.all"
-# ],
-# "daily": [
-# "crm.tasks.daily"
-# ],
-# "hourly": [
-# "crm.tasks.hourly"
-# ],
-# "weekly": [
-# "crm.tasks.weekly"
-# ],
-# "monthly": [
-# "crm.tasks.monthly"
-# ],
-# }
-
-# Testing
-# -------
-
-# before_tests = "crm.install.before_tests"
-
-# Overriding Methods
-# ------------------------------
-#
-# override_whitelisted_methods = {
-# "frappe.desk.doctype.event.event.get_events": "crm.event.get_events"
-# }
-#
-# each overriding function accepts a `data` argument;
-# generated from the base implementation of the doctype dashboard,
-# along with any modifications made in other Frappe apps
-# override_doctype_dashboards = {
-# "Task": "crm.task.get_dashboard_data"
-# }
-
-# exempt linked doctypes from being automatically cancelled
-#
-# auto_cancel_exempted_doctypes = ["Auto Repeat"]
-
-# Ignore links to specified DocTypes when deleting documents
-# -----------------------------------------------------------
-
-# ignore_links_on_delete = ["Communication", "ToDo"]
-
-# Request Events
-# ----------------
-# before_request = ["crm.utils.before_request"]
-# after_request = ["crm.utils.after_request"]
-
-# Job Events
-# ----------
-# before_job = ["crm.utils.before_job"]
-# after_job = ["crm.utils.after_job"]
-
-# User Data Protection
-# --------------------
-
-# user_data_fields = [
-# {
-# "doctype": "{doctype_1}",
-# "filter_by": "{filter_by}",
-# "redact_fields": ["{field_1}", "{field_2}"],
-# "partial": 1,
-# },
-# {
-# "doctype": "{doctype_2}",
-# "filter_by": "{filter_by}",
-# "partial": 1,
-# },
-# {
-# "doctype": "{doctype_3}",
-# "strict": False,
-# },
-# {
-# "doctype": "{doctype_4}"
-# }
-# ]
-
-# Authentication and authorization
-# --------------------------------
-
-# auth_hooks = [
-# "crm.auth.validate"
-# ]
diff --git a/crm/install.py b/crm/install.py
deleted file mode 100644
index 594b1ea5f..000000000
--- a/crm/install.py
+++ /dev/null
@@ -1,242 +0,0 @@
-# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
-# MIT License. See license.txt
-from __future__ import unicode_literals
-import click
-import frappe
-
-from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
-
-def before_install():
- pass
-
-def after_install(force=False):
- add_default_lead_statuses()
- add_default_deal_statuses()
- add_default_communication_statuses()
- add_default_fields_layout(force)
- add_property_setter()
- add_email_template_custom_fields()
- add_default_industries()
- add_default_lead_sources()
- frappe.db.commit()
-
-def add_default_lead_statuses():
- statuses = {
- "New": {
- "color": "gray",
- "position": 1,
- },
- "Contacted": {
- "color": "orange",
- "position": 2,
- },
- "Nurture": {
- "color": "blue",
- "position": 3,
- },
- "Qualified": {
- "color": "green",
- "position": 4,
- },
- "Unqualified": {
- "color": "red",
- "position": 5,
- },
- "Junk": {
- "color": "purple",
- "position": 6,
- },
- }
-
- for status in statuses:
- if frappe.db.exists("CRM Lead Status", status):
- continue
-
- doc = frappe.new_doc("CRM Lead Status")
- doc.lead_status = status
- doc.color = statuses[status]["color"]
- doc.position = statuses[status]["position"]
- doc.insert()
-
-def add_default_deal_statuses():
- statuses = {
- "Qualification": {
- "color": "gray",
- "position": 1,
- },
- "Demo/Making": {
- "color": "orange",
- "position": 2,
- },
- "Proposal/Quotation": {
- "color": "blue",
- "position": 3,
- },
- "Negotiation": {
- "color": "yellow",
- "position": 4,
- },
- "Ready to Close": {
- "color": "purple",
- "position": 5,
- },
- "Won": {
- "color": "green",
- "position": 6,
- },
- "Lost": {
- "color": "red",
- "position": 7,
- },
- }
-
- for status in statuses:
- if frappe.db.exists("CRM Deal Status", status):
- continue
-
- doc = frappe.new_doc("CRM Deal Status")
- doc.deal_status = status
- doc.color = statuses[status]["color"]
- doc.position = statuses[status]["position"]
- doc.insert()
-
-def add_default_communication_statuses():
- statuses = ["Open", "Replied"]
-
- for status in statuses:
- if frappe.db.exists("CRM Communication Status", status):
- continue
-
- doc = frappe.new_doc("CRM Communication Status")
- doc.status = status
- doc.insert()
-
-def add_default_fields_layout(force=False):
- quick_entry_layouts = {
- "CRM Lead-Quick Entry": {
- "doctype": "CRM Lead",
- "layout": '[{"label":"Person","fields":["salutation","first_name","last_name","email","mobile_no", "gender"],"hideLabel":true},{"label":"Organization","fields":["organization","website","no_of_employees","territory","annual_revenue","industry"],"hideLabel":true,"hideBorder":false},{"label":"Other","columns":2,"fields":["status","lead_owner"],"hideLabel":true,"hideBorder":false}]'
- },
- "CRM Deal-Quick Entry": {
- "doctype": "CRM Deal",
- "layout": '[{"label": "Select Organization", "fields": ["organization"], "hideLabel": true, "editable": true}, {"label": "Organization Details", "fields": ["organization_name", "website", "no_of_employees", "territory", "annual_revenue", "industry"], "hideLabel": true, "editable": true}, {"label": "Select Contact", "fields": ["contact"], "hideLabel": true, "editable": true}, {"label": "Contact Details", "fields": ["salutation", "first_name", "last_name", "email", "mobile_no", "gender"], "hideLabel": true, "editable": true}, {"label": "Other", "columns": 2, "fields": ["status", "deal_owner"], "hideLabel": true}]'
- },
- "Contact-Quick Entry": {
- "doctype": "Contact",
- "layout": '[{"label":"Salutation","columns":1,"fields":["salutation"],"hideLabel":true},{"label":"Full Name","columns":2,"hideBorder":true,"fields":["first_name","last_name"],"hideLabel":true},{"label":"Email","columns":1,"hideBorder":true,"fields":["email_id"],"hideLabel":true},{"label":"Mobile No. & Gender","columns":2,"hideBorder":true,"fields":["mobile_no","gender"],"hideLabel":true},{"label":"Organization","columns":1,"hideBorder":true,"fields":["company_name"],"hideLabel":true},{"label":"Designation","columns":1,"hideBorder":true,"fields":["designation"],"hideLabel":true},{"label":"Address","columns":1,"hideBorder":true,"fields":["address"],"hideLabel":true}]'
- },
- "CRM Organization-Quick Entry": {
- "doctype": "CRM Organization",
- "layout": '[{"label":"Organization Name","columns":1,"fields":["organization_name"],"hideLabel":true},{"label":"Website & Revenue","columns":2,"hideBorder":true,"fields":["website","annual_revenue"],"hideLabel":true},{"label":"Territory","columns":1,"hideBorder":true,"fields":["territory"],"hideLabel":true},{"label":"No of Employees & Industry","columns":2,"hideBorder":true,"fields":["no_of_employees","industry"],"hideLabel":true},{"label":"Address","columns":1,"hideBorder":true,"fields":["address"],"hideLabel":true}]'
- },
- "Address-Quick Entry": {
- "doctype": "Address",
- "layout": '[{"label":"Address","columns":1,"fields":["address_title","address_type","address_line1","address_line2","city","state","country","pincode"],"hideLabel":true}]'
- },
- }
-
- sidebar_fields_layouts = {
- "CRM Lead-Side Panel": {
- "doctype": "CRM Lead",
- "layout": '[{"label": "Details", "name": "details", "opened": true, "fields": ["organization", "website", "territory", "industry", "job_title", "source", "lead_owner"]}, {"label": "Person", "name": "person_tab", "opened": true, "fields": ["salutation", "first_name", "last_name", "email", "mobile_no"]}]'
- },
- "CRM Deal-Side Panel": {
- "doctype": "CRM Deal",
- "layout": '[{"label":"Contacts","name":"contacts_section","opened":true,"editable":false,"contacts":[]},{"label":"Organization Details","name":"organization_tab","opened":true,"fields":["organization","website","territory","annual_revenue","close_date","probability","next_step","deal_owner"]}]'
- },
- "Contact-Side Panel": {
- "doctype": "Contact",
- "layout": '[{"label":"Details","name":"details","opened":true,"fields":["salutation","first_name","last_name","email_id","mobile_no","gender","company_name","designation","address"]}]'
- },
- "CRM Organization-Side Panel": {
- "doctype": "CRM Organization",
- "layout": '[{"label":"Details","name":"details","opened":true,"fields":["organization_name","website","territory","industry","no_of_employees","address"]}]'
- },
- }
-
- for layout in quick_entry_layouts:
- if frappe.db.exists("CRM Fields Layout", layout):
- if force:
- frappe.delete_doc("CRM Fields Layout", layout)
- else:
- continue
-
- doc = frappe.new_doc("CRM Fields Layout")
- doc.type = "Quick Entry"
- doc.dt = quick_entry_layouts[layout]["doctype"]
- doc.layout = quick_entry_layouts[layout]["layout"]
- doc.insert()
-
- for layout in sidebar_fields_layouts:
- if frappe.db.exists("CRM Fields Layout", layout):
- if force:
- frappe.delete_doc("CRM Fields Layout", layout)
- else:
- continue
-
- doc = frappe.new_doc("CRM Fields Layout")
- doc.type = "Side Panel"
- doc.dt = sidebar_fields_layouts[layout]["doctype"]
- doc.layout = sidebar_fields_layouts[layout]["layout"]
- doc.insert()
-
-def add_property_setter():
- if not frappe.db.exists("Property Setter", {"name": "Contact-main-search_fields"}):
- doc = frappe.new_doc("Property Setter")
- doc.doctype_or_field = "DocType"
- doc.doc_type = "Contact"
- doc.property = "search_fields"
- doc.property_type = "Data"
- doc.value = "email_id"
- doc.insert()
-
-def add_email_template_custom_fields():
- if not frappe.get_meta("Email Template").has_field("enabled"):
- click.secho("* Installing Custom Fields in Email Template")
-
- create_custom_fields(
- {
- "Email Template": [
- {
- "default": "0",
- "fieldname": "enabled",
- "fieldtype": "Check",
- "label": "Enabled",
- "insert_after": "",
- },
- {
- "fieldname": "reference_doctype",
- "fieldtype": "Link",
- "label": "Doctype",
- "options": "DocType",
- "insert_after": "enabled",
- },
- ]
- }
- )
-
- frappe.clear_cache(doctype="Email Template")
-
-
-def add_default_industries():
- industries = ["Accounting", "Advertising", "Aerospace", "Agriculture", "Airline", "Apparel & Accessories", "Automotive", "Banking", "Biotechnology", "Broadcasting", "Brokerage", "Chemical", "Computer", "Consulting", "Consumer Products", "Cosmetics", "Defense", "Department Stores", "Education", "Electronics", "Energy", "Entertainment & Leisure, Executive Search", "Financial Services", "Food", "Beverage & Tobacco", "Grocery", "Health Care", "Internet Publishing", "Investment Banking", "Legal", "Manufacturing", "Motion Picture & Video", "Music", "Newspaper Publishers", "Online Auctions", "Pension Funds", "Pharmaceuticals", "Private Equity", "Publishing", "Real Estate", "Retail & Wholesale", "Securities & Commodity Exchanges", "Service", "Soap & Detergent", "Software", "Sports", "Technology", "Telecommunications", "Television", "Transportation", "Venture Capital"]
-
- for industry in industries:
- if frappe.db.exists("CRM Industry", industry):
- continue
-
- doc = frappe.new_doc("CRM Industry")
- doc.industry = industry
- doc.insert()
-
-
-def add_default_lead_sources():
- lead_sources = ["Existing Customer", "Reference", "Advertisement", "Cold Calling", "Exhibition", "Supplier Reference", "Mass Mailing", "Customer's Vendor", "Campaign", "Walk In"]
-
- for source in lead_sources:
- if frappe.db.exists("CRM Lead Source", source):
- continue
-
- doc = frappe.new_doc("CRM Lead Source")
- doc.source_name = source
- doc.insert()
diff --git a/crm/integrations/twilio/api.py b/crm/integrations/twilio/api.py
deleted file mode 100644
index 1ea4d67dc..000000000
--- a/crm/integrations/twilio/api.py
+++ /dev/null
@@ -1,182 +0,0 @@
-from werkzeug.wrappers import Response
-import json
-
-import frappe
-from frappe import _
-from .twilio_handler import Twilio, IncomingCall, TwilioCallDetails
-from .utils import parse_mobile_no
-
-@frappe.whitelist()
-def is_enabled():
- return frappe.db.get_single_value("Twilio Settings", "enabled")
-
-@frappe.whitelist()
-def generate_access_token():
- """Returns access token that is required to authenticate Twilio Client SDK.
- """
- twilio = Twilio.connect()
- if not twilio:
- return {}
-
- from_number = frappe.db.get_value('Twilio Agents', frappe.session.user, 'twilio_number')
- if not from_number:
- return {
- "ok": False,
- "error": "caller_phone_identity_missing",
- "detail": "Phone number is not mapped to the caller"
- }
-
- token=twilio.generate_voice_access_token(identity=frappe.session.user)
- return {
- 'token': frappe.safe_decode(token)
- }
-
-@frappe.whitelist(allow_guest=True)
-def voice(**kwargs):
- """This is a webhook called by twilio to get instructions when the voice call request comes to twilio server.
- """
- def _get_caller_number(caller):
- identity = caller.replace('client:', '').strip()
- user = Twilio.emailid_from_identity(identity)
- return frappe.db.get_value('Twilio Agents', user, 'twilio_number')
-
- args = frappe._dict(kwargs)
- twilio = Twilio.connect()
- if not twilio:
- return
-
- assert args.AccountSid == twilio.account_sid
- assert args.ApplicationSid == twilio.application_sid
-
- # Generate TwiML instructions to make a call
- from_number = _get_caller_number(args.Caller)
- resp = twilio.generate_twilio_dial_response(from_number, args.To)
-
- call_details = TwilioCallDetails(args, call_from=from_number)
- create_call_log(call_details)
- return Response(resp.to_xml(), mimetype='text/xml')
-
-@frappe.whitelist(allow_guest=True)
-def twilio_incoming_call_handler(**kwargs):
- args = frappe._dict(kwargs)
- call_details = TwilioCallDetails(args)
- create_call_log(call_details)
-
- resp = IncomingCall(args.From, args.To).process()
- return Response(resp.to_xml(), mimetype='text/xml')
-
-def create_call_log(call_details: TwilioCallDetails):
- call_log = frappe.get_doc({**call_details.to_dict(),
- 'doctype': 'CRM Call Log',
- 'medium': 'Twilio'
- })
- call_log.reference_docname, call_log.reference_doctype = get_lead_or_deal_from_number(call_log)
- call_log.flags.ignore_permissions = True
- call_log.save()
- frappe.db.commit()
-
-def update_call_log(call_sid, status=None):
- """Update call log status.
- """
- twilio = Twilio.connect()
- if not (twilio and frappe.db.exists("CRM Call Log", call_sid)): return
-
- call_details = twilio.get_call_info(call_sid)
- call_log = frappe.get_doc("CRM Call Log", call_sid)
- call_log.status = TwilioCallDetails.get_call_status(status or call_details.status)
- call_log.duration = call_details.duration
- call_log.start_time = get_datetime_from_timestamp(call_details.start_time)
- call_log.end_time = get_datetime_from_timestamp(call_details.end_time)
- if call_log.note and call_log.reference_docname:
- frappe.db.set_value("FCRM Note", call_log.note, "reference_doctype", call_log.reference_doctype)
- frappe.db.set_value("FCRM Note", call_log.note, "reference_docname", call_log.reference_docname)
- call_log.flags.ignore_permissions = True
- call_log.save()
- frappe.db.commit()
-
-@frappe.whitelist(allow_guest=True)
-def update_recording_info(**kwargs):
- try:
- args = frappe._dict(kwargs)
- recording_url = args.RecordingUrl
- call_sid = args.CallSid
- update_call_log(call_sid)
- frappe.db.set_value("CRM Call Log", call_sid, "recording_url", recording_url)
- except:
- frappe.log_error(title=_("Failed to capture Twilio recording"))
-
-@frappe.whitelist(allow_guest=True)
-def update_call_status_info(**kwargs):
- try:
- args = frappe._dict(kwargs)
- parent_call_sid = args.ParentCallSid
- update_call_log(parent_call_sid, status=args.CallStatus)
-
- call_info = {
- 'ParentCallSid': args.ParentCallSid,
- 'CallSid': args.CallSid,
- 'CallStatus': args.CallStatus,
- 'CallDuration': args.CallDuration,
- 'From': args.From,
- 'To': args.To,
- }
-
- client = Twilio.get_twilio_client()
- client.calls(args.ParentCallSid).user_defined_messages.create(
- content=json.dumps(call_info)
- )
- except:
- frappe.log_error(title=_("Failed to update Twilio call status"))
-
-def get_datetime_from_timestamp(timestamp):
- from datetime import datetime
- from pytz import timezone
-
- if not timestamp: return None
-
- datetime_utc_tz_str = timestamp.strftime('%Y-%m-%d %H:%M:%S%z')
- datetime_utc_tz = datetime.strptime(datetime_utc_tz_str, '%Y-%m-%d %H:%M:%S%z')
- system_timezone = frappe.utils.get_system_timezone()
- converted_datetime = datetime_utc_tz.astimezone(timezone(system_timezone))
- return frappe.utils.format_datetime(converted_datetime, 'yyyy-MM-dd HH:mm:ss')
-
-@frappe.whitelist()
-def add_note_to_call_log(call_sid, note):
- """Add note to call log. based on child call sid.
- """
- twilio = Twilio.connect()
- if not twilio: return
-
- call_details = twilio.get_call_info(call_sid)
- sid = call_sid if call_details.direction == 'inbound' else call_details.parent_call_sid
-
- frappe.db.set_value("CRM Call Log", sid, "note", note)
- frappe.db.commit()
-
-def get_lead_or_deal_from_number(call):
- """Get lead/deal from the given number.
- """
-
- def find_record(doctype, mobile_no, where=''):
- mobile_no = parse_mobile_no(mobile_no)
-
- query = f"""
- SELECT name, mobile_no
- FROM `tab{doctype}`
- WHERE CONCAT('+', REGEXP_REPLACE(mobile_no, '[^0-9]', '')) = {mobile_no}
- """
-
- data = frappe.db.sql(query + where, as_dict=True)
- return data[0].name if data else None
-
- doctype = "CRM Deal"
- number = call.get('to') if call.type == 'Outgoing' else call.get('from')
-
- doc = find_record(doctype, number) or None
- if not doc:
- doctype = "CRM Lead"
- doc = find_record(doctype, number, 'AND converted is not True')
- if not doc:
- doc = find_record(doctype, number)
-
- return doc, doctype
\ No newline at end of file
diff --git a/crm/integrations/twilio/twilio_handler.py b/crm/integrations/twilio/twilio_handler.py
deleted file mode 100644
index 1ece92966..000000000
--- a/crm/integrations/twilio/twilio_handler.py
+++ /dev/null
@@ -1,267 +0,0 @@
-from twilio.rest import Client as TwilioClient
-from twilio.jwt.access_token import AccessToken
-from twilio.jwt.access_token.grants import VoiceGrant
-from twilio.twiml.voice_response import VoiceResponse, Dial
-from .utils import get_public_url, merge_dicts
-
-import frappe
-from frappe import _
-from frappe.utils.password import get_decrypted_password
-
-class Twilio:
- """Twilio connector over TwilioClient.
- """
- def __init__(self, settings):
- """
- :param settings: `Twilio Settings` doctype
- """
- self.settings = settings
- self.account_sid = settings.account_sid
- self.application_sid = settings.twiml_sid
- self.api_key = settings.api_key
- self.api_secret = settings.get_password("api_secret")
- self.twilio_client = self.get_twilio_client()
-
- @classmethod
- def connect(self):
- """Make a twilio connection.
- """
- settings = frappe.get_doc("Twilio Settings")
- if not (settings and settings.enabled):
- return
- return Twilio(settings=settings)
-
- def get_phone_numbers(self):
- """Get account's twilio phone numbers.
- """
- numbers = self.twilio_client.incoming_phone_numbers.list()
- return [n.phone_number for n in numbers]
-
- def generate_voice_access_token(self, identity: str, ttl=60*60):
- """Generates a token required to make voice calls from the browser.
- """
- # identity is used by twilio to identify the user uniqueness at browser(or any endpoints).
- identity = self.safe_identity(identity)
-
- # Create access token with credentials
- token = AccessToken(self.account_sid, self.api_key, self.api_secret, identity=identity, ttl=ttl)
-
- # Create a Voice grant and add to token
- voice_grant = VoiceGrant(
- outgoing_application_sid=self.application_sid,
- incoming_allow=True, # Allow incoming calls
- )
- token.add_grant(voice_grant)
- return token.to_jwt()
-
- @classmethod
- def safe_identity(cls, identity: str):
- """Create a safe identity by replacing unsupported special charaters `@` with (at)).
- Twilio Client JS fails to make a call connection if identity has special characters like @, [, / etc)
- https://www.twilio.com/docs/voice/client/errors (#31105)
- """
- return identity.replace('@', '(at)')
-
- @classmethod
- def emailid_from_identity(cls, identity: str):
- """Convert safe identity string into emailID.
- """
- return identity.replace('(at)', '@')
-
- def get_recording_status_callback_url(self):
- url_path = "/api/method/crm.integrations.twilio.api.update_recording_info"
- return get_public_url(url_path)
-
- def get_update_call_status_callback_url(self):
- url_path = "/api/method/crm.integrations.twilio.api.update_call_status_info"
- return get_public_url(url_path)
-
- def generate_twilio_dial_response(self, from_number: str, to_number: str):
- """Generates voice call instructions to forward the call to agents Phone.
- """
- resp = VoiceResponse()
- dial = Dial(
- caller_id=from_number,
- record=self.settings.record_calls,
- recording_status_callback=self.get_recording_status_callback_url(),
- recording_status_callback_event='completed'
- )
- dial.number(
- to_number,
- status_callback_event='initiated ringing answered completed',
- status_callback=self.get_update_call_status_callback_url(),
- status_callback_method='POST'
- )
- resp.append(dial)
- return resp
-
- def get_call_info(self, call_sid):
- return self.twilio_client.calls(call_sid).fetch()
-
- def generate_twilio_client_response(self, client, ring_tone='at'):
- """Generates voice call instructions to forward the call to agents computer.
- """
- resp = VoiceResponse()
- dial = Dial(
- ring_tone=ring_tone,
- record=self.settings.record_calls,
- recording_status_callback=self.get_recording_status_callback_url(),
- recording_status_callback_event='completed'
- )
- dial.client(
- client,
- status_callback_event='initiated ringing answered completed',
- status_callback=self.get_update_call_status_callback_url(),
- status_callback_method='POST'
- )
- resp.append(dial)
- return resp
-
- @classmethod
- def get_twilio_client(self):
- twilio_settings = frappe.get_doc("Twilio Settings")
- if not twilio_settings.enabled:
- frappe.throw(_("Please enable twilio settings before making a call."))
-
- auth_token = get_decrypted_password("Twilio Settings", "Twilio Settings", 'auth_token')
- client = TwilioClient(twilio_settings.account_sid, auth_token)
-
- return client
-
-class IncomingCall:
- def __init__(self, from_number, to_number, meta=None):
- self.from_number = from_number
- self.to_number = to_number
- self.meta = meta
-
- def process(self):
- """Process the incoming call
- * Figure out who is going to pick the call (call attender)
- * Check call attender settings and forward the call to Phone
- """
- twilio = Twilio.connect()
- owners = get_twilio_number_owners(self.to_number)
- attender = get_the_call_attender(owners, self.from_number)
-
- if not attender:
- resp = VoiceResponse()
- resp.say(_('Agent is unavailable to take the call, please call after some time.'))
- return resp
-
- if attender['call_receiving_device'] == 'Phone':
- return twilio.generate_twilio_dial_response(self.from_number, attender['mobile_no'])
- else:
- return twilio.generate_twilio_client_response(twilio.safe_identity(attender['name']))
-
-def get_twilio_number_owners(phone_number):
- """Get list of users who is using the phone_number.
- >>> get_twilio_number_owners('+11234567890')
- {
- 'owner1': {'name': '..', 'mobile_no': '..', 'call_receiving_device': '...'},
- 'owner2': {....}
- }
- """
- # remove special characters from phone number and get only digits also remove white spaces
- # keep + sign in the number at start of the number
- phone_number = ''.join([c for c in phone_number if c.isdigit() or c == '+'])
- user_voice_settings = frappe.get_all(
- 'Twilio Agents',
- filters={'twilio_number': phone_number},
- fields=["name", "call_receiving_device"]
- )
- user_wise_voice_settings = {user['name']: user for user in user_voice_settings}
-
- user_general_settings = frappe.get_all(
- 'User',
- filters = [['name', 'IN', user_wise_voice_settings.keys()]],
- fields = ['name', 'mobile_no']
- )
- user_wise_general_settings = {user['name']: user for user in user_general_settings}
-
- return merge_dicts(user_wise_general_settings, user_wise_voice_settings)
-
-def get_active_loggedin_users(users):
- """Filter the current loggedin users from the given users list
- """
- rows = frappe.db.sql("""
- SELECT `user`
- FROM `tabSessions`
- WHERE `user` IN %(users)s
- """, {'users': users})
- return [row[0] for row in set(rows)]
-
-def get_the_call_attender(owners, caller=None):
- """Get attender details from list of owners
- """
- if not owners: return
- current_loggedin_users = get_active_loggedin_users(list(owners.keys()))
-
- if len(current_loggedin_users) > 1 and caller:
- deal_owner = frappe.db.get_value('CRM Deal', {'mobile_no': caller}, 'deal_owner')
- if not deal_owner:
- deal_owner = frappe.db.get_value('CRM Lead', {'mobile_no': caller, 'converted': False}, 'lead_owner')
- for user in current_loggedin_users:
- if user == deal_owner:
- current_loggedin_users = [user]
-
- for name, details in owners.items():
- if ((details['call_receiving_device'] == 'Phone' and details['mobile_no']) or
- (details['call_receiving_device'] == 'Computer' and name in current_loggedin_users)):
- return details
-
-
-class TwilioCallDetails:
- def __init__(self, call_info, call_from = None, call_to = None):
- self.call_info = call_info
- self.account_sid = call_info.get('AccountSid')
- self.application_sid = call_info.get('ApplicationSid')
- self.call_sid = call_info.get('CallSid')
- self.call_status = self.get_call_status(call_info.get('CallStatus'))
- self._call_from = call_from or call_info.get('From')
- self._call_to = call_to or call_info.get('To')
-
- def get_direction(self):
- if self.call_info.get('Caller').lower().startswith('client'):
- return 'Outgoing'
- return 'Incoming'
-
- def get_from_number(self):
- return self._call_from or self.call_info.get('From')
-
- def get_to_number(self):
- return self._call_to or self.call_info.get('To')
-
- @classmethod
- def get_call_status(cls, twilio_status):
- """Convert Twilio given status into system status.
- """
- twilio_status = twilio_status or ''
- return ' '.join(twilio_status.split('-')).title()
-
- def to_dict(self):
- """Convert call details into dict.
- """
- direction = self.get_direction()
- from_number = self.get_from_number()
- to_number = self.get_to_number()
- caller = ''
- receiver = ''
-
- if direction == 'Outgoing':
- caller = self.call_info.get('Caller')
- identity = caller.replace('client:', '').strip()
- caller = Twilio.emailid_from_identity(identity) if identity else ''
- else:
- owners = get_twilio_number_owners(to_number)
- attender = get_the_call_attender(owners, from_number)
- receiver = attender['name'] if attender else ''
-
- return {
- 'type': direction,
- 'status': self.call_status,
- 'id': self.call_sid,
- 'from': from_number,
- 'to': to_number,
- 'receiver': receiver,
- 'caller': caller,
- }
\ No newline at end of file
diff --git a/crm/integrations/twilio/utils.py b/crm/integrations/twilio/utils.py
deleted file mode 100644
index 2f0376842..000000000
--- a/crm/integrations/twilio/utils.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from frappe.utils import get_url
-
-
-def get_public_url(path: str=None):
- return get_url().split(":8", 1)[0] + path
-
-
-def merge_dicts(d1: dict, d2: dict):
- """Merge dicts of dictionaries.
- >>> merge_dicts(
- {'name1': {'age': 20}, 'name2': {'age': 30}},
- {'name1': {'phone': '+xxx'}, 'name2': {'phone': '+yyy'}, 'name3': {'phone': '+zzz'}}
- )
- ... {'name1': {'age': 20, 'phone': '+xxx'}, 'name2': {'age': 30, 'phone': '+yyy'}}
- """
- return {k:{**v, **d2.get(k, {})} for k, v in d1.items()}
-
-def parse_mobile_no(mobile_no: str):
- """Parse mobile number to remove spaces, brackets, etc.
- >>> parse_mobile_no('+91 (766) 667 6666')
- ... '+917666676666'
- """
- return ''.join([c for c in mobile_no if c.isdigit() or c == '+'])
\ No newline at end of file
diff --git a/crm/modules.txt b/crm/modules.txt
deleted file mode 100644
index e23619140..000000000
--- a/crm/modules.txt
+++ /dev/null
@@ -1 +0,0 @@
-FCRM
\ No newline at end of file
diff --git a/crm/overrides/contact.py b/crm/overrides/contact.py
deleted file mode 100644
index 9845c3982..000000000
--- a/crm/overrides/contact.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# import frappe
-from frappe import _
-from frappe.contacts.doctype.contact.contact import Contact
-
-
-class CustomContact(Contact):
- @staticmethod
- def default_list_data():
- columns = [
- {
- 'label': 'Name',
- 'type': 'Data',
- 'key': 'full_name',
- 'width': '17rem',
- },
- {
- 'label': 'Email',
- 'type': 'Data',
- 'key': 'email_id',
- 'width': '12rem',
- },
- {
- 'label': 'Phone',
- 'type': 'Data',
- 'key': 'mobile_no',
- 'width': '12rem',
- },
- {
- 'label': 'Organization',
- 'type': 'Data',
- 'key': 'company_name',
- 'width': '12rem',
- },
- {
- 'label': 'Last Modified',
- 'type': 'Datetime',
- 'key': 'modified',
- 'width': '8rem',
- },
- ]
- rows = [
- "name",
- "full_name",
- "company_name",
- "email_id",
- "mobile_no",
- "modified",
- "image",
- ]
- return {'columns': columns, 'rows': rows}
diff --git a/crm/overrides/email_template.py b/crm/overrides/email_template.py
deleted file mode 100644
index 3a4e30f7c..000000000
--- a/crm/overrides/email_template.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# import frappe
-from frappe import _
-from frappe.email.doctype.email_template.email_template import EmailTemplate
-
-
-class CustomEmailTemplate(EmailTemplate):
- @staticmethod
- def default_list_data():
- columns = [
- {
- 'label': 'Name',
- 'type': 'Data',
- 'key': 'name',
- 'width': '17rem',
- },
- {
- 'label': 'Subject',
- 'type': 'Data',
- 'key': 'subject',
- 'width': '12rem',
- },
- {
- 'label': 'Enabled',
- 'type': 'Check',
- 'key': 'enabled',
- 'width': '6rem',
- },
- {
- 'label': 'Doctype',
- 'type': 'Link',
- 'key': 'reference_doctype',
- 'width': '12rem',
- },
- {
- 'label': 'Last Modified',
- 'type': 'Datetime',
- 'key': 'modified',
- 'width': '8rem',
- },
- ]
- rows = [
- "name",
- "enabled",
- "use_html",
- "reference_doctype",
- "subject",
- "response",
- "response_html",
- "modified",
- ]
- return {'columns': columns, 'rows': rows}
diff --git a/crm/overrides/user.py b/crm/overrides/user.py
deleted file mode 100644
index d938825c2..000000000
--- a/crm/overrides/user.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# import frappe
-from frappe import _
-from frappe.core.doctype.user.user import User
-from crm.api.demo import validate_reset_password
-
-
-class CustomUser(User):
- def validate_reset_password(self):
- # restrict demo user to reset password
- validate_reset_password(self)
diff --git a/crm/patches.txt b/crm/patches.txt
deleted file mode 100644
index b14faf132..000000000
--- a/crm/patches.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-[pre_model_sync]
-# Patches added in this section will be executed before doctypes are migrated
-# Read docs to understand patches: https://frappeframework.com/docs/v14/user/en/database-migrations
-crm.patches.v1_0.move_crm_note_data_to_fcrm_note
-
-[post_model_sync]
-# Patches added in this section will be executed after doctypes are migrated
-crm.patches.v1_0.create_email_template_custom_fields
-crm.patches.v1_0.create_default_fields_layout #31/10/2024
-crm.patches.v1_0.create_default_sidebar_fields_layout
-crm.patches.v1_0.update_deal_quick_entry_layout
\ No newline at end of file
diff --git a/crm/patches/v1_0/__init__.py b/crm/patches/v1_0/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/crm/patches/v1_0/create_default_fields_layout.py b/crm/patches/v1_0/create_default_fields_layout.py
deleted file mode 100644
index 383e7ac23..000000000
--- a/crm/patches/v1_0/create_default_fields_layout.py
+++ /dev/null
@@ -1,5 +0,0 @@
-
-from crm.install import add_default_fields_layout
-
-def execute():
- add_default_fields_layout()
\ No newline at end of file
diff --git a/crm/patches/v1_0/create_default_sidebar_fields_layout.py b/crm/patches/v1_0/create_default_sidebar_fields_layout.py
deleted file mode 100644
index 04b65feee..000000000
--- a/crm/patches/v1_0/create_default_sidebar_fields_layout.py
+++ /dev/null
@@ -1,63 +0,0 @@
-import json
-import frappe
-
-def execute():
- if not frappe.db.exists("CRM Fields Layout", {"dt": "CRM Lead", "type": "Side Panel"}):
- create_doctype_fields_layout("CRM Lead")
-
- if not frappe.db.exists("CRM Fields Layout", {"dt": "CRM Deal", "type": "Side Panel"}):
- create_doctype_fields_layout("CRM Deal")
-
-def create_doctype_fields_layout(doctype):
- not_allowed_fieldtypes = [
- "Section Break",
- "Column Break",
- ]
-
- fields = frappe.get_meta(doctype).fields
- fields = [field for field in fields if field.fieldtype not in not_allowed_fieldtypes]
-
- sections = {}
- section_fields = []
- last_section = None
-
- for field in fields:
- if field.fieldtype == "Tab Break" and last_section:
- sections[last_section]["fields"] = section_fields
- last_section = None
- if field.read_only:
- section_fields = []
- continue
- if field.fieldtype == "Tab Break":
- if field.read_only:
- section_fields = []
- continue
- section_fields = []
- last_section = field.fieldname
- sections[field.fieldname] = {
- "label": field.label,
- "name": field.fieldname,
- "opened": True,
- "fields": [],
- }
- if field.fieldname == "contacts_tab":
- sections[field.fieldname]["editable"] = False
- sections[field.fieldname]["contacts"] = []
- else:
- section_fields.append(field.fieldname)
-
- section_fields = []
- for section in sections:
- if section == "contacts_tab":
- sections[section]["name"] = "contacts_section"
- sections[section].pop("fields", None)
- section_fields.append(sections[section])
-
- frappe.get_doc({
- "doctype": "CRM Fields Layout",
- "dt": doctype,
- "type": "Side Panel",
- "layout": json.dumps(section_fields),
- }).insert(ignore_permissions=True)
-
- return section_fields
\ No newline at end of file
diff --git a/crm/patches/v1_0/create_email_template_custom_fields.py b/crm/patches/v1_0/create_email_template_custom_fields.py
deleted file mode 100644
index ea6665d79..000000000
--- a/crm/patches/v1_0/create_email_template_custom_fields.py
+++ /dev/null
@@ -1,5 +0,0 @@
-
-from crm.install import add_email_template_custom_fields
-
-def execute():
- add_email_template_custom_fields()
\ No newline at end of file
diff --git a/crm/patches/v1_0/move_crm_note_data_to_fcrm_note.py b/crm/patches/v1_0/move_crm_note_data_to_fcrm_note.py
deleted file mode 100644
index 2f1b319eb..000000000
--- a/crm/patches/v1_0/move_crm_note_data_to_fcrm_note.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import frappe
-from frappe.model.rename_doc import rename_doc
-
-
-def execute():
-
- if not frappe.db.exists("DocType", "FCRM Note"):
- frappe.flags.ignore_route_conflict_validation = True
- rename_doc("DocType", "CRM Note", "FCRM Note")
- frappe.flags.ignore_route_conflict_validation = False
-
- frappe.reload_doctype("FCRM Note", force=True)
-
- if frappe.db.exists("DocType", "FCRM Note") and frappe.db.count("FCRM Note") > 0:
- return
-
- notes = frappe.db.sql("SELECT * FROM `tabCRM Note`", as_dict=True)
- if notes:
- for note in notes:
- doc = frappe.get_doc({
- "doctype": "FCRM Note",
- "creation": note.get("creation"),
- "modified": note.get("modified"),
- "modified_by": note.get("modified_by"),
- "owner": note.get("owner"),
- "title": note.get("title"),
- "content": note.get("content"),
- "reference_doctype": note.get("reference_doctype"),
- "reference_docname": note.get("reference_docname"),
- })
- doc.db_insert()
\ No newline at end of file
diff --git a/crm/patches/v1_0/update_deal_quick_entry_layout.py b/crm/patches/v1_0/update_deal_quick_entry_layout.py
deleted file mode 100644
index fc52e2acc..000000000
--- a/crm/patches/v1_0/update_deal_quick_entry_layout.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import json
-import frappe
-
-def execute():
- if not frappe.db.exists("CRM Fields Layout", "CRM Deal-Quick Entry"):
- return
-
- deal = frappe.db.get_value("CRM Fields Layout", "CRM Deal-Quick Entry", "layout")
-
- layout = json.loads(deal)
- for section in layout:
- if section.get("label") in ["Select Organization", "Organization Details", "Select Contact", "Contact Details"]:
- section["editable"] = False
-
- frappe.db.set_value("CRM Fields Layout", "CRM Deal-Quick Entry", "layout", json.dumps(layout))
\ No newline at end of file
diff --git a/crm/public/images/logo.png b/crm/public/images/logo.png
deleted file mode 100644
index 5881304f4..000000000
Binary files a/crm/public/images/logo.png and /dev/null differ
diff --git a/crm/public/images/logo.svg b/crm/public/images/logo.svg
deleted file mode 100644
index e203a0f83..000000000
--- a/crm/public/images/logo.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/crm/public/manifest/apple-icon-180.png b/crm/public/manifest/apple-icon-180.png
deleted file mode 100644
index f1fde19d6..000000000
Binary files a/crm/public/manifest/apple-icon-180.png and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-1125-2436.jpg b/crm/public/manifest/apple-splash-1125-2436.jpg
deleted file mode 100644
index 1440fa05a..000000000
Binary files a/crm/public/manifest/apple-splash-1125-2436.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-1136-640.jpg b/crm/public/manifest/apple-splash-1136-640.jpg
deleted file mode 100644
index 8eb7bf6e5..000000000
Binary files a/crm/public/manifest/apple-splash-1136-640.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-1170-2532.jpg b/crm/public/manifest/apple-splash-1170-2532.jpg
deleted file mode 100644
index 8b1b82a7c..000000000
Binary files a/crm/public/manifest/apple-splash-1170-2532.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-1179-2556.jpg b/crm/public/manifest/apple-splash-1179-2556.jpg
deleted file mode 100644
index c0c6833bb..000000000
Binary files a/crm/public/manifest/apple-splash-1179-2556.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-1242-2208.jpg b/crm/public/manifest/apple-splash-1242-2208.jpg
deleted file mode 100644
index f084d3524..000000000
Binary files a/crm/public/manifest/apple-splash-1242-2208.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-1242-2688.jpg b/crm/public/manifest/apple-splash-1242-2688.jpg
deleted file mode 100644
index bdeff56e9..000000000
Binary files a/crm/public/manifest/apple-splash-1242-2688.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-1284-2778.jpg b/crm/public/manifest/apple-splash-1284-2778.jpg
deleted file mode 100644
index 29a25be4f..000000000
Binary files a/crm/public/manifest/apple-splash-1284-2778.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-1290-2796.jpg b/crm/public/manifest/apple-splash-1290-2796.jpg
deleted file mode 100644
index 0fca8c137..000000000
Binary files a/crm/public/manifest/apple-splash-1290-2796.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-1334-750.jpg b/crm/public/manifest/apple-splash-1334-750.jpg
deleted file mode 100644
index ec3ea1b84..000000000
Binary files a/crm/public/manifest/apple-splash-1334-750.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-1488-2266.jpg b/crm/public/manifest/apple-splash-1488-2266.jpg
deleted file mode 100644
index 1d7cb4809..000000000
Binary files a/crm/public/manifest/apple-splash-1488-2266.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-1536-2048.jpg b/crm/public/manifest/apple-splash-1536-2048.jpg
deleted file mode 100644
index de0157417..000000000
Binary files a/crm/public/manifest/apple-splash-1536-2048.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-1620-2160.jpg b/crm/public/manifest/apple-splash-1620-2160.jpg
deleted file mode 100644
index cde84c9b4..000000000
Binary files a/crm/public/manifest/apple-splash-1620-2160.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-1640-2360.jpg b/crm/public/manifest/apple-splash-1640-2360.jpg
deleted file mode 100644
index a33bfd031..000000000
Binary files a/crm/public/manifest/apple-splash-1640-2360.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-1668-2224.jpg b/crm/public/manifest/apple-splash-1668-2224.jpg
deleted file mode 100644
index d8ccacbf2..000000000
Binary files a/crm/public/manifest/apple-splash-1668-2224.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-1668-2388.jpg b/crm/public/manifest/apple-splash-1668-2388.jpg
deleted file mode 100644
index bb2ebc8d5..000000000
Binary files a/crm/public/manifest/apple-splash-1668-2388.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-1792-828.jpg b/crm/public/manifest/apple-splash-1792-828.jpg
deleted file mode 100644
index f085722df..000000000
Binary files a/crm/public/manifest/apple-splash-1792-828.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-2048-1536.jpg b/crm/public/manifest/apple-splash-2048-1536.jpg
deleted file mode 100644
index f4213add2..000000000
Binary files a/crm/public/manifest/apple-splash-2048-1536.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-2048-2732.jpg b/crm/public/manifest/apple-splash-2048-2732.jpg
deleted file mode 100644
index 70b1e47c2..000000000
Binary files a/crm/public/manifest/apple-splash-2048-2732.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-2160-1620.jpg b/crm/public/manifest/apple-splash-2160-1620.jpg
deleted file mode 100644
index 7398911f1..000000000
Binary files a/crm/public/manifest/apple-splash-2160-1620.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-2208-1242.jpg b/crm/public/manifest/apple-splash-2208-1242.jpg
deleted file mode 100644
index 0da15d616..000000000
Binary files a/crm/public/manifest/apple-splash-2208-1242.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-2266-1488.jpg b/crm/public/manifest/apple-splash-2266-1488.jpg
deleted file mode 100644
index 5b8d7f16f..000000000
Binary files a/crm/public/manifest/apple-splash-2266-1488.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-2360-1640.jpg b/crm/public/manifest/apple-splash-2360-1640.jpg
deleted file mode 100644
index 6b112de41..000000000
Binary files a/crm/public/manifest/apple-splash-2360-1640.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-2388-1668.jpg b/crm/public/manifest/apple-splash-2388-1668.jpg
deleted file mode 100644
index 62b35cdf8..000000000
Binary files a/crm/public/manifest/apple-splash-2388-1668.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-2436-1125.jpg b/crm/public/manifest/apple-splash-2436-1125.jpg
deleted file mode 100644
index 30fe3e975..000000000
Binary files a/crm/public/manifest/apple-splash-2436-1125.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-2532-1170.jpg b/crm/public/manifest/apple-splash-2532-1170.jpg
deleted file mode 100644
index 420ecd299..000000000
Binary files a/crm/public/manifest/apple-splash-2532-1170.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-2556-1179.jpg b/crm/public/manifest/apple-splash-2556-1179.jpg
deleted file mode 100644
index a1d4841b0..000000000
Binary files a/crm/public/manifest/apple-splash-2556-1179.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-2688-1242.jpg b/crm/public/manifest/apple-splash-2688-1242.jpg
deleted file mode 100644
index 6c445c960..000000000
Binary files a/crm/public/manifest/apple-splash-2688-1242.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-2732-2048.jpg b/crm/public/manifest/apple-splash-2732-2048.jpg
deleted file mode 100644
index f4a7fe2bc..000000000
Binary files a/crm/public/manifest/apple-splash-2732-2048.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-2778-1284.jpg b/crm/public/manifest/apple-splash-2778-1284.jpg
deleted file mode 100644
index bef50099a..000000000
Binary files a/crm/public/manifest/apple-splash-2778-1284.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-2796-1290.jpg b/crm/public/manifest/apple-splash-2796-1290.jpg
deleted file mode 100644
index e55a267f2..000000000
Binary files a/crm/public/manifest/apple-splash-2796-1290.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-640-1136.jpg b/crm/public/manifest/apple-splash-640-1136.jpg
deleted file mode 100644
index d8bb053cc..000000000
Binary files a/crm/public/manifest/apple-splash-640-1136.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-750-1334.jpg b/crm/public/manifest/apple-splash-750-1334.jpg
deleted file mode 100644
index 4e3713f04..000000000
Binary files a/crm/public/manifest/apple-splash-750-1334.jpg and /dev/null differ
diff --git a/crm/public/manifest/apple-splash-828-1792.jpg b/crm/public/manifest/apple-splash-828-1792.jpg
deleted file mode 100644
index ae35bb0bf..000000000
Binary files a/crm/public/manifest/apple-splash-828-1792.jpg and /dev/null differ
diff --git a/crm/public/manifest/manifest-icon-192.maskable.png b/crm/public/manifest/manifest-icon-192.maskable.png
deleted file mode 100644
index a600fecbd..000000000
Binary files a/crm/public/manifest/manifest-icon-192.maskable.png and /dev/null differ
diff --git a/crm/public/manifest/manifest-icon-512.maskable.png b/crm/public/manifest/manifest-icon-512.maskable.png
deleted file mode 100644
index de352b1ef..000000000
Binary files a/crm/public/manifest/manifest-icon-512.maskable.png and /dev/null differ
diff --git a/crm/templates/__init__.py b/crm/templates/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/crm/templates/emails/crm_invitation.html b/crm/templates/emails/crm_invitation.html
deleted file mode 100644
index 3e74b1d1d..000000000
--- a/crm/templates/emails/crm_invitation.html
+++ /dev/null
@@ -1,4 +0,0 @@
-You have been invited to join Frappe CRM
-
- Accept Invitation
-
diff --git a/crm/templates/pages/__init__.py b/crm/templates/pages/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/crm/uninstall.py b/crm/uninstall.py
deleted file mode 100644
index 34622d0b8..000000000
--- a/crm/uninstall.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
-# MIT License. See license.txt
-from __future__ import unicode_literals
-import click
-import frappe
-
-def before_uninstall():
- delete_email_template_custom_fields()
-
-def delete_email_template_custom_fields():
- if frappe.get_meta("Email Template").has_field("enabled"):
- click.secho("* Uninstalling Custom Fields from Email Template")
-
- fieldnames = (
- "enabled",
- "reference_doctype",
- )
-
- for fieldname in fieldnames:
- frappe.db.delete("Custom Field", {"name": "Email Template-" + fieldname})
-
- frappe.clear_cache(doctype="Email Template")
\ No newline at end of file
diff --git a/crm/www/__init__.py b/crm/www/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 75e2d142b..97051cd69 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -1,5 +1,5 @@
version: "3.7"
-name: crm
+name: next_crm
services:
mariadb:
image: mariadb:10.8
diff --git a/docker/init.sh b/docker/init.sh
index d9b89d811..4cb8361c5 100644
--- a/docker/init.sh
+++ b/docker/init.sh
@@ -30,10 +30,10 @@ bench new-site crm.localhost \
--admin-password admin \
--no-mariadb-socket
-bench --site crm.localhost install-app crm
+bench --site crm.localhost install-app next_crm
bench --site crm.localhost set-config developer_mode 1
bench --site crm.localhost clear-cache
bench --site crm.localhost set-config mute_emails 1
bench use crm.localhost
-bench start
\ No newline at end of file
+bench start
diff --git a/frontend/index.html b/frontend/index.html
index b9a44e485..fdaa97d0b 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -6,9 +6,9 @@
name="viewport"
content="width=device-width, initial-scale=1.0, viewport-fit=cover maximum-scale=1.0, user-scalable=no"
/>
- Frappe CRM
+ Next CRM
-
+
@@ -16,181 +16,181 @@
rel="icon"
type="image/png"
sizes="196x196"
- href="/assets/crm/manifest/apple-icon-180.png"
+ href="/assets/next_crm/manifest/apple-icon-180.png"
/>
diff --git a/frontend/package.json b/frontend/package.json
index 566839632..f794b8d09 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -4,8 +4,8 @@
"version": "0.0.0",
"scripts": {
"dev": "vite",
- "build": "vite build --base=/assets/crm/frontend/ && yarn copy-html-entry",
- "copy-html-entry": "cp ../crm/public/frontend/index.html ../crm/www/crm.html",
+ "build": "vite build --base=/assets/next_crm/frontend/ && yarn copy-html-entry",
+ "copy-html-entry": "cp ../next_crm/public/frontend/index.html ../next_crm/www/next-crm/index.html",
"serve": "vite preview"
},
"dependencies": {
diff --git a/frontend/src/components/Activities/Activities.vue b/frontend/src/components/Activities/Activities.vue
index 2a3f3dd67..c3623dc10 100644
--- a/frontend/src/components/Activities/Activities.vue
+++ b/frontend/src/components/Activities/Activities.vue
@@ -63,8 +63,8 @@
-
-
+
+
@@ -393,9 +393,9 @@
@click="emailBox.showComment = true"
/>
{
}
const all_activities = createResource({
- url: 'crm.api.activities.get_activities',
+ url: 'next_crm.api.activities.get_activities',
params: { name: doc.value.data.name },
cache: ['activity', doc.value.data.name],
auto: true,
- transform: ([versions, calls, notes, tasks, attachments]) => {
+ transform: ([versions, calls, notes, todos, attachments]) => {
if (calls?.length) {
calls.forEach((doc) => {
doc.show_recording = false
@@ -581,14 +581,14 @@ const all_activities = createResource({
}
})
}
- return { versions, calls, notes, tasks, attachments }
+ return { versions, calls, notes, todos, attachments }
},
})
const showWhatsappTemplates = ref(false)
const whatsappMessages = createResource({
- url: 'crm.api.whatsapp.get_whatsapp_messages',
+ url: 'next_crm.api.whatsapp.get_whatsapp_messages',
cache: ['whatsapp_messages', doc.value.data.name],
params: {
reference_doctype: props.doctype,
@@ -626,7 +626,7 @@ function sendTemplate(template) {
showWhatsappTemplates.value = false
capture('send_whatsapp_template', { doctype: props.doctype })
createResource({
- url: 'crm.api.whatsapp.send_whatsapp_template',
+ url: 'next_crm.api.whatsapp.send_whatsapp_template',
params: {
reference_doctype: props.doctype,
reference_name: doc.value.data.name,
@@ -663,9 +663,9 @@ const activities = computed(() => {
} else if (title.value == 'Calls') {
if (!all_activities.data?.calls) return []
return sortByCreation(all_activities.data.calls)
- } else if (title.value == 'Tasks') {
- if (!all_activities.data?.tasks) return []
- return sortByCreation(all_activities.data.tasks)
+ } else if (title.value == 'ToDos') {
+ if (!all_activities.data?.todos) return []
+ return sortByCreation(all_activities.data.todos)
} else if (title.value == 'Notes') {
if (!all_activities.data?.notes) return []
return sortByCreation(all_activities.data.notes)
@@ -731,8 +731,8 @@ const emptyText = computed(() => {
text = 'No Call Logs'
} else if (title.value == 'Notes') {
text = 'No Notes'
- } else if (title.value == 'Tasks') {
- text = 'No Tasks'
+ } else if (title.value == 'ToDos') {
+ text = 'No ToDos'
} else if (title.value == 'Attachments') {
text = 'No Attachments'
} else if (title.value == 'WhatsApp') {
@@ -751,8 +751,8 @@ const emptyTextIcon = computed(() => {
icon = PhoneIcon
} else if (title.value == 'Notes') {
icon = NoteIcon
- } else if (title.value == 'Tasks') {
- icon = TaskIcon
+ } else if (title.value == 'ToDos') {
+ icon = ToDoIcon
} else if (title.value == 'Attachments') {
icon = AttachmentIcon
} else if (title.value == 'WhatsApp') {
@@ -765,10 +765,10 @@ function timelineIcon(activity_type, is_lead) {
let icon
switch (activity_type) {
case 'creation':
- icon = is_lead ? LeadsIcon : DealsIcon
+ icon = is_lead ? LeadsIcon : OpportunitiesIcon
break
- case 'deal':
- icon = DealsIcon
+ case 'opportunity':
+ icon = OpportunitiesIcon
break
case 'comment':
icon = CommentIcon
@@ -801,7 +801,7 @@ watch([reload, reload_email], ([reload_value, reload_email_value]) => {
})
function scroll(hash) {
- if (['tasks', 'notes'].includes(route.hash?.slice(1))) return
+ if (['todos', 'notes'].includes(route.hash?.slice(1))) return
setTimeout(() => {
let el
if (!hash) {
diff --git a/frontend/src/components/Activities/ActivityHeader.vue b/frontend/src/components/Activities/ActivityHeader.vue
index 9f734d4ee..4e39db036 100644
--- a/frontend/src/components/Activities/ActivityHeader.vue
+++ b/frontend/src/components/Activities/ActivityHeader.vue
@@ -46,14 +46,14 @@
{{ __('New Note') }}
- {{ __('New Task') }}
+ {{ __('New ToDo') }}
{
onClick: () => props.modalRef.showNote(),
},
{
- icon: h(TaskIcon, { class: 'h-4 w-4' }),
- label: __('New Task'),
- onClick: () => props.modalRef.showTask(),
+ icon: h(ToDoIcon, { class: 'h-4 w-4' }),
+ label: __('New ToDo'),
+ onClick: () => props.modalRef.showToDo(),
},
{
icon: h(AttachmentIcon, { class: 'h-4 w-4' }),
diff --git a/frontend/src/components/Activities/AllModals.vue b/frontend/src/components/Activities/AllModals.vue
index 1217b0390..c60c2b5e3 100644
--- a/frontend/src/components/Activities/AllModals.vue
+++ b/frontend/src/components/Activities/AllModals.vue
@@ -1,11 +1,11 @@
-
diff --git a/frontend/src/components/Activities/AudioPlayer.vue b/frontend/src/components/Activities/AudioPlayer.vue
index 8ede3c026..40723bb70 100644
--- a/frontend/src/components/Activities/AudioPlayer.vue
+++ b/frontend/src/components/Activities/AudioPlayer.vue
@@ -21,9 +21,7 @@
step="0.01"
@input="(e) => (audio.currentTime = e.target.value)"
/>
-
- {{ formatTime(currentTime) }} / {{ formatTime(duration) }}
-
+ {{ formatTime(currentTime) }} / {{ formatTime(duration) }}
@@ -42,21 +40,9 @@
/>
-
-
-
+
+
+
diff --git a/frontend/src/components/Activities/NoteArea.vue b/frontend/src/components/Activities/NoteArea.vue
index 64b7293af..d93169770 100644
--- a/frontend/src/components/Activities/NoteArea.vue
+++ b/frontend/src/components/Activities/NoteArea.vue
@@ -65,7 +65,7 @@ const { getUser } = usersStore()
async function deleteNote(name) {
await call('frappe.client.delete', {
- doctype: 'FCRM Note',
+ doctype: 'NCRM Note',
name,
})
notes.reload()
diff --git a/frontend/src/components/Activities/TaskArea.vue b/frontend/src/components/Activities/ToDoArea.vue
similarity index 69%
rename from frontend/src/components/Activities/TaskArea.vue
rename to frontend/src/components/Activities/ToDoArea.vue
index 931c0007f..2d489d30e 100644
--- a/frontend/src/components/Activities/TaskArea.vue
+++ b/frontend/src/components/Activities/ToDoArea.vue
@@ -1,29 +1,29 @@
-
-
+
+
- {{ task.title }}
+ {{ todo.custom_title }}
-
- {{ getUser(task.assigned_to).full_name }}
+
+ {{ getUser(todo.allocated_to).full_name }}
-
+
-
+
-
{{ dateFormat(task.due_date, 'D MMM, hh:mm a') }}
+
{{ dateFormat(todo.date, 'D MMM, hh:mm a') }}
@@ -31,19 +31,19 @@
-
- {{ task.priority }}
+
+ {{ todo.priority }}
-
+
@@ -54,15 +54,15 @@
icon: 'trash-2',
onClick: () => {
$dialog({
- title: __('Delete Task'),
- message: __('Are you sure you want to delete this task?'),
+ title: __('Delete ToDo'),
+ message: __('Are you sure you want to delete this todo?'),
actions: [
{
label: __('Delete'),
theme: 'red',
variant: 'solid',
onClick(close) {
- modalRef.deleteTask(task.name)
+ modalRef.deleteToDo(todo.name)
close()
},
},
@@ -82,7 +82,7 @@
@@ -90,17 +90,17 @@
diff --git a/frontend/src/components/ColumnSettings.vue b/frontend/src/components/ColumnSettings.vue
index 817a70c31..b3deb507d 100644
--- a/frontend/src/components/ColumnSettings.vue
+++ b/frontend/src/components/ColumnSettings.vue
@@ -11,9 +11,7 @@
-
+
-
+
-
+
@@ -50,11 +40,7 @@
-
addColumn(e)"
- >
+ addColumn(e)">
-
@@ -295,6 +265,6 @@ watchOnce(
oldValues.value.columns = JSON.parse(JSON.stringify(val.columns))
oldValues.value.rows = JSON.parse(JSON.stringify(val.rows))
oldValues.value.isDefault = val.is_default
- }
+ },
)
diff --git a/frontend/src/components/CommentBox.vue b/frontend/src/components/CommentBox.vue
index 8b61d09bd..abd1c2375 100644
--- a/frontend/src/components/CommentBox.vue
+++ b/frontend/src/components/CommentBox.vue
@@ -107,7 +107,7 @@ const props = defineProps({
},
doctype: {
type: String,
- default: 'CRM Lead',
+ default: 'Lead',
},
editorProps: {
type: Object,
diff --git a/frontend/src/components/CommunicationArea.vue b/frontend/src/components/CommunicationArea.vue
index 384b75fc0..8010d504c 100644
--- a/frontend/src/components/CommunicationArea.vue
+++ b/frontend/src/components/CommunicationArea.vue
@@ -54,9 +54,7 @@
v-model:attachments="attachments"
:doctype="doctype"
:subject="subject"
- :placeholder="
- __('Hi John, \n\nCan you please provide more details on this...')
- "
+ :placeholder="__('Hi John, \n\nCan you please provide more details on this...')"
/>
@@ -97,7 +95,7 @@ import { ref, watch, computed } from 'vue'
const props = defineProps({
doctype: {
type: String,
- default: 'CRM Lead',
+ default: 'Lead',
},
})
@@ -121,14 +119,14 @@ const subject = computed(() => {
let prefix = ''
if (doc.value.data?.lead_name) {
prefix = doc.value.data.lead_name
- } else if (doc.value.data?.organization) {
- prefix = doc.value.data.organization
+ } else if (doc.value.data?.customer) {
+ prefix = doc.value.data.customer
}
return `${prefix} (#${doc.value.data.name})`
})
const signature = createResource({
- url: 'crm.api.get_user_signature',
+ url: 'next_crm.api.get_user_signature',
cache: 'user-email-signature',
auto: true,
})
@@ -137,9 +135,7 @@ function setSignature(editor) {
if (!signature.data) return
signature.data = signature.data.replace(/\n/g, '
')
let emailContent = editor.getHTML()
- emailContent = emailContent.startsWith('
')
- ? emailContent.slice(7)
- : emailContent
+ emailContent = emailContent.startsWith('
') ? emailContent.slice(7) : emailContent
editor.commands.setContent(signature.data + emailContent)
editor.commands.focus('start')
}
@@ -152,7 +148,7 @@ watch(
editor.commands.focus()
setSignature(editor)
}
- }
+ },
)
watch(
@@ -161,7 +157,7 @@ watch(
if (value) {
newCommentEditor.value.editor.commands.focus()
}
- }
+ },
)
const commentEmpty = computed(() => {
@@ -206,7 +202,7 @@ async function sendComment() {
})
if (comment && attachments.value.length) {
capture('comment_attachments_added')
- await call('crm.api.comment.add_attachments', {
+ await call('next_crm.api.comment.add_attachments', {
name: comment.name,
attachments: attachments.value.map((x) => x.name),
})
diff --git a/frontend/src/components/Controls/Link.vue b/frontend/src/components/Controls/Link.vue
index 3bcb3d7f7..5f3d9e87b 100644
--- a/frontend/src/components/Controls/Link.vue
+++ b/frontend/src/components/Controls/Link.vue
@@ -42,12 +42,7 @@
-
clearValue(close)"
- >
+ clearValue(close)">
@@ -92,10 +87,7 @@ const valuePropPassed = computed(() => 'value' in attrs)
const value = computed({
get: () => (valuePropPassed.value ? attrs.value : props.modelValue),
set: (val) => {
- return (
- val?.value &&
- emit(valuePropPassed.value ? 'change' : 'update:modelValue', val?.value)
- )
+ return val?.value && emit(valuePropPassed.value ? 'change' : 'update:modelValue', val?.value)
},
})
@@ -110,13 +102,13 @@ watchDebounced(
text.value = val
reload(val)
},
- { debounce: 300, immediate: true }
+ { debounce: 300, immediate: true },
)
watchDebounced(
() => props.doctype,
() => reload(''),
- { debounce: 300, immediate: true }
+ { debounce: 300, immediate: true },
)
const options = createResource({
@@ -146,12 +138,7 @@ const options = createResource({
})
function reload(val) {
- if (
- options.data?.length &&
- val === options.params?.txt &&
- props.doctype === options.params?.doctype
- )
- return
+ if (options.data?.length && val === options.params?.txt && props.doctype === options.params?.doctype) return
options.update({
params: {
diff --git a/frontend/src/components/Controls/MultiselectInput.vue b/frontend/src/components/Controls/MultiselectInput.vue
index a5cb6928e..fd4af4f55 100644
--- a/frontend/src/components/Controls/MultiselectInput.vue
+++ b/frontend/src/components/Controls/MultiselectInput.vue
@@ -12,11 +12,7 @@
@keydown.delete.capture.stop="removeLastValue"
>
-
+
@@ -42,27 +38,15 @@
-
-
+
+
-
+
{{ option.label }}
@@ -86,12 +70,7 @@
diff --git a/frontend/src/components/EmailEditor.vue b/frontend/src/components/EmailEditor.vue
index b33f22dfd..da9e0b7a0 100644
--- a/frontend/src/components/EmailEditor.vue
+++ b/frontend/src/components/EmailEditor.vue
@@ -192,7 +192,7 @@ const props = defineProps({
},
doctype: {
type: String,
- default: 'CRM Lead',
+ default: 'Lead',
},
subject: {
type: String,
diff --git a/frontend/src/components/FadedScrollableDiv.vue b/frontend/src/components/FadedScrollableDiv.vue
index 3cc44638c..12cd066ce 100644
--- a/frontend/src/components/FadedScrollableDiv.vue
+++ b/frontend/src/components/FadedScrollableDiv.vue
@@ -1,10 +1,5 @@
-
+
@@ -28,9 +23,7 @@ const props = defineProps({
const scrollableDiv = ref(null)
const maskStyle = ref('none')
-const side = computed(() =>
- props.orientation == 'horizontal' ? 'right' : 'bottom'
-)
+const side = computed(() => (props.orientation == 'horizontal' ? 'right' : 'bottom'))
function updateMaskStyle() {
if (!scrollableDiv.value) return
@@ -45,10 +38,7 @@ function updateMaskStyle() {
maskStyle.value = 'none'
// faded on both sides
- if (
- (side.value == 'right' && scrollWidth > clientWidth) ||
- (side.value == 'bottom' && scrollHeight > clientHeight)
- ) {
+ if ((side.value == 'right' && scrollWidth > clientWidth) || (side.value == 'bottom' && scrollHeight > clientHeight)) {
maskStyle.value = `linear-gradient(to ${side.value}, transparent, black ${props.maskLength}px, black calc(100% - ${props.maskLength}px), transparent);`
}
@@ -61,10 +51,7 @@ function updateMaskStyle() {
}
// faded on right or bottom
- if (
- (side.value == 'right' && scrollLeft == 0) ||
- (side.value == 'bottom' && scrollTop == 0)
- ) {
+ if ((side.value == 'right' && scrollLeft == 0) || (side.value == 'bottom' && scrollTop == 0)) {
maskStyle.value = `linear-gradient(to ${side.value}, black calc(100% - ${props.maskLength}px), transparent 100%);`
}
diff --git a/frontend/src/components/FilesUploader/FilesUploaderArea.vue b/frontend/src/components/FilesUploader/FilesUploaderArea.vue
index 2f9402983..367b860eb 100644
--- a/frontend/src/components/FilesUploader/FilesUploaderArea.vue
+++ b/frontend/src/components/FilesUploader/FilesUploaderArea.vue
@@ -163,7 +163,7 @@ const makeAttachmentsPublic = ref(props.options.makeAttachmentsPublic || false)
onMounted(() => {
createResource({
- url: 'crm.api.get_file_uploader_defaults',
+ url: 'next_crm.api.get_file_uploader_defaults',
params: { doctype: props.doctype },
cache: ['file_uploader_defaults', props.doctype],
auto: true,
diff --git a/frontend/src/components/Filter.vue b/frontend/src/components/Filter.vue
index 5a44c11ca..cc890ee79 100644
--- a/frontend/src/components/Filter.vue
+++ b/frontend/src/components/Filter.vue
@@ -193,7 +193,7 @@ const emit = defineEmits(['update'])
const list = defineModel()
const filterableFields = createResource({
- url: 'crm.api.doc.get_filterable_fields',
+ url: 'next_crm.api.doc.get_filterable_fields',
cache: ['filterableFields', props.doctype],
params: {
doctype: props.doctype,
diff --git a/frontend/src/components/GroupBy.vue b/frontend/src/components/GroupBy.vue
index a856d0cd5..f3fc10ca7 100644
--- a/frontend/src/components/GroupBy.vue
+++ b/frontend/src/components/GroupBy.vue
@@ -2,21 +2,14 @@
setGroupBy(e)">
-
+
@@ -49,7 +42,7 @@ const groupByValue = ref({
})
const groupByOptions = createResource({
- url: 'crm.api.doc.get_group_by_fields',
+ url: 'next_crm.api.doc.get_group_by_fields',
cache: ['groupByOptions', props.doctype],
params: {
doctype: props.doctype,
@@ -70,8 +63,6 @@ const options = computed(() => {
if (!groupByOptions.data) return []
if (!list.value?.data?.group_by_field) return groupByOptions.data
groupByValue.value = list.value.data.group_by_field
- return groupByOptions.data.filter(
- (option) => option !== groupByValue.value.value
- )
+ return groupByOptions.data.filter((option) => option !== groupByValue.value.value)
})
diff --git a/frontend/src/components/Icons/CRMLogo.vue b/frontend/src/components/Icons/CRMLogo.vue
index dffdb82ab..7efdae418 100644
--- a/frontend/src/components/Icons/CRMLogo.vue
+++ b/frontend/src/components/Icons/CRMLogo.vue
@@ -1,18 +1,12 @@
-
+
+
-
diff --git a/frontend/src/components/Icons/OrganizationsIcon.vue b/frontend/src/components/Icons/CustomersIcon.vue
similarity index 100%
rename from frontend/src/components/Icons/OrganizationsIcon.vue
rename to frontend/src/components/Icons/CustomersIcon.vue
diff --git a/frontend/src/components/Icons/ERPNextIcon.vue b/frontend/src/components/Icons/ERPNextIcon.vue
index e512b92db..f6afd9e62 100644
--- a/frontend/src/components/Icons/ERPNextIcon.vue
+++ b/frontend/src/components/Icons/ERPNextIcon.vue
@@ -1,20 +1,14 @@
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
diff --git a/frontend/src/components/Icons/DealsIcon.vue b/frontend/src/components/Icons/OpportunitiesIcon.vue
similarity index 100%
rename from frontend/src/components/Icons/DealsIcon.vue
rename to frontend/src/components/Icons/OpportunitiesIcon.vue
diff --git a/frontend/src/components/Icons/TaskIcon.vue b/frontend/src/components/Icons/ToDoIcon.vue
similarity index 100%
rename from frontend/src/components/Icons/TaskIcon.vue
rename to frontend/src/components/Icons/ToDoIcon.vue
diff --git a/frontend/src/components/Icons/TaskPriorityIcon.vue b/frontend/src/components/Icons/ToDoPriorityIcon.vue
similarity index 53%
rename from frontend/src/components/Icons/TaskPriorityIcon.vue
rename to frontend/src/components/Icons/ToDoPriorityIcon.vue
index dc2fdf6c2..a05a4d0d9 100644
--- a/frontend/src/components/Icons/TaskPriorityIcon.vue
+++ b/frontend/src/components/Icons/ToDoPriorityIcon.vue
@@ -2,17 +2,20 @@
diff --git a/frontend/src/components/ListViews/OrganizationsListView.vue b/frontend/src/components/ListViews/CustomersListView.vue
similarity index 81%
rename from frontend/src/components/ListViews/OrganizationsListView.vue
rename to frontend/src/components/ListViews/CustomersListView.vue
index 6099ba8d0..01c1fbcf7 100644
--- a/frontend/src/components/ListViews/OrganizationsListView.vue
+++ b/frontend/src/components/ListViews/CustomersListView.vue
@@ -4,8 +4,8 @@
:rows="rows"
:options="{
getRowRoute: (row) => ({
- name: 'Organization',
- params: { organizationId: row.name },
+ name: 'Customer',
+ params: { customerId: row.name },
query: { view: route.query.view, viewType: route.params.viewType },
}),
selectable: options.selectable,
@@ -33,22 +33,11 @@
-
+
-
-
+
- emit('likeDoc', { name: row.name, liked: isLiked(item) })
- "
+ @click.stop.prevent="() => emit('likeDoc', { name: row.name, liked: isLiked(item) })"
>
@@ -113,9 +94,7 @@
-
+
@@ -133,7 +112,7 @@
{
const listBulkActionsRef = ref(null)
defineExpose({
- customListActions: computed(
- () => listBulkActionsRef.value?.customListActions
- ),
+ customListActions: computed(() => listBulkActionsRef.value?.customListActions),
})
diff --git a/frontend/src/components/ListViews/EmailTemplatesListView.vue b/frontend/src/components/ListViews/EmailTemplatesListView.vue
index 88ada4a9c..23bca810e 100644
--- a/frontend/src/components/ListViews/EmailTemplatesListView.vue
+++ b/frontend/src/components/ListViews/EmailTemplatesListView.vue
@@ -29,12 +29,7 @@
-
+