Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 198f8fdf39 | |||
| f24bb7e78e | |||
| c8d0c64b33 | |||
| bf61808503 | |||
| 7896ad02ed | |||
| 1014bee496 | |||
| 0b47780f86 | |||
| 0a5fbc26c5 | |||
| 201eb36091 | |||
| a9197b7a75 | |||
| 5cbd7aee97 | |||
| e819d8aca0 | |||
| 707722de6d | |||
| 155bed7d19 | |||
| 80d7c52be1 | |||
| c1cec5b6da | |||
| f0ad4d5172 | |||
| 17d0584f12 | |||
| 88380a848d | |||
| b1691bf4c1 | |||
| 7b47bd169d | |||
| 79adb23276 | |||
| a607d41366 | |||
| e6400c939b | |||
| 7d5b6efb17 |
@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "Frappe Bench",
|
||||
"name": "Jingrow Bench",
|
||||
"forwardPorts": [8000, 9000, 6787],
|
||||
"remoteUser": "frappe",
|
||||
"remoteUser": "jingrow",
|
||||
"settings": {
|
||||
"terminal.integrated.defaultProfile.linux": "bash",
|
||||
"debug.node.autoAttach": "disabled"
|
||||
},
|
||||
"dockerComposeFile": "./docker-compose.yml",
|
||||
"service": "frappe",
|
||||
"workspaceFolder": "/workspace/frappe-bench",
|
||||
"service": "jingrow",
|
||||
"workspaceFolder": "/workspace/jingrow-bench",
|
||||
"postCreateCommand": "bash /workspace/scripts/init.sh",
|
||||
"shutdownAction": "stopCompose",
|
||||
"extensions": [
|
||||
|
||||
@ -29,14 +29,14 @@ services:
|
||||
redis-socketio:
|
||||
image: redis:alpine
|
||||
|
||||
frappe:
|
||||
image: frappe/bench:latest
|
||||
jingrow:
|
||||
image: jingrow/bench:latest
|
||||
command: sleep infinity
|
||||
environment:
|
||||
- SHELL=/bin/bash
|
||||
volumes:
|
||||
- ..:/workspace:cached
|
||||
working_dir: /workspace/frappe-bench
|
||||
working_dir: /workspace/jingrow-bench
|
||||
ports:
|
||||
- 8000-8005:8000-8005
|
||||
- 9000-9005:9000-9005
|
||||
|
||||
12
.github/helper/update_pot_file.sh
vendored
12
.github/helper/update_pot_file.sh
vendored
@ -4,9 +4,9 @@ cd ~ || exit
|
||||
|
||||
echo "Setting Up Bench..."
|
||||
|
||||
pip install frappe-bench
|
||||
bench -v init frappe-bench --skip-assets --skip-redis-config-generation --python "$(which python)" --frappe-branch "${BASE_BRANCH}"
|
||||
cd ./frappe-bench || exit
|
||||
pip install jingrow-bench
|
||||
bench -v init jingrow-bench --skip-assets --skip-redis-config-generation --python "$(which python)" --jingrow-branch "${BASE_BRANCH}"
|
||||
cd ./jingrow-bench || exit
|
||||
|
||||
echo "Get FCRM..."
|
||||
bench get-app --skip-assets crm "${GITHUB_WORKSPACE}"
|
||||
@ -18,11 +18,11 @@ cd ./apps/crm || exit
|
||||
|
||||
echo "Configuring git user..."
|
||||
git config user.email "developers@erpnext.com"
|
||||
git config user.name "frappe-pr-bot"
|
||||
git config user.name "jingrow-pr-bot"
|
||||
|
||||
echo "Setting the correct git remote..."
|
||||
# Here, the git remote is a local file path by default. Let's change it to the upstream repo.
|
||||
git remote set-url upstream https://github.com/frappe/crm.git
|
||||
git remote set-url upstream http://git.jingrow.com/jingrow/crm.git
|
||||
|
||||
echo "Creating a new branch..."
|
||||
isodate=$(date -u +"%Y-%m-%d")
|
||||
@ -37,4 +37,4 @@ gh auth setup-git
|
||||
git push -u upstream "${branch_name}"
|
||||
|
||||
echo "Creating a PR..."
|
||||
gh pr create --fill --base "${BASE_BRANCH}" --head "${branch_name}" -R frappe/crm
|
||||
gh pr create --fill --base "${BASE_BRANCH}" --head "${branch_name}" -R jingrow/crm
|
||||
4
.github/workflows/builds.yml
vendored
4
.github/workflows/builds.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
- name: Set Branch
|
||||
run: |
|
||||
export APPS_JSON='[{"url": "https://github.com/${{ github.repository }}","branch": "${{ github.ref_name }}"}]'
|
||||
export APPS_JSON='[{"url": "http://git.jingrow.com/${{ github.repository }}","branch": "${{ github.ref_name }}"}]'
|
||||
echo "APPS_JSON_BASE64=$(echo $APPS_JSON | base64 -w 0)" >> $GITHUB_ENV
|
||||
echo "FRAPPE_BRANCH=${{ github.ref_type == 'tag' || github.ref_name == 'main' && 'version-15' || 'develop' }}" >> $GITHUB_ENV
|
||||
|
||||
@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: frappe/frappe_docker
|
||||
repository: jingrow/jingrow_docker
|
||||
path: builds
|
||||
|
||||
- name: Build and push
|
||||
|
||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@ -73,13 +73,13 @@ jobs:
|
||||
|
||||
- name: Setup
|
||||
run: |
|
||||
pip install frappe-bench
|
||||
bench init --skip-redis-config-generation --skip-assets --python "$(which python)" ~/frappe-bench
|
||||
pip install jingrow-bench
|
||||
bench init --skip-redis-config-generation --skip-assets --python "$(which python)" ~/jingrow-bench
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
|
||||
|
||||
- name: Install
|
||||
working-directory: /home/runner/frappe-bench
|
||||
working-directory: /home/runner/jingrow-bench
|
||||
run: |
|
||||
bench get-app crm $GITHUB_WORKSPACE
|
||||
bench setup requirements --dev
|
||||
@ -90,7 +90,7 @@ jobs:
|
||||
CI: 'Yes'
|
||||
|
||||
- name: Run Tests
|
||||
working-directory: /home/runner/frappe-bench
|
||||
working-directory: /home/runner/jingrow-bench
|
||||
run: |
|
||||
bench --site test_site set-config allow_tests true
|
||||
bench --site test_site run-tests --app crm
|
||||
|
||||
8
.github/workflows/on_release.yml
vendored
8
.github/workflows/on_release.yml
vendored
@ -25,8 +25,8 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
GIT_AUTHOR_NAME: "Frappe PR Bot"
|
||||
GIT_AUTHOR_EMAIL: "developers@frappe.io"
|
||||
GIT_COMMITTER_NAME: "Frappe PR Bot"
|
||||
GIT_COMMITTER_EMAIL: "developers@frappe.io"
|
||||
GIT_AUTHOR_NAME: "Jingrow PR Bot"
|
||||
GIT_AUTHOR_EMAIL: "developers@jingrow.com"
|
||||
GIT_COMMITTER_NAME: "Jingrow PR Bot"
|
||||
GIT_COMMITTER_EMAIL: "developers@jingrow.com"
|
||||
run: npx semantic-release
|
||||
6
.github/workflows/release_notes.yml
vendored
6
.github/workflows/release_notes.yml
vendored
@ -27,13 +27,13 @@ jobs:
|
||||
steps:
|
||||
- name: Update notes
|
||||
run: |
|
||||
NEW_NOTES=$(gh api --method POST -H "Accept: application/vnd.github+json" /repos/frappe/crm/releases/generate-notes -f tag_name=$RELEASE_TAG \
|
||||
NEW_NOTES=$(gh api --method POST -H "Accept: application/vnd.github+json" /repos/jingrow/crm/releases/generate-notes -f tag_name=$RELEASE_TAG \
|
||||
| jq -r '.body' \
|
||||
| sed -E '/^\* (chore|ci|test|docs|style)/d' \
|
||||
| sed -E 's/by @mergify //'
|
||||
)
|
||||
RELEASE_ID=$(gh api -H "Accept: application/vnd.github+json" /repos/frappe/crm/releases/tags/$RELEASE_TAG | jq -r '.id')
|
||||
gh api --method PATCH -H "Accept: application/vnd.github+json" /repos/frappe/crm/releases/$RELEASE_ID -f body="$NEW_NOTES"
|
||||
RELEASE_ID=$(gh api -H "Accept: application/vnd.github+json" /repos/jingrow/crm/releases/tags/$RELEASE_TAG | jq -r '.id')
|
||||
gh api --method PATCH -H "Accept: application/vnd.github+json" /repos/jingrow/crm/releases/$RELEASE_ID -f body="$NEW_NOTES"
|
||||
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -1,3 +1,3 @@
|
||||
[submodule "frappe-ui"]
|
||||
path = frappe-ui
|
||||
url = https://github.com/frappe/frappe-ui
|
||||
[submodule "jingrow-ui"]
|
||||
path = jingrow-ui
|
||||
url = http://git.jingrow.com/jingrow/jingrow-ui
|
||||
|
||||
28
.vscode/launch.json
vendored
28
.vscode/launch.json
vendored
@ -8,11 +8,11 @@
|
||||
"name": "Bench Web",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py",
|
||||
"program": "${workspaceFolder}/jingrow-bench/apps/jingrow/jingrow/utils/bench_helper.py",
|
||||
"args": [
|
||||
"frappe", "serve", "--port", "8000", "--noreload", "--nothreading"
|
||||
"jingrow", "serve", "--port", "8000", "--noreload", "--nothreading"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/frappe-bench/sites",
|
||||
"cwd": "${workspaceFolder}/jingrow-bench/sites",
|
||||
"env": {
|
||||
"DEV_SERVER": "1"
|
||||
}
|
||||
@ -21,11 +21,11 @@
|
||||
"name": "Bench Default Worker",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py",
|
||||
"program": "${workspaceFolder}/jingrow-bench/apps/jingrow/jingrow/utils/bench_helper.py",
|
||||
"args": [
|
||||
"frappe", "worker", "--queue", "default"
|
||||
"jingrow", "worker", "--queue", "default"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/frappe-bench/sites",
|
||||
"cwd": "${workspaceFolder}/jingrow-bench/sites",
|
||||
"env": {
|
||||
"DEV_SERVER": "1"
|
||||
}
|
||||
@ -34,11 +34,11 @@
|
||||
"name": "Bench Short Worker",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py",
|
||||
"program": "${workspaceFolder}/jingrow-bench/apps/jingrow/jingrow/utils/bench_helper.py",
|
||||
"args": [
|
||||
"frappe", "worker", "--queue", "short"
|
||||
"jingrow", "worker", "--queue", "short"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/frappe-bench/sites",
|
||||
"cwd": "${workspaceFolder}/jingrow-bench/sites",
|
||||
"env": {
|
||||
"DEV_SERVER": "1"
|
||||
}
|
||||
@ -47,11 +47,11 @@
|
||||
"name": "Bench Long Worker",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py",
|
||||
"program": "${workspaceFolder}/jingrow-bench/apps/jingrow/jingrow/utils/bench_helper.py",
|
||||
"args": [
|
||||
"frappe", "worker", "--queue", "long"
|
||||
"jingrow", "worker", "--queue", "long"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/frappe-bench/sites",
|
||||
"cwd": "${workspaceFolder}/jingrow-bench/sites",
|
||||
"env": {
|
||||
"DEV_SERVER": "1"
|
||||
}
|
||||
@ -60,8 +60,8 @@
|
||||
"name": "Honcho SocketIO Watch Schedule Worker",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "/home/frappe/.local/bin/honcho",
|
||||
"cwd": "${workspaceFolder}/frappe-bench",
|
||||
"program": "/home/jingrow/.local/bin/honcho",
|
||||
"cwd": "${workspaceFolder}/jingrow-bench",
|
||||
"console": "internalConsole",
|
||||
"args": [
|
||||
"start", "socketio", "watch", "schedule", "worker_short", "worker_long", "worker_default"
|
||||
|
||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -1,5 +1,5 @@
|
||||
{
|
||||
"python.defaultInterpreterPath": "frappe-bench/env/bin/python",
|
||||
"python.defaultInterpreterPath": "jingrow-bench/env/bin/python",
|
||||
"debug.node.autoAttach": "disabled",
|
||||
"sqltools.connections": [
|
||||
{
|
||||
|
||||
204
README.md
204
README.md
@ -1,203 +1,7 @@
|
||||
<div align="center" markdown="1">
|
||||
## Crm
|
||||
|
||||
<a href="https://frappe.io/products/crm">
|
||||
<img src=".github/logo.svg" height="80" alt="Frappe CRM Logo">
|
||||
</a>
|
||||
app description
|
||||
|
||||
<h1>Frappe CRM</h1>
|
||||
#### License
|
||||
|
||||
**Simplify Sales, Amplify Relationships**
|
||||
|
||||
[](https://github.com/frappe/crm/releases)
|
||||
|
||||
<div>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset=".github/screenshots/FrappeCRMHeroImage.png">
|
||||
<img width="1402" alt="Frappe CRM Hero Image" src=".github/screenshots/FrappeCRMHeroImage.png">
|
||||
</picture>
|
||||
</div>
|
||||
|
||||
[Live Demo](https://frappecrm-demo.frappe.cloud/api/method/crm.api.demo.login) - [Website](https://frappe.io/crm) - [Documentation](https://docs.frappe.io/crm)
|
||||
|
||||
</div>
|
||||
|
||||
## Frappe CRM
|
||||
|
||||
Frappe CRM is a simple, affordable, open-source CRM tool designed for modern sales teams with unlimited users. Frappe CRM is crafted for providing a great user experience, packed with features for core CRM activities helping you build strong customer relationships while keeping things clean and organised.
|
||||
|
||||
### Motivation
|
||||
|
||||
The motivation behind building Frappe CRM stems from the need for a simple, customizable, and open-source solution tailored to modern business needs. Many existing CRMs are either too complex, overly generic, or locked behind steep pricing models that hinder accessibility and flexibility. Frappe CRM was designed to bridge this gap, offering a tool that empowers businesses to manage their customer relationships seamlessly while being easy to adapt to specific workflows. Built on the Frappe framework, it prioritizes usability, extensibility, and affordability, making it an ideal choice for growing teams and organizations looking for a CRM that aligns with their unique processes.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **User-Friendly and Flexible:** A simple, intuitive interface that’s easy to navigate and highly customizable, enabling teams to adapt it to their specific processes effortlessly.
|
||||
- **All-in-One Lead/Deal Page:** Consolidate all essential actions and details—like activities, comments, notes, tasks, and more—into a single page for a seamless workflow experience.
|
||||
- **Kanban View:** Manage leads and deals visually with a drag-and-drop Kanban board, offering clarity and efficiency in tracking progress across stages.
|
||||
- **Custom Views:** Design personalized views to organize and display leads and deals using custom filters, sorting, and columns, ensuring quick access to the most relevant information.
|
||||
|
||||
<details>
|
||||
<summary>Screenshots</summary>
|
||||
|
||||
<div>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset=".github/screenshots/LeadList.png">
|
||||
<img width="1402" alt="Lead List" src=".github/screenshots/LeadList.png">
|
||||
</picture>
|
||||
</div>
|
||||
<div>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset=".github/screenshots/LeadPage.png">
|
||||
<img width="1402" alt="Lead Page" src=".github/screenshots/LeadPage.png">
|
||||
</picture>
|
||||
</div>
|
||||
<div>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset=".github/screenshots/EmailTemplate.png">
|
||||
<img width="1402" alt="Email Template" src=".github/screenshots/EmailTemplate.png">
|
||||
</picture>
|
||||
</div>
|
||||
<div>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset=".github/screenshots/CallUI.png">
|
||||
<img width="1402" alt="Call UI" src=".github/screenshots/CallUI.png">
|
||||
</picture>
|
||||
</div>
|
||||
<div>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset=".github/screenshots/CallLog.png">
|
||||
<img width="1402" alt="Call Log" src=".github/screenshots/CallLog.png">
|
||||
</picture>
|
||||
</div>
|
||||
|
||||
</details>
|
||||
|
||||
### Integrations
|
||||
|
||||
- **Twilio:** Integrate Twilio to make and receive calls from the CRM. You can also record calls. It is a built-in integration.
|
||||
- **Exotel:** Integrate Exotel to make and receive calls via agents mobile phone 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.
|
||||
- **ERPNext:** Integrate with [ERPNext](https://erpnext.com) to extend the CRM capabilities to include invoicing, accounting, and more.
|
||||
|
||||
### Under the Hood
|
||||
|
||||
- [Frappe Framework](https://github.com/frappe/frappe): A full-stack web application framework.
|
||||
- [Frappe UI](https://github.com/frappe/frappe-ui): A Vue-based UI library, to provide a modern user interface.
|
||||
|
||||
### Compatibility
|
||||
This app is compatible with the following versions of Frappe and ERPNext:
|
||||
|
||||
| CRM branch | Stability | Frappe branch | ERPNext branch |
|
||||
| :-------------------- | :-------- | :------------------- | :------------------- |
|
||||
| main - v1.x | stable | v15.x | v15.x |
|
||||
| develop - future/v2.x | unstable | develop - future/v16 | develop - future/v16 |
|
||||
|
||||
## Getting Started (Production)
|
||||
|
||||
### Managed Hosting
|
||||
|
||||
Get started with your personal or business site with a few clicks on Frappe Cloud - our official hosting service.
|
||||
<div>
|
||||
<a href="https://frappecloud.com/crm/signup" target="_blank">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://frappe.io/files/try-on-fc-white.png">
|
||||
<img src="https://frappe.io/files/try-on-fc-black.png" alt="Try on Frappe Cloud" height="28" />
|
||||
</picture>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
### Self Hosting
|
||||
|
||||
Follow these steps to set up Frappe CRM in production:
|
||||
|
||||
**Step 1**: Download the easy install script
|
||||
|
||||
```bash
|
||||
wget https://frappe.io/easy-install.py
|
||||
```
|
||||
|
||||
**Step 2**: Run the deployment command
|
||||
|
||||
```bash
|
||||
python3 ./easy-install.py deploy \
|
||||
--project=crm_prod_setup \
|
||||
--email=email.example.com \
|
||||
--image=ghcr.io/frappe/crm \
|
||||
--version=stable \
|
||||
--app=crm \
|
||||
--sitename subdomain.domain.tld
|
||||
```
|
||||
|
||||
Replace the following parameters with your values:
|
||||
|
||||
- `email.example.com`: Your email address
|
||||
- `subdomain.domain.tld`: Your domain name where CRM will be hosted
|
||||
|
||||
The script will set up a production-ready instance of Frappe CRM with all the necessary configurations in about 5 minutes.
|
||||
|
||||
## Getting Started (Development)
|
||||
|
||||
### Local Setup
|
||||
|
||||
1. [Setup Bench](https://docs.frappe.io/framework/user/en/installation).
|
||||
1. In the frappe-bench directory, run `bench start` and keep it running.
|
||||
1. Open a new terminal session and cd into `frappe-bench` directory and run following commands:
|
||||
```sh
|
||||
$ bench get-app crm
|
||||
$ bench new-site sitename.localhost --install-app crm
|
||||
$ bench browse sitename.localhost --user Administrator
|
||||
```
|
||||
1. Access the crm page at `sitename.localhost:8000/crm` in your web browser.
|
||||
|
||||
**For Frontend Development**
|
||||
1. Open a new terminal session and cd into `frappe-bench/apps/crm`, and run the following commands:
|
||||
```
|
||||
yarn install
|
||||
yarn dev
|
||||
```
|
||||
1. Now, you can access the site on vite dev server at `http://sitename.localhost:8080`
|
||||
|
||||
**Note:** You'll find all the code related to Frappe CRM's frontend inside `frappe-bench/apps/crm/frontend`
|
||||
|
||||
### Docker
|
||||
|
||||
You need Docker, docker-compose and git setup on your machine. Refer [Docker documentation](https://docs.docker.com/). After that, follow below steps:
|
||||
|
||||
**Step 1**: Setup folder and download the required files
|
||||
|
||||
mkdir frappe-crm
|
||||
cd frappe-crm
|
||||
|
||||
# Download the docker-compose file
|
||||
wget -O docker-compose.yml https://raw.githubusercontent.com/frappe/crm/develop/docker/docker-compose.yml
|
||||
|
||||
# Download the setup script
|
||||
wget -O init.sh https://raw.githubusercontent.com/frappe/crm/develop/docker/init.sh
|
||||
|
||||
**Step 2**: Run the container and daemonize it
|
||||
|
||||
docker compose up -d
|
||||
|
||||
**Step 3**: The site [http://crm.localhost:8000/crm](http://crm.localhost:8000/crm) should now be available. The default credentials are:
|
||||
|
||||
- Username: Administrator
|
||||
- Password: admin
|
||||
|
||||
## Learn and connect
|
||||
|
||||
- [Telegram Public Group](https://t.me/frappecrm)
|
||||
- [Discuss Forum](https://discuss.frappe.io/c/frappe-crm)
|
||||
- [Documentation](https://docs.frappe.io/crm)
|
||||
- [YouTube](https://www.youtube.com/channel/UCn3bV5kx77HsVwtnlCeEi_A)
|
||||
- [X/Twitter](https://x.com/frappetech)
|
||||
|
||||
<br>
|
||||
<br>
|
||||
<div align="center" style="padding-top: 0.75rem;">
|
||||
<a href="https://frappe.io" target="_blank">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://frappe.io/files/Frappe-white.png">
|
||||
<img src="https://frappe.io/files/Frappe-black.png" alt="Frappe Technologies" height="28"/>
|
||||
</picture>
|
||||
</a>
|
||||
</div>
|
||||
mit
|
||||
@ -1,4 +1,4 @@
|
||||
|
||||
__version__ = "1.53.1"
|
||||
__title__ = "Frappe CRM"
|
||||
__title__ = "Jingrow CRM"
|
||||
|
||||
|
||||
@ -1,27 +1,27 @@
|
||||
import frappe
|
||||
import jingrow
|
||||
from bs4 import BeautifulSoup
|
||||
from frappe.config import get_modules_from_all_apps_for_user
|
||||
from frappe.core.api.file import get_max_file_size
|
||||
from frappe.translate import get_all_translations
|
||||
from frappe.utils import cstr, split_emails, validate_email_address
|
||||
from frappe.utils.telemetry import POSTHOG_HOST_FIELD, POSTHOG_PROJECT_FIELD
|
||||
from jingrow.config import get_modules_from_all_apps_for_user
|
||||
from jingrow.core.api.file import get_max_file_size
|
||||
from jingrow.translate import get_all_translations
|
||||
from jingrow.utils import cstr, split_emails, validate_email_address
|
||||
from jingrow.utils.telemetry import POSTHOG_HOST_FIELD, POSTHOG_PROJECT_FIELD
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@jingrow.whitelist(allow_guest=True)
|
||||
def get_translations():
|
||||
if frappe.session.user != "Guest":
|
||||
language = frappe.db.get_value("User", frappe.session.user, "language")
|
||||
if jingrow.session.user != "Guest":
|
||||
language = jingrow.db.get_value("User", jingrow.session.user, "language")
|
||||
else:
|
||||
language = frappe.db.get_single_value("System Settings", "language")
|
||||
language = jingrow.db.get_single_value("System Settings", "language")
|
||||
|
||||
return get_all_translations(language)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def get_user_signature():
|
||||
user = frappe.session.user
|
||||
user = jingrow.session.user
|
||||
user_email_signature = (
|
||||
frappe.db.get_value(
|
||||
jingrow.db.get_value(
|
||||
"User",
|
||||
user,
|
||||
"email_signature",
|
||||
@ -30,7 +30,7 @@ def get_user_signature():
|
||||
else None
|
||||
)
|
||||
|
||||
signature = user_email_signature or frappe.db.get_value(
|
||||
signature = user_email_signature or jingrow.db.get_value(
|
||||
"Email Account",
|
||||
{"default_outgoing": 1, "add_signature": 1},
|
||||
"signature",
|
||||
@ -50,18 +50,18 @@ def get_user_signature():
|
||||
return content
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.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(),
|
||||
"posthog_project_id": jingrow.conf.get(POSTHOG_PROJECT_FIELD),
|
||||
"posthog_host": jingrow.conf.get(POSTHOG_HOST_FIELD),
|
||||
"enable_telemetry": jingrow.get_system_settings("enable_telemetry"),
|
||||
"telemetry_site_age": jingrow.utils.telemetry.site_age(),
|
||||
}
|
||||
|
||||
|
||||
def check_app_permission():
|
||||
if frappe.session.user == "Administrator":
|
||||
if jingrow.session.user == "Administrator":
|
||||
return True
|
||||
|
||||
allowed_modules = get_modules_from_all_apps_for_user()
|
||||
@ -69,7 +69,7 @@ def check_app_permission():
|
||||
if "FCRM" not in allowed_modules:
|
||||
return False
|
||||
|
||||
roles = frappe.get_roles()
|
||||
roles = jingrow.get_roles()
|
||||
if any(
|
||||
role in ["System Manager", "Sales User", "Sales Manager"] for role in roles
|
||||
):
|
||||
@ -78,31 +78,31 @@ def check_app_permission():
|
||||
return False
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@jingrow.whitelist(allow_guest=True)
|
||||
def accept_invitation(key: str | None = None):
|
||||
if not key:
|
||||
frappe.throw("Invalid or expired key")
|
||||
jingrow.throw("Invalid or expired key")
|
||||
|
||||
result = frappe.db.get_all("CRM Invitation", filters={"key": key}, pluck="name")
|
||||
result = jingrow.db.get_all("CRM Invitation", filters={"key": key}, pluck="name")
|
||||
if not result:
|
||||
frappe.throw("Invalid or expired key")
|
||||
jingrow.throw("Invalid or expired key")
|
||||
|
||||
invitation = frappe.get_doc("CRM Invitation", result[0])
|
||||
invitation = jingrow.get_pg("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"
|
||||
jingrow.local.login_manager.login_as(invitation.email)
|
||||
jingrow.local.response["type"] = "redirect"
|
||||
jingrow.local.response["location"] = "/crm"
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def invite_by_email(emails: str, role: str):
|
||||
frappe.only_for(["Sales Manager", "System Manager"])
|
||||
jingrow.only_for(["Sales Manager", "System Manager"])
|
||||
|
||||
if role not in ["System Manager", "Sales Manager", "Sales User"]:
|
||||
frappe.throw("Cannot invite for this role")
|
||||
jingrow.throw("Cannot invite for this role")
|
||||
|
||||
if not emails:
|
||||
return
|
||||
@ -110,8 +110,8 @@ def invite_by_email(emails: str, role: str):
|
||||
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(
|
||||
existing_members = jingrow.db.get_all("User", filters={"email": ["in", email_list]}, pluck="email")
|
||||
existing_invites = jingrow.db.get_all(
|
||||
"CRM Invitation",
|
||||
filters={
|
||||
"email": ["in", email_list],
|
||||
@ -123,7 +123,7 @@ def invite_by_email(emails: str, role: str):
|
||||
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)
|
||||
jingrow.get_pg(pagetype="CRM Invitation", email=email, role=role).insert(ignore_permissions=True)
|
||||
|
||||
return {
|
||||
"existing_members": existing_members,
|
||||
@ -132,17 +132,17 @@ def invite_by_email(emails: str, role: str):
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_file_uploader_defaults(doctype: str):
|
||||
@jingrow.whitelist()
|
||||
def get_file_uploader_defaults(pagetype: str):
|
||||
max_number_of_files = None
|
||||
make_attachments_public = False
|
||||
if doctype:
|
||||
meta = frappe.get_meta(doctype)
|
||||
if pagetype:
|
||||
meta = jingrow.get_meta(pagetype)
|
||||
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"),
|
||||
"allowed_file_types": jingrow.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),
|
||||
|
||||
@ -1,28 +1,28 @@
|
||||
import json
|
||||
|
||||
import frappe
|
||||
import jingrow
|
||||
from bs4 import BeautifulSoup
|
||||
from frappe import _
|
||||
from frappe.desk.form.load import get_docinfo
|
||||
from frappe.query_builder import JoinType
|
||||
from jingrow import _
|
||||
from jingrow.desk.form.load import get_docinfo
|
||||
from jingrow.query_builder import JoinType
|
||||
|
||||
from crm.fcrm.doctype.crm_call_log.crm_call_log import parse_call_log
|
||||
from crm.fcrm.pagetype.crm_call_log.crm_call_log import parse_call_log
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def get_activities(name):
|
||||
if frappe.db.exists("CRM Deal", name):
|
||||
if jingrow.db.exists("CRM Deal", name):
|
||||
return get_deal_activities(name)
|
||||
elif frappe.db.exists("CRM Lead", name):
|
||||
elif jingrow.db.exists("CRM Lead", name):
|
||||
return get_lead_activities(name)
|
||||
else:
|
||||
frappe.throw(_("Document not found"), frappe.DoesNotExistError)
|
||||
jingrow.throw(_("Document not found"), jingrow.DoesNotExistError)
|
||||
|
||||
|
||||
def get_deal_activities(name):
|
||||
get_docinfo("", "CRM Deal", name)
|
||||
docinfo = frappe.response["docinfo"]
|
||||
deal_meta = frappe.get_meta("CRM Deal")
|
||||
docinfo = jingrow.response["docinfo"]
|
||||
deal_meta = jingrow.get_meta("CRM Deal")
|
||||
deal_fields = {
|
||||
field.fieldname: {"label": field.label, "options": field.options} for field in deal_meta.fields
|
||||
}
|
||||
@ -35,8 +35,8 @@ def get_deal_activities(name):
|
||||
"first_responded_on",
|
||||
]
|
||||
|
||||
doc = frappe.db.get_values("CRM Deal", name, ["creation", "owner", "lead"])[0]
|
||||
lead = doc[2]
|
||||
pg = jingrow.db.get_values("CRM Deal", name, ["creation", "owner", "lead"])[0]
|
||||
lead = pg[2]
|
||||
|
||||
activities = []
|
||||
calls = []
|
||||
@ -52,8 +52,8 @@ def get_deal_activities(name):
|
||||
activities.append(
|
||||
{
|
||||
"activity_type": "creation",
|
||||
"creation": doc[0],
|
||||
"owner": doc[1],
|
||||
"creation": pg[0],
|
||||
"owner": pg[1],
|
||||
"data": creation_text,
|
||||
"is_lead": False,
|
||||
}
|
||||
@ -166,8 +166,8 @@ def get_deal_activities(name):
|
||||
|
||||
def get_lead_activities(name):
|
||||
get_docinfo("", "CRM Lead", name)
|
||||
docinfo = frappe.response["docinfo"]
|
||||
lead_meta = frappe.get_meta("CRM Lead")
|
||||
docinfo = jingrow.response["docinfo"]
|
||||
lead_meta = jingrow.get_meta("CRM Lead")
|
||||
lead_fields = {
|
||||
field.fieldname: {"label": field.label, "options": field.options} for field in lead_meta.fields
|
||||
}
|
||||
@ -180,12 +180,12 @@ def get_lead_activities(name):
|
||||
"first_responded_on",
|
||||
]
|
||||
|
||||
doc = frappe.db.get_values("CRM Lead", name, ["creation", "owner"])[0]
|
||||
pg = jingrow.db.get_values("CRM Lead", name, ["creation", "owner"])[0]
|
||||
activities = [
|
||||
{
|
||||
"activity_type": "creation",
|
||||
"creation": doc[0],
|
||||
"owner": doc[1],
|
||||
"creation": pg[0],
|
||||
"owner": pg[1],
|
||||
"data": "created this lead",
|
||||
"is_lead": True,
|
||||
}
|
||||
@ -296,11 +296,11 @@ def get_lead_activities(name):
|
||||
return activities, calls, notes, tasks, attachments
|
||||
|
||||
|
||||
def get_attachments(doctype, name):
|
||||
def get_attachments(pagetype, name):
|
||||
return (
|
||||
frappe.db.get_all(
|
||||
jingrow.db.get_all(
|
||||
"File",
|
||||
filters={"attached_to_doctype": doctype, "attached_to_name": name},
|
||||
filters={"attached_to_pagetype": pagetype, "attached_to_name": name},
|
||||
fields=[
|
||||
"name",
|
||||
"file_name",
|
||||
@ -355,7 +355,7 @@ def parse_grouped_versions(versions):
|
||||
|
||||
|
||||
def get_linked_calls(name):
|
||||
calls = frappe.db.get_all(
|
||||
calls = jingrow.db.get_all(
|
||||
"CRM Call Log",
|
||||
filters={"reference_docname": name},
|
||||
fields=[
|
||||
@ -375,7 +375,7 @@ def get_linked_calls(name):
|
||||
],
|
||||
)
|
||||
|
||||
linked_calls = frappe.db.get_all(
|
||||
linked_calls = jingrow.db.get_all(
|
||||
"Dynamic Link", filters={"link_name": name, "parenttype": "CRM Call Log"}, pluck="parent"
|
||||
)
|
||||
|
||||
@ -383,10 +383,10 @@ def get_linked_calls(name):
|
||||
tasks = []
|
||||
|
||||
if linked_calls:
|
||||
CallLog = frappe.qb.DocType("CRM Call Log")
|
||||
Link = frappe.qb.DocType("Dynamic Link")
|
||||
CallLog = jingrow.qb.PageType("CRM Call Log")
|
||||
Link = jingrow.qb.PageType("Dynamic Link")
|
||||
query = (
|
||||
frappe.qb.from_(CallLog)
|
||||
jingrow.qb.from_(CallLog)
|
||||
.select(
|
||||
CallLog.name,
|
||||
CallLog.caller,
|
||||
@ -401,7 +401,7 @@ def get_linked_calls(name):
|
||||
CallLog.recording_url,
|
||||
CallLog.creation,
|
||||
CallLog.note,
|
||||
Link.link_doctype,
|
||||
Link.link_pagetype,
|
||||
Link.link_name,
|
||||
)
|
||||
.join(Link, JoinType.inner)
|
||||
@ -411,24 +411,24 @@ def get_linked_calls(name):
|
||||
_calls = query.run(as_dict=True)
|
||||
|
||||
for call in _calls:
|
||||
if call.get("link_doctype") == "FCRM Note":
|
||||
if call.get("link_pagetype") == "FCRM Note":
|
||||
notes.append(call.link_name)
|
||||
elif call.get("link_doctype") == "CRM Task":
|
||||
elif call.get("link_pagetype") == "CRM Task":
|
||||
tasks.append(call.link_name)
|
||||
|
||||
_calls = [call for call in _calls if call.get("link_doctype") not in ["FCRM Note", "CRM Task"]]
|
||||
_calls = [call for call in _calls if call.get("link_pagetype") not in ["FCRM Note", "CRM Task"]]
|
||||
if _calls:
|
||||
calls = calls + _calls
|
||||
|
||||
if notes:
|
||||
notes = frappe.db.get_all(
|
||||
notes = jingrow.db.get_all(
|
||||
"FCRM Note",
|
||||
filters={"name": ("in", notes)},
|
||||
fields=["name", "title", "content", "owner", "modified"],
|
||||
)
|
||||
|
||||
if tasks:
|
||||
tasks = frappe.db.get_all(
|
||||
tasks = jingrow.db.get_all(
|
||||
"CRM Task",
|
||||
filters={"name": ("in", tasks)},
|
||||
fields=[
|
||||
@ -449,7 +449,7 @@ def get_linked_calls(name):
|
||||
|
||||
|
||||
def get_linked_notes(name):
|
||||
notes = frappe.db.get_all(
|
||||
notes = jingrow.db.get_all(
|
||||
"FCRM Note",
|
||||
filters={"reference_docname": name},
|
||||
fields=["name", "title", "content", "owner", "modified"],
|
||||
@ -458,7 +458,7 @@ def get_linked_notes(name):
|
||||
|
||||
|
||||
def get_linked_tasks(name):
|
||||
tasks = frappe.db.get_all(
|
||||
tasks = jingrow.db.get_all(
|
||||
"CRM Task",
|
||||
filters={"reference_docname": name},
|
||||
fields=[
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import frappe
|
||||
import jingrow
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def get_assignment_rules_list():
|
||||
assignment_rules = []
|
||||
for docname in frappe.get_all(
|
||||
for docname in jingrow.get_all(
|
||||
"Assignment Rule", filters={"document_type": ["in", ["CRM Lead", "CRM Deal"]]}
|
||||
):
|
||||
doc = frappe.get_value(
|
||||
pg = jingrow.get_value(
|
||||
"Assignment Rule",
|
||||
docname,
|
||||
fieldname=[
|
||||
@ -18,15 +18,15 @@ def get_assignment_rules_list():
|
||||
],
|
||||
as_dict=True,
|
||||
)
|
||||
users_exists = bool(frappe.db.exists("Assignment Rule User", {"parent": docname.name}))
|
||||
assignment_rules.append({**doc, "users_exists": users_exists})
|
||||
users_exists = bool(jingrow.db.exists("Assignment Rule User", {"parent": docname.name}))
|
||||
assignment_rules.append({**pg, "users_exists": users_exists})
|
||||
return assignment_rules
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def duplicate_assignment_rule(docname, new_name):
|
||||
doc = frappe.get_doc("Assignment Rule", docname)
|
||||
doc.name = new_name
|
||||
doc.assignment_rule_name = new_name
|
||||
doc.insert()
|
||||
return doc
|
||||
pg = jingrow.get_pg("Assignment Rule", docname)
|
||||
pg.name = new_name
|
||||
pg.assignment_rule_name = new_name
|
||||
pg.insert()
|
||||
return pg
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import frappe
|
||||
import jingrow
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@jingrow.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
|
||||
from jingrow.utils.html_utils import get_icon_html
|
||||
from jingrow.utils.password import get_decrypted_password
|
||||
from jingrow.utils.oauth import get_oauth2_authorize_url, get_oauth_keys
|
||||
|
||||
out = []
|
||||
providers = frappe.get_all(
|
||||
providers = jingrow.get_all(
|
||||
"Social Login Key",
|
||||
filters={"enable_social_login": 1},
|
||||
fields=["name", "client_id", "base_url", "provider_name", "icon"],
|
||||
|
||||
@ -1,53 +1,53 @@
|
||||
from collections.abc import Iterable
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
import jingrow
|
||||
from jingrow import _
|
||||
from bs4 import BeautifulSoup
|
||||
from crm.fcrm.doctype.crm_notification.crm_notification import notify_user
|
||||
from crm.fcrm.pagetype.crm_notification.crm_notification import notify_user
|
||||
|
||||
|
||||
def on_update(self, method):
|
||||
notify_mentions(self)
|
||||
|
||||
|
||||
def notify_mentions(doc):
|
||||
def notify_mentions(pg):
|
||||
"""
|
||||
Extract mentions from `content`, and notify.
|
||||
`content` must have `HTML` content.
|
||||
"""
|
||||
content = getattr(doc, "content", None)
|
||||
content = getattr(pg, "content", None)
|
||||
if not content:
|
||||
return
|
||||
mentions = extract_mentions(content)
|
||||
reference_doc = frappe.get_doc(doc.reference_doctype, doc.reference_name)
|
||||
reference_pg = jingrow.get_pg(pg.reference_pagetype, pg.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()
|
||||
owner = jingrow.get_cached_value("User", pg.owner, "full_name")
|
||||
pagetype = pg.reference_pagetype
|
||||
if pagetype.startswith("CRM "):
|
||||
pagetype = pagetype[4:].lower()
|
||||
name = (
|
||||
reference_doc.lead_name
|
||||
if doctype == "lead"
|
||||
else reference_doc.organization or reference_doc.lead_name
|
||||
reference_pg.lead_name
|
||||
if pagetype == "lead"
|
||||
else reference_pg.organization or reference_pg.lead_name
|
||||
)
|
||||
notification_text = f"""
|
||||
<div class="mb-2 leading-5 text-ink-gray-5">
|
||||
<span class="font-medium text-ink-gray-9">{ owner }</span>
|
||||
<span>{ _('mentioned you in {0}').format(doctype) }</span>
|
||||
<span>{ _('mentioned you in {0}').format(pagetype) }</span>
|
||||
<span class="font-medium text-ink-gray-9">{ name }</span>
|
||||
</div>
|
||||
"""
|
||||
notify_user(
|
||||
{
|
||||
"owner": doc.owner,
|
||||
"owner": pg.owner,
|
||||
"assigned_to": mention.email,
|
||||
"notification_type": "Mention",
|
||||
"message": doc.content,
|
||||
"message": pg.content,
|
||||
"notification_text": notification_text,
|
||||
"reference_doctype": "Comment",
|
||||
"reference_docname": doc.name,
|
||||
"redirect_to_doctype": doc.reference_doctype,
|
||||
"redirect_to_docname": doc.reference_name,
|
||||
"reference_pagetype": "Comment",
|
||||
"reference_docname": pg.name,
|
||||
"redirect_to_pagetype": pg.reference_pagetype,
|
||||
"redirect_to_docname": pg.reference_name,
|
||||
}
|
||||
)
|
||||
|
||||
@ -59,12 +59,12 @@ def extract_mentions(html):
|
||||
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"))
|
||||
jingrow._dict(full_name=d.get("data-label"), email=d.get("data-id"))
|
||||
)
|
||||
return mentions
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def add_attachments(name: str, attachments: Iterable[str | dict]) -> None:
|
||||
"""Add attachments to the given Comment
|
||||
|
||||
@ -74,7 +74,7 @@ def add_attachments(name: str, attachments: Iterable[str | dict]) -> None:
|
||||
# loop through attachments
|
||||
for a in attachments:
|
||||
if isinstance(a, str):
|
||||
attach = frappe.db.get_value(
|
||||
attach = jingrow.db.get_value(
|
||||
"File", {"name": a}, ["file_url", "is_private"], as_dict=1
|
||||
)
|
||||
file_args = {
|
||||
@ -82,7 +82,7 @@ def add_attachments(name: str, attachments: Iterable[str | dict]) -> None:
|
||||
"is_private": attach.is_private,
|
||||
}
|
||||
elif isinstance(a, dict) and "fcontent" in a and "fname" in a:
|
||||
# dict returned by frappe.attach_print()
|
||||
# dict returned by jingrow.attach_print()
|
||||
file_args = {
|
||||
"file_name": a["fname"],
|
||||
"content": a["fcontent"],
|
||||
@ -93,12 +93,12 @@ def add_attachments(name: str, attachments: Iterable[str | dict]) -> None:
|
||||
|
||||
file_args.update(
|
||||
{
|
||||
"attached_to_doctype": "Comment",
|
||||
"attached_to_pagetype": "Comment",
|
||||
"attached_to_name": name,
|
||||
"folder": "Home/Attachments",
|
||||
}
|
||||
)
|
||||
|
||||
_file = frappe.new_doc("File")
|
||||
_file = jingrow.new_pg("File")
|
||||
_file.update(file_args)
|
||||
_file.save(ignore_permissions=True)
|
||||
|
||||
@ -1,39 +1,39 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
import jingrow
|
||||
from jingrow import _
|
||||
|
||||
|
||||
def validate(doc, method):
|
||||
update_deals_email_mobile_no(doc)
|
||||
def validate(pg, method):
|
||||
update_deals_email_mobile_no(pg)
|
||||
|
||||
|
||||
def update_deals_email_mobile_no(doc):
|
||||
linked_deals = frappe.get_all(
|
||||
def update_deals_email_mobile_no(pg):
|
||||
linked_deals = jingrow.get_all(
|
||||
"CRM Contacts",
|
||||
filters={"contact": doc.name, "is_primary": 1},
|
||||
filters={"contact": pg.name, "is_primary": 1},
|
||||
fields=["parent"],
|
||||
)
|
||||
|
||||
for linked_deal in linked_deals:
|
||||
deal = frappe.db.get_values("CRM Deal", linked_deal.parent, ["email", "mobile_no"], as_dict=True)[0]
|
||||
if deal.email != doc.email_id or deal.mobile_no != doc.mobile_no:
|
||||
frappe.db.set_value(
|
||||
deal = jingrow.db.get_values("CRM Deal", linked_deal.parent, ["email", "mobile_no"], as_dict=True)[0]
|
||||
if deal.email != pg.email_id or deal.mobile_no != pg.mobile_no:
|
||||
jingrow.db.set_value(
|
||||
"CRM Deal",
|
||||
linked_deal.parent,
|
||||
{
|
||||
"email": doc.email_id,
|
||||
"mobile_no": doc.mobile_no,
|
||||
"email": pg.email_id,
|
||||
"mobile_no": pg.mobile_no,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.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)
|
||||
if not jingrow.has_permission("Contact", "read", contact):
|
||||
jingrow.throw("Not permitted", jingrow.PermissionError)
|
||||
|
||||
deal_names = frappe.get_all(
|
||||
deal_names = jingrow.get_all(
|
||||
"CRM Contacts",
|
||||
filters={"contact": contact, "parenttype": "CRM Deal"},
|
||||
fields=["parent"],
|
||||
@ -43,7 +43,7 @@ def get_linked_deals(contact):
|
||||
# get deals data
|
||||
deals = []
|
||||
for d in deal_names:
|
||||
deal = frappe.get_cached_doc(
|
||||
deal = jingrow.get_cached_pg(
|
||||
"CRM Deal",
|
||||
d.parent,
|
||||
fields=[
|
||||
@ -63,13 +63,13 @@ def get_linked_deals(contact):
|
||||
return deals
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.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)
|
||||
if not jingrow.has_permission("Contact", "write", contact):
|
||||
jingrow.throw("Not permitted", jingrow.PermissionError)
|
||||
|
||||
contact = frappe.get_cached_doc("Contact", contact)
|
||||
contact = jingrow.get_cached_pg("Contact", contact)
|
||||
|
||||
if field == "email":
|
||||
email = {"email_id": value, "is_primary": 1 if len(contact.email_ids) == 0 else 0}
|
||||
@ -78,19 +78,19 @@ def create_new(contact, field, value):
|
||||
mobile_no = {"phone": value, "is_primary_mobile_no": 1 if len(contact.phone_nos) == 0 else 0}
|
||||
contact.append("phone_nos", mobile_no)
|
||||
else:
|
||||
frappe.throw("Invalid field")
|
||||
jingrow.throw("Invalid field")
|
||||
|
||||
contact.save()
|
||||
return True
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.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)
|
||||
if not jingrow.has_permission("Contact", "write", contact):
|
||||
jingrow.throw("Not permitted", jingrow.PermissionError)
|
||||
|
||||
contact = frappe.get_doc("Contact", contact)
|
||||
contact = jingrow.get_pg("Contact", contact)
|
||||
|
||||
if field == "email":
|
||||
for email in contact.email_ids:
|
||||
@ -106,31 +106,31 @@ def set_as_primary(contact, field, value):
|
||||
else:
|
||||
phone.set(name, 0)
|
||||
else:
|
||||
frappe.throw("Invalid field")
|
||||
jingrow.throw("Invalid field")
|
||||
|
||||
contact.save()
|
||||
return True
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def search_emails(txt: str):
|
||||
doctype = "Contact"
|
||||
meta = frappe.get_meta(doctype)
|
||||
pagetype = "Contact"
|
||||
meta = jingrow.get_meta(pagetype)
|
||||
filters = [["Contact", "email_id", "is", "set"]]
|
||||
|
||||
if meta.get("fields", {"fieldname": "enabled", "fieldtype": "Check"}):
|
||||
filters.append([doctype, "enabled", "=", 1])
|
||||
filters.append([pagetype, "enabled", "=", 1])
|
||||
if meta.get("fields", {"fieldname": "disabled", "fieldtype": "Check"}):
|
||||
filters.append([doctype, "disabled", "!=", 1])
|
||||
filters.append([pagetype, "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}%"])
|
||||
or_filters.append([pagetype, f.strip(), "like", f"%{txt}%"])
|
||||
|
||||
results = frappe.get_list(
|
||||
doctype,
|
||||
results = jingrow.get_list(
|
||||
pagetype,
|
||||
filters=filters,
|
||||
fields=search_fields,
|
||||
or_filters=or_filters,
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
import jingrow
|
||||
from jingrow import _
|
||||
|
||||
from crm.fcrm.doctype.crm_dashboard.crm_dashboard import create_default_manager_dashboard
|
||||
from crm.fcrm.pagetype.crm_dashboard.crm_dashboard import create_default_manager_dashboard
|
||||
from crm.utils import sales_user_only
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def reset_to_default():
|
||||
frappe.only_for("System Manager")
|
||||
jingrow.only_for("System Manager")
|
||||
create_default_manager_dashboard(force=True)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
@sales_user_only
|
||||
def get_dashboard(from_date="", to_date="", user=""):
|
||||
"""
|
||||
@ -21,28 +21,28 @@ def get_dashboard(from_date="", to_date="", user=""):
|
||||
"""
|
||||
|
||||
if not from_date or not to_date:
|
||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||
from_date = jingrow.utils.get_first_day(from_date or jingrow.utils.nowdate())
|
||||
to_date = jingrow.utils.get_last_day(to_date or jingrow.utils.nowdate())
|
||||
|
||||
roles = frappe.get_roles(frappe.session.user)
|
||||
roles = jingrow.get_roles(jingrow.session.user)
|
||||
is_sales_user = "Sales User" in roles and "Sales Manager" not in roles and "System Manager" not in roles
|
||||
if is_sales_user and not user:
|
||||
user = frappe.session.user
|
||||
user = jingrow.session.user
|
||||
|
||||
dashboard = frappe.db.exists("CRM Dashboard", "Manager Dashboard")
|
||||
dashboard = jingrow.db.exists("CRM Dashboard", "Manager Dashboard")
|
||||
|
||||
layout = []
|
||||
|
||||
if not dashboard:
|
||||
layout = json.loads(create_default_manager_dashboard())
|
||||
frappe.db.commit()
|
||||
jingrow.db.commit()
|
||||
else:
|
||||
layout = json.loads(frappe.db.get_value("CRM Dashboard", "Manager Dashboard", "layout") or "[]")
|
||||
layout = json.loads(jingrow.db.get_value("CRM Dashboard", "Manager Dashboard", "layout") or "[]")
|
||||
|
||||
for l in layout:
|
||||
method_name = f"get_{l['name']}"
|
||||
if hasattr(frappe.get_attr("crm.api.dashboard"), method_name):
|
||||
method = getattr(frappe.get_attr("crm.api.dashboard"), method_name)
|
||||
if hasattr(jingrow.get_attr("crm.api.dashboard"), method_name):
|
||||
method = getattr(jingrow.get_attr("crm.api.dashboard"), method_name)
|
||||
l["data"] = method(from_date, to_date, user)
|
||||
else:
|
||||
l["data"] = None
|
||||
@ -50,24 +50,24 @@ def get_dashboard(from_date="", to_date="", user=""):
|
||||
return layout
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
@sales_user_only
|
||||
def get_chart(name, type, from_date="", to_date="", user=""):
|
||||
"""
|
||||
Get number chart data for the dashboard.
|
||||
"""
|
||||
if not from_date or not to_date:
|
||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||
from_date = jingrow.utils.get_first_day(from_date or jingrow.utils.nowdate())
|
||||
to_date = jingrow.utils.get_last_day(to_date or jingrow.utils.nowdate())
|
||||
|
||||
roles = frappe.get_roles(frappe.session.user)
|
||||
roles = jingrow.get_roles(jingrow.session.user)
|
||||
is_sales_user = "Sales User" in roles and "Sales Manager" not in roles and "System Manager" not in roles
|
||||
if is_sales_user and not user:
|
||||
user = frappe.session.user
|
||||
user = jingrow.session.user
|
||||
|
||||
method_name = f"get_{name}"
|
||||
if hasattr(frappe.get_attr("crm.api.dashboard"), method_name):
|
||||
method = getattr(frappe.get_attr("crm.api.dashboard"), method_name)
|
||||
if hasattr(jingrow.get_attr("crm.api.dashboard"), method_name):
|
||||
method = getattr(jingrow.get_attr("crm.api.dashboard"), method_name)
|
||||
return method(from_date, to_date, user)
|
||||
else:
|
||||
return {"error": _("Invalid chart name")}
|
||||
@ -79,14 +79,14 @@ def get_total_leads(from_date, to_date, user=""):
|
||||
"""
|
||||
conds = ""
|
||||
|
||||
diff = frappe.utils.date_diff(to_date, from_date)
|
||||
diff = jingrow.utils.date_diff(to_date, from_date)
|
||||
if diff == 0:
|
||||
diff = 1
|
||||
|
||||
if user:
|
||||
conds += f" AND lead_owner = '{user}'"
|
||||
|
||||
result = frappe.db.sql(
|
||||
result = jingrow.db.sql(
|
||||
f"""
|
||||
SELECT
|
||||
COUNT(CASE
|
||||
@ -107,7 +107,7 @@ def get_total_leads(from_date, to_date, user=""):
|
||||
{
|
||||
"from_date": from_date,
|
||||
"to_date": to_date,
|
||||
"prev_from_date": frappe.utils.add_days(from_date, -diff),
|
||||
"prev_from_date": jingrow.utils.add_days(from_date, -diff),
|
||||
},
|
||||
as_dict=1,
|
||||
)
|
||||
@ -134,14 +134,14 @@ def get_ongoing_deals(from_date, to_date, user=""):
|
||||
"""
|
||||
conds = ""
|
||||
|
||||
diff = frappe.utils.date_diff(to_date, from_date)
|
||||
diff = jingrow.utils.date_diff(to_date, from_date)
|
||||
if diff == 0:
|
||||
diff = 1
|
||||
|
||||
if user:
|
||||
conds += f" AND d.deal_owner = '{user}'"
|
||||
|
||||
result = frappe.db.sql(
|
||||
result = jingrow.db.sql(
|
||||
f"""
|
||||
SELECT
|
||||
COUNT(CASE
|
||||
@ -165,7 +165,7 @@ def get_ongoing_deals(from_date, to_date, user=""):
|
||||
{
|
||||
"from_date": from_date,
|
||||
"to_date": to_date,
|
||||
"prev_from_date": frappe.utils.add_days(from_date, -diff),
|
||||
"prev_from_date": jingrow.utils.add_days(from_date, -diff),
|
||||
},
|
||||
as_dict=1,
|
||||
)
|
||||
@ -192,14 +192,14 @@ def get_average_ongoing_deal_value(from_date, to_date, user=""):
|
||||
"""
|
||||
conds = ""
|
||||
|
||||
diff = frappe.utils.date_diff(to_date, from_date)
|
||||
diff = jingrow.utils.date_diff(to_date, from_date)
|
||||
if diff == 0:
|
||||
diff = 1
|
||||
|
||||
if user:
|
||||
conds += f" AND d.deal_owner = '{user}'"
|
||||
|
||||
result = frappe.db.sql(
|
||||
result = jingrow.db.sql(
|
||||
f"""
|
||||
SELECT
|
||||
AVG(CASE
|
||||
@ -223,7 +223,7 @@ def get_average_ongoing_deal_value(from_date, to_date, user=""):
|
||||
{
|
||||
"from_date": from_date,
|
||||
"to_date": to_date,
|
||||
"prev_from_date": frappe.utils.add_days(from_date, -diff),
|
||||
"prev_from_date": jingrow.utils.add_days(from_date, -diff),
|
||||
},
|
||||
as_dict=1,
|
||||
)
|
||||
@ -247,7 +247,7 @@ def get_won_deals(from_date, to_date, user=""):
|
||||
Get won deal count for the dashboard, and also calculate average deal value for won deals.
|
||||
"""
|
||||
|
||||
diff = frappe.utils.date_diff(to_date, from_date)
|
||||
diff = jingrow.utils.date_diff(to_date, from_date)
|
||||
if diff == 0:
|
||||
diff = 1
|
||||
|
||||
@ -256,7 +256,7 @@ def get_won_deals(from_date, to_date, user=""):
|
||||
if user:
|
||||
conds += f" AND d.deal_owner = '{user}'"
|
||||
|
||||
result = frappe.db.sql(
|
||||
result = jingrow.db.sql(
|
||||
f"""
|
||||
SELECT
|
||||
COUNT(CASE
|
||||
@ -280,7 +280,7 @@ def get_won_deals(from_date, to_date, user=""):
|
||||
{
|
||||
"from_date": from_date,
|
||||
"to_date": to_date,
|
||||
"prev_from_date": frappe.utils.add_days(from_date, -diff),
|
||||
"prev_from_date": jingrow.utils.add_days(from_date, -diff),
|
||||
},
|
||||
as_dict=1,
|
||||
)
|
||||
@ -306,7 +306,7 @@ def get_average_won_deal_value(from_date, to_date, user=""):
|
||||
Get won deal count for the dashboard, and also calculate average deal value for won deals.
|
||||
"""
|
||||
|
||||
diff = frappe.utils.date_diff(to_date, from_date)
|
||||
diff = jingrow.utils.date_diff(to_date, from_date)
|
||||
if diff == 0:
|
||||
diff = 1
|
||||
|
||||
@ -315,7 +315,7 @@ def get_average_won_deal_value(from_date, to_date, user=""):
|
||||
if user:
|
||||
conds += f" AND d.deal_owner = '{user}'"
|
||||
|
||||
result = frappe.db.sql(
|
||||
result = jingrow.db.sql(
|
||||
f"""
|
||||
SELECT
|
||||
AVG(CASE
|
||||
@ -339,7 +339,7 @@ def get_average_won_deal_value(from_date, to_date, user=""):
|
||||
{
|
||||
"from_date": from_date,
|
||||
"to_date": to_date,
|
||||
"prev_from_date": frappe.utils.add_days(from_date, -diff),
|
||||
"prev_from_date": jingrow.utils.add_days(from_date, -diff),
|
||||
},
|
||||
as_dict=1,
|
||||
)
|
||||
@ -363,7 +363,7 @@ def get_average_deal_value(from_date, to_date, user=""):
|
||||
Get average deal value for the dashboard.
|
||||
"""
|
||||
|
||||
diff = frappe.utils.date_diff(to_date, from_date)
|
||||
diff = jingrow.utils.date_diff(to_date, from_date)
|
||||
if diff == 0:
|
||||
diff = 1
|
||||
|
||||
@ -372,7 +372,7 @@ def get_average_deal_value(from_date, to_date, user=""):
|
||||
if user:
|
||||
conds += f" AND d.deal_owner = '{user}'"
|
||||
|
||||
result = frappe.db.sql(
|
||||
result = jingrow.db.sql(
|
||||
f"""
|
||||
SELECT
|
||||
AVG(CASE
|
||||
@ -396,7 +396,7 @@ def get_average_deal_value(from_date, to_date, user=""):
|
||||
{
|
||||
"from_date": from_date,
|
||||
"to_date": to_date,
|
||||
"prev_from_date": frappe.utils.add_days(from_date, -diff),
|
||||
"prev_from_date": jingrow.utils.add_days(from_date, -diff),
|
||||
},
|
||||
as_dict=1,
|
||||
)
|
||||
@ -421,7 +421,7 @@ def get_average_time_to_close_a_lead(from_date, to_date, user=""):
|
||||
Get average time to close deals for the dashboard.
|
||||
"""
|
||||
|
||||
diff = frappe.utils.date_diff(to_date, from_date)
|
||||
diff = jingrow.utils.date_diff(to_date, from_date)
|
||||
if diff == 0:
|
||||
diff = 1
|
||||
|
||||
@ -430,10 +430,10 @@ def get_average_time_to_close_a_lead(from_date, to_date, user=""):
|
||||
if user:
|
||||
conds += f" AND d.deal_owner = '{user}'"
|
||||
|
||||
prev_from_date = frappe.utils.add_days(from_date, -diff)
|
||||
prev_from_date = jingrow.utils.add_days(from_date, -diff)
|
||||
prev_to_date = from_date
|
||||
|
||||
result = frappe.db.sql(
|
||||
result = jingrow.db.sql(
|
||||
f"""
|
||||
SELECT
|
||||
AVG(CASE WHEN d.closed_date >= %(from_date)s AND d.closed_date < DATE_ADD(%(to_date)s, INTERVAL 1 DAY)
|
||||
@ -475,7 +475,7 @@ def get_average_time_to_close_a_deal(from_date, to_date, user=""):
|
||||
Get average time to close deals for the dashboard.
|
||||
"""
|
||||
|
||||
diff = frappe.utils.date_diff(to_date, from_date)
|
||||
diff = jingrow.utils.date_diff(to_date, from_date)
|
||||
if diff == 0:
|
||||
diff = 1
|
||||
|
||||
@ -484,10 +484,10 @@ def get_average_time_to_close_a_deal(from_date, to_date, user=""):
|
||||
if user:
|
||||
conds += f" AND d.deal_owner = '{user}'"
|
||||
|
||||
prev_from_date = frappe.utils.add_days(from_date, -diff)
|
||||
prev_from_date = jingrow.utils.add_days(from_date, -diff)
|
||||
prev_to_date = from_date
|
||||
|
||||
result = frappe.db.sql(
|
||||
result = jingrow.db.sql(
|
||||
f"""
|
||||
SELECT
|
||||
AVG(CASE WHEN d.closed_date >= %(from_date)s AND d.closed_date < DATE_ADD(%(to_date)s, INTERVAL 1 DAY)
|
||||
@ -538,14 +538,14 @@ def get_sales_trend(from_date="", to_date="", user=""):
|
||||
deal_conds = ""
|
||||
|
||||
if not from_date or not to_date:
|
||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||
from_date = jingrow.utils.get_first_day(from_date or jingrow.utils.nowdate())
|
||||
to_date = jingrow.utils.get_last_day(to_date or jingrow.utils.nowdate())
|
||||
|
||||
if user:
|
||||
lead_conds += f" AND lead_owner = '{user}'"
|
||||
deal_conds += f" AND deal_owner = '{user}'"
|
||||
|
||||
result = frappe.db.sql(
|
||||
result = jingrow.db.sql(
|
||||
f"""
|
||||
SELECT
|
||||
DATE_FORMAT(date, '%%Y-%%m-%%d') AS date,
|
||||
@ -585,7 +585,7 @@ def get_sales_trend(from_date="", to_date="", user=""):
|
||||
|
||||
sales_trend = [
|
||||
{
|
||||
"date": frappe.utils.get_datetime(row.date).strftime("%Y-%m-%d"),
|
||||
"date": jingrow.utils.get_datetime(row.date).strftime("%Y-%m-%d"),
|
||||
"leads": row.leads or 0,
|
||||
"deals": row.deals or 0,
|
||||
"won_deals": row.won_deals or 0,
|
||||
@ -630,7 +630,7 @@ def get_forecasted_revenue(from_date="", to_date="", user=""):
|
||||
if user:
|
||||
deal_conds += f" AND d.deal_owner = '{user}'"
|
||||
|
||||
result = frappe.db.sql(
|
||||
result = jingrow.db.sql(
|
||||
f"""
|
||||
SELECT
|
||||
DATE_FORMAT(d.expected_closure_date, '%Y-%m') AS month,
|
||||
@ -657,7 +657,7 @@ def get_forecasted_revenue(from_date="", to_date="", user=""):
|
||||
)
|
||||
|
||||
for row in result:
|
||||
row["month"] = frappe.utils.get_datetime(row["month"]).strftime("%Y-%m-01")
|
||||
row["month"] = jingrow.utils.get_datetime(row["month"]).strftime("%Y-%m-01")
|
||||
row["forecasted"] = row["forecasted"] or ""
|
||||
row["actual"] = row["actual"] or ""
|
||||
|
||||
@ -697,8 +697,8 @@ def get_funnel_conversion(from_date="", to_date="", user=""):
|
||||
deal_conds = ""
|
||||
|
||||
if not from_date or not to_date:
|
||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||
from_date = jingrow.utils.get_first_day(from_date or jingrow.utils.nowdate())
|
||||
to_date = jingrow.utils.get_last_day(to_date or jingrow.utils.nowdate())
|
||||
|
||||
if user:
|
||||
lead_conds += f" AND lead_owner = '{user}'"
|
||||
@ -707,7 +707,7 @@ def get_funnel_conversion(from_date="", to_date="", user=""):
|
||||
result = []
|
||||
|
||||
# Get total leads
|
||||
total_leads = frappe.db.sql(
|
||||
total_leads = jingrow.db.sql(
|
||||
f"""
|
||||
SELECT COUNT(*) AS count
|
||||
FROM `tabCRM Lead`
|
||||
@ -760,13 +760,13 @@ def get_deals_by_stage_axis(from_date="", to_date="", user=""):
|
||||
deal_conds = ""
|
||||
|
||||
if not from_date or not to_date:
|
||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||
from_date = jingrow.utils.get_first_day(from_date or jingrow.utils.nowdate())
|
||||
to_date = jingrow.utils.get_last_day(to_date or jingrow.utils.nowdate())
|
||||
|
||||
if user:
|
||||
deal_conds += f" AND d.deal_owner = '{user}'"
|
||||
|
||||
result = frappe.db.sql(
|
||||
result = jingrow.db.sql(
|
||||
f"""
|
||||
SELECT
|
||||
d.status AS stage,
|
||||
@ -810,13 +810,13 @@ def get_deals_by_stage_donut(from_date="", to_date="", user=""):
|
||||
deal_conds = ""
|
||||
|
||||
if not from_date or not to_date:
|
||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||
from_date = jingrow.utils.get_first_day(from_date or jingrow.utils.nowdate())
|
||||
to_date = jingrow.utils.get_last_day(to_date or jingrow.utils.nowdate())
|
||||
|
||||
if user:
|
||||
deal_conds += f" AND d.deal_owner = '{user}'"
|
||||
|
||||
result = frappe.db.sql(
|
||||
result = jingrow.db.sql(
|
||||
f"""
|
||||
SELECT
|
||||
d.status AS stage,
|
||||
@ -855,13 +855,13 @@ def get_lost_deal_reasons(from_date="", to_date="", user=""):
|
||||
deal_conds = ""
|
||||
|
||||
if not from_date or not to_date:
|
||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||
from_date = jingrow.utils.get_first_day(from_date or jingrow.utils.nowdate())
|
||||
to_date = jingrow.utils.get_last_day(to_date or jingrow.utils.nowdate())
|
||||
|
||||
if user:
|
||||
deal_conds += f" AND d.deal_owner = '{user}'"
|
||||
|
||||
result = frappe.db.sql(
|
||||
result = jingrow.db.sql(
|
||||
f"""
|
||||
SELECT
|
||||
d.lost_reason AS reason,
|
||||
@ -908,13 +908,13 @@ def get_leads_by_source(from_date="", to_date="", user=""):
|
||||
lead_conds = ""
|
||||
|
||||
if not from_date or not to_date:
|
||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||
from_date = jingrow.utils.get_first_day(from_date or jingrow.utils.nowdate())
|
||||
to_date = jingrow.utils.get_last_day(to_date or jingrow.utils.nowdate())
|
||||
|
||||
if user:
|
||||
lead_conds += f" AND lead_owner = '{user}'"
|
||||
|
||||
result = frappe.db.sql(
|
||||
result = jingrow.db.sql(
|
||||
f"""
|
||||
SELECT
|
||||
IFNULL(source, 'Empty') AS source,
|
||||
@ -950,13 +950,13 @@ def get_deals_by_source(from_date="", to_date="", user=""):
|
||||
deal_conds = ""
|
||||
|
||||
if not from_date or not to_date:
|
||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||
from_date = jingrow.utils.get_first_day(from_date or jingrow.utils.nowdate())
|
||||
to_date = jingrow.utils.get_last_day(to_date or jingrow.utils.nowdate())
|
||||
|
||||
if user:
|
||||
deal_conds += f" AND deal_owner = '{user}'"
|
||||
|
||||
result = frappe.db.sql(
|
||||
result = jingrow.db.sql(
|
||||
f"""
|
||||
SELECT
|
||||
IFNULL(source, 'Empty') AS source,
|
||||
@ -992,13 +992,13 @@ def get_deals_by_territory(from_date="", to_date="", user=""):
|
||||
deal_conds = ""
|
||||
|
||||
if not from_date or not to_date:
|
||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||
from_date = jingrow.utils.get_first_day(from_date or jingrow.utils.nowdate())
|
||||
to_date = jingrow.utils.get_last_day(to_date or jingrow.utils.nowdate())
|
||||
|
||||
if user:
|
||||
deal_conds += f" AND d.deal_owner = '{user}'"
|
||||
|
||||
result = frappe.db.sql(
|
||||
result = jingrow.db.sql(
|
||||
f"""
|
||||
SELECT
|
||||
IFNULL(d.territory, 'Empty') AS territory,
|
||||
@ -1048,13 +1048,13 @@ def get_deals_by_salesperson(from_date="", to_date="", user=""):
|
||||
deal_conds = ""
|
||||
|
||||
if not from_date or not to_date:
|
||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||
from_date = jingrow.utils.get_first_day(from_date or jingrow.utils.nowdate())
|
||||
to_date = jingrow.utils.get_last_day(to_date or jingrow.utils.nowdate())
|
||||
|
||||
if user:
|
||||
deal_conds += f" AND d.deal_owner = '{user}'"
|
||||
|
||||
result = frappe.db.sql(
|
||||
result = jingrow.db.sql(
|
||||
f"""
|
||||
SELECT
|
||||
IFNULL(u.full_name, d.deal_owner) AS salesperson,
|
||||
@ -1097,8 +1097,8 @@ def get_base_currency_symbol():
|
||||
"""
|
||||
Get the base currency symbol from the system settings.
|
||||
"""
|
||||
base_currency = frappe.db.get_single_value("FCRM Settings", "currency") or "USD"
|
||||
return frappe.db.get_value("Currency", base_currency, "symbol") or ""
|
||||
base_currency = jingrow.db.get_single_value("FCRM Settings", "currency") or "USD"
|
||||
return jingrow.db.get_value("Currency", base_currency, "symbol") or ""
|
||||
|
||||
|
||||
def get_deal_status_change_counts(from_date, to_date, deal_conds=""):
|
||||
@ -1112,7 +1112,7 @@ def get_deal_status_change_counts(from_date, to_date, deal_conds=""):
|
||||
...
|
||||
]
|
||||
"""
|
||||
result = frappe.db.sql(
|
||||
result = jingrow.db.sql(
|
||||
f"""
|
||||
SELECT
|
||||
scl.to AS stage,
|
||||
|
||||
@ -1,31 +1,31 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.auth import LoginManager
|
||||
import jingrow
|
||||
from jingrow import _
|
||||
from jingrow.auth import LoginManager
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@jingrow.whitelist(allow_guest=True)
|
||||
def login():
|
||||
if not frappe.conf.demo_username or not frappe.conf.demo_password:
|
||||
if not jingrow.conf.demo_username or not jingrow.conf.demo_password:
|
||||
return
|
||||
frappe.local.response["redirect_to"] = "/crm"
|
||||
jingrow.local.response["redirect_to"] = "/crm"
|
||||
login_manager = LoginManager()
|
||||
login_manager.authenticate(frappe.conf.demo_username, frappe.conf.demo_password)
|
||||
login_manager.authenticate(jingrow.conf.demo_username, jingrow.conf.demo_password)
|
||||
login_manager.post_login()
|
||||
frappe.local.response["type"] = "redirect"
|
||||
frappe.local.response["location"] = frappe.local.response["redirect_to"]
|
||||
jingrow.local.response["type"] = "redirect"
|
||||
jingrow.local.response["location"] = jingrow.local.response["redirect_to"]
|
||||
|
||||
|
||||
def validate_reset_password(doc, event):
|
||||
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_reset_password(pg, event):
|
||||
if jingrow.conf.demo_username and jingrow.session.user == jingrow.conf.demo_username:
|
||||
jingrow.throw(
|
||||
_("Password cannot be reset by Demo User {}").format(jingrow.bold(jingrow.conf.demo_username)),
|
||||
jingrow.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,
|
||||
def validate_user(pg, event):
|
||||
if jingrow.conf.demo_username and jingrow.session.user == jingrow.conf.demo_username and pg.new_password:
|
||||
jingrow.throw(
|
||||
_("Password cannot be reset by Demo User {}").format(jingrow.bold(jingrow.conf.demo_username)),
|
||||
jingrow.PermissionError,
|
||||
)
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import frappe
|
||||
from frappe.query_builder import Order
|
||||
import jingrow
|
||||
from jingrow.query_builder import Order
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def get_notifications():
|
||||
Notification = frappe.qb.DocType("CRM Notification")
|
||||
Notification = jingrow.qb.PageType("CRM Notification")
|
||||
query = (
|
||||
frappe.qb.from_(Notification)
|
||||
jingrow.qb.from_(Notification)
|
||||
.select("*")
|
||||
.where(Notification.to_user == frappe.session.user)
|
||||
.where(Notification.to_user == jingrow.session.user)
|
||||
.orderby("creation", order=Order.desc)
|
||||
)
|
||||
notifications = query.run(as_dict=True)
|
||||
@ -20,7 +20,7 @@ def get_notifications():
|
||||
"creation": notification.creation,
|
||||
"from_user": {
|
||||
"name": notification.from_user,
|
||||
"full_name": frappe.get_value(
|
||||
"full_name": jingrow.get_value(
|
||||
"User", notification.from_user, "full_name"
|
||||
),
|
||||
},
|
||||
@ -29,14 +29,14 @@ def get_notifications():
|
||||
"read": notification.read,
|
||||
"hash": get_hash(notification),
|
||||
"notification_text": notification.notification_text,
|
||||
"notification_type_doctype": notification.notification_type_doctype,
|
||||
"notification_type_doc": notification.notification_type_doc,
|
||||
"reference_doctype": (
|
||||
"deal" if notification.reference_doctype == "CRM Deal" else "lead"
|
||||
"notification_type_pagetype": notification.notification_type_pagetype,
|
||||
"notification_type_pg": notification.notification_type_pg,
|
||||
"reference_pagetype": (
|
||||
"deal" if notification.reference_pagetype == "CRM Deal" else "lead"
|
||||
),
|
||||
"reference_name": notification.reference_name,
|
||||
"route_name": (
|
||||
"Deal" if notification.reference_doctype == "CRM Deal" else "Lead"
|
||||
"Deal" if notification.reference_pagetype == "CRM Deal" else "Lead"
|
||||
),
|
||||
}
|
||||
)
|
||||
@ -44,30 +44,30 @@ def get_notifications():
|
||||
return _notifications
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def mark_as_read(user=None, doc=None):
|
||||
user = user or frappe.session.user
|
||||
@jingrow.whitelist()
|
||||
def mark_as_read(user=None, pg=None):
|
||||
user = user or jingrow.session.user
|
||||
filters = {"to_user": user, "read": False}
|
||||
or_filters = []
|
||||
if doc:
|
||||
if pg:
|
||||
or_filters = [
|
||||
{"comment": doc},
|
||||
{"notification_type_doc": doc},
|
||||
{"comment": pg},
|
||||
{"notification_type_pg": pg},
|
||||
]
|
||||
for n in frappe.get_all("CRM Notification", filters=filters, or_filters=or_filters):
|
||||
d = frappe.get_doc("CRM Notification", n.name)
|
||||
for n in jingrow.get_all("CRM Notification", filters=filters, or_filters=or_filters):
|
||||
d = jingrow.get_pg("CRM Notification", n.name)
|
||||
d.read = True
|
||||
d.save()
|
||||
|
||||
def get_hash(notification):
|
||||
_hash = ""
|
||||
if notification.type == "Mention" and notification.notification_type_doc:
|
||||
_hash = "#" + notification.notification_type_doc
|
||||
if notification.type == "Mention" and notification.notification_type_pg:
|
||||
_hash = "#" + notification.notification_type_pg
|
||||
|
||||
if notification.type == "WhatsApp":
|
||||
_hash = "#whatsapp"
|
||||
|
||||
if notification.type == "Assignment" and notification.notification_type_doctype == "CRM Task":
|
||||
if notification.type == "Assignment" and notification.notification_type_pagetype == "CRM Task":
|
||||
_hash = "#tasks"
|
||||
if "has been removed by" in notification.message:
|
||||
_hash = ""
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import frappe
|
||||
import jingrow
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def get_first_lead():
|
||||
lead = frappe.get_all(
|
||||
lead = jingrow.get_all(
|
||||
"CRM Lead",
|
||||
filters={"converted": 0},
|
||||
fields=["name"],
|
||||
@ -13,9 +13,9 @@ def get_first_lead():
|
||||
return lead[0].name if lead else None
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def get_first_deal():
|
||||
deal = frappe.get_all(
|
||||
deal = jingrow.get_all(
|
||||
"CRM Deal",
|
||||
fields=["name"],
|
||||
order_by="creation",
|
||||
|
||||
@ -1,22 +1,22 @@
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
from frappe.desk.form.assign_to import set_status
|
||||
from frappe.model import no_value_fields
|
||||
from frappe.model.document import get_controller
|
||||
from frappe.utils import make_filter_tuple
|
||||
import jingrow
|
||||
from jingrow import _
|
||||
from jingrow.custom.pagetype.property_setter.property_setter import make_property_setter
|
||||
from jingrow.desk.form.assign_to import set_status
|
||||
from jingrow.model import no_value_fields
|
||||
from jingrow.model.page import get_controller
|
||||
from jingrow.utils import make_filter_tuple
|
||||
from pypika import Criterion
|
||||
|
||||
from crm.api.views import get_views
|
||||
from crm.fcrm.doctype.crm_form_script.crm_form_script import get_form_script
|
||||
from crm.fcrm.pagetype.crm_form_script.crm_form_script import get_form_script
|
||||
from crm.utils import get_dynamic_linked_docs, get_linked_docs
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def sort_options(doctype: str):
|
||||
fields = frappe.get_meta(doctype).fields
|
||||
@jingrow.whitelist()
|
||||
def sort_options(pagetype: str):
|
||||
fields = jingrow.get_meta(pagetype).fields
|
||||
fields = [field for field in fields if field.fieldtype not in no_value_fields]
|
||||
fields = [
|
||||
{
|
||||
@ -44,8 +44,8 @@ def sort_options(doctype: str):
|
||||
return fields
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_filterable_fields(doctype: str):
|
||||
@jingrow.whitelist()
|
||||
def get_filterable_fields(pagetype: str):
|
||||
allowed_fieldtypes = [
|
||||
"Check",
|
||||
"Data",
|
||||
@ -64,26 +64,26 @@ def get_filterable_fields(doctype: str):
|
||||
"Datetime",
|
||||
]
|
||||
|
||||
c = get_controller(doctype)
|
||||
c = get_controller(pagetype)
|
||||
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 PageFields
|
||||
PageField = jingrow.qb.PageType("PageField")
|
||||
pg_fields = get_pagetype_fields_meta(PageField, pagetype, allowed_fieldtypes, restricted_fields)
|
||||
res.extend(pg_fields)
|
||||
|
||||
# append Custom Fields
|
||||
CustomField = frappe.qb.DocType("Custom Field")
|
||||
custom_fields = get_doctype_fields_meta(CustomField, doctype, allowed_fieldtypes, restricted_fields)
|
||||
CustomField = jingrow.qb.PageType("Custom Field")
|
||||
custom_fields = get_pagetype_fields_meta(CustomField, pagetype, allowed_fieldtypes, restricted_fields)
|
||||
res.extend(custom_fields)
|
||||
|
||||
# append standard fields (getting error when using frappe.model.std_fields)
|
||||
# append standard fields (getting error when using jingrow.model.std_fields)
|
||||
standard_fields = [
|
||||
{"fieldname": "name", "fieldtype": "Link", "label": "ID", "options": doctype},
|
||||
{"fieldname": "name", "fieldtype": "Link", "label": "ID", "options": pagetype},
|
||||
{"fieldname": "owner", "fieldtype": "Link", "label": "Created By", "options": "User"},
|
||||
{
|
||||
"fieldname": "modified_by",
|
||||
@ -110,8 +110,8 @@ def get_filterable_fields(doctype: str):
|
||||
return res
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_group_by_fields(doctype: str):
|
||||
@jingrow.whitelist()
|
||||
def get_group_by_fields(pagetype: str):
|
||||
allowed_fieldtypes = [
|
||||
"Check",
|
||||
"Data",
|
||||
@ -126,7 +126,7 @@ def get_group_by_fields(doctype: str):
|
||||
"Datetime",
|
||||
]
|
||||
|
||||
fields = frappe.get_meta(doctype).fields
|
||||
fields = jingrow.get_meta(pagetype).fields
|
||||
fields = [
|
||||
field
|
||||
for field in fields
|
||||
@ -161,32 +161,32 @@ def get_group_by_fields(doctype: str):
|
||||
return fields
|
||||
|
||||
|
||||
def get_doctype_fields_meta(DocField, doctype, allowed_fieldtypes, restricted_fields):
|
||||
parent = "parent" if DocField._table_name == "tabDocField" else "dt"
|
||||
def get_pagetype_fields_meta(PageField, pagetype, allowed_fieldtypes, restricted_fields):
|
||||
parent = "parent" if PageField._table_name == "tabPageField" else "dt"
|
||||
return (
|
||||
frappe.qb.from_(DocField)
|
||||
jingrow.qb.from_(PageField)
|
||||
.select(
|
||||
DocField.fieldname,
|
||||
DocField.fieldtype,
|
||||
DocField.label,
|
||||
DocField.name,
|
||||
DocField.options,
|
||||
PageField.fieldname,
|
||||
PageField.fieldtype,
|
||||
PageField.label,
|
||||
PageField.name,
|
||||
PageField.options,
|
||||
)
|
||||
.where(DocField[parent] == doctype)
|
||||
.where(DocField.hidden == False) # noqa: E712
|
||||
.where(Criterion.any([DocField.fieldtype == i for i in allowed_fieldtypes]))
|
||||
.where(Criterion.all([DocField.fieldname != i for i in restricted_fields]))
|
||||
.where(PageField[parent] == pagetype)
|
||||
.where(PageField.hidden == False) # noqa: E712
|
||||
.where(Criterion.any([PageField.fieldtype == i for i in allowed_fieldtypes]))
|
||||
.where(Criterion.all([PageField.fieldname != i for i in restricted_fields]))
|
||||
.run(as_dict=True)
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_quick_filters(doctype: str, cached: bool = True):
|
||||
meta = frappe.get_meta(doctype, cached)
|
||||
@jingrow.whitelist()
|
||||
def get_quick_filters(pagetype: str, cached: bool = True):
|
||||
meta = jingrow.get_meta(pagetype, cached)
|
||||
quick_filters = []
|
||||
|
||||
if global_settings := frappe.db.exists("CRM Global Settings", {"dt": doctype, "type": "Quick Filters"}):
|
||||
_quick_filters = frappe.db.get_value("CRM Global Settings", global_settings, "json")
|
||||
if global_settings := jingrow.db.exists("CRM Global Settings", {"dt": pagetype, "type": "Quick Filters"}):
|
||||
_quick_filters = jingrow.db.get_value("CRM Global Settings", global_settings, "json")
|
||||
_quick_filters = json.loads(_quick_filters) or []
|
||||
|
||||
fields = []
|
||||
@ -218,14 +218,14 @@ def get_quick_filters(doctype: str, cached: bool = True):
|
||||
}
|
||||
)
|
||||
|
||||
if doctype == "CRM Lead":
|
||||
if pagetype == "CRM Lead":
|
||||
quick_filters = [filter for filter in quick_filters if filter.get("fieldname") != "converted"]
|
||||
|
||||
return quick_filters
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_quick_filters(quick_filters: str, old_filters: str, doctype: str):
|
||||
@jingrow.whitelist()
|
||||
def update_quick_filters(quick_filters: str, old_filters: str, pagetype: str):
|
||||
quick_filters = json.loads(quick_filters)
|
||||
old_filters = json.loads(old_filters)
|
||||
|
||||
@ -233,49 +233,49 @@ def update_quick_filters(quick_filters: str, old_filters: str, doctype: str):
|
||||
removed_filters = [filter for filter in old_filters if filter not in quick_filters]
|
||||
|
||||
# update or create global quick filter settings
|
||||
create_update_global_settings(doctype, quick_filters)
|
||||
create_update_global_settings(pagetype, quick_filters)
|
||||
|
||||
# remove old filters
|
||||
for filter in removed_filters:
|
||||
update_in_standard_filter(filter, doctype, 0)
|
||||
update_in_standard_filter(filter, pagetype, 0)
|
||||
|
||||
# add new filters
|
||||
for filter in new_filters:
|
||||
update_in_standard_filter(filter, doctype, 1)
|
||||
update_in_standard_filter(filter, pagetype, 1)
|
||||
|
||||
|
||||
def create_update_global_settings(doctype, quick_filters):
|
||||
if global_settings := frappe.db.exists("CRM Global Settings", {"dt": doctype, "type": "Quick Filters"}):
|
||||
frappe.db.set_value("CRM Global Settings", global_settings, "json", json.dumps(quick_filters))
|
||||
def create_update_global_settings(pagetype, quick_filters):
|
||||
if global_settings := jingrow.db.exists("CRM Global Settings", {"dt": pagetype, "type": "Quick Filters"}):
|
||||
jingrow.db.set_value("CRM Global Settings", global_settings, "json", json.dumps(quick_filters))
|
||||
else:
|
||||
# create CRM Global Settings doc
|
||||
doc = frappe.new_doc("CRM Global Settings")
|
||||
doc.dt = doctype
|
||||
doc.type = "Quick Filters"
|
||||
doc.json = json.dumps(quick_filters)
|
||||
doc.insert()
|
||||
# create CRM Global Settings pg
|
||||
pg = jingrow.new_pg("CRM Global Settings")
|
||||
pg.dt = pagetype
|
||||
pg.type = "Quick Filters"
|
||||
pg.json = json.dumps(quick_filters)
|
||||
pg.insert()
|
||||
|
||||
|
||||
def update_in_standard_filter(fieldname, doctype, value):
|
||||
if property_name := frappe.db.exists(
|
||||
def update_in_standard_filter(fieldname, pagetype, value):
|
||||
if property_name := jingrow.db.exists(
|
||||
"Property Setter",
|
||||
{"doc_type": doctype, "field_name": fieldname, "property": "in_standard_filter"},
|
||||
{"pg_type": pagetype, "field_name": fieldname, "property": "in_standard_filter"},
|
||||
):
|
||||
frappe.db.set_value("Property Setter", property_name, "value", value)
|
||||
jingrow.db.set_value("Property Setter", property_name, "value", value)
|
||||
else:
|
||||
make_property_setter(
|
||||
doctype,
|
||||
pagetype,
|
||||
fieldname,
|
||||
"in_standard_filter",
|
||||
value,
|
||||
"Check",
|
||||
validate_fields_for_doctype=False,
|
||||
validate_fields_for_pagetype=False,
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def get_data(
|
||||
doctype: str,
|
||||
pagetype: str,
|
||||
filters: dict,
|
||||
order_by: str,
|
||||
page_length=20,
|
||||
@ -290,11 +290,11 @@ def get_data(
|
||||
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 "[]")
|
||||
filters = jingrow._dict(filters)
|
||||
rows = jingrow.parse_json(rows or "[]")
|
||||
columns = jingrow.parse_json(columns or "[]")
|
||||
kanban_fields = jingrow.parse_json(kanban_fields or "[]")
|
||||
kanban_columns = jingrow.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
|
||||
@ -304,33 +304,33 @@ def get_data(
|
||||
value = filters[key]
|
||||
if isinstance(value, list):
|
||||
if "@me" in value:
|
||||
value[value.index("@me")] = frappe.session.user
|
||||
value[value.index("@me")] = jingrow.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 + "%"
|
||||
value[i] = "%" + jingrow.session.user + "%"
|
||||
elif value == "@me":
|
||||
filters[key] = frappe.session.user
|
||||
filters[key] = jingrow.session.user
|
||||
|
||||
if default_filters:
|
||||
default_filters = frappe.parse_json(default_filters)
|
||||
default_filters = jingrow.parse_json(default_filters)
|
||||
filters.update(default_filters)
|
||||
|
||||
is_default = True
|
||||
data = []
|
||||
_list = get_controller(doctype)
|
||||
_list = get_controller(pagetype)
|
||||
default_rows = []
|
||||
if hasattr(_list, "default_list_data"):
|
||||
default_rows = _list.default_list_data().get("rows")
|
||||
|
||||
meta = frappe.get_meta(doctype)
|
||||
meta = jingrow.get_meta(pagetype)
|
||||
|
||||
if view_type != "kanban":
|
||||
if columns or rows:
|
||||
custom_view = True
|
||||
is_default = False
|
||||
columns = frappe.parse_json(columns)
|
||||
rows = frappe.parse_json(rows)
|
||||
columns = jingrow.parse_json(columns)
|
||||
rows = jingrow.parse_json(rows)
|
||||
|
||||
if not columns:
|
||||
columns = [
|
||||
@ -342,16 +342,16 @@ def get_data(
|
||||
rows = ["name"]
|
||||
|
||||
default_view_filters = {
|
||||
"dt": doctype,
|
||||
"dt": pagetype,
|
||||
"type": view_type or "list",
|
||||
"is_standard": 1,
|
||||
"user": frappe.session.user,
|
||||
"user": jingrow.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)
|
||||
if not custom_view and jingrow.db.exists("CRM View Settings", default_view_filters):
|
||||
list_view_settings = jingrow.get_pg("CRM View Settings", default_view_filters)
|
||||
columns = jingrow.parse_json(list_view_settings.columns)
|
||||
rows = jingrow.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
|
||||
@ -376,8 +376,8 @@ def get_data(
|
||||
rows.append(group_by_field)
|
||||
|
||||
data = (
|
||||
frappe.get_list(
|
||||
doctype,
|
||||
jingrow.get_list(
|
||||
pagetype,
|
||||
fields=rows,
|
||||
filters=filters,
|
||||
order_by=order_by,
|
||||
@ -385,16 +385,16 @@ def get_data(
|
||||
)
|
||||
or []
|
||||
)
|
||||
data = parse_list_data(data, doctype)
|
||||
data = parse_list_data(data, pagetype)
|
||||
|
||||
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)
|
||||
field_meta = jingrow.get_meta(pagetype).get_field(column_field)
|
||||
if field_meta.fieldtype == "Link":
|
||||
kanban_columns = frappe.get_all(
|
||||
kanban_columns = jingrow.get_all(
|
||||
field_meta.options,
|
||||
fields=["name"],
|
||||
order_by="modified asc",
|
||||
@ -433,13 +433,13 @@ def get_data(
|
||||
|
||||
if order:
|
||||
column_data = get_records_based_on_order(
|
||||
doctype, rows, column_filters, page_length, order
|
||||
pagetype, rows, column_filters, page_length, order
|
||||
)
|
||||
else:
|
||||
column_data = frappe.get_list(
|
||||
doctype,
|
||||
column_data = jingrow.get_list(
|
||||
pagetype,
|
||||
fields=rows,
|
||||
filters=convert_filter_to_tuple(doctype, column_filters),
|
||||
filters=convert_filter_to_tuple(pagetype, column_filters),
|
||||
order_by=order_by,
|
||||
page_length=page_length,
|
||||
)
|
||||
@ -447,9 +447,9 @@ def get_data(
|
||||
new_filters = filters.copy()
|
||||
new_filters.update({column_field: kc.get("name")})
|
||||
|
||||
all_count = frappe.get_list(
|
||||
doctype,
|
||||
filters=convert_filter_to_tuple(doctype, new_filters),
|
||||
all_count = jingrow.get_list(
|
||||
pagetype,
|
||||
filters=convert_filter_to_tuple(pagetype, new_filters),
|
||||
fields="count(*) as total_count",
|
||||
)[0].total_count
|
||||
|
||||
@ -457,7 +457,7 @@ def get_data(
|
||||
kc["count"] = len(column_data)
|
||||
|
||||
for d in column_data:
|
||||
getCounts(d, doctype)
|
||||
getCounts(d, pagetype)
|
||||
|
||||
if order:
|
||||
column_data = sorted(
|
||||
@ -467,7 +467,7 @@ def get_data(
|
||||
|
||||
data.append({"column": kc, "fields": kanban_fields, "data": column_data})
|
||||
|
||||
fields = frappe.get_meta(doctype).fields
|
||||
fields = jingrow.get_meta(pagetype).fields
|
||||
fields = [field for field in fields if field.fieldtype not in no_value_fields]
|
||||
fields = [
|
||||
{
|
||||
@ -503,7 +503,7 @@ def get_data(
|
||||
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")
|
||||
is_default = jingrow.db.get_value("CRM View Settings", custom_view_name, "load_default_columns")
|
||||
|
||||
if group_by_field and view_type == "group_by":
|
||||
|
||||
@ -552,40 +552,40 @@ def get_data(
|
||||
"page_length": page_length,
|
||||
"page_length_count": page_length_count,
|
||||
"is_default": is_default,
|
||||
"views": get_views(doctype),
|
||||
"total_count": frappe.get_list(doctype, filters=filters, fields="count(*) as total_count")[
|
||||
"views": get_views(pagetype),
|
||||
"total_count": jingrow.get_list(pagetype, filters=filters, fields="count(*) as total_count")[
|
||||
0
|
||||
].total_count,
|
||||
"row_count": len(data),
|
||||
"form_script": get_form_script(doctype),
|
||||
"list_script": get_form_script(doctype, "List"),
|
||||
"form_script": get_form_script(pagetype),
|
||||
"list_script": get_form_script(pagetype, "List"),
|
||||
"view_type": view_type,
|
||||
}
|
||||
|
||||
|
||||
def parse_list_data(data, doctype):
|
||||
_list = get_controller(doctype)
|
||||
def parse_list_data(data, pagetype):
|
||||
_list = get_controller(pagetype)
|
||||
if hasattr(_list, "parse_list_data"):
|
||||
data = _list.parse_list_data(data)
|
||||
return data
|
||||
|
||||
|
||||
def convert_filter_to_tuple(doctype, filters):
|
||||
def convert_filter_to_tuple(pagetype, filters):
|
||||
if isinstance(filters, dict):
|
||||
filters_items = filters.items()
|
||||
filters = []
|
||||
for key, value in filters_items:
|
||||
filters.append(make_filter_tuple(doctype, key, value))
|
||||
filters.append(make_filter_tuple(pagetype, key, value))
|
||||
return filters
|
||||
|
||||
|
||||
def get_records_based_on_order(doctype, rows, filters, page_length, order):
|
||||
def get_records_based_on_order(pagetype, rows, filters, page_length, order):
|
||||
records = []
|
||||
filters = convert_filter_to_tuple(doctype, filters)
|
||||
filters = convert_filter_to_tuple(pagetype, filters)
|
||||
in_filters = filters.copy()
|
||||
in_filters.append([doctype, "name", "in", order[:page_length]])
|
||||
records = frappe.get_list(
|
||||
doctype,
|
||||
in_filters.append([pagetype, "name", "in", order[:page_length]])
|
||||
records = jingrow.get_list(
|
||||
pagetype,
|
||||
fields=rows,
|
||||
filters=in_filters,
|
||||
order_by="creation desc",
|
||||
@ -594,9 +594,9 @@ def get_records_based_on_order(doctype, rows, filters, page_length, order):
|
||||
|
||||
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,
|
||||
not_in_filters.append([pagetype, "name", "not in", order])
|
||||
remaining_records = jingrow.get_list(
|
||||
pagetype,
|
||||
fields=rows,
|
||||
filters=not_in_filters,
|
||||
order_by="creation desc",
|
||||
@ -608,8 +608,8 @@ def get_records_based_on_order(doctype, rows, filters, page_length, order):
|
||||
return records
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False, only_required=False):
|
||||
@jingrow.whitelist()
|
||||
def get_fields_meta(pagetype, restricted_fieldtypes=None, as_array=False, only_required=False):
|
||||
not_allowed_fieldtypes = [
|
||||
"Tab Break",
|
||||
"Section Break",
|
||||
@ -617,14 +617,14 @@ def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False, only_re
|
||||
]
|
||||
|
||||
if restricted_fieldtypes:
|
||||
restricted_fieldtypes = frappe.parse_json(restricted_fieldtypes)
|
||||
restricted_fieldtypes = jingrow.parse_json(restricted_fieldtypes)
|
||||
not_allowed_fieldtypes += restricted_fieldtypes
|
||||
|
||||
fields = frappe.get_meta(doctype).fields
|
||||
fields = jingrow.get_meta(pagetype).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": "name", "fieldtype": "Link", "label": "ID", "options": pagetype},
|
||||
{"fieldname": "owner", "fieldtype": "Link", "label": "Created By", "options": "User"},
|
||||
{
|
||||
"fieldname": "modified_by",
|
||||
@ -654,22 +654,22 @@ def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False, only_re
|
||||
for field in fields:
|
||||
fields_meta[field.get("fieldname")] = field
|
||||
if field.get("fieldtype") == "Table":
|
||||
_fields = frappe.get_meta(field.get("options")).fields
|
||||
_fields = jingrow.get_meta(field.get("options")).fields
|
||||
fields_meta[field.get("fieldname")] = {"df": field, "fields": _fields}
|
||||
|
||||
return fields_meta
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def remove_assignments(doctype, name, assignees, ignore_permissions=False):
|
||||
assignees = frappe.parse_json(assignees)
|
||||
@jingrow.whitelist()
|
||||
def remove_assignments(pagetype, name, assignees, ignore_permissions=False):
|
||||
assignees = jingrow.parse_json(assignees)
|
||||
|
||||
if not assignees:
|
||||
return
|
||||
|
||||
for assign_to in assignees:
|
||||
set_status(
|
||||
doctype,
|
||||
pagetype,
|
||||
name,
|
||||
todo=None,
|
||||
assign_to=assign_to,
|
||||
@ -678,13 +678,13 @@ def remove_assignments(doctype, name, assignees, ignore_permissions=False):
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_assigned_users(doctype, name, default_assigned_to=None):
|
||||
assigned_users = frappe.get_all(
|
||||
@jingrow.whitelist()
|
||||
def get_assigned_users(pagetype, name, default_assigned_to=None):
|
||||
assigned_users = jingrow.get_all(
|
||||
"ToDo",
|
||||
fields=["allocated_to"],
|
||||
filters={
|
||||
"reference_type": doctype,
|
||||
"reference_type": pagetype,
|
||||
"reference_name": name,
|
||||
"status": ("!=", "Cancelled"),
|
||||
},
|
||||
@ -699,12 +699,12 @@ def get_assigned_users(doctype, name, default_assigned_to=None):
|
||||
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"]
|
||||
@jingrow.whitelist()
|
||||
def get_fields(pagetype: str, allow_all_fieldtypes: bool = False):
|
||||
not_allowed_fieldtypes = [*list(jingrow.model.no_value_fields), "Read Only"]
|
||||
if allow_all_fieldtypes:
|
||||
not_allowed_fieldtypes = []
|
||||
fields = frappe.get_meta(doctype).fields
|
||||
fields = jingrow.get_meta(pagetype).fields
|
||||
|
||||
_fields = []
|
||||
|
||||
@ -715,196 +715,196 @@ def get_fields(doctype: str, allow_all_fieldtypes: bool = False):
|
||||
return _fields
|
||||
|
||||
|
||||
def getCounts(d, doctype):
|
||||
def getCounts(d, pagetype):
|
||||
d["_email_count"] = (
|
||||
frappe.db.count(
|
||||
jingrow.db.count(
|
||||
"Communication",
|
||||
filters={
|
||||
"reference_doctype": doctype,
|
||||
"reference_pagetype": pagetype,
|
||||
"reference_name": d.get("name"),
|
||||
"communication_type": "Communication",
|
||||
},
|
||||
)
|
||||
or 0
|
||||
)
|
||||
d["_email_count"] = d["_email_count"] + frappe.db.count(
|
||||
d["_email_count"] = d["_email_count"] + jingrow.db.count(
|
||||
"Communication",
|
||||
filters={
|
||||
"reference_doctype": doctype,
|
||||
"reference_pagetype": pagetype,
|
||||
"reference_name": d.get("name"),
|
||||
"communication_type": "Automated Message",
|
||||
},
|
||||
)
|
||||
d["_comment_count"] = frappe.db.count(
|
||||
d["_comment_count"] = jingrow.db.count(
|
||||
"Comment",
|
||||
filters={"reference_doctype": doctype, "reference_name": d.get("name"), "comment_type": "Comment"},
|
||||
filters={"reference_pagetype": pagetype, "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["_task_count"] = jingrow.db.count(
|
||||
"CRM Task", filters={"reference_pagetype": pagetype, "reference_docname": d.get("name")}
|
||||
)
|
||||
d["_note_count"] = frappe.db.count(
|
||||
"FCRM Note", filters={"reference_doctype": doctype, "reference_docname": d.get("name")}
|
||||
d["_note_count"] = jingrow.db.count(
|
||||
"FCRM Note", filters={"reference_pagetype": pagetype, "reference_docname": d.get("name")}
|
||||
)
|
||||
return d
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_linked_docs_of_document(doctype, docname):
|
||||
@jingrow.whitelist()
|
||||
def get_linked_docs_of_document(pagetype, docname):
|
||||
try:
|
||||
doc = frappe.get_doc(doctype, docname)
|
||||
except frappe.DoesNotExistError:
|
||||
pg = jingrow.get_pg(pagetype, docname)
|
||||
except jingrow.DoesNotExistError:
|
||||
return []
|
||||
|
||||
linked_docs = get_linked_docs(doc)
|
||||
dynamic_linked_docs = get_dynamic_linked_docs(doc)
|
||||
linked_docs = get_linked_docs(pg)
|
||||
dynamic_linked_docs = get_dynamic_linked_docs(pg)
|
||||
|
||||
linked_docs.extend(dynamic_linked_docs)
|
||||
linked_docs = list({doc["reference_docname"]: doc for doc in linked_docs}.values())
|
||||
linked_docs = list({pg["reference_docname"]: pg for pg in linked_docs}.values())
|
||||
|
||||
docs_data = []
|
||||
for doc in linked_docs:
|
||||
if not doc.get("reference_doctype") or not doc.get("reference_docname"):
|
||||
for pg in linked_docs:
|
||||
if not pg.get("reference_pagetype") or not pg.get("reference_docname"):
|
||||
continue
|
||||
|
||||
try:
|
||||
data = frappe.get_doc(doc["reference_doctype"], doc["reference_docname"])
|
||||
except (frappe.DoesNotExistError, frappe.ValidationError):
|
||||
data = jingrow.get_pg(pg["reference_pagetype"], pg["reference_docname"])
|
||||
except (jingrow.DoesNotExistError, jingrow.ValidationError):
|
||||
continue
|
||||
|
||||
title = data.get("title")
|
||||
if data.doctype == "CRM Call Log":
|
||||
if data.pagetype == "CRM Call Log":
|
||||
title = f"Call from {data.get('from')} to {data.get('to')}"
|
||||
|
||||
if data.doctype == "CRM Deal":
|
||||
if data.pagetype == "CRM Deal":
|
||||
title = data.get("organization")
|
||||
|
||||
if data.doctype == "CRM Notification":
|
||||
if data.pagetype == "CRM Notification":
|
||||
title = data.get("message")
|
||||
|
||||
docs_data.append(
|
||||
{
|
||||
"doc": data.doctype,
|
||||
"pg": data.pagetype,
|
||||
"title": title or data.get("name"),
|
||||
"reference_docname": doc["reference_docname"],
|
||||
"reference_doctype": doc["reference_doctype"],
|
||||
"reference_docname": pg["reference_docname"],
|
||||
"reference_pagetype": pg["reference_pagetype"],
|
||||
}
|
||||
)
|
||||
return docs_data
|
||||
|
||||
|
||||
def remove_doc_link(doctype, docname):
|
||||
if not doctype or not docname:
|
||||
def remove_pg_link(pagetype, docname):
|
||||
if not pagetype or not docname:
|
||||
return
|
||||
|
||||
try:
|
||||
linked_doc_data = frappe.get_doc(doctype, docname)
|
||||
if doctype == "CRM Notification":
|
||||
linked_pg_data = jingrow.get_pg(pagetype, docname)
|
||||
if pagetype == "CRM Notification":
|
||||
delete_notification_type = {
|
||||
"notification_type_doctype": "",
|
||||
"notification_type_doc": "",
|
||||
"notification_type_pagetype": "",
|
||||
"notification_type_pg": "",
|
||||
}
|
||||
delete_references = {
|
||||
"reference_doctype": "",
|
||||
"reference_pagetype": "",
|
||||
"reference_name": "",
|
||||
}
|
||||
if linked_doc_data.get("notification_type_doctype") == linked_doc_data.get("reference_doctype"):
|
||||
if linked_pg_data.get("notification_type_pagetype") == linked_pg_data.get("reference_pagetype"):
|
||||
delete_references.update(delete_notification_type)
|
||||
|
||||
linked_doc_data.update(delete_references)
|
||||
linked_pg_data.update(delete_references)
|
||||
else:
|
||||
linked_doc_data.update(
|
||||
linked_pg_data.update(
|
||||
{
|
||||
"reference_doctype": "",
|
||||
"reference_pagetype": "",
|
||||
"reference_docname": "",
|
||||
}
|
||||
)
|
||||
linked_doc_data.save(ignore_permissions=True)
|
||||
except (frappe.DoesNotExistError, frappe.ValidationError):
|
||||
linked_pg_data.save(ignore_permissions=True)
|
||||
except (jingrow.DoesNotExistError, jingrow.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
def remove_contact_link(doctype, docname):
|
||||
if not doctype or not docname:
|
||||
def remove_contact_link(pagetype, docname):
|
||||
if not pagetype or not docname:
|
||||
return
|
||||
|
||||
try:
|
||||
linked_doc_data = frappe.get_doc(doctype, docname)
|
||||
linked_doc_data.update(
|
||||
linked_pg_data = jingrow.get_pg(pagetype, docname)
|
||||
linked_pg_data.update(
|
||||
{
|
||||
"contact": None,
|
||||
"contacts": [],
|
||||
}
|
||||
)
|
||||
linked_doc_data.save(ignore_permissions=True)
|
||||
except (frappe.DoesNotExistError, frappe.ValidationError):
|
||||
linked_pg_data.save(ignore_permissions=True)
|
||||
except (jingrow.DoesNotExistError, jingrow.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def remove_linked_doc_reference(items, remove_contact=None, delete=False):
|
||||
@jingrow.whitelist()
|
||||
def remove_linked_pg_reference(items, remove_contact=None, delete=False):
|
||||
if isinstance(items, str):
|
||||
items = frappe.parse_json(items)
|
||||
items = jingrow.parse_json(items)
|
||||
|
||||
for item in items:
|
||||
if not item.get("doctype") or not item.get("docname"):
|
||||
if not item.get("pagetype") or not item.get("docname"):
|
||||
continue
|
||||
|
||||
try:
|
||||
if remove_contact:
|
||||
remove_contact_link(item["doctype"], item["docname"])
|
||||
remove_contact_link(item["pagetype"], item["docname"])
|
||||
else:
|
||||
remove_doc_link(item["doctype"], item["docname"])
|
||||
remove_pg_link(item["pagetype"], item["docname"])
|
||||
if delete:
|
||||
frappe.delete_doc(item["doctype"], item["docname"])
|
||||
except (frappe.DoesNotExistError, frappe.ValidationError):
|
||||
jingrow.delete_pg(item["pagetype"], item["docname"])
|
||||
except (jingrow.DoesNotExistError, jingrow.ValidationError):
|
||||
# Skip if document doesn't exist or has validation errors
|
||||
continue
|
||||
|
||||
return "success"
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def delete_bulk_docs(doctype, items, delete_linked=False):
|
||||
from frappe.desk.reportview import delete_bulk
|
||||
@jingrow.whitelist()
|
||||
def delete_bulk_docs(pagetype, items, delete_linked=False):
|
||||
from jingrow.desk.reportview import delete_bulk
|
||||
|
||||
if not doctype:
|
||||
frappe.throw("Doctype is required")
|
||||
if not pagetype:
|
||||
jingrow.throw("Pagetype is required")
|
||||
|
||||
if not items:
|
||||
frappe.throw("Items are required")
|
||||
jingrow.throw("Items are required")
|
||||
|
||||
items = frappe.parse_json(items)
|
||||
items = jingrow.parse_json(items)
|
||||
if not isinstance(items, list):
|
||||
frappe.throw("Items must be a list")
|
||||
jingrow.throw("Items must be a list")
|
||||
|
||||
for doc in items:
|
||||
for pg in items:
|
||||
try:
|
||||
if not frappe.db.exists(doctype, doc):
|
||||
frappe.log_error(f"Document {doctype} {doc} does not exist", "Bulk Delete Error")
|
||||
if not jingrow.db.exists(pagetype, pg):
|
||||
jingrow.log_error(f"Document {pagetype} {pg} does not exist", "Bulk Delete Error")
|
||||
continue
|
||||
|
||||
linked_docs = get_linked_docs_of_document(doctype, doc)
|
||||
for linked_doc in linked_docs:
|
||||
if not linked_doc.get("reference_doctype") or not linked_doc.get("reference_docname"):
|
||||
linked_docs = get_linked_docs_of_document(pagetype, pg)
|
||||
for linked_pg in linked_docs:
|
||||
if not linked_pg.get("reference_pagetype") or not linked_pg.get("reference_docname"):
|
||||
continue
|
||||
|
||||
remove_linked_doc_reference(
|
||||
remove_linked_pg_reference(
|
||||
[
|
||||
{
|
||||
"doctype": linked_doc["reference_doctype"],
|
||||
"docname": linked_doc["reference_docname"],
|
||||
"pagetype": linked_pg["reference_pagetype"],
|
||||
"docname": linked_pg["reference_docname"],
|
||||
}
|
||||
],
|
||||
remove_contact=doctype == "Contact",
|
||||
remove_contact=pagetype == "Contact",
|
||||
delete=delete_linked,
|
||||
)
|
||||
except Exception as e:
|
||||
frappe.log_error(
|
||||
f"Error processing linked docs for {doctype} {doc}: {str(e)}", "Bulk Delete Error"
|
||||
jingrow.log_error(
|
||||
f"Error processing linked docs for {pagetype} {pg}: {str(e)}", "Bulk Delete Error"
|
||||
)
|
||||
|
||||
if len(items) > 10:
|
||||
frappe.enqueue("frappe.desk.reportview.delete_bulk", doctype=doctype, items=items)
|
||||
jingrow.enqueue("jingrow.desk.reportview.delete_bulk", pagetype=pagetype, items=items)
|
||||
else:
|
||||
delete_bulk(doctype, items)
|
||||
delete_bulk(pagetype, items)
|
||||
return "success"
|
||||
@ -1,9 +1,9 @@
|
||||
import frappe
|
||||
import jingrow
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def get_users():
|
||||
users = frappe.qb.get_query(
|
||||
users = jingrow.qb.get_query(
|
||||
"User",
|
||||
fields=[
|
||||
"name",
|
||||
@ -20,10 +20,10 @@ def get_users():
|
||||
).run(as_dict=1)
|
||||
|
||||
for user in users:
|
||||
if frappe.session.user == user.name:
|
||||
if jingrow.session.user == user.name:
|
||||
user.session_user = True
|
||||
|
||||
user.roles = frappe.get_roles(user.name)
|
||||
user.roles = jingrow.get_roles(user.name)
|
||||
|
||||
user.role = ""
|
||||
|
||||
@ -36,10 +36,10 @@ def get_users():
|
||||
elif "Guest" in user.roles:
|
||||
user.role = "Guest"
|
||||
|
||||
if frappe.session.user == user.name:
|
||||
if jingrow.session.user == user.name:
|
||||
user.session_user = True
|
||||
|
||||
user.is_telephony_agent = frappe.db.exists("CRM Telephony Agent", {"user": user.name})
|
||||
user.is_telephony_agent = jingrow.db.exists("CRM Telephony Agent", {"user": user.name})
|
||||
|
||||
crm_users = []
|
||||
|
||||
@ -51,9 +51,9 @@ def get_users():
|
||||
return users, crm_users
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def get_organizations():
|
||||
organizations = frappe.qb.get_query(
|
||||
organizations = jingrow.qb.get_query(
|
||||
"CRM Organization",
|
||||
fields=["*"],
|
||||
order_by="name asc",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import frappe
|
||||
import jingrow
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def create_email_account(data):
|
||||
service = data.get("service")
|
||||
service_config = email_service_config.get(service)
|
||||
@ -9,9 +9,9 @@ def create_email_account(data):
|
||||
return "Service not supported"
|
||||
|
||||
try:
|
||||
email_doc = frappe.get_doc(
|
||||
email_pg = jingrow.get_pg(
|
||||
{
|
||||
"doctype": "Email Account",
|
||||
"pagetype": "Email Account",
|
||||
"email_id": data.get("email_id"),
|
||||
"email_account_name": data.get("email_account_name"),
|
||||
"service": service,
|
||||
@ -29,25 +29,25 @@ def create_email_account(data):
|
||||
**service_config,
|
||||
}
|
||||
)
|
||||
if service == "Frappe Mail":
|
||||
email_doc.api_key = data.get("api_key")
|
||||
email_doc.api_secret = data.get("api_secret")
|
||||
email_doc.frappe_mail_site = data.get("frappe_mail_site")
|
||||
email_doc.append_to = "CRM Lead"
|
||||
if service == "Jingrow Mail":
|
||||
email_pg.api_key = data.get("api_key")
|
||||
email_pg.api_secret = data.get("api_secret")
|
||||
email_pg.jingrow_mail_site = data.get("jingrow_mail_site")
|
||||
email_pg.append_to = "CRM Lead"
|
||||
else:
|
||||
email_doc.append("imap_folder", {"append_to": "CRM Lead", "folder_name": "INBOX"})
|
||||
email_doc.password = data.get("password")
|
||||
email_pg.append("imap_folder", {"append_to": "CRM Lead", "folder_name": "INBOX"})
|
||||
email_pg.password = data.get("password")
|
||||
# validate whether the credentials are correct
|
||||
email_doc.get_incoming_server()
|
||||
email_pg.get_incoming_server()
|
||||
|
||||
# if correct credentials, save the email account
|
||||
email_doc.save()
|
||||
email_pg.save()
|
||||
except Exception as e:
|
||||
frappe.throw(str(e))
|
||||
jingrow.throw(str(e))
|
||||
|
||||
|
||||
email_service_config = {
|
||||
"Frappe Mail": {
|
||||
"Jingrow Mail": {
|
||||
"domain": None,
|
||||
"password": None,
|
||||
"awaiting_password": 0,
|
||||
|
||||
@ -1,82 +1,82 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
import jingrow
|
||||
from jingrow import _
|
||||
|
||||
from crm.fcrm.doctype.crm_notification.crm_notification import notify_user
|
||||
from crm.fcrm.pagetype.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"
|
||||
owner = frappe.db.get_value(doc.reference_type, doc.reference_name, fieldname)
|
||||
def after_insert(pg, method):
|
||||
if pg.reference_type in ["CRM Lead", "CRM Deal"] and pg.reference_name and pg.allocated_to:
|
||||
fieldname = "lead_owner" if pg.reference_type == "CRM Lead" else "deal_owner"
|
||||
owner = jingrow.db.get_value(pg.reference_type, pg.reference_name, fieldname)
|
||||
if not owner:
|
||||
frappe.db.set_value(
|
||||
doc.reference_type, doc.reference_name, fieldname, doc.allocated_to, update_modified=False
|
||||
jingrow.db.set_value(
|
||||
pg.reference_type, pg.reference_name, fieldname, pg.allocated_to, update_modified=False
|
||||
)
|
||||
|
||||
if doc.reference_type in ["CRM Lead", "CRM Deal", "CRM Task"] and doc.reference_name and doc.allocated_to:
|
||||
notify_assigned_user(doc)
|
||||
if pg.reference_type in ["CRM Lead", "CRM Deal", "CRM Task"] and pg.reference_name and pg.allocated_to:
|
||||
notify_assigned_user(pg)
|
||||
|
||||
|
||||
def on_update(doc, method):
|
||||
def on_update(pg, 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
|
||||
pg.has_value_changed("status")
|
||||
and pg.status == "Cancelled"
|
||||
and pg.reference_type in ["CRM Lead", "CRM Deal", "CRM Task"]
|
||||
and pg.reference_name
|
||||
and pg.allocated_to
|
||||
):
|
||||
notify_assigned_user(doc, is_cancelled=True)
|
||||
notify_assigned_user(pg, 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)
|
||||
def notify_assigned_user(pg, is_cancelled=False):
|
||||
_pg = jingrow.get_pg(pg.reference_type, pg.reference_name)
|
||||
owner = jingrow.get_cached_value("User", jingrow.session.user, "full_name")
|
||||
notification_text = get_notification_text(owner, pg, _pg, is_cancelled)
|
||||
|
||||
message = (
|
||||
_("Your assignment on {0} {1} has been removed by {2}").format(
|
||||
doc.reference_type, doc.reference_name, owner
|
||||
pg.reference_type, pg.reference_name, owner
|
||||
)
|
||||
if is_cancelled
|
||||
else _("{0} assigned a {1} {2} to you").format(owner, doc.reference_type, doc.reference_name)
|
||||
else _("{0} assigned a {1} {2} to you").format(owner, pg.reference_type, pg.reference_name)
|
||||
)
|
||||
|
||||
redirect_to_doctype, redirect_to_name = get_redirect_to_doc(doc)
|
||||
redirect_to_pagetype, redirect_to_name = get_redirect_to_pg(pg)
|
||||
|
||||
notify_user(
|
||||
{
|
||||
"owner": frappe.session.user,
|
||||
"assigned_to": doc.allocated_to,
|
||||
"owner": jingrow.session.user,
|
||||
"assigned_to": pg.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,
|
||||
"reference_pagetype": pg.reference_type,
|
||||
"reference_docname": pg.reference_name,
|
||||
"redirect_to_pagetype": redirect_to_pagetype,
|
||||
"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
|
||||
def get_notification_text(owner, pg, reference_pg, is_cancelled=False):
|
||||
name = pg.reference_name
|
||||
pagetype = pg.reference_type
|
||||
|
||||
if doctype.startswith("CRM "):
|
||||
doctype = doctype[4:].lower()
|
||||
if pagetype.startswith("CRM "):
|
||||
pagetype = pagetype[4:].lower()
|
||||
|
||||
if doctype in ["lead", "deal"]:
|
||||
if pagetype in ["lead", "deal"]:
|
||||
name = (
|
||||
reference_doc.lead_name or name
|
||||
if doctype == "lead"
|
||||
else reference_doc.organization or reference_doc.lead_name or name
|
||||
reference_pg.lead_name or name
|
||||
if pagetype == "lead"
|
||||
else reference_pg.organization or reference_pg.lead_name or name
|
||||
)
|
||||
|
||||
if is_cancelled:
|
||||
return f"""
|
||||
<div class="mb-2 leading-5 text-ink-gray-5">
|
||||
<span>{ _('Your assignment on {0} {1} has been removed by {2}').format(
|
||||
doctype,
|
||||
pagetype,
|
||||
f'<span class="font-medium text-ink-gray-9">{ name }</span>',
|
||||
f'<span class="font-medium text-ink-gray-9">{ owner }</span>'
|
||||
) }</span>
|
||||
@ -87,18 +87,18 @@ def get_notification_text(owner, doc, reference_doc, is_cancelled=False):
|
||||
<div class="mb-2 leading-5 text-ink-gray-5">
|
||||
<span class="font-medium text-ink-gray-9">{ owner }</span>
|
||||
<span>{ _('assigned a {0} {1} to you').format(
|
||||
doctype,
|
||||
pagetype,
|
||||
f'<span class="font-medium text-ink-gray-9">{ name }</span>'
|
||||
) }</span>
|
||||
</div>
|
||||
"""
|
||||
|
||||
if doctype == "task":
|
||||
if pagetype == "task":
|
||||
if is_cancelled:
|
||||
return f"""
|
||||
<div class="mb-2 leading-5 text-ink-gray-5">
|
||||
<span>{ _('Your assignment on task {0} has been removed by {1}').format(
|
||||
f'<span class="font-medium text-ink-gray-9">{ reference_doc.title }</span>',
|
||||
f'<span class="font-medium text-ink-gray-9">{ reference_pg.title }</span>',
|
||||
f'<span class="font-medium text-ink-gray-9">{ owner }</span>'
|
||||
) }</span>
|
||||
</div>
|
||||
@ -107,15 +107,15 @@ def get_notification_text(owner, doc, reference_doc, is_cancelled=False):
|
||||
<div class="mb-2 leading-5 text-ink-gray-5">
|
||||
<span class="font-medium text-ink-gray-9">{ owner }</span>
|
||||
<span>{ _('assigned a new task {0} to you').format(
|
||||
f'<span class="font-medium text-ink-gray-9">{ reference_doc.title }</span>'
|
||||
f'<span class="font-medium text-ink-gray-9">{ reference_pg.title }</span>'
|
||||
) }</span>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
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
|
||||
def get_redirect_to_pg(pg):
|
||||
if pg.reference_type == "CRM Task":
|
||||
reference_pg = jingrow.get_pg(pg.reference_type, pg.reference_name)
|
||||
return reference_pg.reference_pagetype, reference_pg.reference_docname
|
||||
|
||||
return doc.reference_type, doc.reference_name
|
||||
return pg.reference_type, pg.reference_name
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
import frappe
|
||||
import jingrow
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def add_existing_users(users, role="Sales User"):
|
||||
"""
|
||||
Add existing users to the CRM by assigning them a role (Sales User or Sales Manager).
|
||||
:param users: List of user names to be added
|
||||
"""
|
||||
frappe.only_for(["System Manager", "Sales Manager"])
|
||||
users = frappe.parse_json(users)
|
||||
jingrow.only_for(["System Manager", "Sales Manager"])
|
||||
users = jingrow.parse_json(users)
|
||||
|
||||
for user in users:
|
||||
add_user(user, role)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def update_user_role(user, new_role):
|
||||
"""
|
||||
Update the role of the user to Sales Manager, Sales User, or System Manager.
|
||||
@ -22,28 +22,28 @@ def update_user_role(user, new_role):
|
||||
:param new_role: The new role to assign (Sales Manager or Sales User)
|
||||
"""
|
||||
|
||||
frappe.only_for(["System Manager", "Sales Manager"])
|
||||
jingrow.only_for(["System Manager", "Sales Manager"])
|
||||
|
||||
if new_role not in ["System Manager", "Sales Manager", "Sales User"]:
|
||||
frappe.throw("Cannot assign this role")
|
||||
jingrow.throw("Cannot assign this role")
|
||||
|
||||
user_doc = frappe.get_doc("User", user)
|
||||
user_pg = jingrow.get_pg("User", user)
|
||||
|
||||
if new_role == "System Manager":
|
||||
user_doc.append_roles("System Manager", "Sales Manager", "Sales User")
|
||||
user_doc.set("block_modules", [])
|
||||
user_pg.append_roles("System Manager", "Sales Manager", "Sales User")
|
||||
user_pg.set("block_modules", [])
|
||||
if new_role == "Sales Manager":
|
||||
user_doc.append_roles("Sales Manager", "Sales User")
|
||||
user_doc.remove_roles("System Manager")
|
||||
user_pg.append_roles("Sales Manager", "Sales User")
|
||||
user_pg.remove_roles("System Manager")
|
||||
if new_role == "Sales User":
|
||||
user_doc.append_roles("Sales User")
|
||||
user_doc.remove_roles("Sales Manager", "System Manager")
|
||||
update_module_in_user(user_doc, "FCRM")
|
||||
user_pg.append_roles("Sales User")
|
||||
user_pg.remove_roles("Sales Manager", "System Manager")
|
||||
update_module_in_user(user_pg, "FCRM")
|
||||
|
||||
user_doc.save(ignore_permissions=True)
|
||||
user_pg.save(ignore_permissions=True)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def add_user(user, role):
|
||||
"""
|
||||
Add a user means adding role (Sales User or/and Sales Manager) to the user.
|
||||
@ -53,28 +53,28 @@ def add_user(user, role):
|
||||
update_user_role(user, role)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def remove_user(user):
|
||||
"""
|
||||
Remove a user means removing Sales User & Sales Manager roles from the user.
|
||||
:param user: The name of the user to be removed
|
||||
"""
|
||||
frappe.only_for(["System Manager", "Sales Manager"])
|
||||
jingrow.only_for(["System Manager", "Sales Manager"])
|
||||
|
||||
user_doc = frappe.get_doc("User", user)
|
||||
roles = [d.role for d in user_doc.roles]
|
||||
user_pg = jingrow.get_pg("User", user)
|
||||
roles = [d.role for d in user_pg.roles]
|
||||
|
||||
if "Sales User" in roles:
|
||||
user_doc.remove_roles("Sales User")
|
||||
user_pg.remove_roles("Sales User")
|
||||
if "Sales Manager" in roles:
|
||||
user_doc.remove_roles("Sales Manager")
|
||||
user_pg.remove_roles("Sales Manager")
|
||||
|
||||
user_doc.save(ignore_permissions=True)
|
||||
frappe.msgprint(f"User {user} has been removed from CRM roles.")
|
||||
user_pg.save(ignore_permissions=True)
|
||||
jingrow.msgprint(f"User {user} has been removed from CRM roles.")
|
||||
|
||||
|
||||
def update_module_in_user(user, module):
|
||||
block_modules = frappe.get_all(
|
||||
block_modules = jingrow.get_all(
|
||||
"Module Def",
|
||||
fields=["name as module"],
|
||||
filters={"name": ["!=", module]},
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import frappe
|
||||
import jingrow
|
||||
from pypika import Criterion
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_views(doctype):
|
||||
View = frappe.qb.DocType("CRM View Settings")
|
||||
@jingrow.whitelist()
|
||||
def get_views(pagetype):
|
||||
View = jingrow.qb.PageType("CRM View Settings")
|
||||
query = (
|
||||
frappe.qb.from_(View)
|
||||
jingrow.qb.from_(View)
|
||||
.select("*")
|
||||
.where(Criterion.any([View.user == "", View.user == frappe.session.user]))
|
||||
.where(Criterion.any([View.user == "", View.user == jingrow.session.user]))
|
||||
)
|
||||
if doctype:
|
||||
query = query.where(View.dt == doctype)
|
||||
if pagetype:
|
||||
query = query.where(View.dt == pagetype)
|
||||
views = query.run(as_dict=True)
|
||||
return views
|
||||
|
||||
@ -1,63 +1,63 @@
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
import jingrow
|
||||
from jingrow import _
|
||||
|
||||
from crm.api.doc import get_assigned_users
|
||||
from crm.fcrm.doctype.crm_notification.crm_notification import notify_user
|
||||
from crm.api.pg import get_assigned_users
|
||||
from crm.fcrm.pagetype.crm_notification.crm_notification import notify_user
|
||||
|
||||
|
||||
def validate(doc, method):
|
||||
if doc.type == "Incoming" and doc.get("from"):
|
||||
name, doctype = get_lead_or_deal_from_number(doc.get("from"))
|
||||
def validate(pg, method):
|
||||
if pg.type == "Incoming" and pg.get("from"):
|
||||
name, pagetype = get_lead_or_deal_from_number(pg.get("from"))
|
||||
if name != None:
|
||||
doc.reference_doctype = doctype
|
||||
doc.reference_name = name
|
||||
pg.reference_pagetype = pagetype
|
||||
pg.reference_name = name
|
||||
|
||||
if doc.type == "Outgoing" and doc.get("to"):
|
||||
name, doctype = get_lead_or_deal_from_number(doc.get("to"))
|
||||
if pg.type == "Outgoing" and pg.get("to"):
|
||||
name, pagetype = get_lead_or_deal_from_number(pg.get("to"))
|
||||
if name != None:
|
||||
doc.reference_doctype = doctype
|
||||
doc.reference_name = name
|
||||
pg.reference_pagetype = pagetype
|
||||
pg.reference_name = name
|
||||
|
||||
|
||||
def on_update(doc, method):
|
||||
frappe.publish_realtime(
|
||||
def on_update(pg, method):
|
||||
jingrow.publish_realtime(
|
||||
"whatsapp_message",
|
||||
{
|
||||
"reference_doctype": doc.reference_doctype,
|
||||
"reference_name": doc.reference_name,
|
||||
"reference_pagetype": pg.reference_pagetype,
|
||||
"reference_name": pg.reference_name,
|
||||
},
|
||||
)
|
||||
|
||||
notify_agent(doc)
|
||||
notify_agent(pg)
|
||||
|
||||
|
||||
def notify_agent(doc):
|
||||
if doc.type == "Incoming":
|
||||
doctype = doc.reference_doctype
|
||||
if doctype and doctype.startswith("CRM "):
|
||||
doctype = doctype[4:].lower()
|
||||
def notify_agent(pg):
|
||||
if pg.type == "Incoming":
|
||||
pagetype = pg.reference_pagetype
|
||||
if pagetype and pagetype.startswith("CRM "):
|
||||
pagetype = pagetype[4:].lower()
|
||||
notification_text = f"""
|
||||
<div class="mb-2 leading-5 text-ink-gray-5">
|
||||
<span class="font-medium text-ink-gray-9">{ _('You') }</span>
|
||||
<span>{ _('received a whatsapp message in {0}').format(doctype) }</span>
|
||||
<span class="font-medium text-ink-gray-9">{ doc.reference_name }</span>
|
||||
<span>{ _('received a whatsapp message in {0}').format(pagetype) }</span>
|
||||
<span class="font-medium text-ink-gray-9">{ pg.reference_name }</span>
|
||||
</div>
|
||||
"""
|
||||
assigned_users = get_assigned_users(doc.reference_doctype, doc.reference_name)
|
||||
assigned_users = get_assigned_users(pg.reference_pagetype, pg.reference_name)
|
||||
for user in assigned_users:
|
||||
notify_user(
|
||||
{
|
||||
"owner": doc.owner,
|
||||
"owner": pg.owner,
|
||||
"assigned_to": user,
|
||||
"notification_type": "WhatsApp",
|
||||
"message": doc.message,
|
||||
"message": pg.message,
|
||||
"notification_text": notification_text,
|
||||
"reference_doctype": "WhatsApp Message",
|
||||
"reference_docname": doc.name,
|
||||
"redirect_to_doctype": doc.reference_doctype,
|
||||
"redirect_to_docname": doc.reference_name,
|
||||
"reference_pagetype": "WhatsApp Message",
|
||||
"reference_docname": pg.name,
|
||||
"redirect_to_pagetype": pg.reference_pagetype,
|
||||
"redirect_to_docname": pg.reference_name,
|
||||
}
|
||||
)
|
||||
|
||||
@ -65,28 +65,28 @@ def notify_agent(doc):
|
||||
def get_lead_or_deal_from_number(number):
|
||||
"""Get lead/deal from the given number."""
|
||||
|
||||
def find_record(doctype, mobile_no, where=""):
|
||||
def find_record(pagetype, mobile_no, where=""):
|
||||
mobile_no = parse_mobile_no(mobile_no)
|
||||
|
||||
query = f"""
|
||||
SELECT name, mobile_no
|
||||
FROM `tab{doctype}`
|
||||
FROM `tab{pagetype}`
|
||||
WHERE CONCAT('+', REGEXP_REPLACE(mobile_no, '[^0-9]', '')) = {mobile_no}
|
||||
"""
|
||||
|
||||
data = frappe.db.sql(query + where, as_dict=True)
|
||||
data = jingrow.db.sql(query + where, as_dict=True)
|
||||
return data[0].name if data else None
|
||||
|
||||
doctype = "CRM Deal"
|
||||
pagetype = "CRM Deal"
|
||||
|
||||
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)
|
||||
pg = find_record(pagetype, number) or None
|
||||
if not pg:
|
||||
pagetype = "CRM Lead"
|
||||
pg = find_record(pagetype, number, "AND converted is not True")
|
||||
if not pg:
|
||||
pg = find_record(pagetype, number)
|
||||
|
||||
return doc, doctype
|
||||
return pg, pagetype
|
||||
|
||||
|
||||
def parse_mobile_no(mobile_no: str):
|
||||
@ -97,37 +97,37 @@ def parse_mobile_no(mobile_no: str):
|
||||
return "".join([c for c in mobile_no if c.isdigit() or c == "+"])
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def is_whatsapp_enabled():
|
||||
if not frappe.db.exists("DocType", "WhatsApp Settings"):
|
||||
if not jingrow.db.exists("PageType", "WhatsApp Settings"):
|
||||
return False
|
||||
return frappe.get_cached_value("WhatsApp Settings", "WhatsApp Settings", "enabled")
|
||||
return jingrow.get_cached_value("WhatsApp Settings", "WhatsApp Settings", "enabled")
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def is_whatsapp_installed():
|
||||
if not frappe.db.exists("DocType", "WhatsApp Settings"):
|
||||
if not jingrow.db.exists("PageType", "WhatsApp Settings"):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_whatsapp_messages(reference_doctype, reference_name):
|
||||
@jingrow.whitelist()
|
||||
def get_whatsapp_messages(reference_pagetype, reference_name):
|
||||
# twilio integration app is not compatible with crm app
|
||||
# crm has its own twilio integration in built
|
||||
if "twilio_integration" in frappe.get_installed_apps():
|
||||
if "twilio_integration" in jingrow.get_installed_apps():
|
||||
return []
|
||||
if not frappe.db.exists("DocType", "WhatsApp Message"):
|
||||
if not jingrow.db.exists("PageType", "WhatsApp Message"):
|
||||
return []
|
||||
messages = []
|
||||
|
||||
if reference_doctype == "CRM Deal":
|
||||
lead = frappe.db.get_value(reference_doctype, reference_name, "lead")
|
||||
if reference_pagetype == "CRM Deal":
|
||||
lead = jingrow.db.get_value(reference_pagetype, reference_name, "lead")
|
||||
if lead:
|
||||
messages = frappe.get_all(
|
||||
messages = jingrow.get_all(
|
||||
"WhatsApp Message",
|
||||
filters={
|
||||
"reference_doctype": "CRM Lead",
|
||||
"reference_pagetype": "CRM Lead",
|
||||
"reference_name": lead,
|
||||
},
|
||||
fields=[
|
||||
@ -146,17 +146,17 @@ def get_whatsapp_messages(reference_doctype, reference_name):
|
||||
"creation",
|
||||
"message",
|
||||
"status",
|
||||
"reference_doctype",
|
||||
"reference_pagetype",
|
||||
"reference_name",
|
||||
"template_parameters",
|
||||
"template_header_parameters",
|
||||
],
|
||||
)
|
||||
|
||||
messages += frappe.get_all(
|
||||
messages += jingrow.get_all(
|
||||
"WhatsApp Message",
|
||||
filters={
|
||||
"reference_doctype": reference_doctype,
|
||||
"reference_pagetype": reference_pagetype,
|
||||
"reference_name": reference_name,
|
||||
},
|
||||
fields=[
|
||||
@ -175,7 +175,7 @@ def get_whatsapp_messages(reference_doctype, reference_name):
|
||||
"creation",
|
||||
"message",
|
||||
"status",
|
||||
"reference_doctype",
|
||||
"reference_pagetype",
|
||||
"reference_name",
|
||||
"template_parameters",
|
||||
"template_header_parameters",
|
||||
@ -188,7 +188,7 @@ def get_whatsapp_messages(reference_doctype, reference_name):
|
||||
# Iterate through template messages
|
||||
for template_message in template_messages:
|
||||
# Find the template that this message is using
|
||||
template = frappe.get_doc("WhatsApp Templates", template_message["template"])
|
||||
template = jingrow.get_pg("WhatsApp Templates", template_message["template"])
|
||||
|
||||
# If the template is found, add the template details to the template message
|
||||
if template:
|
||||
@ -249,9 +249,9 @@ def get_whatsapp_messages(reference_doctype, reference_name):
|
||||
return [message for message in messages if message["content_type"] != "reaction"]
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def create_whatsapp_message(
|
||||
reference_doctype,
|
||||
reference_pagetype,
|
||||
reference_name,
|
||||
message,
|
||||
to,
|
||||
@ -259,20 +259,20 @@ def create_whatsapp_message(
|
||||
reply_to,
|
||||
content_type="text",
|
||||
):
|
||||
doc = frappe.new_doc("WhatsApp Message")
|
||||
pg = jingrow.new_pg("WhatsApp Message")
|
||||
|
||||
if reply_to:
|
||||
reply_doc = frappe.get_doc("WhatsApp Message", reply_to)
|
||||
doc.update(
|
||||
reply_pg = jingrow.get_pg("WhatsApp Message", reply_to)
|
||||
pg.update(
|
||||
{
|
||||
"is_reply": True,
|
||||
"reply_to_message_id": reply_doc.message_id,
|
||||
"reply_to_message_id": reply_pg.message_id,
|
||||
}
|
||||
)
|
||||
|
||||
doc.update(
|
||||
pg.update(
|
||||
{
|
||||
"reference_doctype": reference_doctype,
|
||||
"reference_pagetype": reference_pagetype,
|
||||
"reference_name": reference_name,
|
||||
"message": message or attach,
|
||||
"to": to,
|
||||
@ -280,16 +280,16 @@ def create_whatsapp_message(
|
||||
"content_type": content_type,
|
||||
}
|
||||
)
|
||||
doc.insert(ignore_permissions=True)
|
||||
return doc.name
|
||||
pg.insert(ignore_permissions=True)
|
||||
return pg.name
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def send_whatsapp_template(reference_doctype, reference_name, template, to):
|
||||
doc = frappe.new_doc("WhatsApp Message")
|
||||
doc.update(
|
||||
@jingrow.whitelist()
|
||||
def send_whatsapp_template(reference_pagetype, reference_name, template, to):
|
||||
pg = jingrow.new_pg("WhatsApp Message")
|
||||
pg.update(
|
||||
{
|
||||
"reference_doctype": reference_doctype,
|
||||
"reference_pagetype": reference_pagetype,
|
||||
"reference_name": reference_name,
|
||||
"message_type": "Template",
|
||||
"message": "Template message",
|
||||
@ -299,27 +299,27 @@ def send_whatsapp_template(reference_doctype, reference_name, template, to):
|
||||
"to": to,
|
||||
}
|
||||
)
|
||||
doc.insert(ignore_permissions=True)
|
||||
return doc.name
|
||||
pg.insert(ignore_permissions=True)
|
||||
return pg.name
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def react_on_whatsapp_message(emoji, reply_to_name):
|
||||
reply_to_doc = frappe.get_doc("WhatsApp Message", reply_to_name)
|
||||
to = reply_to_doc.type == "Incoming" and reply_to_doc.get("from") or reply_to_doc.to
|
||||
doc = frappe.new_doc("WhatsApp Message")
|
||||
doc.update(
|
||||
reply_to_pg = jingrow.get_pg("WhatsApp Message", reply_to_name)
|
||||
to = reply_to_pg.type == "Incoming" and reply_to_pg.get("from") or reply_to_pg.to
|
||||
pg = jingrow.new_pg("WhatsApp Message")
|
||||
pg.update(
|
||||
{
|
||||
"reference_doctype": reply_to_doc.reference_doctype,
|
||||
"reference_name": reply_to_doc.reference_name,
|
||||
"reference_pagetype": reply_to_pg.reference_pagetype,
|
||||
"reference_name": reply_to_pg.reference_name,
|
||||
"message": emoji,
|
||||
"to": to,
|
||||
"reply_to_message_id": reply_to_doc.message_id,
|
||||
"reply_to_message_id": reply_to_pg.message_id,
|
||||
"content_type": "reaction",
|
||||
}
|
||||
)
|
||||
doc.insert(ignore_permissions=True)
|
||||
return doc.name
|
||||
pg.insert(ignore_permissions=True)
|
||||
return pg.name
|
||||
|
||||
|
||||
def parse_template_parameters(string, parameters):
|
||||
@ -331,16 +331,16 @@ def parse_template_parameters(string, parameters):
|
||||
|
||||
|
||||
def get_from_name(message):
|
||||
doc = frappe.get_doc(message["reference_doctype"], message["reference_name"])
|
||||
pg = jingrow.get_pg(message["reference_pagetype"], message["reference_name"])
|
||||
from_name = ""
|
||||
if message["reference_doctype"] == "CRM Deal":
|
||||
if doc.get("contacts"):
|
||||
for c in doc.get("contacts"):
|
||||
if message["reference_pagetype"] == "CRM Deal":
|
||||
if pg.get("contacts"):
|
||||
for c in pg.get("contacts"):
|
||||
if c.is_primary:
|
||||
from_name = c.full_name or c.mobile_no
|
||||
break
|
||||
else:
|
||||
from_name = doc.get("lead_name")
|
||||
from_name = pg.get("lead_name")
|
||||
else:
|
||||
from_name = " ".join(filter(None, [doc.get("first_name"), doc.get("last_name")]))
|
||||
from_name = " ".join(filter(None, [pg.get("first_name"), pg.get("last_name")]))
|
||||
return from_name
|
||||
|
||||
@ -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 Call Log", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import UnitTestCase
|
||||
|
||||
|
||||
class TestCRMCallLog(UnitTestCase):
|
||||
pass
|
||||
@ -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 Communication Status", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -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 CRMCommunicationStatus(Document):
|
||||
pass
|
||||
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import UnitTestCase
|
||||
|
||||
|
||||
class TestCRMCommunicationStatus(UnitTestCase):
|
||||
pass
|
||||
@ -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 CRMContacts(Document):
|
||||
pass
|
||||
@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("CRM Dashboard", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -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
|
||||
@ -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 Status", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -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 CRMDealStatus(Document):
|
||||
pass
|
||||
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import UnitTestCase
|
||||
|
||||
|
||||
class TestCRMDealStatus(UnitTestCase):
|
||||
pass
|
||||
@ -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 CRMDropdownItem(Document):
|
||||
pass
|
||||
@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("CRM Exotel Settings", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -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 Fields Layout", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import UnitTestCase
|
||||
|
||||
|
||||
class TestCRMFieldsLayout(UnitTestCase):
|
||||
pass
|
||||
@ -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
|
||||
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import UnitTestCase
|
||||
|
||||
|
||||
class TestCRMFormScript(UnitTestCase):
|
||||
pass
|
||||
@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("CRM Global Settings", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class CRMGlobalSettings(Document):
|
||||
pass
|
||||
@ -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
|
||||
@ -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) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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 Status", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -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 CRMLeadStatus(Document):
|
||||
pass
|
||||
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import UnitTestCase
|
||||
|
||||
|
||||
class TestCRMLeadStatus(UnitTestCase):
|
||||
pass
|
||||
@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("CRM Lost Reason", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class CRMLostReason(Document):
|
||||
pass
|
||||
@ -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 Notification", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -1,37 +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):
|
||||
if self.to_user:
|
||||
frappe.publish_realtime("crm_notification", user= self.to_user)
|
||||
|
||||
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)
|
||||
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import UnitTestCase
|
||||
|
||||
|
||||
class TestCRMNotification(UnitTestCase):
|
||||
pass
|
||||
@ -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) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -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
|
||||
@ -1,9 +0,0 @@
|
||||
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("CRM Product", {
|
||||
product_code: function (frm) {
|
||||
if (!frm.doc.product_name)
|
||||
frm.set_value("product_name", frm.doc.product_code);
|
||||
}
|
||||
});
|
||||
@ -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
|
||||
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import UnitTestCase
|
||||
|
||||
|
||||
class TestCRMServiceLevelAgreement(UnitTestCase):
|
||||
pass
|
||||
@ -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),
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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 Task", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -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
|
||||
@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("CRM Telephony Agent", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class CRMTelephonyPhone(Document):
|
||||
pass
|
||||
@ -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) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -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
|
||||
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import UnitTestCase
|
||||
|
||||
|
||||
class TestCRMTerritory(UnitTestCase):
|
||||
pass
|
||||
@ -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 Twilio Settings", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import UnitTestCase
|
||||
|
||||
|
||||
class TestCRMTwilioSettings(UnitTestCase):
|
||||
pass
|
||||
@ -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 View Settings", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -1,246 +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 get_route_name(view.doctype)
|
||||
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 get_route_name(view.doctype)
|
||||
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 set_as_default(name=None, type=None, doctype=None):
|
||||
if name:
|
||||
frappe.db.set_value("CRM View Settings", name, "is_default", 1)
|
||||
else:
|
||||
doc = create_or_update_standard_view({"type": type, "doctype": doctype, "is_default": 1})
|
||||
name = doc.name
|
||||
|
||||
# remove default from other views of same user
|
||||
frappe.db.set_value(
|
||||
"CRM View Settings",
|
||||
{"name": ("!=", name), "user": frappe.session.user, "is_default": 1},
|
||||
"is_default",
|
||||
0,
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_or_update_standard_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 "[]")
|
||||
view.column_field = view.column_field or "status"
|
||||
|
||||
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_standard": 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 get_route_name(view.doctype)
|
||||
doc.load_default_columns = view.load_default_columns or False
|
||||
doc.filters = json.dumps(filters)
|
||||
doc.order_by = view.order_by or "modified desc"
|
||||
doc.group_by_field = view.group_by_field or "owner"
|
||||
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 = view.is_default or False
|
||||
doc.save()
|
||||
else:
|
||||
doc = frappe.new_doc("CRM View Settings")
|
||||
|
||||
label = "List"
|
||||
if view.type == "group_by":
|
||||
label = "Group By"
|
||||
elif view.type == "kanban":
|
||||
label = "Kanban"
|
||||
|
||||
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 get_route_name(view.doctype)
|
||||
doc.load_default_columns = view.load_default_columns or False
|
||||
doc.filters = json.dumps(filters)
|
||||
doc.order_by = view.order_by or "modified desc"
|
||||
doc.group_by_field = view.group_by_field or "owner"
|
||||
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_standard = True
|
||||
doc.is_default = view.is_default or False
|
||||
doc.insert()
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
def get_route_name(doctype):
|
||||
# Example: "CRM Lead" -> "Leads"
|
||||
if doctype.startswith("CRM "):
|
||||
doctype = doctype[4:]
|
||||
|
||||
if doctype[-1] != "s":
|
||||
doctype += "s"
|
||||
|
||||
return doctype
|
||||
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import UnitTestCase
|
||||
|
||||
|
||||
class TestCRMViewSettings(UnitTestCase):
|
||||
pass
|
||||
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import UnitTestCase
|
||||
|
||||
|
||||
class TestERPNextCRMSettings(UnitTestCase):
|
||||
pass
|
||||
@ -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) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import UnitTestCase
|
||||
|
||||
|
||||
class TestFCRMNote(UnitTestCase):
|
||||
pass
|
||||
@ -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
|
||||
8
crm/fcrm/pagetype/crm_call_log/crm_call_log.js
Normal file
8
crm/fcrm/pagetype/crm_call_log/crm_call_log.js
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2023, JINGROW and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// jingrow.ui.form.on("CRM Call Log", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -5,7 +5,7 @@
|
||||
"autoname": "field:id",
|
||||
"creation": "2023-08-28 00:23:36.229137",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"pagetype": "PageType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@ -17,7 +17,7 @@
|
||||
"duration",
|
||||
"medium",
|
||||
"start_time",
|
||||
"reference_doctype",
|
||||
"reference_pagetype",
|
||||
"reference_docname",
|
||||
"column_break_ufnp",
|
||||
"to",
|
||||
@ -105,14 +105,14 @@
|
||||
"options": "FCRM Note"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.type == 'Incoming'",
|
||||
"depends_on": "eval:pg.type == 'Incoming'",
|
||||
"fieldname": "receiver",
|
||||
"fieldtype": "Link",
|
||||
"label": "Call Received By",
|
||||
"options": "User"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.type == 'Outgoing'",
|
||||
"depends_on": "eval:pg.type == 'Outgoing'",
|
||||
"fieldname": "caller",
|
||||
"fieldtype": "Link",
|
||||
"label": "Caller",
|
||||
@ -120,16 +120,16 @@
|
||||
},
|
||||
{
|
||||
"default": "CRM Lead",
|
||||
"fieldname": "reference_doctype",
|
||||
"fieldname": "reference_pagetype",
|
||||
"fieldtype": "Link",
|
||||
"label": "Reference Document Type",
|
||||
"options": "DocType"
|
||||
"options": "PageType"
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_docname",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Reference Name",
|
||||
"options": "reference_doctype"
|
||||
"options": "reference_pagetype"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_kebz",
|
||||
@ -1,14 +1,14 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# Copyright (c) 2023, JINGROW and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
import jingrow
|
||||
from jingrow.model.page import Page
|
||||
|
||||
from crm.integrations.api import get_contact_by_phone_number
|
||||
from crm.utils import seconds_to_duration
|
||||
|
||||
|
||||
class CRMCallLog(Document):
|
||||
class CRMCallLog(Page):
|
||||
@staticmethod
|
||||
def default_list_data():
|
||||
columns = [
|
||||
@ -74,7 +74,7 @@ class CRMCallLog(Document):
|
||||
"to",
|
||||
"note",
|
||||
"recording_url",
|
||||
"reference_doctype",
|
||||
"reference_pagetype",
|
||||
"reference_docname",
|
||||
"creation",
|
||||
]
|
||||
@ -83,16 +83,16 @@ class CRMCallLog(Document):
|
||||
def parse_list_data(calls):
|
||||
return [parse_call_log(call) for call in calls] if calls else []
|
||||
|
||||
def has_link(self, doctype, name):
|
||||
def has_link(self, pagetype, name):
|
||||
for link in self.links:
|
||||
if link.link_doctype == doctype and link.link_name == name:
|
||||
if link.link_pagetype == pagetype and link.link_name == name:
|
||||
return True
|
||||
|
||||
def link_with_reference_doc(self, reference_doctype, reference_name):
|
||||
if self.has_link(reference_doctype, reference_name):
|
||||
def link_with_reference_pg(self, reference_pagetype, reference_name):
|
||||
if self.has_link(reference_pagetype, reference_name):
|
||||
return
|
||||
|
||||
self.append("links", {"link_doctype": reference_doctype, "link_name": reference_name})
|
||||
self.append("links", {"link_pagetype": reference_pagetype, "link_name": reference_name})
|
||||
|
||||
|
||||
def parse_call_log(call):
|
||||
@ -102,7 +102,7 @@ def parse_call_log(call):
|
||||
call["activity_type"] = "incoming_call"
|
||||
contact = get_contact_by_phone_number(call.get("from"))
|
||||
receiver = (
|
||||
frappe.db.get_values("User", call.get("receiver"), ["full_name", "user_image"])[0]
|
||||
jingrow.db.get_values("User", call.get("receiver"), ["full_name", "user_image"])[0]
|
||||
if call.get("receiver")
|
||||
else [None, None]
|
||||
)
|
||||
@ -118,7 +118,7 @@ def parse_call_log(call):
|
||||
call["activity_type"] = "outgoing_call"
|
||||
contact = get_contact_by_phone_number(call.get("to"))
|
||||
caller = (
|
||||
frappe.db.get_values("User", call.get("caller"), ["full_name", "user_image"])[0]
|
||||
jingrow.db.get_values("User", call.get("caller"), ["full_name", "user_image"])[0]
|
||||
if call.get("caller")
|
||||
else [None, None]
|
||||
)
|
||||
@ -134,9 +134,9 @@ def parse_call_log(call):
|
||||
return call
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def get_call_log(name):
|
||||
call = frappe.get_cached_doc(
|
||||
call = jingrow.get_cached_pg(
|
||||
"CRM Call Log",
|
||||
name,
|
||||
fields=[
|
||||
@ -150,7 +150,7 @@ def get_call_log(name):
|
||||
"to",
|
||||
"note",
|
||||
"recording_url",
|
||||
"reference_doctype",
|
||||
"reference_pagetype",
|
||||
"reference_docname",
|
||||
"creation",
|
||||
],
|
||||
@ -162,26 +162,26 @@ def get_call_log(name):
|
||||
tasks = []
|
||||
|
||||
if call.get("note"):
|
||||
note = frappe.get_cached_doc("FCRM Note", call.get("note")).as_dict()
|
||||
note = jingrow.get_cached_pg("FCRM Note", call.get("note")).as_dict()
|
||||
notes.append(note)
|
||||
|
||||
if call.get("reference_doctype") and call.get("reference_docname"):
|
||||
if call.get("reference_doctype") == "CRM Lead":
|
||||
if call.get("reference_pagetype") and call.get("reference_docname"):
|
||||
if call.get("reference_pagetype") == "CRM Lead":
|
||||
call["_lead"] = call.get("reference_docname")
|
||||
elif call.get("reference_doctype") == "CRM Deal":
|
||||
elif call.get("reference_pagetype") == "CRM Deal":
|
||||
call["_deal"] = call.get("reference_docname")
|
||||
|
||||
if call.get("links"):
|
||||
for link in call.get("links"):
|
||||
if link.get("link_doctype") == "CRM Task":
|
||||
task = frappe.get_cached_doc("CRM Task", link.get("link_name")).as_dict()
|
||||
if link.get("link_pagetype") == "CRM Task":
|
||||
task = jingrow.get_cached_pg("CRM Task", link.get("link_name")).as_dict()
|
||||
tasks.append(task)
|
||||
elif link.get("link_doctype") == "FCRM Note":
|
||||
note = frappe.get_cached_doc("FCRM Note", link.get("link_name")).as_dict()
|
||||
elif link.get("link_pagetype") == "FCRM Note":
|
||||
note = jingrow.get_cached_pg("FCRM Note", link.get("link_name")).as_dict()
|
||||
notes.append(note)
|
||||
elif link.get("link_doctype") == "CRM Lead":
|
||||
elif link.get("link_pagetype") == "CRM Lead":
|
||||
call["_lead"] = link.get("link_name")
|
||||
elif link.get("link_doctype") == "CRM Deal":
|
||||
elif link.get("link_pagetype") == "CRM Deal":
|
||||
call["_deal"] = link.get("link_name")
|
||||
|
||||
call["_tasks"] = tasks
|
||||
@ -189,13 +189,13 @@ def get_call_log(name):
|
||||
return call
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@jingrow.whitelist()
|
||||
def create_lead_from_call_log(call_log, lead_details=None):
|
||||
lead = frappe.new_doc("CRM Lead")
|
||||
lead_details = frappe.parse_json(lead_details or "{}")
|
||||
lead = jingrow.new_pg("CRM Lead")
|
||||
lead_details = jingrow.parse_json(lead_details or "{}")
|
||||
|
||||
if not lead_details.get("lead_owner"):
|
||||
lead_details["lead_owner"] = frappe.session.user
|
||||
lead_details["lead_owner"] = jingrow.session.user
|
||||
if not lead_details.get("mobile_no"):
|
||||
lead_details["mobile_no"] = call_log.get("from") or ""
|
||||
if not lead_details.get("first_name"):
|
||||
@ -207,8 +207,8 @@ def create_lead_from_call_log(call_log, lead_details=None):
|
||||
lead.save(ignore_permissions=True)
|
||||
|
||||
# link call log with lead
|
||||
call_log = frappe.get_doc("CRM Call Log", call_log.get("name"))
|
||||
call_log.link_with_reference_doc("CRM Lead", lead.name)
|
||||
call_log = jingrow.get_pg("CRM Call Log", call_log.get("name"))
|
||||
call_log.link_with_reference_pg("CRM Lead", lead.name)
|
||||
call_log.save(ignore_permissions=True)
|
||||
|
||||
return lead.name
|
||||
9
crm/fcrm/pagetype/crm_call_log/test_crm_call_log.py
Normal file
9
crm/fcrm/pagetype/crm_call_log/test_crm_call_log.py
Normal file
@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2023, JINGROW and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import jingrow
|
||||
from jingrow.tests import UnitTestCase
|
||||
|
||||
|
||||
class TestCRMCallLog(UnitTestCase):
|
||||
pass
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user