├── .gitignore
├── .travis.yml
├── .travis
└── prepare.sh
├── README.md
├── app.py
├── crm
├── __init__.py
├── apps
│ ├── __init__.py
│ ├── address
│ │ ├── __init__.py
│ │ ├── graphql
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── mutations.py
│ │ │ ├── queries.py
│ │ │ └── types.py
│ │ └── models.py
│ ├── admin
│ │ ├── __init__.py
│ │ ├── config.py
│ │ ├── converters.py
│ │ ├── formatters.py
│ │ ├── mixins.py
│ │ ├── models.py
│ │ ├── templates
│ │ │ └── admin
│ │ │ │ ├── extra.html
│ │ │ │ ├── index.html
│ │ │ │ └── model
│ │ │ │ ├── create.html
│ │ │ │ ├── details.html
│ │ │ │ ├── edit.html
│ │ │ │ ├── inline_form.html
│ │ │ │ ├── inline_list_base.html
│ │ │ │ ├── inner_field_list.html
│ │ │ │ ├── list.html
│ │ │ │ └── row_actions.html
│ │ └── views.py
│ ├── alert
│ │ ├── __init__.py
│ │ ├── graphql
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── mutations.py
│ │ │ ├── queries.py
│ │ │ └── types.py
│ │ └── models.py
│ ├── api
│ │ ├── __init__.py
│ │ ├── models.py
│ │ └── views.py
│ ├── comment
│ │ ├── __init__.py
│ │ ├── graphql
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── mutations.py
│ │ │ ├── queries.py
│ │ │ └── types.py
│ │ └── models.py
│ ├── company
│ │ ├── __init__.py
│ │ ├── graphql
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── mutations.py
│ │ │ ├── queries.py
│ │ │ └── types.py
│ │ └── models.py
│ ├── contact
│ │ ├── __init__.py
│ │ ├── forms.py
│ │ ├── graphql
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── mutations.py
│ │ │ ├── queries.py
│ │ │ └── types.py
│ │ └── models.py
│ ├── country
│ │ ├── __init__.py
│ │ ├── countries.py
│ │ ├── graphql
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── mutations.py
│ │ │ ├── queries.py
│ │ │ └── types.py
│ │ └── models.py
│ ├── currency
│ │ ├── __init__.py
│ │ ├── graphql
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── mutations.py
│ │ │ ├── queries.py
│ │ │ └── types.py
│ │ └── models.py
│ ├── deal
│ │ ├── __init__.py
│ │ ├── graphql
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── mutations.py
│ │ │ ├── queries.py
│ │ │ └── types.py
│ │ └── models.py
│ ├── email
│ │ ├── __init__.py
│ │ └── models.py
│ ├── event
│ │ ├── __init__.py
│ │ ├── graphql
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── mutations.py
│ │ │ ├── queries.py
│ │ │ └── types.py
│ │ └── models.py
│ ├── home
│ │ ├── __init__.py
│ │ ├── models.py
│ │ ├── templates
│ │ │ └── home
│ │ │ │ ├── 401.html
│ │ │ │ └── index.html
│ │ └── views.py
│ ├── image
│ │ ├── __init__.py
│ │ ├── graphql
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── mutations.py
│ │ │ ├── queries.py
│ │ │ └── types.py
│ │ └── models.py
│ ├── knowledge
│ │ ├── __init__.py
│ │ ├── graphql
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── mutations.py
│ │ │ ├── queries.py
│ │ │ └── types.py
│ │ └── models.py
│ ├── link
│ │ ├── __init__.py
│ │ ├── graphql
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── mutations.py
│ │ │ ├── queries.py
│ │ │ └── types.py
│ │ └── models.py
│ ├── message
│ │ ├── __init__.py
│ │ ├── graphql
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── mutations.py
│ │ │ ├── queries.py
│ │ │ └── types.py
│ │ └── models.py
│ ├── organization
│ │ ├── __init__.py
│ │ ├── graphql
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── mutations.py
│ │ │ ├── queries.py
│ │ │ └── types.py
│ │ └── models.py
│ ├── passport
│ │ ├── __init__.py
│ │ ├── graphql
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── mutations.py
│ │ │ ├── queries.py
│ │ │ └── types.py
│ │ └── models.py
│ ├── phone
│ │ ├── __init__.py
│ │ └── models.py
│ ├── project
│ │ ├── __init__.py
│ │ ├── graphql
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── mutations.py
│ │ │ ├── queries.py
│ │ │ └── types.py
│ │ └── models.py
│ ├── reports
│ │ ├── templates
│ │ │ └── reports
│ │ │ │ └── reports.html
│ │ └── views.py
│ ├── sprint
│ │ ├── __init__.py
│ │ ├── graphql
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── mutations.py
│ │ │ ├── queries.py
│ │ │ └── types.py
│ │ └── models.py
│ ├── tag
│ │ ├── __init__.py
│ │ ├── graphql
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── mutations.py
│ │ │ ├── queries.py
│ │ │ └── types.py
│ │ └── models.py
│ ├── task
│ │ ├── __init__.py
│ │ ├── graphql
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── mutations.py
│ │ │ ├── queries.py
│ │ │ └── types.py
│ │ └── models.py
│ └── user
│ │ ├── __init__.py
│ │ ├── graphql
│ │ ├── __init__.py
│ │ ├── arguments.py
│ │ ├── mutations.py
│ │ ├── queries.py
│ │ └── types.py
│ │ ├── models.py
│ │ └── tasks.py
├── cli
│ ├── __init__.py
│ ├── createdb.py
│ ├── dumpcache.py
│ ├── dumpdata.py
│ ├── generate_graphql_docs.py
│ ├── import_data.py
│ ├── load.py
│ ├── loaddata.py
│ ├── loadfixtures.py
│ ├── mailer.py
│ ├── rq_worker.py
│ ├── syncdata.py
│ └── update_currency.py
├── db.py
├── events
│ ├── __init__.py
│ ├── cache_db_updates.py
│ ├── catch_db_updates.py
│ ├── notify_new_task.py
│ ├── send_email_on_new_message.py
│ └── update_auto_fields.py
├── fixtures.py
├── graphql.py
├── mailer.py
├── middlewares
│ ├── __init__.py
│ └── iyo.py
├── rq.py
├── settings.py
├── settings_dev.py
├── settings_prod.py
└── settings_test.py
├── docs
├── AddNewRequirement.md
├── AdminInterface.md
├── AuthenticationMiddleware.md
├── Caching.md
├── Commands.md
├── Configuration.md
├── DBEvents.md
├── GraphqlAdvanced.md
├── GraphqlHTTPClient.md
├── GraphqlOverview.md
├── GraphqlQueriesAndMutations.md
├── GraphqlQueryLanguage.md
├── HowToCreateAppplication.md
├── Installation.md
├── LoadDumpData.md
├── MailinMailout.md
├── Middlewares.md
├── Migrations.md
├── Models.md
├── Philosophy.md
├── PrefabInstallation.md
├── Production.md
├── Running.md
├── Structure.md
├── Views.md
├── assets
│ ├── addfilter.png
│ ├── createview.png
│ ├── detailsview.png
│ ├── editview.png
│ ├── extramenu.png
│ ├── graphiql_docs.png
│ ├── graphql_many.png
│ ├── graphql_query.png
│ ├── iyo-settings1.png
│ ├── iyo-settings2.png
│ ├── listview.png
│ └── mainmenu.png
└── graphqlapi
│ ├── addressarguments.doc.html
│ ├── addresstype.doc.html
│ ├── assets
│ ├── code.css
│ └── require-by.css
│ ├── boolean.doc.html
│ ├── comment.doc.html
│ ├── commentarguments.doc.html
│ ├── commenttypeconnection.doc.html
│ ├── commenttypeedge.doc.html
│ ├── companyarguments.doc.html
│ ├── companytags.doc.html
│ ├── contact.doc.html
│ ├── contactarguments.doc.html
│ ├── contactsubgrouparguments.doc.html
│ ├── contacttypeconnection.doc.html
│ ├── contacttypeedge.doc.html
│ ├── countriesenum.doc.html
│ ├── countryarguments.doc.html
│ ├── createcontactarguments.doc.html
│ ├── createcontacts.doc.html
│ ├── createdealarguments.doc.html
│ ├── createdeals.doc.html
│ ├── currencyarguments.doc.html
│ ├── datetime.doc.html
│ ├── deal.doc.html
│ ├── dealarguments.doc.html
│ ├── dealstate.doc.html
│ ├── dealtype.doc.html
│ ├── dealtypeconnection.doc.html
│ ├── dealtypeedge.doc.html
│ ├── deletecontacts.doc.html
│ ├── deletedeals.doc.html
│ ├── deprecated.doc.html
│ ├── directive.spec.html
│ ├── directivelocation.spec.html
│ ├── enumvalue.spec.html
│ ├── event.doc.html
│ ├── eventarguments.doc.html
│ ├── eventtypeconnection.doc.html
│ ├── eventtypeedge.doc.html
│ ├── field.spec.html
│ ├── float.doc.html
│ ├── fonts
│ └── webfonts
│ │ ├── SalesforceSans-Bold.eot
│ │ ├── SalesforceSans-Bold.svg
│ │ ├── SalesforceSans-Bold.woff
│ │ ├── SalesforceSans-Bold.woff2
│ │ ├── SalesforceSans-BoldItalic.eot
│ │ ├── SalesforceSans-BoldItalic.svg
│ │ ├── SalesforceSans-BoldItalic.woff
│ │ ├── SalesforceSans-BoldItalic.woff2
│ │ ├── SalesforceSans-Italic.eot
│ │ ├── SalesforceSans-Italic.svg
│ │ ├── SalesforceSans-Italic.woff
│ │ ├── SalesforceSans-Italic.woff2
│ │ ├── SalesforceSans-Light.eot
│ │ ├── SalesforceSans-Light.svg
│ │ ├── SalesforceSans-Light.woff
│ │ ├── SalesforceSans-Light.woff2
│ │ ├── SalesforceSans-LightItalic.eot
│ │ ├── SalesforceSans-LightItalic.svg
│ │ ├── SalesforceSans-LightItalic.woff
│ │ ├── SalesforceSans-LightItalic.woff2
│ │ ├── SalesforceSans-Regular.eot
│ │ ├── SalesforceSans-Regular.svg
│ │ ├── SalesforceSans-Regular.woff
│ │ ├── SalesforceSans-Regular.woff2
│ │ ├── SalesforceSans-Thin.eot
│ │ ├── SalesforceSans-Thin.svg
│ │ ├── SalesforceSans-Thin.woff
│ │ ├── SalesforceSans-Thin.woff2
│ │ ├── SalesforceSans-ThinItalic.eot
│ │ ├── SalesforceSans-ThinItalic.svg
│ │ ├── SalesforceSans-ThinItalic.woff
│ │ └── SalesforceSans-ThinItalic.woff2
│ ├── gender.doc.html
│ ├── id.doc.html
│ ├── image.doc.html
│ ├── imagetypeconnection.doc.html
│ ├── imagetypeedge.doc.html
│ ├── include.doc.html
│ ├── index.html
│ ├── inputvalue.spec.html
│ ├── int.doc.html
│ ├── linkarguments.doc.html
│ ├── linktype.doc.html
│ ├── message.doc.html
│ ├── messagearguments.doc.html
│ ├── messagetypeconnection.doc.html
│ ├── messagetypeedge.doc.html
│ ├── mutations.doc.html
│ ├── node.doc.html
│ ├── organization.doc.html
│ ├── organizationarguments.doc.html
│ ├── organizationtypeconnection.doc.html
│ ├── organizationtypeedge.doc.html
│ ├── pageinfo.doc.html
│ ├── passportarguments.doc.html
│ ├── passporttype.doc.html
│ ├── project.doc.html
│ ├── projectarguments.doc.html
│ ├── projecttypeconnection.doc.html
│ ├── projecttypeedge.doc.html
│ ├── query.doc.html
│ ├── schema.spec.html
│ ├── scripts
│ ├── filter-types.js
│ ├── focus-active.js
│ └── toggle-navigation.js
│ ├── skip.doc.html
│ ├── sprint.doc.html
│ ├── sprintarguments.doc.html
│ ├── sprinttypeconnection.doc.html
│ ├── sprinttypeedge.doc.html
│ ├── string.doc.html
│ ├── styles
│ ├── _custom.scss
│ ├── _override.scss
│ ├── graphdoc.css
│ └── graphdoc.scss
│ ├── tagarguments.doc.html
│ ├── task.doc.html
│ ├── taskarguments.doc.html
│ ├── tasktypeconnection.doc.html
│ ├── tasktypeedge.doc.html
│ ├── type.spec.html
│ ├── typekind.spec.html
│ ├── updatecontactarguments.doc.html
│ ├── updatecontacts.doc.html
│ ├── updatedealarguments.doc.html
│ ├── updatedeals.doc.html
│ ├── user.doc.html
│ ├── userarguments.doc.html
│ ├── usertypeconnection.doc.html
│ └── usertypeedge.doc.html
├── frontend
├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .postcssrc.js
├── README.md
├── build
│ ├── build.js
│ ├── check-versions.js
│ ├── dev-client.js
│ ├── dev-server.js
│ ├── utils.js
│ ├── vue-loader.conf.js
│ ├── webpack.base.conf.js
│ ├── webpack.dev.conf.js
│ ├── webpack.prod.conf.js
│ └── webpack.test.conf.js
├── config
│ ├── dev.env.js
│ ├── index.js
│ ├── prod.env.js
│ └── test.env.js
├── index.html
├── package-lock.json
├── package.json
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── axioshelpers.js
│ ├── components
│ │ ├── Deal.vue
│ │ ├── DealsList.vue
│ │ ├── DealsPending.vue
│ │ ├── Home.vue
│ │ └── Reports.vue
│ ├── main.js
│ └── router
│ │ └── index.js
└── test
│ └── unit
│ ├── .eslintrc
│ ├── index.js
│ ├── karma.conf.js
│ └── specs
│ └── Hello.spec.js
├── prepare.sh
├── requirements-dev.apt
├── requirements-testing.pip
├── requirements.apt
├── requirements.brew
├── requirements.npm
├── requirements.pip
├── run_tests.sh
├── setup.py
├── specs
├── AlertManagement.md
├── Assetmanagement.md
├── Beta2Test.md
├── MailinMailout.md
├── Redis.md
├── Requirements.md
└── beta2_basic_test_usecases.md
├── static
└── js
│ ├── apollo.min.js
│ ├── chart.min.js
│ ├── vue-apollo.min.js
│ ├── vue-chart.min.js
│ └── vue.min.js
├── tests
├── __init__.py
├── base_tests.py
└── test_home.py
├── utils
└── google_spreadsheet_example.gs
└── uwsgi.ini
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | ./build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | *.log
55 | local_settings.py
56 |
57 | # Flask stuff:
58 | instance/
59 | .webassets-cache
60 |
61 | # Scrapy stuff:
62 | .scrapy
63 |
64 | # Sphinx documentation
65 | docs/_build/
66 |
67 | # PyBuilder
68 | target/
69 |
70 | # Jupyter Notebook
71 | .ipynb_checkpoints
72 |
73 | # pyenv
74 | .python-version
75 |
76 | # celery beat schedule file
77 | celerybeat-schedule
78 |
79 | # SageMath parsed files
80 | *.sage.py
81 |
82 | # dotenv
83 | .env
84 |
85 | # virtualenv
86 | .venv
87 | venv/
88 | ENV/
89 |
90 | # Spyder project settings
91 | .spyderproject
92 | .spyproject
93 |
94 | # Rope project settings
95 | .ropeproject
96 |
97 | # mkdocs documentation
98 | /site
99 |
100 | # mypy
101 | .mypy_cache/
102 |
103 | db/
104 |
105 | #pycharm
106 | .idea
107 |
108 | # data dir
109 | data
110 |
111 | # db
112 | *.db
113 |
114 | # migrations
115 | migrations
116 |
117 | # static
118 | static
119 |
120 | # graphql schema
121 | schema.graphql
122 |
123 | # tar.gz
124 | *.gz
125 | *.zip
126 |
127 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | dist: trusty
3 | language: python
4 | python:
5 | - "3.5"
6 | install: .travis/prepare.sh
7 | script: ./run_tests.sh
8 |
9 |
10 | addons:
11 | apt:
12 | packages:
13 | - python3-dev
14 | - libffi-dev
15 | - build-essential
16 |
17 | notification:
18 | email:
19 | recipients:
20 | - husseina@greenitglobe.com
21 | on_success: never
22 | on_failure: always
--------------------------------------------------------------------------------
/.travis/prepare.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | export LC_ALL="en_US.UTF-8"
5 | export LC_CTYPE="en_US.UTF-8"
6 | pip install -r requirements-testing.pip
7 | pip install -e .
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## CRM
3 |
4 | product owner: @hamdy_farag (backup xmonader)
5 |
6 | **Philosophy**
7 | - [Why we need our own CRM](docs/Philosophy.md)
8 |
9 | **Basics**
10 | - [Installation](docs/Installation.md)
11 | - [Running](docs/Running.md)
12 | - [Configuration](docs/Configuration.md)
13 | - [Project structure](docs/Structure.md)
14 | - [Authentication](docs/AuthenticationMiddleware.md)
15 | - [Caching](docs/Caching.md)
16 | - [How to define a new SQLALCHEMY model](docs/Models.md)
17 | - [How to add a new required dependency](docs/AddNewRequirement.md)
18 | - [How to develop a new typical application in the CRM](docs/HowToCreateAppplication.md)
19 |
20 | **Advanced**
21 | - [Admin Interface](docs/AdminInterface.md)
22 | - [DB Migrations](docs/Migrations.md)
23 | - [Add custom command](docs/Commands.md)
24 | - [Add middleware](docs/Middlewares.md)
25 | - [Add new SqlAlchemy DB event](docs/DBEvents.md)
26 | - [Load & Dump Data Algorithms](docs/LoadDumpData.md)
27 | - [Mail in/out](docs/MailinMailout.md)
28 |
29 | **Graphql**
30 | - [Intro](docs/GraphqlOverview.md)
31 | - [CRM API General overview](docs/GraphqlQueriesAndMutations.md)
32 | - [Graphql API Docs](http://htmlpreview.github.io/?https://github.com/Incubaid/crm/blob/master/docs/graphqlapi/index.html)
33 | - [GraphQl API Query language](docs/GraphqlQueryLanguage.md)
34 | - [Using HTTP client to access CRM Graphql API](docs/GraphqlHTTPClient.md)
35 | - [Define new Types, Queries, and Mutations](docs/GraphqlAdvanced.md)
36 |
37 | **Deployment To production**
38 |
39 | - [Procedures to update production](docs/Production.md)
40 | - [Install CRM from Prefab/Jumpscale](docs/PrefabInstallation.md)
41 |
42 | **Specs**
43 | - [Requirements](specs/Requirements.md)
44 | - [Alert Management](specs/AlertManagement.md)
45 | - [Asset Management](specs/Assetmanagement.md)
46 | - [Implementation](specs/Implementation.md)
47 | - [Redis](specs/Redis.md)
48 | - [Beta2 Test](specs/Beta2Test.md)
49 | - [Mailin/Mailout](specs/MailinMailout.md)
50 |
--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | from crm import app
4 | from crm.cli import *
5 | from crm.middlewares import *
6 |
7 |
8 | if 'loaddata' not in sys.argv and 'loadfixtures' not in sys.argv:
9 | import crm.events
10 |
--------------------------------------------------------------------------------
/crm/apps/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/__init__.py
--------------------------------------------------------------------------------
/crm/apps/address/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/address/__init__.py
--------------------------------------------------------------------------------
/crm/apps/address/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/address/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/address/graphql/arguments.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene.types.inputobjecttype import InputObjectType
3 |
4 | from crm.apps.country.graphql.arguments import CountryArguments
5 | from crm.graphql import BaseArgument
6 |
7 |
8 | class AddressArguments(InputObjectType, BaseArgument):
9 | """
10 | Address Arguments
11 | """
12 | street_number = graphene.String()
13 | street_name = graphene.String()
14 | city = graphene.String()
15 | state = graphene.String()
16 | country = graphene.Argument(CountryArguments)
17 | zip_code = graphene.String()
18 |
19 | # ****************************************************
20 | # NO NEED FOR CREATE/UPDATE ADDRESS *
21 | # THIS IS DONE IMPLICITLY THROUGH CONTACT & DEAL APIs *
22 | # *****************************************************
23 |
--------------------------------------------------------------------------------
/crm/apps/address/graphql/mutations.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/address/graphql/mutations.py
--------------------------------------------------------------------------------
/crm/apps/address/graphql/queries.py:
--------------------------------------------------------------------------------
1 | from graphene import relay
2 |
3 | from crm.apps.address.graphql.arguments import AddressArguments
4 | from crm.apps.address.graphql.types import AddressType
5 | from crm.graphql import BaseQuery, CRMConnectionField
6 |
7 |
8 | class AddressQuery(BaseQuery):
9 | addresses = CRMConnectionField(
10 | AddressType,
11 | **AddressArguments.fields()
12 | )
13 |
14 | class Meta:
15 | interfaces = (relay.Node,)
16 |
--------------------------------------------------------------------------------
/crm/apps/address/graphql/types.py:
--------------------------------------------------------------------------------
1 | import graphene
2 |
3 | from graphene import relay
4 |
5 | from graphene_sqlalchemy import SQLAlchemyObjectType
6 |
7 | from crm.apps.address.models import Address
8 | from crm.apps.country.graphql.types import CountryType
9 | from crm.graphql import CrmType
10 |
11 |
12 | class AddressType(CrmType, SQLAlchemyObjectType):
13 | country = graphene.Field(CountryType)
14 |
15 | class Meta:
16 | model = Address
17 | interfaces = (relay.Node,)
18 | name = model.__name__
19 |
20 | # CrmType adds author_original, author_last objects rather than ids
21 | exclude_fields = (
22 | 'author_original_id',
23 | 'author_last_id'
24 | )
--------------------------------------------------------------------------------
/crm/apps/address/models.py:
--------------------------------------------------------------------------------
1 | from crm.db import db, BaseModel
2 |
3 |
4 | class Address(db.Model, BaseModel):
5 |
6 | __tablename__ = "addresses"
7 |
8 | street_number = db.Column(
9 | db.String(255),
10 | index=True
11 | )
12 |
13 | street_name = db.Column(
14 | db.String(255),
15 | default="",
16 | index=True
17 | )
18 |
19 | description = db.Column(
20 | db.Text(),
21 | default="",
22 | index=True
23 | )
24 |
25 | city = db.Column(
26 | db.Text(),
27 | default="",
28 | index=True
29 | )
30 |
31 | state = db.Column(
32 | db.Text(),
33 | default="",
34 | index=True
35 | )
36 |
37 | zip_code = db.Column(
38 | db.String(255),
39 | index=True
40 | )
41 |
42 | country_id = db.Column(
43 | db.String(5),
44 | db.ForeignKey('countries.id')
45 | )
46 |
47 | contact_id = db.Column(
48 | db.String(5),
49 | db.ForeignKey('contacts.id')
50 | )
51 |
52 | company_id = db.Column(
53 | db.String(5),
54 | db.ForeignKey('companies.id')
55 | )
56 |
57 | deal_id = db.Column(
58 | db.String(5),
59 | db.ForeignKey('deals.id')
60 | )
61 |
62 | @property
63 | def formatted_address(self):
64 | address = ''
65 | if self.street_name:
66 | if self.street_number:
67 | address += '%s %s, ' % (self.street_number, self.street_name)
68 | else:
69 | address += '%s, ' % self.street_name
70 |
71 | if self.state:
72 | address += '%s, ' % self.state
73 | if self.city:
74 | address += '%s, ' % self.city
75 | if self.country:
76 | address += str(self.country.name)
77 |
78 | if self.zip_code:
79 | address += ' (zip code: %s)' % self.zip_code
80 |
81 | return address
82 |
83 | def __str__(self):
84 | return self.formatted_address
85 |
--------------------------------------------------------------------------------
/crm/apps/admin/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/admin/__init__.py
--------------------------------------------------------------------------------
/crm/apps/admin/config.py:
--------------------------------------------------------------------------------
1 | NAV_BAR_ORDER = {
2 | 'MAIN': ["User", "Company", "Contact", "Organization", "Deal", "Project", "Sprint", "Task", "Event", "Currency"],
3 | 'EXTRA': ["Link", "Comment", "Message", "Tag", "KnowledgeBaseCategory", "KnowledgeBase"],
4 | }
5 |
--------------------------------------------------------------------------------
/crm/apps/admin/converters.py:
--------------------------------------------------------------------------------
1 | import sqlalchemy
2 | import flask_admin
3 | from flask_admin.form.fields import Select2Field
4 | from flask_admin.model.form import converts
5 |
6 | class EnumField(Select2Field):
7 | def __init__(self, column, **kwargs):
8 | assert isinstance(column.type, sqlalchemy.sql.sqltypes.Enum)
9 |
10 | def coercer(value):
11 | # coerce incoming value into an enum value
12 | if isinstance(value, column.type.enum_class):
13 | return value
14 | elif isinstance(value, str):
15 | return column.type.enum_class[value]
16 | else:
17 | assert False
18 |
19 | super(EnumField, self).__init__(
20 | choices=[(v, v) for v in column.type.enums],
21 | coerce=coercer,
22 | **kwargs)
23 |
24 | def pre_validate(self, form):
25 | # we need to override the default SelectField validation because it
26 | # apparently tries to directly compare the field value with the choice
27 | # key; it is not clear how that could ever work in cases where the
28 | # values and choice keys must be different types
29 |
30 | for (v, _) in self.choices:
31 | if self.data == self.coerce(v):
32 | break
33 | else:
34 | raise ValueError(self.gettext('Not a valid choice'))
35 |
36 |
37 | class CustomAdminConverter(flask_admin.contrib.sqla.form.AdminModelConverter):
38 | @converts("sqlalchemy.sql.sqltypes.Enum")
39 | def conv_enum(self, field_args, **extra):
40 | return EnumField(column=extra["column"], **field_args)
41 |
42 |
--------------------------------------------------------------------------------
/crm/apps/admin/mixins.py:
--------------------------------------------------------------------------------
1 | """
2 | ADMIN INTERFACE RELATED LOGIC
3 | """
4 |
5 |
6 | class AdminLinksMixin(object):
7 | ADMIN_EDIT_LINK = "/{modelname}/edit/?id={modelid}"
8 | ADMIN_LIST_LINK = "/{modelname}/"
9 | ADMIN_VIEW_LINK = "/{modelname}/details/?id={modelid}"
10 |
11 | ADMIN_CREATE_LINK = "/{modelname}/new/?id={modelid}"
12 |
13 | ADMIN_EDIT_LINK_MODAL = "/{modelname}/edit/?id={modelid}" # &modal=True"
14 | ADMIN_VIEW_LINK_MODAL = "/{modelname}/details/?id={modelid}"
15 | ADMIN_CREATE_LINK_MODAL = "/{modelname}/new/?url=/{modelname}"
16 |
17 | def _format_link(self, link):
18 | d = {
19 | 'modelname': self.__class__.__name__.lower()
20 | }
21 |
22 | if '{modelid}' in link:
23 | d['modelid'] = self.id
24 |
25 | return link.format(**d)
26 |
27 | def admin_list_link(self):
28 | return self._format_link(AdminLinksMixin.ADMIN_LIST_LINK)
29 |
30 | def admin_edit_link(self):
31 | return self._format_link(AdminLinksMixin.ADMIN_EDIT_LINK)
32 |
33 | def admin_view_link(self):
34 | return self._format_link(AdminLinksMixin.ADMIN_VIEW_LINK)
35 |
36 | def admin_create_link(self):
37 | return self._format_link(AdminLinksMixin.ADMIN_CREATE_LINK)
38 |
39 | def admin_edit_link_modal(self):
40 | return self._format_link(AdminLinksMixin.ADMIN_EDIT_LINK_MODAL)
41 |
42 | def admin_view_link_modal(self):
43 | return self._format_link(AdminLinksMixin.ADMIN_VIEW_LINK_MODAL)
44 |
45 | def admin_create_link_modal(self):
46 | return self._format_link(AdminLinksMixin.ADMIN_CREATE_LINK_MODAL)
47 |
--------------------------------------------------------------------------------
/crm/apps/admin/models.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/admin/models.py
--------------------------------------------------------------------------------
/crm/apps/admin/templates/admin/extra.html:
--------------------------------------------------------------------------------
1 | {% import 'admin/static.html' as admin_static with context %}
2 | {% import 'admin/model/layout.html' as model_layout with context %}
3 |
4 |
5 | {# ---------------------- Forms -------------------------- #}
6 | {% macro render_tabular_field(form, field, kwargs={}, caller=None) %}
7 |
8 |
9 |
10 | {% set direct_error = h.is_field_error(field.errors) %}
11 |
29 |
30 | {% endmacro %}
31 |
32 | {% macro render_tabular_form_fields(form, form_opts=None) %}
33 | {% if form.hidden_tag is defined %}
34 | {{ form.hidden_tag() }}
35 | {% else %}
36 | {% if csrf_token %}
37 |
38 | {% endif %}
39 | {% for f in form if f.widget.input_type == 'hidden' %}
40 | {{ f }}
41 | {% endfor %}
42 | {% endif %}
43 |
44 | {% if form_opts and form_opts.form_rules %}
45 | {% for r in form_opts.form_rules %}
46 | {{ r(form, form_opts=form_opts) }}
47 | {% endfor %}
48 | {% else %}
49 | {% for f in form if f.widget.input_type != 'hidden' %}
50 | {% set kwargs = {} %}
51 | {% if form_opts %}
52 | {% set kwargs = form_opts.widget_args.get(f.short_name, {}) %}
53 | {% endif %}
54 | {% if form._obj %}
55 | {% do kwargs.update({'pk': form._obj.id}) %}
56 | {% do kwargs.update({'obj': form._obj}) %}
57 | {% endif %}
58 | {{ render_tabular_field(form, f, kwargs) }}
59 | {% endfor %}
60 | {% endif %}
61 | {% endmacro %}
62 |
63 |
--------------------------------------------------------------------------------
/crm/apps/admin/templates/admin/index.html:
--------------------------------------------------------------------------------
1 | {% extends 'admin/master.html' %}
2 |
3 | {% block body %}
4 |
5 | Quick links
6 |
7 |
8 |
9 |
10 | My profile
11 | Reports
12 | {% set tasksview = "ownstasksview" %}
13 | {% if tasksview in g._admin_view._template_args['filtered_objects'] %}
14 | {% set tofilterview, filtername = g._admin_view._template_args['filtered_objects'][tasksview] %}
15 | My Tasks
16 | {% endif %}
17 | {% for item in ["contact", "company", "user", "organization", "project", "sprint"] %}
18 | Create new {{item}}
19 | {% endfor %}
20 |
21 | {% endblock %}
22 |
--------------------------------------------------------------------------------
/crm/apps/admin/templates/admin/model/create.html:
--------------------------------------------------------------------------------
1 | {% extends 'admin/master.html' %}
2 | {% import 'admin/lib.html' as lib with context %}
3 | {% from 'admin/lib.html' import extra with context %} {# backward compatible #}
4 |
5 | {% block head %}
6 | {{ super() }}
7 | {{ lib.form_css() }}
8 | {% endblock %}
9 |
10 | {% block body %}
11 | {% block navlinks %}
12 |
20 | {% endblock %}
21 |
22 | {% block create_form %}
23 | {{ lib.render_form(form, return_url, {}, form_opts) }}
24 | {% endblock %}
25 | {% endblock %}
26 |
27 | {% block tail %}
28 | {{ super() }}
29 | {{ lib.form_js() }}
30 | {% endblock %}
31 |
--------------------------------------------------------------------------------
/crm/apps/admin/templates/admin/model/details.html:
--------------------------------------------------------------------------------
1 | {% extends 'admin/master.html' %}
2 | {% import 'admin/lib.html' as lib with context %}
3 |
4 | {% block body %}
5 | {% block navlinks %}
6 |
24 | {% endblock %}
25 |
26 | {% block details_search %}
27 |
28 | {{ _gettext('Filter') }}
29 |
30 |
31 | {% endblock %}
32 |
33 | {% block details_table %}
34 |
35 |
36 |
37 | {% for c, name in details_columns %}
38 | {% set attr = getattr(model, c) %}
39 | {% if attr or (hasattr(attr, 'length') and attr|length > 0) %}
40 |
41 |
42 | {% set potentialviewname = (c+"view").lower()%}
43 | {% if potentialviewname in g._admin_view._template_args['filtered_objects'] %}
44 | {% set tofilterview, filtername = g._admin_view._template_args['filtered_objects'][potentialviewname]%}
45 | {{name}}
46 | {% else %}
47 | {{ name }}
48 | {% endif %}
49 |
50 |
51 |
52 | {% if hasattr(attr, "admin_view_link") %}
53 | {{get_value(model, c)}}
54 | {% else %}
55 | {{ get_value(model, c) }}
56 | {% endif %}
57 |
58 |
59 | {% endif %}
60 | {% endfor %}
61 |
62 | {% endblock %}
63 | {% endblock %}
64 |
65 | {% block tail %}
66 | {{ super() }}
67 |
68 | {% endblock %}
69 |
--------------------------------------------------------------------------------
/crm/apps/admin/templates/admin/model/edit.html:
--------------------------------------------------------------------------------
1 | {% extends 'admin/master.html' %}
2 | {% import 'admin/lib.html' as lib with context %}
3 | {% from 'admin/lib.html' import extra with context %} {# backward compatible #}
4 |
5 | {% block head %}
6 | {{ super() }}
7 | {{ lib.form_css() }}
8 | {% endblock %}
9 |
10 | {% block body %}
11 | {% block navlinks %}
12 |
30 | {% endblock %}
31 |
32 | {% block edit_form %}
33 | {{ lib.render_form(form, return_url, {}, form_opts) }}
34 | {% endblock %}
35 | {% endblock %}
36 |
37 | {% block tail %}
38 | {{ super() }}
39 | {{ lib.form_js() }}
40 | {% endblock %}
41 |
--------------------------------------------------------------------------------
/crm/apps/admin/templates/admin/model/inline_form.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {% import 'admin/lib.html' as lib with context %}
4 | {% import 'admin/extra.html' as extralib with context %}
5 |
6 |
7 |
8 | {{ extralib.render_tabular_form_fields(field.form, form_opts=form_opts) }}
9 |
10 |
--------------------------------------------------------------------------------
/crm/apps/admin/templates/admin/model/inner_field_list.html:
--------------------------------------------------------------------------------
1 | {% import 'admin/model/inline_list_base.html' as base with context %}
2 |
3 | {% macro render_field(field) %}
4 | {{ field }}
5 |
6 | {% if h.is_field_error(field.errors) %}
7 |
12 | {% endif %}
13 | {% endmacro %}
14 |
15 | {{ base.render_inline_fields(field, template, render_field, check) }}
16 |
--------------------------------------------------------------------------------
/crm/apps/admin/templates/admin/model/row_actions.html:
--------------------------------------------------------------------------------
1 | {% import 'admin/lib.html' as lib with context %}
2 |
3 |
4 | {% macro link(action, url, icon_class=None) %}
5 |
6 |
7 |
8 | {% endmacro %}
9 |
10 | {% macro view_row(action, row_id, row) %}
11 | {{ link(action, get_url('.details_view', id=row_id), 'fa fa-eye glyphicon glyphicon-eye-open') }}
12 | {% endmacro %}
13 |
14 | {% macro view_row_popup(action, row_id, row) %}
15 | {{ lib.add_modal_button(url=get_url('.details_view', id=row_id, url=return_url, modal=True), title=action.title, content=' ') }}
16 | {% endmacro %}
17 |
18 | {% macro edit_row(action, row_id, row) %}
19 | {{ link(action, get_url('.edit_view', id=row_id), 'fa fa-pencil glyphicon glyphicon-pencil') }}
20 | {% endmacro %}
21 |
22 | {% macro edit_row_popup(action, row_id, row) %}
23 | {{ lib.add_modal_button(url=get_url('.edit_view', id=row_id, url=return_url, modal=True), title=action.title, content=' ') }}
24 | {% endmacro %}
25 |
26 | {% macro delete_row(action, row_id, row) %}
27 |
39 | {% endmacro %}
40 |
41 |
42 | {% macro view_row(action, row_id, row) %}
43 | {{ link(action, get_url('.details_view', id=row_id), 'fa fa-eye glyphicon glyphicon-eye-open') }}
44 | {% endmacro %}
45 |
46 |
47 | {% macro edit_row(action, row_id, row) %}
48 | {{ link(action, get_url('.edit_view', id=row_id), 'fa fa-pencil glyphicon glyphicon-pencil') }}
49 | {% endmacro %}
50 |
--------------------------------------------------------------------------------
/crm/apps/alert/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/alert/__init__.py
--------------------------------------------------------------------------------
/crm/apps/alert/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/alert/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/alert/graphql/arguments.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/alert/graphql/arguments.py
--------------------------------------------------------------------------------
/crm/apps/alert/graphql/mutations.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/alert/graphql/mutations.py
--------------------------------------------------------------------------------
/crm/apps/alert/graphql/queries.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/alert/graphql/queries.py
--------------------------------------------------------------------------------
/crm/apps/alert/graphql/types.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/alert/graphql/types.py
--------------------------------------------------------------------------------
/crm/apps/api/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/api/__init__.py
--------------------------------------------------------------------------------
/crm/apps/api/models.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/api/models.py
--------------------------------------------------------------------------------
/crm/apps/api/views.py:
--------------------------------------------------------------------------------
1 | from flask import request, jsonify
2 |
3 | from flask_graphql import GraphQLView
4 |
5 | from crm import app
6 |
7 |
8 | @app.route('/api', methods=["POST"])
9 | def api():
10 | if request.headers.get('Content-Type', '').lower() != 'application/json':
11 | return jsonify(errors=['Only accepts Content-Type: application/json']), 400
12 |
13 | data = request.json
14 | query = data.get('query', None)
15 | if not query:
16 | return jsonify(errors=['query field is missing']), 400
17 | if query:
18 | try:
19 | execresult = app.graphql_schema.execute(query)
20 | if execresult.errors:
21 | # BAD REQUEST ON ERRORS
22 | return jsonify(errors=[str(e) for e in execresult.errors]), 400
23 | result = list(execresult.data.items())[0][1]
24 | if result is None:
25 | return '', 404
26 | return jsonify(execresult.data), 200
27 |
28 | except Exception as ex:
29 | return jsonify(errors=[str(ex)]), 400
30 |
31 |
32 | app.add_url_rule('/graphql', view_func=GraphQLView.as_view('graphql', schema=app.graphql_schema, graphiql=True))
33 |
--------------------------------------------------------------------------------
/crm/apps/comment/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/comment/__init__.py
--------------------------------------------------------------------------------
/crm/apps/comment/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/comment/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/comment/graphql/arguments.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene.types.inputobjecttype import InputObjectType
3 |
4 |
5 | class CommentArguments(InputObjectType):
6 | uid = graphene.String()
7 | content = graphene.String()
8 | company = graphene.Field('crm.apps.company.graphql.arguments.CompanyArguments')
9 | contact = graphene.Field('crm.apps.contact.graphql.arguments.ContactArguments')
10 | user = graphene.Field('crm.apps.user.graphql.arguments.UserArguments')
11 | deal = graphene.Field('crm.apps.deal.graphql.arguments.DealArguments')
12 | task = graphene.Field('crm.apps.task.graphql.arguments.TaskArguments')
13 | organization = graphene.Field('crm.apps.organization.graphql.arguments.OrganizationArguments')
14 | project = graphene.Field('crm.apps.project.graphql.arguments.ProjectArguments')
15 | sprint = graphene.Field('crm.apps.sprint.graphql.arguments.SprintArguments')
16 | link = graphene.Field('crm.apps.link.graphql.arguments.LinkArguments')
17 | event = graphene.Field('crm.apps.event.graphql.arguments.EventArguments')
18 |
19 |
20 | class CreateCommentArguments(CommentArguments):
21 | content = graphene.String(required=True)
22 |
23 |
24 | class UpdateCommentArguments(CommentArguments):
25 | uid = graphene.String(required=True)
26 |
--------------------------------------------------------------------------------
/crm/apps/comment/graphql/mutations.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/comment/graphql/mutations.py
--------------------------------------------------------------------------------
/crm/apps/comment/graphql/queries.py:
--------------------------------------------------------------------------------
1 | import graphene
2 |
3 | from .types import CommentType
4 | from crm.graphql import BaseQuery
5 |
6 |
7 | class CommentQuery(BaseQuery):
8 | comments = graphene.List(CommentType)
9 |
10 | def resolve_comments(self, args, context, info):
11 | query = CommentType.get_query(context)
12 | return query.all()
13 |
--------------------------------------------------------------------------------
/crm/apps/comment/graphql/types.py:
--------------------------------------------------------------------------------
1 | from graphene import relay
2 | from graphene_sqlalchemy import SQLAlchemyObjectType
3 |
4 | from crm.apps.comment.models import Comment
5 |
6 |
7 | class CommentType(SQLAlchemyObjectType):
8 |
9 | class Meta:
10 | model = Comment
11 | interfaces = (relay.Node,)
12 | name = model.__name__
13 |
--------------------------------------------------------------------------------
/crm/apps/comment/models.py:
--------------------------------------------------------------------------------
1 | from crm.db import db, BaseModel
2 |
3 |
4 | class Comment(db.Model, BaseModel):
5 |
6 | __tablename__ = "comments"
7 |
8 | # should be markdown.
9 | content = db.Column(
10 | db.Text(),
11 | index=True
12 | )
13 |
14 | company_id = db.Column(
15 | db.String(5),
16 | db.ForeignKey("companies.id")
17 | )
18 |
19 | contact_id = db.Column(
20 | db.String(5),
21 | db.ForeignKey("contacts.id")
22 | )
23 |
24 | user_id = db.Column(
25 | db.String(5),
26 | db.ForeignKey("users.id")
27 | )
28 |
29 | deal_id = db.Column(
30 | db.String(5),
31 | db.ForeignKey("deals.id")
32 | )
33 |
34 | task_id = db.Column(
35 | db.String(5),
36 | db.ForeignKey("tasks.id")
37 | )
38 |
39 | organization_id = db.Column(
40 | db.String(5),
41 | db.ForeignKey("organizations.id")
42 | )
43 |
44 | project_id = db.Column(
45 | db.String(5),
46 | db.ForeignKey("projects.id")
47 | )
48 |
49 | sprint_id = db.Column(
50 | db.String(5),
51 | db.ForeignKey("sprints.id")
52 | )
53 |
54 | link_id = db.Column(
55 | db.String(5),
56 | db.ForeignKey("links.id")
57 | )
58 | event_id = db.Column(
59 | db.String,
60 | db.ForeignKey("events.id")
61 | )
62 |
63 | # alert_id = db.Column(
64 | # db.String,
65 | # db.ForeignKey("alerts.id")
66 | # )
67 | #
68 | # alert_source_id = db.Column(
69 | # db.String,
70 | # db.ForeignKey("alertsources.id")
71 | # )
72 |
73 | knowledge_base_id = db.Column(
74 | db.String,
75 | db.ForeignKey("knowledgebases.id")
76 | )
77 |
78 | def __str__(self):
79 | return self.content
80 |
--------------------------------------------------------------------------------
/crm/apps/company/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/company/__init__.py
--------------------------------------------------------------------------------
/crm/apps/company/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/company/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/company/graphql/arguments.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene.types.inputobjecttype import InputObjectType
3 |
4 | from crm.apps.address.graphql.arguments import AddressArguments
5 | from crm.apps.comment.graphql.arguments import CommentArguments
6 | from crm.apps.contact.graphql.arguments import ContactArguments
7 | from crm.apps.deal.graphql.arguments import DealArguments
8 | from crm.apps.link.graphql.arguments import LinkArguments
9 | from crm.apps.message.graphql.arguments import MessageArguments
10 | from crm.apps.tag.graphql.arguments import TagArguments
11 | from crm.apps.task.graphql.arguments import TaskArguments
12 |
13 |
14 | class CompanyArguments(InputObjectType):
15 | uid = graphene.String()
16 | name = graphene.String()
17 | description = graphene.String()
18 | vatnumber = graphene.String()
19 | website = graphene.String()
20 | emails = graphene.String()
21 | telephones = graphene.String()
22 | owner = graphene.Argument('crm.apps.user.graphql.arguments.UserArguments')
23 | ownerbackup = graphene.Argument('crm.apps.user.graphql.arguments.UserArguments')
24 |
25 | deals = graphene.List(DealArguments)
26 | comments = graphene.List(CommentArguments)
27 | tasks = graphene.List(TaskArguments)
28 | messages = graphene.List(MessageArguments)
29 | links = graphene.List(LinkArguments)
30 | contacts = graphene.List(ContactArguments)
31 |
32 | tags = graphene.List(TagArguments)
33 | addresses = graphene.List(AddressArguments)
34 |
35 |
36 | class CreateCompanyArguments(CompanyArguments):
37 | name = graphene.String(required=True)
38 |
39 |
40 | class UpdateCompanyArguments(CompanyArguments):
41 | uid = graphene.String(required=True)
42 |
--------------------------------------------------------------------------------
/crm/apps/company/graphql/mutations.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/company/graphql/mutations.py
--------------------------------------------------------------------------------
/crm/apps/company/graphql/queries.py:
--------------------------------------------------------------------------------
1 | import graphene
2 |
3 | from .types import CompanyType
4 |
5 | from crm.graphql import BaseQuery
6 |
7 |
8 |
9 | class CompanyQuery(BaseQuery):
10 | companies = graphene.List(CompanyType)
11 |
12 | def resolve_comments(self, args, context, info):
13 | query = CompanyType.get_query(context)
14 | return query.all()
15 |
--------------------------------------------------------------------------------
/crm/apps/company/graphql/types.py:
--------------------------------------------------------------------------------
1 | from graphene import relay
2 |
3 | from graphene_sqlalchemy import SQLAlchemyObjectType
4 |
5 | from crm.apps.company.models import CompanyTags
6 |
7 |
8 | class CompanyType(SQLAlchemyObjectType):
9 |
10 | class Meta:
11 | model = CompanyTags
12 | interfaces = (relay.Node,)
13 | name = model.__name__
14 |
--------------------------------------------------------------------------------
/crm/apps/company/models.py:
--------------------------------------------------------------------------------
1 | from crm.db import db, BaseModel, RootModel, ManyToManyBaseModel
2 | from crm.mailer import sendemail
3 |
4 |
5 | class CompanyTags(db.Model, ManyToManyBaseModel):
6 | __tablename__ = "companies_tags"
7 |
8 | tag_id = db.Column(
9 | db.String(5),
10 | db.ForeignKey('tags.id')
11 | )
12 |
13 | company_id = db.Column(
14 | db.String(5),
15 | db.ForeignKey("companies.id")
16 | )
17 |
18 |
19 | class Company(db.Model, BaseModel, RootModel):
20 |
21 | __tablename__ = "companies"
22 |
23 | name = db.Column(
24 | db.String(255),
25 | nullable=False,
26 | index=True
27 | )
28 |
29 | # should be markdown.
30 | description = db.Column(
31 | db.Text(),
32 | default="",
33 | index=True
34 | )
35 |
36 | vatnumber = db.Column(
37 | db.String(255),
38 | index=True
39 | )
40 |
41 | website = db.Column(
42 | db.String(255),
43 | index=True
44 | )
45 |
46 | emails = db.relationship(
47 | 'Email',
48 | backref='company',
49 | primaryjoin="Company.id==Email.company_id"
50 | )
51 |
52 | telephones = db.relationship(
53 | 'Phone',
54 | backref='company',
55 | primaryjoin="Company.id==Phone.company_id"
56 | )
57 |
58 | deals = db.relationship(
59 | "Deal",
60 | backref="company"
61 | )
62 |
63 | messages = db.relationship(
64 | "Message",
65 | backref="company"
66 | )
67 |
68 | tasks = db.relationship(
69 | "Task",
70 | backref="company"
71 | )
72 |
73 | comments = db.relationship(
74 | "Comment",
75 | backref="company"
76 | )
77 |
78 | contacts = db.relationship(
79 | "Contact",
80 | secondary="companies_contacts",
81 | backref='companies',
82 | )
83 |
84 | owner_id = db.Column(
85 | db.String(5),
86 | db.ForeignKey('users.id')
87 | )
88 |
89 | ownerbackup_id = db.Column(
90 | db.String(5),
91 | db.ForeignKey('users.id')
92 | )
93 |
94 | links = db.relationship(
95 | "Link",
96 | backref="company"
97 | )
98 |
99 | addresses = db.relationship(
100 | "Address",
101 | backref="company"
102 | )
103 |
104 | @property
105 | def notification_emails(self):
106 | """
107 | :return: list of all emails to send notifications to
108 | :rtype: list
109 | """
110 | return [e.email for e in self.emails]
111 |
112 | def __str__(self):
113 | return self.name
114 |
--------------------------------------------------------------------------------
/crm/apps/contact/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/contact/__init__.py
--------------------------------------------------------------------------------
/crm/apps/contact/forms.py:
--------------------------------------------------------------------------------
1 | from wtforms_alchemy import ModelForm
2 |
3 | from .models import Contact
4 |
5 |
6 | class ContactForm(ModelForm):
7 | class Meta:
8 | model = Contact
9 |
--------------------------------------------------------------------------------
/crm/apps/contact/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/contact/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/contact/graphql/arguments.py:
--------------------------------------------------------------------------------
1 | import graphene
2 |
3 | from graphene.types.inputobjecttype import InputObjectType
4 | from crm.apps.address.graphql.arguments import AddressArguments
5 | from crm.apps.comment.graphql.arguments import CommentArguments
6 | from crm.apps.contact.models import Gender
7 | from crm.apps.country.graphql.arguments import CountryArguments
8 | from crm.apps.link.graphql.arguments import LinkArguments
9 | from crm.apps.message.graphql.arguments import MessageArguments
10 | from crm.apps.passport.graphql.arguments import PassportArguments
11 | from crm.apps.task.graphql.arguments import TaskArguments
12 | from crm.graphql import BaseArgument
13 |
14 |
15 | class ContactSubgroupArguments(InputObjectType):
16 | groupname = graphene.String()
17 |
18 |
19 | class ContactArguments(InputObjectType, BaseArgument):
20 | uid = graphene.String()
21 | firstname = graphene.String()
22 | lastname = graphene.String()
23 | description = graphene.String()
24 | bio = graphene.String()
25 | belief_statement = graphene.String()
26 | gender = graphene.Enum.from_enum(Gender)()
27 | message_channels = graphene.String()
28 | owner = graphene.Argument('crm.apps.user.graphql.arguments.UserArguments')
29 | ownerbackup = graphene.Argument('crm.apps.user.graphql.arguments.UserArguments')
30 | parent = graphene.Argument('crm.apps.contact.graphql.arguments.ContactArguments')
31 | emails = graphene.String()
32 | telephones = graphene.String()
33 | tf_app = graphene.Boolean()
34 | tf_web = graphene.Boolean()
35 | referral_code = graphene.String()
36 |
37 | deals = graphene.List('crm.apps.deal.graphql.arguments.DealArguments')
38 | comments = graphene.List(CommentArguments)
39 | tasks = graphene.List(TaskArguments)
40 | messages = graphene.List(MessageArguments)
41 | links = graphene.List(LinkArguments)
42 |
43 | subgroups = graphene.List(ContactSubgroupArguments)
44 | addresses = graphene.List(AddressArguments)
45 | passports = graphene.List(PassportArguments)
46 | countries = graphene.List(CountryArguments)
47 |
48 |
49 | class CreateContactArguments(ContactArguments):
50 | firstname = graphene.String(required=True)
51 | lastname = graphene.String(required=True)
52 |
53 |
54 | class UpdateContactArguments(ContactArguments):
55 | uid = graphene.String(required=True)
56 |
--------------------------------------------------------------------------------
/crm/apps/contact/graphql/queries.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene import relay
3 |
4 | from crm.apps.contact.graphql.arguments import ContactArguments
5 | from crm.apps.contact.graphql.types import ContactType
6 | from crm.graphql import BaseQuery, CRMConnectionField
7 |
8 |
9 | class ContactQuery(BaseQuery):
10 | """
11 | we have 2 queries here contact and contacts
12 | """
13 |
14 | # no need for resplve_contacts function here
15 | contacts = CRMConnectionField(
16 | ContactType,
17 | **ContactArguments.fields()
18 |
19 | )
20 | # contact query to return one contact and takes (uid) argument
21 | # uid is the original object.id in db
22 | contact = graphene.Field(ContactType, uid=graphene.String())
23 |
24 | def resolve_contact(self, context, uid):
25 | return ContactType.get_query(context).filter_by(id=uid).first()
26 |
27 |
28 | class Meta:
29 | interfaces = (relay.Node, )
--------------------------------------------------------------------------------
/crm/apps/contact/graphql/types.py:
--------------------------------------------------------------------------------
1 | from graphene import relay
2 | from graphene_sqlalchemy import SQLAlchemyObjectType
3 |
4 | from crm.apps.contact.models import Contact
5 | from crm.graphql import CrmType
6 |
7 |
8 | class ContactType(CrmType, SQLAlchemyObjectType):
9 |
10 | class Meta:
11 | model = Contact
12 | interfaces = (relay.Node,)
13 | name = model.__name__
14 |
15 | # CrmType adds author_original, author_last objects rather than ids
16 | exclude_fields = (
17 | 'author_original_id',
18 | 'author_last_id'
19 | )
20 |
--------------------------------------------------------------------------------
/crm/apps/country/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/country/__init__.py
--------------------------------------------------------------------------------
/crm/apps/country/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/country/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/country/graphql/arguments.py:
--------------------------------------------------------------------------------
1 | import enum
2 | import graphene
3 | from graphene.types.inputobjecttype import InputObjectType
4 |
5 | from crm.apps.country.countries import countries
6 | from crm.graphql import BaseArgument
7 |
8 | countries = enum.Enum('CountriesEnum', {v.replace(' ', '').replace('-', '').replace(',', '').replace("'", '').replace('.', '').replace('(', '').replace(')',''):k for k,v in countries.items()})
9 |
10 |
11 | class CountryArguments(InputObjectType, BaseArgument):
12 | """
13 | Country Arguments
14 | """
15 | name = graphene.Enum.from_enum(countries)()
16 |
17 | # ****************************************************
18 | # NO NEED FOR CREATE/UPDATE COUNTRY *
19 | # *****************************************************
20 |
--------------------------------------------------------------------------------
/crm/apps/country/graphql/mutations.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/country/graphql/mutations.py
--------------------------------------------------------------------------------
/crm/apps/country/graphql/queries.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene import relay
3 |
4 | from crm.apps.country.graphql.arguments import CountryArguments
5 | from crm.apps.country.graphql.types import CountryType
6 | from crm.graphql import BaseQuery, CRMConnectionField
7 |
8 |
9 | class CountryQuery(BaseQuery):
10 | countries = CRMConnectionField(
11 | CountryType,
12 | **CountryArguments.fields()
13 | )
14 |
15 | country = graphene.Field(CountryType, uid=graphene.String())
16 |
17 | def resolve_country(self, context, uid):
18 | return CountryType.get_query(context).filter_by(id=uid).first()
19 |
20 |
21 | class Meta:
22 | interfaces = (relay.Node, )
--------------------------------------------------------------------------------
/crm/apps/country/graphql/types.py:
--------------------------------------------------------------------------------
1 | from graphene import relay
2 | from graphene_sqlalchemy import SQLAlchemyObjectType
3 |
4 | from crm.apps.country.models import Country
5 | from crm.graphql import CrmType
6 |
7 |
8 | class CountryType(CrmType, SQLAlchemyObjectType):
9 |
10 | class Meta:
11 | model = Country
12 | interfaces = (relay.Node,)
13 | name = model.__name__
14 | # CrmType adds author_original, author_last objects rather than ids
15 | exclude_fields = (
16 | 'author_original_id',
17 | 'author_last_id'
18 | )
19 |
--------------------------------------------------------------------------------
/crm/apps/country/models.py:
--------------------------------------------------------------------------------
1 | from crm.db import db, BaseModel
2 |
3 | from .countries import CountriesEnum
4 |
5 |
6 | class Country(db.Model, BaseModel):
7 |
8 | __tablename__ = "countries"
9 |
10 | name = db.Column(
11 | db.Enum(CountriesEnum),
12 | default=CountriesEnum.BE,
13 | unique=True,
14 | index=True
15 | )
16 |
17 | contacts = db.relationship(
18 | "Contact",
19 | secondary="contacts_countries",
20 | backref="countries"
21 | )
22 |
23 | addresses = db.relationship(
24 | "Address",
25 | backref="country"
26 | )
27 |
28 | passports = db.relationship(
29 | "Passport",
30 | backref="country"
31 | )
32 |
33 | def __str__(self):
34 | return self.name.value
--------------------------------------------------------------------------------
/crm/apps/currency/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/currency/__init__.py
--------------------------------------------------------------------------------
/crm/apps/currency/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/currency/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/currency/graphql/arguments.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene.types.inputobjecttype import InputObjectType
3 |
4 |
5 | class CurrencyArguments(InputObjectType):
6 | """
7 | currency Arguments
8 | """
9 | name = graphene.String(required=True)
10 | value_usd = graphene.Float(required=True)
11 |
--------------------------------------------------------------------------------
/crm/apps/currency/graphql/mutations.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/currency/graphql/mutations.py
--------------------------------------------------------------------------------
/crm/apps/currency/graphql/queries.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/currency/graphql/queries.py
--------------------------------------------------------------------------------
/crm/apps/currency/graphql/types.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/currency/graphql/types.py
--------------------------------------------------------------------------------
/crm/apps/currency/models.py:
--------------------------------------------------------------------------------
1 | from crm import db
2 | from crm.db import BaseModel, RootModel
3 |
4 |
5 | class Currency(db.Model, BaseModel, RootModel):
6 |
7 | __tablename__ = "currencies"
8 |
9 | name = db.Column(
10 | db.String(3),
11 | index=True,
12 | unique=True,
13 | nullable=False,
14 | )
15 |
16 | value_usd = db.Column(
17 | db.Float(),
18 | default=1.0
19 | )
20 |
21 | deals = db.relationship(
22 | "Deal",
23 | backref="currency"
24 | )
25 |
26 | def __str__(self):
27 | return self.name
28 |
--------------------------------------------------------------------------------
/crm/apps/deal/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/deal/__init__.py
--------------------------------------------------------------------------------
/crm/apps/deal/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/deal/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/deal/graphql/arguments.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene.types.inputobjecttype import InputObjectType
3 |
4 | from crm.apps.address.graphql.arguments import AddressArguments
5 | from crm.apps.comment.graphql.arguments import CommentArguments
6 | from crm.apps.currency.graphql.arguments import CurrencyArguments
7 | from crm.apps.deal.models import DealType, DealState
8 | from crm.apps.link.graphql.arguments import LinkArguments
9 | from crm.apps.message.graphql.arguments import MessageArguments
10 | from crm.apps.task.graphql.arguments import TaskArguments
11 | from crm.graphql import BaseArgument
12 |
13 | DealType = graphene.Enum.from_enum(DealType)
14 | DealState = graphene.Enum.from_enum(DealState)
15 |
16 |
17 | class DealArguments(InputObjectType, BaseArgument):
18 | uid = graphene.String()
19 | name = graphene.String()
20 | description = graphene.String()
21 | value = graphene.String()
22 | currency = graphene.Argument(CurrencyArguments)
23 | deal_type = DealType()
24 | deal_state = DealState()
25 | closed_at = graphene.String()
26 |
27 | company_id = graphene.String()
28 | company = graphene.Argument('crm.apps.company.graphql.arguments.CompanyArguments')
29 | contact_id = graphene.String()
30 | contact = graphene.Argument('crm.apps.contact.graphql.arguments.ContactArguments')
31 |
32 | referrer1 = graphene.Argument('crm.apps.contact.graphql.arguments.ContactArguments')
33 | referrer2 = graphene.Argument('crm.apps.contact.graphql.arguments.ContactArguments')
34 | referrer1_id = graphene.String()
35 | referrer2_id = graphene.String()
36 |
37 | comments = graphene.List(CommentArguments)
38 | tasks = graphene.List(TaskArguments)
39 | messages = graphene.List(MessageArguments)
40 | links = graphene.List(LinkArguments)
41 |
42 | is_paid = graphene.Boolean()
43 | referral_code = graphene.String()
44 | shipping_addresses = graphene.List(AddressArguments)
45 |
46 |
47 | class CreateDealArguments(DealArguments):
48 | name = graphene.String(required=True)
49 | value = graphene.Float(required=True)
50 | currency = graphene.Argument(CurrencyArguments, required=True)
51 | deal_type = DealType(required=True)
52 | deal_state = DealState(required=True)
53 |
54 |
55 | class UpdateDealArguments(CreateDealArguments):
56 | uid = graphene.String(required=True)
57 | value = graphene.Float()
--------------------------------------------------------------------------------
/crm/apps/deal/graphql/queries.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene import relay
3 |
4 | from crm.apps.deal.graphql.arguments import DealArguments
5 | from .types import DealType
6 | from crm.graphql import BaseQuery, CRMConnectionField
7 |
8 |
9 |
10 | class DealQuery(BaseQuery):
11 | """
12 | we have 2 queries here Deal and Deals
13 | """
14 |
15 | # no need for resplve_Deals function here
16 | deals = CRMConnectionField(
17 | DealType,
18 | DealArguments.fields()
19 |
20 | )
21 |
22 | # Deal query to return one Deal and takes (uid) argument
23 | # uid is the original object.id in db
24 | deal = graphene.Field(DealType, uid=graphene.String())
25 |
26 | def resolve_deal(self, context, uid):
27 | return DealType.get_query(context).filter_by(id=uid).first()
28 |
29 | class Meta:
30 | interfaces = (relay.Node,)
31 |
--------------------------------------------------------------------------------
/crm/apps/deal/graphql/types.py:
--------------------------------------------------------------------------------
1 | from graphene import relay
2 | from graphene_sqlalchemy.types import SQLAlchemyObjectType
3 |
4 | from crm.apps.deal.models import Deal
5 | from crm.graphql import CrmType
6 |
7 |
8 | class DealType(CrmType, SQLAlchemyObjectType):
9 | class Meta:
10 | model = Deal
11 | interfaces = (relay.Node,)
12 | name = model.__name__
13 |
14 | # CrmType adds author_original, author_last objects rather than ids
15 | exclude_fields = (
16 | 'author_original_id',
17 | 'author_last_id'
18 | )
19 |
--------------------------------------------------------------------------------
/crm/apps/email/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/email/__init__.py
--------------------------------------------------------------------------------
/crm/apps/email/models.py:
--------------------------------------------------------------------------------
1 | from crm import db, BaseModel
2 |
3 |
4 | class Email(db.Model, BaseModel):
5 | __tablename__ = "emails"
6 |
7 | email = db.Column(
8 | db.String(255),
9 | index=True,
10 | nullable=False,
11 | )
12 |
13 | user_id = db.Column(
14 | db.String,
15 | db.ForeignKey("users.id")
16 | )
17 |
18 | contact_id = db.Column(
19 | db.String,
20 | db.ForeignKey("contacts.id")
21 | )
22 |
23 | organization_id = db.Column(
24 | db.String,
25 | db.ForeignKey("organizations.id")
26 | )
27 |
28 | company_id = db.Column(
29 | db.String,
30 | db.ForeignKey("companies.id")
31 | )
32 |
33 | def __str__(self):
34 | return self.email
35 |
--------------------------------------------------------------------------------
/crm/apps/event/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/event/__init__.py
--------------------------------------------------------------------------------
/crm/apps/event/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/event/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/event/graphql/arguments.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene.types.inputobjecttype import InputObjectType
3 |
4 | from crm.apps.comment.graphql.arguments import CommentArguments
5 | from crm.apps.link.graphql.arguments import LinkArguments
6 | from crm.apps.message.graphql.arguments import MessageArguments
7 | from crm.apps.task.graphql.arguments import TaskArguments
8 |
9 |
10 | class EventArguments(InputObjectType):
11 | uid = graphene.String()
12 | title = graphene.String()
13 | description = graphene.String()
14 | contact_event_status = graphene.Float()
15 | event_datetime = graphene.String()
16 |
17 | comments = graphene.List(CommentArguments)
18 | tasks = graphene.List(TaskArguments)
19 | messages = graphene.List(MessageArguments)
20 | links = graphene.List(LinkArguments)
21 | contacts = graphene.List('crm.apps.contact.graphql.arguments.ContactArguments')
22 |
23 |
24 | class CreateEventArguments(EventArguments):
25 | title = graphene.String(required=True)
26 |
27 |
28 | class UpdateEventArguments(EventArguments):
29 | uid = graphene.String(required=False)
--------------------------------------------------------------------------------
/crm/apps/event/graphql/mutations.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/event/graphql/mutations.py
--------------------------------------------------------------------------------
/crm/apps/event/graphql/queries.py:
--------------------------------------------------------------------------------
1 | import graphene
2 |
3 | from .types import EventType
4 | from crm.graphql import BaseQuery
5 |
6 |
7 | class EventQuery(BaseQuery):
8 | events = graphene.List(EventType)
9 |
10 | def resolve_events(self, args, context, info):
11 | query = EventType.get_query(context)
12 | return query.all()
13 |
--------------------------------------------------------------------------------
/crm/apps/event/graphql/types.py:
--------------------------------------------------------------------------------
1 | from graphene import relay
2 |
3 | from graphene_sqlalchemy import SQLAlchemyObjectType
4 |
5 | from crm.apps.event.models import Event
6 |
7 |
8 | class EventType(SQLAlchemyObjectType):
9 |
10 | class Meta:
11 | model = Event
12 | interfaces = (relay.Node,)
13 | name = model.__name__
14 |
--------------------------------------------------------------------------------
/crm/apps/event/models.py:
--------------------------------------------------------------------------------
1 | import enum
2 | from crm.db import db, BaseModel, ManyToManyBaseModel
3 | from datetime import datetime
4 |
5 |
6 | class ContactEventStatus(enum.Enum):
7 | INVITED = 'INVITED'
8 | WONTSHOW = 'WONTSHOW'
9 | ATTENDED = 'ATTENDED'
10 | DENIED = 'DENIED'
11 | COULDNTMAKEIT = 'COULDNTMAKEIT'
12 |
13 | ContactEventStatus.__str__ = lambda self: self.name
14 |
15 |
16 | class Event(db.Model, BaseModel):
17 |
18 | __tablename__ = "events"
19 |
20 | __mapper_args__ = {'polymorphic_identity': 'messages'}
21 |
22 | title = db.Column(
23 | db.String(255),
24 | nullable=False,
25 | index=True
26 | )
27 |
28 | description = db.Column(
29 | db.Text(),
30 | default="",
31 | index=True
32 | )
33 | contact_event_status = db.Column(
34 | db.Enum(ContactEventStatus),
35 | default=ContactEventStatus.INVITED,
36 | )
37 | contacts = db.relationship(
38 | "Contact",
39 | secondary="contacts_events",
40 | backref="events"
41 | )
42 |
43 | comments = db.relationship(
44 | "Comment",
45 | backref="event",
46 | )
47 |
48 | messages = db.relationship(
49 | "Message",
50 | backref="event",
51 | )
52 |
53 | links = db.relationship(
54 | "Link",
55 | backref="event",
56 | )
57 |
58 | tasks = db.relationship(
59 | "Task",
60 | backref="event",
61 | )
62 | event_datetime = db.Column(
63 | db.TIMESTAMP,
64 | default=datetime.utcnow,
65 | onupdate=datetime.utcnow,
66 | nullable=False,
67 | index=True
68 | )
69 |
70 | @property
71 | def notification_emails(self):
72 | """
73 | :return: list of all emails to send notifications to
74 | :rtype: list
75 | """
76 | emails = []
77 | if self.contacts:
78 | for contact in self.contacts:
79 | if contact.notification_emails:
80 | emails.extend(contact.notification_emails)
81 | if self.tasks:
82 | for task in self.tasks:
83 | if task.notification_emails:
84 | emails.extend(task.notification_emails)
85 | return list(set(emails))
86 |
87 | def __str__(self):
88 | return self.title
89 |
90 |
91 | class ContactEvents(db.Model, ManyToManyBaseModel):
92 |
93 | __tablename__ = "contacts_events"
94 |
95 | event_id = db.Column(
96 | db.String(5),
97 | db.ForeignKey('events.id')
98 | )
99 |
100 | contact_id = db.Column(
101 | db.String(5),
102 | db.ForeignKey("contacts.id")
103 | )
104 |
--------------------------------------------------------------------------------
/crm/apps/home/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/home/__init__.py
--------------------------------------------------------------------------------
/crm/apps/home/models.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/home/models.py
--------------------------------------------------------------------------------
/crm/apps/home/templates/home/401.html:
--------------------------------------------------------------------------------
1 |
2 | STOP :: You are not authorized
3 | {{message|safe}}
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/crm/apps/home/templates/home/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hey
4 |
5 |
--------------------------------------------------------------------------------
/crm/apps/home/views.py:
--------------------------------------------------------------------------------
1 | from flask.templating import render_template
2 |
3 | from crm import app
4 |
5 |
6 | @app.route('/')
7 | def hello_world():
8 | return render_template('home/index.html')
9 |
--------------------------------------------------------------------------------
/crm/apps/image/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/image/__init__.py
--------------------------------------------------------------------------------
/crm/apps/image/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/image/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/image/graphql/arguments.py:
--------------------------------------------------------------------------------
1 | from graphene.types.inputobjecttype import InputObjectType
2 |
3 |
4 | class ImageArgument(InputObjectType):
5 | pass
6 |
--------------------------------------------------------------------------------
/crm/apps/image/graphql/mutations.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/image/graphql/mutations.py
--------------------------------------------------------------------------------
/crm/apps/image/graphql/queries.py:
--------------------------------------------------------------------------------
1 | import graphene
2 |
3 | from .types import ImageType
4 | from crm.graphql import BaseQuery
5 |
6 |
7 | class ImageQuery(BaseQuery):
8 | images = graphene.List(ImageType)
9 |
10 | def resolve_images(self, args, context, info):
11 | query = ImageType.get_query(context)
12 | return query.all()
13 |
--------------------------------------------------------------------------------
/crm/apps/image/graphql/types.py:
--------------------------------------------------------------------------------
1 | from graphene import relay
2 |
3 | from graphene_sqlalchemy import SQLAlchemyObjectType
4 |
5 | from crm.apps.image.models import Image
6 |
7 |
8 | class ImageType(SQLAlchemyObjectType):
9 |
10 | class Meta:
11 | model = Image
12 | interfaces = (relay.Node,)
13 | name = model.__name__
14 |
--------------------------------------------------------------------------------
/crm/apps/image/models.py:
--------------------------------------------------------------------------------
1 | import os
2 | from crm.db import db, BaseModel
3 | from crm.settings import IMAGES_DIR, STATIC_URL_PATH
4 |
5 |
6 | class Image(db.Model, BaseModel):
7 |
8 | __tablename__ = "images"
9 |
10 | name = db.Column(
11 | db.String(255)
12 | )
13 |
14 | path = db.Column(
15 | db.String(255)
16 | )
17 |
18 | contact_id = db.Column(
19 | db.String(5),
20 | db.ForeignKey('contacts.id')
21 | )
22 |
23 | @property
24 | def imgurl(self):
25 | return os.path.join(STATIC_URL_PATH, "uploads", "images", self.path)
26 |
27 | @property
28 | def fullpath(self):
29 | return os.path.join(IMAGES_DIR, self.path)
30 |
31 | @property
32 | def as_image(self):
33 | return ' '.format(imgurl=self.imgurl)
34 |
35 | def __str__(self):
36 | return self.path
37 |
--------------------------------------------------------------------------------
/crm/apps/knowledge/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/knowledge/__init__.py
--------------------------------------------------------------------------------
/crm/apps/knowledge/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/knowledge/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/knowledge/graphql/arguments.py:
--------------------------------------------------------------------------------
1 | from graphene.types.inputobjecttype import InputObjectType
2 |
3 |
4 | class KnowledgebaseArgument(InputObjectType):
5 | pass
6 |
--------------------------------------------------------------------------------
/crm/apps/knowledge/graphql/mutations.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/knowledge/graphql/mutations.py
--------------------------------------------------------------------------------
/crm/apps/knowledge/graphql/queries.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/knowledge/graphql/queries.py
--------------------------------------------------------------------------------
/crm/apps/knowledge/graphql/types.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/knowledge/graphql/types.py
--------------------------------------------------------------------------------
/crm/apps/knowledge/models.py:
--------------------------------------------------------------------------------
1 | from crm.db import db, BaseModel
2 |
3 |
4 | class KnowledgeBase(db.Model, BaseModel):
5 |
6 | __tablename__ = "knowledgebases"
7 |
8 | title = db.Column(
9 | db.String(255),
10 | nullable=False,
11 | index=True
12 | )
13 |
14 | category_id = db.Column(
15 | db.String(5),
16 | db.ForeignKey("knowledgebase_categories.id")
17 | )
18 |
19 | author_id = db.Column(
20 | db.String(5),
21 | db.ForeignKey("users.id")
22 | )
23 |
24 | content = db.Column(
25 | db.Text(),
26 | index=True
27 | )
28 |
29 | tasks = db.relationship(
30 | "Task",
31 | backref="knowledge_base"
32 | )
33 |
34 | comments = db.relationship(
35 | "Comment",
36 | backref="knowledge_base"
37 | )
38 |
39 | def __str__(self):
40 | return self.title
41 |
42 |
43 | class KnowledgeBaseCategory(db.Model, BaseModel):
44 |
45 | __tablename__ = "knowledgebase_categories"
46 |
47 | name = db.Column(
48 | db.String(255),
49 | nullable=False,
50 | index=True
51 | )
52 |
53 | description = db.Column(
54 | db.Text()
55 | )
56 |
57 | knowledge_bases = db.relationship(
58 | "KnowledgeBase",
59 | backref="category"
60 | )
61 |
62 | def __str__(self):
63 | return self.name
64 |
--------------------------------------------------------------------------------
/crm/apps/link/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/link/__init__.py
--------------------------------------------------------------------------------
/crm/apps/link/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/link/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/link/graphql/arguments.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene.types.inputobjecttype import InputObjectType
3 |
4 |
5 | class LinkArguments(InputObjectType):
6 | uid = graphene.String()
7 | url = graphene.String()
8 | labels = graphene.String()
9 | contact_id = graphene.String()
10 | user_id = graphene.String()
11 | deal_id = graphene.String()
12 | task_id = graphene.String()
13 | organization_id = graphene.String()
14 | project_id = graphene.String()
15 | sprint_id = graphene.String()
16 | event_id = graphene.String()
17 | comments = graphene.List('crm.apps.comment.graphql.arguments.CommentArguments')
18 |
19 |
20 | class CreateLinkArguments(LinkArguments):
21 | url = graphene.String(required=True)
22 |
23 |
24 | class UpdateLinkArguments(LinkArguments):
25 | uid = graphene.String(requied=True)
--------------------------------------------------------------------------------
/crm/apps/link/graphql/mutations.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/link/graphql/mutations.py
--------------------------------------------------------------------------------
/crm/apps/link/graphql/queries.py:
--------------------------------------------------------------------------------
1 | import graphene
2 |
3 | from .types import LinkType
4 | from crm.graphql import BaseQuery
5 |
6 |
7 | class LinkQuery(BaseQuery):
8 | links = graphene.List(LinkType)
9 |
10 | def resolve_links(self, args, context, info):
11 | query = LinkType.get_query(context)
12 | return query.all()
13 |
--------------------------------------------------------------------------------
/crm/apps/link/graphql/types.py:
--------------------------------------------------------------------------------
1 | from graphene_sqlalchemy import SQLAlchemyObjectType
2 |
3 | from crm.apps.link.models import Link
4 |
5 |
6 | class LinkType(SQLAlchemyObjectType):
7 |
8 | class Meta:
9 | model = Link
10 |
11 |
--------------------------------------------------------------------------------
/crm/apps/link/models.py:
--------------------------------------------------------------------------------
1 | from crm.db import db, BaseModel
2 |
3 |
4 | class Link(db.Model, BaseModel):
5 |
6 | __tablename__ = "links"
7 |
8 | url = db.Column(
9 | db.String(255),
10 | nullable=False,
11 | index=True
12 | )
13 |
14 | filename =db.Column(
15 | db.String()
16 | )
17 |
18 | labels = db.Column(
19 | db.Text(),
20 | index=True
21 | )
22 |
23 | contact_id = db.Column(
24 | db.String,
25 | db.ForeignKey("contacts.id")
26 | )
27 |
28 | user_id = db.Column(
29 | db.String,
30 | db.ForeignKey("users.id")
31 | )
32 |
33 | deal_id = db.Column(
34 | db.String,
35 | db.ForeignKey("deals.id")
36 | )
37 |
38 | task_id = db.Column(
39 | db.String,
40 | db.ForeignKey("tasks.id")
41 | )
42 |
43 | organization_id = db.Column(
44 | db.String,
45 | db.ForeignKey("organizations.id")
46 | )
47 |
48 | project_id = db.Column(
49 | db.String,
50 | db.ForeignKey("projects.id")
51 | )
52 |
53 | sprint_id = db.Column(
54 | db.String,
55 | db.ForeignKey("sprints.id")
56 | )
57 |
58 | company_id = db.Column(
59 | db.String,
60 | db.ForeignKey("companies.id")
61 | )
62 | event_id = db.Column(
63 | db.String,
64 | db.ForeignKey("events.id")
65 | )
66 | message_id = db.Column(
67 | db.String(),
68 | db.ForeignKey("messages.id")
69 | )
70 |
71 | # alert_id = db.Column(
72 | # db.String,
73 | # db.ForeignKey("alerts.id")
74 | # )
75 | #
76 | # alert_source_id = db.Column(
77 | # db.String,
78 | # db.ForeignKey("alertsources.id")
79 | # )
80 |
81 | comments = db.relationship(
82 | "Comment",
83 | backref="link"
84 | )
85 |
86 | def __str__(self):
87 | return self.filename or self.url
88 |
--------------------------------------------------------------------------------
/crm/apps/message/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/message/__init__.py
--------------------------------------------------------------------------------
/crm/apps/message/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/message/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/message/graphql/arguments.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene.types.inputobjecttype import InputObjectType
3 |
4 |
5 | class MessageArguments(InputObjectType):
6 | uid = graphene.String()
7 | title = graphene.String()
8 | content = graphene.String()
9 | channel = graphene.String()
10 | time_tosend = graphene.String()
11 | time_sent = graphene.String()
12 | author = graphene.Field('crm.apps.contact.graphql.arguments.ContactArguments')
13 | contact = graphene.Field('crm.apps.contact.graphql.arguments.ContactArguments')
14 | user = graphene.Field('crm.apps.user.graphql.arguments.UserArguments')
15 | deal = graphene.Field('crm.apps.deal.graphql.arguments.DealArguments')
16 | task = graphene.Field('crm.apps.task.graphql.arguments.TaskArguments')
17 | organization = graphene.Field('crm.apps.organization.graphql.arguments.OrganizationArguments')
18 | project = graphene.Field('crm.apps.project.graphql.arguments.ProjectArguments')
19 | sprint = graphene.Field('crm.apps.sprint.graphql.arguments.SprintArguments')
20 | event_id = graphene.Field('crm.apps.event.graphql.arguments.EventArguments')
21 | comments = graphene.List('crm.apps.comment.graphql.arguments.CommentArguments')
22 |
23 |
24 | class CreateMessageArguments(MessageArguments):
25 | title = graphene.String(required=True)
26 |
27 |
28 | class UpdateMessageArguments(MessageArguments):
29 | uid = graphene.String(requied=True)
--------------------------------------------------------------------------------
/crm/apps/message/graphql/mutations.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/message/graphql/mutations.py
--------------------------------------------------------------------------------
/crm/apps/message/graphql/queries.py:
--------------------------------------------------------------------------------
1 | import graphene
2 |
3 | from .types import MessageType
4 |
5 | from crm.graphql import BaseQuery
6 |
7 |
8 | class MessageQuery(BaseQuery):
9 | messages = graphene.List(MessageType)
10 |
11 | def resolve_messages(self, args, context, info):
12 | query = MessageType.get_query(context)
13 | return query.all()
14 |
--------------------------------------------------------------------------------
/crm/apps/message/graphql/types.py:
--------------------------------------------------------------------------------
1 | from graphene import relay
2 |
3 | from graphene_sqlalchemy import SQLAlchemyObjectType
4 |
5 | from crm.apps.message.models import Message
6 |
7 |
8 | class MessageType(SQLAlchemyObjectType):
9 |
10 | class Meta:
11 | model = Message
12 | interfaces = (relay.Node,)
13 | name = model.__name__
14 |
--------------------------------------------------------------------------------
/crm/apps/organization/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/organization/__init__.py
--------------------------------------------------------------------------------
/crm/apps/organization/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/organization/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/organization/graphql/arguments.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene.types.inputobjecttype import InputObjectType
3 |
4 |
5 | class OrganizationArguments(InputObjectType):
6 | uid = graphene.String()
7 | name = graphene.String()
8 | description = graphene.String()
9 | emails = graphene.String()
10 | telephones = graphene.String()
11 | tasks = graphene.List('crm.apps.task.graphql.arguments.TaskArguments')
12 | comments = graphene.List('crm.apps.comment.graphql.arguments.CommentArguments')
13 | messages = graphene.List('crm.apps.message.graphql.arguments.MessageArguments')
14 | users = graphene.List('crm.apps.user.graphql.arguments.UserArguments')
15 | links = graphene.List('crm.apps.link.graphql.arguments.LinkArguments')
16 | owner_id = graphene.String()
17 | parent_id = graphene.String()
18 |
19 |
20 | class CreateOrganizationArguments(OrganizationArguments):
21 | name = graphene.String(required=True)
22 |
23 |
24 | class UpdateOrganizationArguments(OrganizationArguments):
25 | uid = graphene.String(required=True)
26 |
--------------------------------------------------------------------------------
/crm/apps/organization/graphql/mutations.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/organization/graphql/mutations.py
--------------------------------------------------------------------------------
/crm/apps/organization/graphql/queries.py:
--------------------------------------------------------------------------------
1 | import graphene
2 |
3 | from .types import OrganizationType
4 | from crm.graphql import BaseQuery
5 |
6 |
7 | class OrganizationQuery(BaseQuery):
8 | organizations = graphene.List(OrganizationType)
9 |
10 | def resolve_organizations(self, args, context, info):
11 | query = OrganizationType.get_query(context)
12 | return query.all()
13 |
--------------------------------------------------------------------------------
/crm/apps/organization/graphql/types.py:
--------------------------------------------------------------------------------
1 | from graphene import relay
2 | from graphene_sqlalchemy import SQLAlchemyObjectType
3 |
4 | from crm.apps.organization.models import Organization
5 |
6 |
7 | class OrganizationType(SQLAlchemyObjectType):
8 |
9 | class Meta:
10 | model = Organization
11 | interfaces = (relay.Node,)
12 | name = model.__name__
13 |
--------------------------------------------------------------------------------
/crm/apps/organization/models.py:
--------------------------------------------------------------------------------
1 | from crm.db import db, BaseModel, RootModel
2 | from crm.mailer import sendemail
3 |
4 |
5 | class Organization(db.Model, BaseModel, RootModel):
6 |
7 | __tablename__ = "organizations"
8 |
9 | name = db.Column(
10 | db.String(255),
11 | nullable=False,
12 | index=True
13 | )
14 |
15 | # should be markdown
16 | description = db.Column(
17 | db.Text(),
18 | default="",
19 | index=True
20 | )
21 |
22 | emails = db.relationship(
23 | 'Email',
24 | backref='organization',
25 | primaryjoin="Organization.id==Email.organization_id"
26 | )
27 |
28 | tasks = db.relationship(
29 | "Task",
30 | backref="organization"
31 | )
32 |
33 | comments = db.relationship(
34 | "Comment",
35 | backref="organization"
36 | )
37 |
38 | users = db.relationship(
39 | "User",
40 | secondary="users_organizations",
41 | secondaryjoin="User.id==UsersOrganizations.user_id",
42 | backref="organizations"
43 | )
44 |
45 | links = db.relationship(
46 | "Link",
47 | backref="organization"
48 | )
49 |
50 | messages = db.relationship(
51 | "Message",
52 | backref="organization"
53 | )
54 |
55 | owner_id = db.Column(
56 | db.String(5),
57 | db.ForeignKey('users.id')
58 | )
59 |
60 | parent_id = db.Column(
61 | db.String(5),
62 | db.ForeignKey("organizations.id")
63 | )
64 |
65 | @property
66 | def notification_emails(self):
67 | """
68 | :return: list of all emails to send notifications to
69 | :rtype: list
70 | """
71 | return [e.email for e in self.emails]
72 |
73 | def __str__(self):
74 | return self.name
75 |
--------------------------------------------------------------------------------
/crm/apps/passport/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/passport/__init__.py
--------------------------------------------------------------------------------
/crm/apps/passport/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/passport/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/passport/graphql/arguments.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene.types.inputobjecttype import InputObjectType
3 |
4 | from crm.apps.country.graphql.arguments import CountryArguments
5 |
6 |
7 | class PassportArguments(InputObjectType):
8 | passport_fullname = graphene.String()
9 | passport_number = graphene.String()
10 | issuance_date = graphene.String()
11 | expiration_date = graphene.String()
12 | country = graphene.Argument(CountryArguments)
13 | contact = graphene.Argument('crm.apps.contact.graphql.arguments.ContactArguments')
14 | contact_id = graphene.String()
15 |
16 |
17 | class CreateContactArguments(PassportArguments):
18 | passport_fullname = graphene.String(required=True)
19 | passport_number = graphene.String(required=True)
20 | issuance_date = graphene.String(required=True)
21 | expiration_date = graphene.String(required=True)
22 |
23 |
24 | class UpdateContactArguments(PassportArguments):
25 | uid = graphene.String(required=True)
26 |
--------------------------------------------------------------------------------
/crm/apps/passport/graphql/mutations.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/passport/graphql/mutations.py
--------------------------------------------------------------------------------
/crm/apps/passport/graphql/queries.py:
--------------------------------------------------------------------------------
1 | import graphene
2 |
3 | from .types import PassportType
4 | from crm.graphql import BaseQuery
5 |
6 |
7 | class PassportQuery(BaseQuery):
8 | passports = graphene.List(PassportType)
9 |
10 | def resolve_passports(self, args, context, info):
11 | query = PassportType.get_query(context)
12 | return query.all()
13 |
--------------------------------------------------------------------------------
/crm/apps/passport/graphql/types.py:
--------------------------------------------------------------------------------
1 | from graphene import relay
2 |
3 | from graphene_sqlalchemy import SQLAlchemyObjectType
4 |
5 | from crm.apps.passport.models import Passport
6 | from crm.graphql import CrmType
7 |
8 |
9 | class PassportType(CrmType, SQLAlchemyObjectType):
10 |
11 | class Meta:
12 | model = Passport
13 | interfaces = (relay.Node,)
14 | name = model.__name__
15 |
16 | # CrmType adds author_original, author_last objects rather than ids
17 | exclude_fields = (
18 | 'author_original_id',
19 | 'author_last_id'
20 | )
21 |
22 |
--------------------------------------------------------------------------------
/crm/apps/passport/models.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | from crm.db import db, BaseModel
3 |
4 |
5 | class Passport(db.Model, BaseModel):
6 |
7 | __tablename__ = "passports"
8 |
9 | passport_fullname = db.Column(
10 | db.String(255),
11 | nullable=False,
12 | index=True
13 | )
14 |
15 | passport_number = db.Column(
16 | db.Text(),
17 | index=True,
18 | nullable=False
19 | )
20 |
21 | issuance_date = db.Column(
22 | db.Date(),
23 | default=datetime.date(1990, 1, 1),
24 | nullable=False
25 | )
26 | expiration_date = db.Column(
27 | db.Date(),
28 | default=datetime.date(2020, 1, 1),
29 | nullable=False
30 | )
31 | country_id = db.Column(
32 | db.String(5),
33 | db.ForeignKey('countries.id')
34 | )
35 |
36 | contact_id = db.Column(
37 | db.String(5),
38 | db.ForeignKey('contacts.id')
39 | )
40 |
41 | def __str__(self):
42 | return "Passport {}".format(self.passport_fullname)
43 |
--------------------------------------------------------------------------------
/crm/apps/phone/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/phone/__init__.py
--------------------------------------------------------------------------------
/crm/apps/phone/models.py:
--------------------------------------------------------------------------------
1 | from crm import db, BaseModel
2 |
3 |
4 | class Phone(db.Model, BaseModel):
5 | __tablename__ = "phones"
6 |
7 | telephone = db.Column(
8 | db.String(255),
9 | index=True,
10 | nullable=False,
11 | )
12 |
13 | user_id = db.Column(
14 | db.String,
15 | db.ForeignKey("users.id")
16 | )
17 |
18 | contact_id = db.Column(
19 | db.String,
20 | db.ForeignKey("contacts.id")
21 | )
22 |
23 | company_id = db.Column(
24 | db.String,
25 | db.ForeignKey("companies.id")
26 | )
27 |
28 | def __str__(self):
29 | return self.telephone
30 |
31 |
--------------------------------------------------------------------------------
/crm/apps/project/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/project/__init__.py
--------------------------------------------------------------------------------
/crm/apps/project/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/project/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/project/graphql/arguments.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene.types.inputobjecttype import InputObjectType
3 |
4 |
5 | class ProjectArguments(InputObjectType):
6 | uid = graphene.String()
7 | name = graphene.String()
8 | description = graphene.String()
9 | start_date = graphene.String()
10 | deadline = graphene.String()
11 |
12 | comments = graphene.List('crm.apps.comment.graphql.arguments.CommentArguments')
13 | messages = graphene.List('crm.apps.message.graphql.arguments.MessageArguments')
14 | links = graphene.List('crm.apps.link.graphql.arguments.LinkArguments')
15 | tasks = graphene.List('crm.apps.task.graphql.arguments.TaskArguments')
16 | sprints = graphene.List('crm.apps.sprint.graphql.arguments.SprintArguments')
17 | contacts = graphene.List('crm.apps.contact.graphql.arguments.ContactArguments')
18 | promoter_id = graphene.String()
19 | guardian_id = graphene.String()
20 |
21 |
22 | class CreateProjectArguments(ProjectArguments):
23 | name = graphene.String(required=True)
24 |
25 |
26 | class UpdateProjectArguments(ProjectArguments):
27 | uid = graphene.String(requied=True)
28 |
--------------------------------------------------------------------------------
/crm/apps/project/graphql/mutations.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/project/graphql/mutations.py
--------------------------------------------------------------------------------
/crm/apps/project/graphql/queries.py:
--------------------------------------------------------------------------------
1 | import graphene
2 |
3 | from .types import ProjectType
4 |
5 | from crm.graphql import BaseQuery
6 |
7 |
8 | class ProjectQuery(BaseQuery):
9 | projects = graphene.List(ProjectType)
10 |
11 | def resolve_projects(self, args, context, info):
12 | query = ProjectType.get_query(context)
13 | return query.all()
14 |
--------------------------------------------------------------------------------
/crm/apps/project/graphql/types.py:
--------------------------------------------------------------------------------
1 | from graphene import relay
2 |
3 | from graphene_sqlalchemy import SQLAlchemyObjectType
4 |
5 | from crm.apps.project.models import Project
6 |
7 |
8 | class ProjectType(SQLAlchemyObjectType):
9 |
10 | class Meta:
11 | model = Project
12 | interfaces = (relay.Node,)
13 | name = model.__name__
14 |
--------------------------------------------------------------------------------
/crm/apps/reports/views.py:
--------------------------------------------------------------------------------
1 | from flask_admin import BaseView, Admin
2 |
3 |
4 | from crm import app
5 |
6 |
7 | class ReportsAdminView(BaseView):
8 | def __init__(self, *args, **kwargs):
9 | self._default_view = True
10 | super(ReportsAdminView, self).__init__(*args, **kwargs)
11 | self.admin = app.admin
12 |
13 |
14 | @app.route('/reports', methods=["GET"])
15 | def reports():
16 | return ReportsAdminView().render('reports/reports.html'), 200
17 |
--------------------------------------------------------------------------------
/crm/apps/sprint/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/sprint/__init__.py
--------------------------------------------------------------------------------
/crm/apps/sprint/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/sprint/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/sprint/graphql/arguments.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene.types.inputobjecttype import InputObjectType
3 |
4 |
5 | class SprintArguments(InputObjectType):
6 | uid = graphene.String()
7 | name = graphene.String()
8 | description = graphene.String()
9 | start_date = graphene.String()
10 | deadline = graphene.String()
11 | contacts = graphene.List('crm.apps.contact.graphql.arguments.ContactArguments')
12 | tasks = graphene.List('crm.apps.task.graphql.arguments.TaskArguments')
13 | comments = graphene.List('crm.apps.comment.graphql.arguments.CommentArguments')
14 | messages = graphene.List('crm.apps.message.graphql.arguments.MessageArguments')
15 | links = graphene.List('crm.apps.link.graphql.arguments.LinkArguments')
16 | owner_id = graphene.String()
17 | project_id = graphene.String()
18 |
19 |
20 | class CreateSprintArguments(SprintArguments):
21 | name = graphene.String(required=True)
22 |
23 |
24 | class UpdateSprintArguments(SprintArguments):
25 | uid = graphene.String(required=True)
26 |
--------------------------------------------------------------------------------
/crm/apps/sprint/graphql/mutations.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/sprint/graphql/mutations.py
--------------------------------------------------------------------------------
/crm/apps/sprint/graphql/queries.py:
--------------------------------------------------------------------------------
1 | import graphene
2 |
3 | from .types import SprintType
4 |
5 | from crm.graphql import BaseQuery
6 |
7 |
8 | class SprintQuery(BaseQuery):
9 | sprints = graphene.List(SprintType)
10 |
11 | def resolve_sprints(self, args, context, info):
12 | query = SprintType.get_query(context)
13 | return query.all()
14 |
--------------------------------------------------------------------------------
/crm/apps/sprint/graphql/types.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene import relay
3 |
4 | from graphene_sqlalchemy import SQLAlchemyObjectType
5 |
6 | from crm.apps.sprint.models import Sprint
7 |
8 |
9 | class SprintType(SQLAlchemyObjectType):
10 |
11 | class Meta:
12 | model = Sprint
13 | interfaces = (relay.Node,)
14 | name = model.__name__
15 |
16 |
--------------------------------------------------------------------------------
/crm/apps/sprint/models.py:
--------------------------------------------------------------------------------
1 | from crm.db import db, BaseModel, RootModel
2 | from crm.mailer import sendemail
3 |
4 |
5 | class Sprint(db.Model, BaseModel, RootModel):
6 |
7 | __tablename__ = "sprints"
8 |
9 | name = db.Column(
10 | db.String(255),
11 | nullable=False,
12 | index=True
13 | )
14 |
15 | # should be markdown.
16 | description = db.Column(
17 | db.Text(),
18 | default="",
19 | index=True
20 | )
21 |
22 | start_date = db.Column(
23 | db.TIMESTAMP,
24 | index=True
25 | )
26 |
27 | deadline = db.Column(
28 | db.TIMESTAMP,
29 | index=True
30 | )
31 |
32 | contacts = db.relationship(
33 | "Contact",
34 | secondary="contacts_sprints",
35 | backref="sprints"
36 | )
37 |
38 | tasks = db.relationship(
39 | "Task",
40 | backref="sprint"
41 | )
42 |
43 | comments = db.relationship(
44 | "Comment",
45 | backref="sprint"
46 | )
47 |
48 | links = db.relationship(
49 | "Link",
50 | backref="sprint"
51 | )
52 |
53 | messages = db.relationship(
54 | "Message",
55 | backref="sprint"
56 | )
57 |
58 | owner_id = db.Column(
59 | db.String(5),
60 | db.ForeignKey('users.id')
61 | )
62 |
63 | project_id = db.Column(
64 | db.String(5),
65 | db.ForeignKey('projects.id')
66 | )
67 |
68 | @property
69 | def notification_emails(self):
70 | """
71 | :return: list of all emails to send notifications to
72 | :rtype: list
73 | """
74 | emails = []
75 |
76 | if self.contacts:
77 | for contact in self.contacts:
78 | if contact.notification_emails:
79 | emails.extend(contact.notification_emails)
80 |
81 | if self.owner:
82 | if self.owner.notification_emails:
83 | emails.extend(self.owner.notification_emails)
84 |
85 | if self.tasks:
86 | for task in self.tasks:
87 | if task.notification_emails:
88 | emails.extend(task.notification_emails)
89 |
90 | return list(set(emails))
91 |
92 | @property
93 | def percentage_done(self):
94 | pass
95 |
96 | @property
97 | def hours_open(self):
98 | pass
99 |
100 | @property
101 | def hours_open_person_avg(self):
102 | pass
103 |
104 | @property
105 | def hours_open_person_max(self):
106 | pass
107 |
108 | def __str__(self):
109 | return self.name
110 |
--------------------------------------------------------------------------------
/crm/apps/tag/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/tag/__init__.py
--------------------------------------------------------------------------------
/crm/apps/tag/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/tag/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/tag/graphql/arguments.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene.types.inputobjecttype import InputObjectType
3 |
4 |
5 | class TagArguments(InputObjectType):
6 | tag = graphene.String()
7 |
8 |
--------------------------------------------------------------------------------
/crm/apps/tag/graphql/mutations.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/tag/graphql/mutations.py
--------------------------------------------------------------------------------
/crm/apps/tag/graphql/queries.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/tag/graphql/queries.py
--------------------------------------------------------------------------------
/crm/apps/tag/graphql/types.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/tag/graphql/types.py
--------------------------------------------------------------------------------
/crm/apps/tag/models.py:
--------------------------------------------------------------------------------
1 | from crm.db import db, BaseModel
2 |
3 |
4 | class Tag(db.Model, BaseModel):
5 | """
6 | Model for any kind of tags
7 | """
8 |
9 | __tablename__ = "tags"
10 |
11 | tag = db.Column(
12 | db.String(),
13 | nullable=False,
14 | unique=True,
15 | index=True
16 | )
17 |
18 | companies = db.relationship(
19 | "Company",
20 | secondary="companies_tags",
21 | backref="tags"
22 | )
23 |
24 | def __str__(self):
25 | return self.tag
--------------------------------------------------------------------------------
/crm/apps/task/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/task/__init__.py
--------------------------------------------------------------------------------
/crm/apps/task/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/task/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/task/graphql/arguments.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene.types.inputobjecttype import InputObjectType
3 |
4 | from crm.apps.task.models import TaskType, TaskPriority, TaskState
5 |
6 |
7 | class TaskArguments(InputObjectType):
8 | uid = graphene.String()
9 | title = graphene.String()
10 | description = graphene.String()
11 | type = graphene.Enum.from_enum(TaskType)
12 | priority = graphene.Enum.from_enum(TaskPriority)
13 | state = graphene.Enum.from_enum(TaskState)
14 |
15 | assignee = graphene.Field('crm.apps.user.graphql.arguments.UserArguments')
16 | deadline = graphene.String()
17 | eta = graphene.String()
18 | time_estimate = graphene.Int()
19 | time_done = graphene.Int()
20 | company = graphene.Field('crm.apps.company.graphql.arguments.CompanyArguments')
21 | contact = graphene.Field('crm.apps.contact.graphql.arguments.ContactArguments')
22 | user = graphene.Field('crm.apps.user.graphql.arguments.UserArguments')
23 | deal = graphene.Field('crm.apps.deal.graphql.arguments.DealArguments')
24 | organization = graphene.Field('crm.apps.organization.graphql.arguments.OrganizationArguments')
25 | project = graphene.Field('crm.apps.project.graphql.arguments.ProjectArguments')
26 | sprint = graphene.Field('crm.apps.sprint.graphql.arguments.SprintArguments')
27 | event = graphene.Field('crm.apps.event.graphql.arguments.EventArguments')
28 | comments = graphene.List('crm.apps.comment.graphql.arguments.CommentArguments')
29 | messages = graphene.List('crm.apps.message.graphql.arguments.MessageArguments')
30 | links = graphene.List('crm.apps.link.graphql.arguments.LinkArguments')
31 |
32 |
33 | class CreateTaskArguments(TaskArguments):
34 | title = graphene.String(required=True)
35 |
36 |
37 | class UpdateTaskArguments(TaskArguments):
38 | uid = graphene.String(requied=True)
--------------------------------------------------------------------------------
/crm/apps/task/graphql/mutations.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/task/graphql/mutations.py
--------------------------------------------------------------------------------
/crm/apps/task/graphql/queries.py:
--------------------------------------------------------------------------------
1 | import graphene
2 |
3 | from .types import TaskType
4 |
5 | from crm.graphql import BaseQuery
6 |
7 |
8 | class TaskQuery(BaseQuery):
9 | tasks = graphene.List(TaskType)
10 |
11 | def resolve_tasks(self, args, context, info):
12 | query = TaskType.get_query(context)
13 | return query.all()
14 |
--------------------------------------------------------------------------------
/crm/apps/task/graphql/types.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene import relay
3 |
4 | from graphene_sqlalchemy import SQLAlchemyObjectType
5 |
6 | from crm.apps.task.models import Task
7 |
8 |
9 | class TaskType(SQLAlchemyObjectType):
10 |
11 | uid = graphene.String()
12 |
13 | class Meta:
14 | model = Task
15 | interfaces = (relay.Node,)
16 | name = model.__name__
--------------------------------------------------------------------------------
/crm/apps/user/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/user/__init__.py
--------------------------------------------------------------------------------
/crm/apps/user/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/user/graphql/__init__.py
--------------------------------------------------------------------------------
/crm/apps/user/graphql/arguments.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene.types.inputobjecttype import InputObjectType
3 |
4 |
5 | class UserArguments(InputObjectType):
6 | uid = graphene.String()
7 | username = graphene.String()
8 | firstname = graphene.String()
9 | lastname = graphene.String()
10 | description = graphene.String()
11 | message_channels = graphene.String()
12 | emails = graphene.String()
13 | telephones = graphene.String()
14 | tasks = graphene.List('crm.apps.task.graphql.arguments.TaskArguments')
15 | owns_tasks = graphene.List('crm.apps.task.graphql.arguments.TaskArguments')
16 | comments = graphene.List('crm.apps.comment.graphql.arguments.CommentArguments')
17 | messages = graphene.List('crm.apps.message.graphql.arguments.MessageArguments')
18 | links = graphene.List('crm.apps.link.graphql.arguments.LinkArguments')
19 | owns_contacts = graphene.List('crm.apps.contact.graphql.arguments.ContactArguments')
20 | owns_backup_contacts = graphene.List('crm.apps.contact.graphql.arguments.ContactArguments')
21 | owns_companies = graphene.List('crm.apps.company.graphql.arguments.CompanyArguments')
22 | owns_backup_companies = graphene.List('crm.apps.company.graphql.arguments.CompanyArguments')
23 | owns_organizations = graphene.List('crm.apps.organization.graphql.arguments.OrganizationArguments')
24 | owns_sprints = graphene.List('crm.apps.sprint.graphql.arguments.SprintArguments')
25 | promoter_projects = graphene.List('crm.apps.project.graphql.arguments.ProjectArguments')
26 | guardian_projects = graphene.List('crm.apps.project.graphql.arguments.ProjectArguments')
27 |
28 |
29 | class CreateUserArguments(UserArguments):
30 | username = graphene.String(required=True)
31 |
32 |
33 | class UpdateUserArguments(UserArguments):
34 | uid = graphene.String(requied=True)
--------------------------------------------------------------------------------
/crm/apps/user/graphql/mutations.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/apps/user/graphql/mutations.py
--------------------------------------------------------------------------------
/crm/apps/user/graphql/queries.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from .types import UserType
3 |
4 | from crm.graphql import BaseQuery
5 |
6 |
7 | class UserQuery(BaseQuery):
8 | users = graphene.List(UserType)
9 |
10 | def resolve_users(self, args, context, info):
11 | query = UserType.get_query(context)
12 | return query.all()
13 |
--------------------------------------------------------------------------------
/crm/apps/user/graphql/types.py:
--------------------------------------------------------------------------------
1 | from graphene import relay
2 |
3 | from graphene_sqlalchemy import SQLAlchemyObjectType
4 |
5 | from crm.apps.user.models import User
6 | from crm.graphql import CrmType
7 |
8 |
9 | class UserType(CrmType, SQLAlchemyObjectType):
10 |
11 | class Meta:
12 | model = User
13 | interfaces = (relay.Node,)
14 | name = model.__name__
15 | exclude_fields = (
16 | 'author_original_id',
17 | 'author_last_id'
18 | )
19 |
--------------------------------------------------------------------------------
/crm/apps/user/tasks.py:
--------------------------------------------------------------------------------
1 | # RQ tasks
2 |
3 |
4 | def update_last_login_time(user_id, time):
5 | """
6 | Update last login time.
7 | Used in iyo middleware
8 | :param user_id: user ID
9 | :param time: last login time
10 | """
11 | from crm.db import db
12 | from .models import User
13 |
14 | u = User.query.filter_by(id=user_id).first()
15 | u.last_login = time
16 | db.session.add(u)
17 | db.session.commit()
18 |
19 |
--------------------------------------------------------------------------------
/crm/cli/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Import and expose all modules in this package.
3 | Now any added module/command will be exposed automatically
4 | """
5 |
6 | import pkgutil
7 | import inspect
8 |
9 | __all__ = []
10 |
11 | for loader, name, is_pkg in pkgutil.walk_packages(__path__):
12 | module = loader.find_module(name).load_module(name)
13 |
14 | for name, value in inspect.getmembers(module):
15 | if name.startswith('__'):
16 | continue
17 |
18 | globals()[name] = value
19 | __all__.append(name)
20 |
--------------------------------------------------------------------------------
/crm/cli/createdb.py:
--------------------------------------------------------------------------------
1 | from crm import app
2 |
3 | from sqlalchemy_utils import create_database, database_exists
4 |
5 |
6 | @app.cli.command()
7 | def createdb():
8 | """
9 | Create DB
10 | """
11 | if not database_exists(app.config['SQLALCHEMY_DATABASE_URI']):
12 | create_database(app.config['SQLALCHEMY_DATABASE_URI'])
13 | print("DB created.")
14 |
--------------------------------------------------------------------------------
/crm/cli/dumpdata.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import ujson as json
4 |
5 | from crm import app
6 | from crm.db import RootModel
7 |
8 |
9 | @app.cli.command()
10 | def dumpdata():
11 | """
12 | Dump data table models into filesystem.
13 | Only Root models are dumped
14 | 'Company', 'Contact', 'Deal',
15 | 'Sprint', 'Project', 'Organization','User'
16 | """
17 | data_dir = app.config["DATA_DIR"]
18 | if not os.path.exists(data_dir):
19 | os.makedirs(data_dir)
20 |
21 | for model in RootModel.__subclasses__():
22 | model_dir = os.path.abspath(os.path.join(data_dir, model.__name__))
23 | if not os.path.exists(model_dir):
24 | os.mkdir(model_dir)
25 |
26 | for obj in model.query.all():
27 | obj_as_str = str(obj).replace('/', '_')
28 | if len(obj_as_str) > 100:
29 | obj_as_str = obj_as_str[:100]
30 |
31 | record_path = os.path.abspath(os.path.join(
32 | model_dir, '%s_%s.json' % (obj.id, obj_as_str)))
33 | data = obj.as_dict()
34 | with open(record_path, 'w') as f:
35 | json.dump(data, f, indent=4, sort_keys=True)
36 |
--------------------------------------------------------------------------------
/crm/cli/generate_graphql_docs.py:
--------------------------------------------------------------------------------
1 | from subprocess import Popen, PIPE
2 |
3 | from crm import app
4 |
5 |
6 | @app.cli.command()
7 | def generate_graphql_docs():
8 | """
9 | Generates schema.graphql IDL file and the GraphQL API documentation for queries and mutations.
10 |
11 | requires graphdoc to be installed.
12 |
13 | """
14 | from crm import app
15 | sc = app.graphql_schema
16 |
17 | with open('./schema.graphql', "w") as f:
18 | f.write(str(sc))
19 |
20 | p = Popen(['graphdoc', '--force', '-s', './schema.graphql', '-o',
21 | 'docs/graphqlapi'], stdout=PIPE, stderr=PIPE)
22 |
23 | p.communicate()[0]
24 |
25 | if p.returncode != 0:
26 | print("Failed to generate graphqlapi docs.")
27 | exit(1)
28 |
--------------------------------------------------------------------------------
/crm/cli/import_data.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/crm/cli/import_data.py
--------------------------------------------------------------------------------
/crm/cli/load.py:
--------------------------------------------------------------------------------
1 | from crm import app
2 | from crm.apps.contact.models import SubgroupName, Subgroup, ActivityType, Activity
3 | from crm.apps.currency.models import Currency
4 | from crm.db import db
5 | from crm.apps.country.countries import CountriesEnum
6 | from crm.apps.country.models import Country
7 |
8 |
9 | @app.cli.command()
10 | def load():
11 | """
12 | Add missing enum data to tables that use these enums
13 | i.e contact.countries, contact.subgroups
14 | """
15 | for country in CountriesEnum:
16 | c = Country.query.filter_by(name=country).first()
17 | if c is not None:
18 | continue
19 | c = Country(name=country)
20 | db.session.add(c)
21 |
22 | for subgroup in SubgroupName:
23 | s = Subgroup.query.filter_by(groupname=subgroup).first()
24 | if s is not None:
25 | continue
26 | s = Subgroup(groupname=subgroup)
27 | db.session.add(s)
28 |
29 | for activity in ActivityType:
30 | a = Activity.query.filter_by(type=activity).first()
31 | if a is not None:
32 | continue
33 | a = Activity(type=activity)
34 | db.session.add(a)
35 |
36 | for currency in ['USD', 'EUR', 'AED', 'GBP', 'BTC']:
37 | c = Currency.query.filter_by(name=currency).first()
38 | if c is not None:
39 | continue
40 | c = Currency(name=currency)
41 | db.session.add(c)
42 |
43 | db.session.commit()
--------------------------------------------------------------------------------
/crm/cli/loadfixtures.py:
--------------------------------------------------------------------------------
1 | from crm import app
2 | from crm.fixtures import generate_fixtures
3 |
4 |
5 | @app.cli.command()
6 | def loadfixtures():
7 | """
8 | populate DB with Test/Random Data
9 | """
10 | generate_fixtures()
11 |
--------------------------------------------------------------------------------
/crm/cli/rq_worker.py:
--------------------------------------------------------------------------------
1 | from rq import Queue, Connection, Worker
2 | from crm import app
3 | from crm.rq import conn, redis_url
4 |
5 |
6 | @app.cli.command()
7 | def rq_worker():
8 | listen = ['default']
9 |
10 | print('Connecting to redis on %s' % redis_url)
11 | with Connection(conn):
12 | worker = Worker(list(map(Queue, listen)))
13 | worker.work()
--------------------------------------------------------------------------------
/crm/cli/syncdata.py:
--------------------------------------------------------------------------------
1 | import time
2 | import os
3 | import subprocess
4 |
5 | from crm import app
6 | from crm.cli.dumpcache import _dump
7 |
8 |
9 | def _push(number_commits):
10 | print('\n\t\t\033[94mPUSHING %d COMMITS(s) TO REMOTE REPO\033[0m\n' % number_commits)
11 | data_dir = app.config["DATA_DIR"]
12 | dot_git = os.path.abspath(os.path.join(data_dir, '.git'))
13 |
14 | p = subprocess.Popen(
15 | [
16 | 'git',
17 | '--git-dir=%s' % dot_git,
18 | '--work-tree=%s' % os.path.abspath(data_dir),
19 | 'push'
20 | ]
21 | )
22 |
23 | out1, out2 = p.communicate()
24 |
25 | if not p.returncode == 0:
26 | print('Error committing files to git')
27 | print(out1, out2)
28 | print('\n\t\t\033[92mPUSH SUCCESSFUL\033[0m\n')
29 |
30 |
31 | @app.cli.command()
32 | def syncdata():
33 | """
34 | Sync Cached DB changes in Redis to file system (DATA_DIR)
35 | Push changes.
36 | """
37 | while True:
38 | dumped_keys_length = _dump()
39 | if dumped_keys_length > 0:
40 | _push(dumped_keys_length)
41 | print('\t\tWAITING')
42 | time.sleep(5)
43 |
--------------------------------------------------------------------------------
/crm/cli/update_currency.py:
--------------------------------------------------------------------------------
1 | import time
2 | import datetime
3 | import requests
4 |
5 | from crm import app
6 | from crm.apps.currency.models import Currency
7 | from crm.db import db
8 |
9 | session = requests.Session()
10 |
11 |
12 | @app.cli.command()
13 | def update_currency_rates():
14 | """
15 | Updates Currencies exchange rates to USD
16 | """
17 | while True:
18 | res = session.get('http://apilayer.net/api/live?access_key=955ebff5d2c404fcfb383b587a02a97b¤cies=AED,GBP,EUR,BTC&format=1')
19 | if res.status_code != 200:
20 | print('error getting currency rates : ', res.content)
21 | time.sleep(21600)
22 | continue
23 |
24 | res = res.json()['quotes']
25 | AED = round(1.0/res['USDAED'], 2)
26 | GBP = round(1.0/res['USDGBP'], 2)
27 | EUR = round(1.0/res['USDEUR'], 2)
28 | BTC = round(1.0/res['USDBTC'], 2)
29 |
30 |
31 | Currency.query.filter_by(name='AED').update({'value_usd': AED})
32 | Currency.query.filter_by(name='GBP').update({'value_usd': GBP})
33 | Currency.query.filter_by(name='EUR').update({'value_usd': EUR})
34 | Currency.query.filter_by(name='BTC').update({'value_usd': BTC})
35 | print('Last update : ', datetime.datetime.now())
36 | db.session.commit()
37 | print('Now sleeping for 6 hours')
38 | time.sleep(21600)
39 |
--------------------------------------------------------------------------------
/crm/events/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | THIS PACKAGE IS THE PLACE FOR SQLALCHEMY EVENT HANDLERS
4 |
5 | """
6 |
7 | # Import and expose all modules in this package.
8 | # Now any added module/command will be exposed automatically
9 |
10 | import pkgutil
11 | import inspect
12 |
13 |
14 | __all__ = []
15 |
16 | for loader, name, is_pkg in pkgutil.walk_packages(__path__):
17 | module = loader.find_module(name).load_module(name)
18 |
19 | for name, value in inspect.getmembers(module):
20 | if name.startswith('__'):
21 | continue
22 |
23 | globals()[name] = value
24 | __all__.append(name)
25 |
--------------------------------------------------------------------------------
/crm/events/catch_db_updates.py:
--------------------------------------------------------------------------------
1 | # from sqlalchemy.event import listen
2 | #
3 | # from crm.db import db
4 | #
5 | #
6 | # def catch_db_updates_after_flush(db_session, flush_context):
7 | # """
8 | # WARNING
9 | # ----------------------------------------------------------
10 | # THIS FUNCTION IS CALLED SEVERAL TIMES.
11 | # WHATEVER IS PUT HERE SHOULD BE IDEMPOTENT OPERATIONS ONLY
12 | # ----------------------------------------------------------
13 | #
14 | #
15 | #
16 | # :param db_session: DB session
17 | # :param flush_context: Internal UOWTransaction object which handles the details of the flush.
18 | # """
19 | #
20 | # # db_session.dirty, db_session.created, db_session.new
21 | # # are vanished during (after_commit) & (after_transaction_end) events
22 | # # we cache these values in db_session.info['changes'] so we can process them later
23 | # # after data is written to DB
24 | #
25 | # # at this point of time we can't call model.as_dict()
26 | # # and cache this value, because object is not saved in db yet
27 | # # so we save it in temp location db_session.info['changes']
28 | # # we catch these cached values on another event (after transaction completes)
29 | # # we process them there
30 | #
31 | # db_session.info['changes'] = {
32 | # 'updated': db_session.dirty,
33 | # 'created': db_session.new,
34 | # 'deleted': db_session.deleted
35 | # }
36 | #
37 | # listen(db.session, 'after_flush', catch_db_updates_after_flush)
38 |
--------------------------------------------------------------------------------
/crm/events/notify_new_task.py:
--------------------------------------------------------------------------------
1 | import os
2 | from sqlalchemy import event
3 | from flask import request
4 |
5 | from crm.apps.message.models import Message
6 | from crm.apps.task.models import Task
7 |
8 |
9 | @event.listens_for(Task, 'after_insert')
10 | def receive_after_insert(mapper, connection, task):
11 | if request:
12 | url_root = request.url_root.strip('/')
13 | else:
14 | host = os.getenv('DOMAIN')
15 | if not host:
16 | print('Missing DOMAIN env variable. emails are not going to be sent')
17 | return
18 | url_root = 'https://{}'.format(host)
19 |
20 | if task.assignee:
21 | msg = Message(
22 | title='You have a new assigned task',
23 | content='Title: {} Description:{} Task Link: {} '.format(task.title, task.description, url_root + task.admin_view_link(), task),
24 | user=task.assignee,
25 | task=task,
26 | forced_destinations=','.join([e.email for e in task.assignee.emails])
27 | )
28 |
29 | message_tbl = Message.__table__
30 | # Create a message for assignee only that he/she has new task
31 | connection.execute(
32 | message_tbl.insert().
33 | values(
34 | id=msg.uid,
35 | title=msg.title,
36 | content=msg.content,
37 | user_id=msg.user.id
38 | )
39 | )
40 |
41 |
42 | @event.listens_for(Task, 'after_update')
43 | def receive_after_update(mapper, connection, task):
44 | if request:
45 | url_root = request.url_root.strip('/')
46 | else:
47 | host = os.getenv('DOMAIN')
48 | if not host:
49 | print('Missing DOMAIN env variable. emails are not going to be sent')
50 | return
51 | url_root = 'https://{}'.format(host)
52 |
53 | if task.assignee:
54 | msg = Message(
55 | title='Your assigned task has been updated',
56 | content='Title: {} Description:{} Task Link: {} '.format(task.title, task.description, url_root + task.admin_view_link(), task),
57 | user=task.assignee,
58 | task=task,
59 | forced_destinations=','.join([e.email for e in task.assignee.emails])
60 | )
61 |
62 | message_tbl = Message.__table__
63 | # Create a message for assignee only that he/she has new task
64 | connection.execute(
65 | message_tbl.insert().
66 | values(
67 | id=msg.uid,
68 | title=msg.title,
69 | content=msg.content,
70 | user_id=msg.user.id
71 | )
72 | )
73 |
--------------------------------------------------------------------------------
/crm/events/update_auto_fields.py:
--------------------------------------------------------------------------------
1 | from crm.db import db
2 |
3 | from sqlalchemy.event import listen
4 |
5 |
6 | def update_auto_fields_before_flush(db_session, flush_context, instances):
7 | """
8 | WARNING
9 | ----------------------------------------------------------
10 | THIS FUNCTION IS CALLED SEVERAL TIMES.
11 | WHATEVER IS PUT HERE SHOULD BE IDEMPOTENT OPERATIONS ONLY
12 | ----------------------------------------------------------
13 |
14 | This is called after u do session.add() before any operation on objects
15 | It's the right place to manipulate objects we need to alter before trying
16 | to Hit DB
17 |
18 | so we update (id) field and original author here
19 | we update last author on updated objects
20 | using model.update_auto_fields() which is idempotent
21 |
22 | :param db_session: DB session
23 | :param flush_context: Internal UOWTransaction object which handles the details of the flush.
24 | :param instances: affected instances, but usually holds value of None
25 | """
26 |
27 | # Update ID and original_author
28 | # object.update_auto_fields() of course is aware of saved object ids but not newly created ids
29 | # We keep track of all ids generated here and guarantee uniqueness for each model
30 |
31 | ids = {}
32 |
33 | for created in db_session.new:
34 | cls_name = created.__class__.__name__
35 | ids[cls_name] = ids.get(cls_name, [])
36 |
37 | while True:
38 | created.update_auto_fields()
39 | if created.id not in ids[cls_name]:
40 | ids[cls_name].append(created.id)
41 | break
42 | else:
43 | # Forcing created.id = None makes created.update_auto_fields() generates new id
44 | # sinc obj.uid returns id if set, but if None it generates another uid
45 | created.id = None
46 |
47 | # Update last modifier
48 | for updated in db_session.dirty:
49 | if not db_session.is_modified(updated):
50 | continue
51 | updated.update_auto_fields(update=True)
52 |
53 | listen(db.session, 'before_flush', update_auto_fields_before_flush)
54 |
--------------------------------------------------------------------------------
/crm/middlewares/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Import and expose all modules in this package.
3 | Now any added module/command will be exposed automatically
4 | """
5 |
6 | import pkgutil
7 | import inspect
8 | import os
9 |
10 | __all__ = []
11 |
12 | for loader, name, is_pkg in pkgutil.walk_packages(__path__):
13 | excluded_middlewares = [md.strip() for md in os.getenv('EXCLUDED_MIDDLEWARES', '').split(',')]
14 | if name in excluded_middlewares:
15 | continue
16 |
17 | module = loader.find_module(name).load_module(name)
18 |
19 | for name, value in inspect.getmembers(module):
20 | if name.startswith('__'):
21 | continue
22 |
23 | globals()[name] = value
24 | __all__.append(name)
25 |
--------------------------------------------------------------------------------
/crm/rq.py:
--------------------------------------------------------------------------------
1 | import os
2 | import redis
3 | from rq import Queue
4 |
5 | redis_url = os.getenv('CACHE_BACKEND_URI')
6 | if redis_url:
7 | redis_url = redis_url[:-2] + '/1'
8 | else:
9 | redis_url = 'redis://localhost:6379/1'
10 |
11 | conn = redis.from_url(redis_url)
12 |
13 | queue = Queue(connection=conn)
14 |
--------------------------------------------------------------------------------
/crm/settings.py:
--------------------------------------------------------------------------------
1 | import os
2 | from importlib import import_module
3 | from os.path import dirname
4 |
5 | LOGGING_CONF = {
6 | 'version': 1,
7 | 'formatters': {'default': {
8 | 'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s',
9 | }},
10 | 'handlers': {'wsgi': {
11 | 'class': 'logging.StreamHandler',
12 | 'formatter': 'default'
13 | }},
14 | 'root': {
15 | 'level': 'INFO',
16 | 'handlers': ['wsgi']
17 | }
18 | }
19 |
20 |
21 | # ../crm.
22 | STATIC_DIR = os.path.abspath(
23 | os.path.join(
24 | dirname(dirname(__file__)),
25 | 'static'
26 | )
27 | )
28 |
29 | STATIC_URL_PATH = "/" + os.path.relpath(STATIC_DIR)
30 |
31 | IMAGES_DIR = os.path.join(STATIC_DIR, "uploads", "images")
32 |
33 | ATTACHMENTS_DIR = os.path.join(STATIC_DIR, "uploads", "attachments")
34 |
35 | DATA_DIR = os.getenv('DATA_DIR')
36 |
37 | CACHE_BACKEND_URI = os.getenv('CACHE_BACKEND_URI', "http://127.0.0.1:6379")
38 |
39 | SENDGRID_API_KEY = os.getenv('SENDGRID_API_KEY')
40 |
41 | SUPPORT_EMAIL = os.getenv('SUPPORT_EMAIL')
42 |
43 | SQLALCHEMY_DATABASE_URI = os.getenv('SQLALCHEMY_DATABASE_URI')
44 |
45 | ######################
46 | # Leave as the last line
47 | ########################
48 |
49 | # Load env settings into globals
50 | settings_module = 'crm.settings_%s' % os.getenv("ENV", 'dev')
51 | env_settings = import_module(settings_module).__dict__
52 | globals().update(env_settings)
53 |
--------------------------------------------------------------------------------
/crm/settings_dev.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | DEBUG = True
4 | Testing = True
5 |
6 | DB_PATH = os.path.abspath(
7 | os.path.join(
8 | os.path.dirname(os.path.dirname(__file__)),
9 | 'dev.db'
10 | )
11 | )
12 |
13 | SQLALCHEMY_DATABASE_URI = os.getenv(
14 | 'SQLALCHEMY_DATABASE_URI',
15 | 'sqlite:///%s' % DB_PATH
16 | )
17 |
18 | SQLALCHEMY_TRACK_MODIFICATIONS = True
19 | SQLALCHEMY_ECHO = False
20 | SQLALCHEMY_RECORD_QUERIES = True
21 |
22 | CACHE_BACKEND_URI = 'memory://'
23 |
24 | DATA_DIR = 'data'
25 |
26 | SUPPORT_EMAIL = os.getenv('SUPPORT_EMAIL', None)
27 | SENDGRID_API_KEY = os.getenv("SENDGRID_API_KEY", None)
28 |
--------------------------------------------------------------------------------
/crm/settings_prod.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | DEBUG = False
4 |
5 |
6 | SQLALCHEMY_TRACK_MODIFICATIONS = False
7 |
8 | DATA_DIR = os.getenv('DATA_DIR', '/opt/code/github/incubaid/data_crm')
9 |
--------------------------------------------------------------------------------
/crm/settings_test.py:
--------------------------------------------------------------------------------
1 | from .settings_dev import *
--------------------------------------------------------------------------------
/docs/AddNewRequirement.md:
--------------------------------------------------------------------------------
1 | ## How to add new dependency
2 |
3 | **System requirements**
4 |
5 | - Linux/Ubuntu : add it to `requiremtns.apt`
6 | - Mac : add it to `requiremtns.brew`
7 |
8 | **Python package**
9 |
10 | - Add to `requiremtns.pip` & `requiremtns-testing.pip` using the following procedure
11 | - Create and activate new virtualenv `virtualenv -p python3 my_env` && `. my_env/bin/activate`
12 | - Install old requirements `pip install -r requiremtns.pip`
13 | - Save installed package in a file using `pip freeze > file1.txt` or using a tool like [pepreqs](https://github.com/bndr/pipreqs)
14 | - Install new packag(s)
15 | - Save installed package in a file using `pip freeze > file2.txt` or using a tool like [pepreqs](https://github.com/bndr/pipreqs)
16 | - Get difference between 2 files `diff file1.txt file2.txt`
17 | - Add difference package to `requirements.pip` & `requirements-testing.pip`
18 |
19 | **Nodejs package**
20 | - Add to `requirements.npm`
21 |
22 |
--------------------------------------------------------------------------------
/docs/AuthenticationMiddleware.md:
--------------------------------------------------------------------------------
1 | ## Authentication
2 | - We don't support authentication on CRM level unfortuantely, we depend on 3rd party [oauth](https://oauth.net/) services
3 | like [IYO](https://itsyou.online) to do so
4 | - If you're going to implement your own authentication mechanism, then you need a [Middleware](https://en.wikipedia.org/wiki/Middleware) to do so
5 | - you can [Define your own middlewares](Middlewares.md) in `crm.middlewares` package
6 | - By default in production mode we have [IYO](https://itsyou.online) authentication support using this 2 steps process:
7 | - we use [Caddy Server](https://caddyserver.com/) with [IYO](https://itsyou.online) to do redirection and authentication for us
8 | and put a JWT token in request header
9 |
10 | - We use a [Middleware](https://en.wikipedia.org/wiki/Middleware) `crm.middlewares.iyo` to :
11 | - Validate the JWT token, extract user info out of it
12 | - If user not in `users` table, we create a new user, otherwise we update user info
13 | - We set session['user'] entry for that user, so later if we found info in session
14 | we authorize user directly without hitting the Database
15 | - If you want to run the app without `crm.middlewares.iyo` then before running the app you may do `export EXCLUDED_MIDDLEWARES=iyo`
16 | - All session data are invalidated when you restart the CRM app
17 | > This done by generating new `app.secret_key` settings for CRM app every time is started
18 |
19 | ###### Writing your own Authentication middleware
20 |
21 | - Put a new middleware in `crm.middlewares` package
22 | - You can get current flask session and request from `flask.session` and`flask.request`
23 | - write a middleare function decorated by `@app.before_request` and hamdle auth lofic there
24 | ```python
25 | from crm import app
26 | from flask import session, request
27 |
28 | @app.before_request
29 | def my_middleware():
30 | pass
31 | ```
32 |
--------------------------------------------------------------------------------
/docs/Caching.md:
--------------------------------------------------------------------------------
1 | # Caching
2 |
3 | - We use [Flask Cache](https://pythonhosted.org/Flask-Cache/)
4 | - For now We've 2 Types of supported cache backends
5 | - Memory
6 | - [Redis](https://redis.io/)
7 |
8 | > we can always add more backends easily since [Flask Cache](https://pythonhosted.org/Flask-Cache/) supports many other backends
9 |
10 | - In development mode `memory` cache is used unless you explicitly do on of the following
11 | - Set `CACHE_BACKEND_URI` environment variable i.e `export CACHE_BACKEND_URI=redis://{ip}:{port}/{db_number}`
12 | - Changed the CACHE_BACKEND_URI setting in `crm.settings_dev.py` explicitly to refer to a[Redis](https://redis.io/) URL
13 |
14 | - In production mode, You **must use `Redis`** by executing : ``export CACHE_BACKEND_URI=redis://{ip}:{port}/{db_number}``
15 |
16 | **Problems with Memory Cache**
17 |
18 | All will work fine but some commands like `flask dumpcache` won't work.
19 | becasue cached memory in CRM app can't be accessed by another process run by another command `flask dumpcache`
20 | If you're testing `flask dumpcache` or similar command, consider use a [Redis](https://redis.io/) cache
21 |
--------------------------------------------------------------------------------
/docs/Configuration.md:
--------------------------------------------------------------------------------
1 | ## Configuration
2 |
3 | ### Settings files
4 |
5 | ###### Shared settings between all environments
6 |
7 | goes inside `crm.settings.py`
8 |
9 | - `STATIC_DIR` static files directory
10 | - `STATIC_URL_PATH` static files URL prefix
11 | - `IMAGES_DIR` where images uploads goes
12 | - `LOGGING_CONF` logging configuration
13 | - `ATTACHMENTS_DIR` Attachments directory for the [Mailin Feature](MailinMailout.md)
14 | ###### Development mode settings
15 |
16 | goes inside ```crm.settings_dev.py```
17 |
18 | - `DEBUG = True` Enable debugging
19 | - `Testing = True` Enable testing mode
20 | - [SQLALCHEMY](https://www.sqlalchemy.org/) debugging options
21 | - `SQLALCHEMY_TRACK_MODIFICATIONS = True`
22 | - `SQLALCHEMY_ECHO = True`
23 | - `SQLALCHEMY_RECORD_QUERIES = True`
24 |
25 |
26 | ###### Production mode settings
27 |
28 | goes inside ```crm.settings_prod.py```
29 |
30 | - `DEBUG = FALSE` Disable debugging
31 | - [SQLALCHEMY](https://www.sqlalchemy.org/) debugging options
32 | - `SQLALCHEMY_TRACK_MODIFICATIONS = False`
33 | - `SQLALCHEMY_ECHO=False` & `SQLALCHEMY_RECORD_QUERIES=False` no need to add them they're set to `False` by default
34 |
35 |
36 | ###### Environment variables settings
37 | - `export CACHE_BACKEND_URI=redis://{ip}:{port}/{db_number}` to set a [redis](https://redis.io/) cache backend
38 | > In **production mode**, must be set explicitly to a [redis](https://redis.io/) URL.
39 |
40 | > In **development mode**, Not required to set, but the default Cache will be **memory**
41 |
42 | > In **development mode** command `flask dumpcache` won't work if Cache backend is **memory**
43 |
44 | > Memory cache in a CRM app process can't be accessed by another process like `flask dumpcache`
45 | so if you want to use `flask dumpcache` you have to use shared cache like [redis](https://redis.io/)
46 |
47 | - `export SQLALCHEMY_DATABASE_URI=postgresql://{user}:{pass}@{ip}:{port}/{db_name}` Use [Postgresql](https://www.postgresql.org/) db
48 | > In **production mode**, must be set explicitly to an [RDBMS](https://en.wikipedia.org/wiki/Relational_database_management_system) like [Postgres](https://www.postgresql.org/)
49 |
50 | > In **development mode** Not required but default is to use a [Sqlite](https://www.sqlite.org/) db called `db.dev` in root dir
51 |
52 | - `export EXCLUDED_MIDDLEWARES=iyo,mw2,mw3` Quickly disable a comma separated list of middlewares
53 |
54 | > In **development mode only** if you want to disable some modules during development like [IYO](https://itsyou.online) authentication middleware
55 | You just have to provide comma separated list of middleware module names
56 |
57 | - `export DATA_DIR={path}` change the path where `flask dumpdata` exports DB data to and `flask loaddata` load data into DB from
58 | > In **production mode** is set by default to `/opt/code/github/incubaid/data_crm`
59 |
60 | > In **development mode** is set by default to `data` dir under the root directory
61 |
62 | - export `SENDGRID_API_KEY` for [Mail In/Out](MailinMailOut.md).
63 |
--------------------------------------------------------------------------------
/docs/DBEvents.md:
--------------------------------------------------------------------------------
1 | ## DB Events
2 |
3 | - DB Events are functions/callbacks to be executed on responding to some action like Model update/delete/create
4 | - More info on this topic and list of all supported [SQLALCHEMY Events](- List of all [SqlAlchemy events](http://docs.sqlalchemy.org/en/latest/orm/events.html))
5 |
6 | **Registering a new hook/event**
7 |
8 | - All events are added in package `crm.events`
9 | - Add a new module there with a function call back to be called when some event happen
10 | - Register your function call back by binding it to some event
11 |
12 | - Example: `crm.events.catch_db_updates.py`
13 |
14 | ```python
15 | from sqlalchemy.event import listen
16 |
17 | from crm.db import db
18 |
19 |
20 | def catch_db_updates_after_flush(db_session, flush_context):
21 |
22 | db_session.info['changes'] = {
23 | 'updated': db_session.dirty,
24 | 'created': db_session.new,
25 | 'deleted': db_session.deleted
26 | }
27 |
28 | listen(db.session, 'after_flush', catch_db_updates_after_flush)
29 |
30 | ```
31 |
32 | - **db.session.new** contains all newly created objects but, *No many to many models are included*
33 | - **db.session.dirty** contains all updated objects
34 | - **db.session.deleted** contains list of deleted objects
35 |
36 |
37 | **WARNING**
38 | - DB Events are disabled when you execute command ```flask loaddata``` because usually you don't want
39 | some actions that may manipulate your data to be executed when you try to load a data mirror in physical DB
40 |
41 | - Most Events won't work with bulk updates and bulk inserts unfortunately
42 |
43 | ## Explanation of existing DB events handlers in CRM
44 |
45 | **`crm.events.update_auto_fields.py`**
46 |
47 | - registers an `after_flush` event callback which gets newly created non many to many models and assign them a unique ID string
48 | and add original author id to model. Also add latest author user id to model
49 |
50 | **`crm.events.catch_db_updates.py`**
51 |
52 | - registers an `after_flush` event callback which gets values of `db.session.new`, `db.session.dirty` and `db.session.deleted` and cache them
53 | in `db.session.info['changes']` because we need to access these data when the event `after_transaction` is triggered
54 | but during `after_transaction` we have a problem that `db.session.new`, `db.session.dirty` and `db.session.deleted` are all empty, so we cache these data
55 | in temp location where we can access them in the appropriate time
56 |
57 |
58 | **`crm.events.cache_db_updates.py`**
59 |
60 | - registers an `after_transaction` event callback which gets all db updates saved in `db.session.info['changes']`
61 | and adds them to the existing cache backend. so we can keep track of any change to DB in memory and we can dump it into JSON
62 | files in `DATA_DIR` automatically
63 |
--------------------------------------------------------------------------------
/docs/GraphqlQueryLanguage.md:
--------------------------------------------------------------------------------
1 | ### Query Language
2 | We use our own query language to query Data in CRM
3 | In any query, you define a field name and its value as a query string i.e `contacts({'firstname': 'my-query'}){..}`
4 |
5 | #### Query String structure
6 |
7 | - Exact match `=`
8 | - Use string you want to match directly i.e `contacts({'firstname': 'Jack'}){..}`
9 | - Not equals `!=`
10 | - put `~` before your string i.e `contacts({'firstname': '~Jack'}){..}`
11 | - contains
12 | - `contacts({'firstname': 'contains(ali)'}){..}`
13 | - like
14 | - `contacts({'firstname': 'contains(ali%)'}){..}`
15 | - null
16 | - `contacts({'lastname': 'null'}){..}`
17 | - not null
18 | - `contacts({'lastname': '~null'}){..}`
19 | - in
20 | - `contacts({'lastname': 'in(ali, fathy)'}){..}`
21 | - not in
22 | - `contacts({'lastname': '~in(ali, fathy)'}){..}`
23 | - `>,<,>=,<=`
24 | - `deals({'value': '>=(10)'}){..}` or `deals({'value': '>=10'}){..}`
25 | - `deals({'created_at': '>=(1999-02-02)'}){..}`
26 | - `deals({'created_at': '<=1999-02-02'}){..}`
27 | - ranges
28 | - `deals({'value': '[4, 10]'}){..}` means `4 <= value <= 10`
29 | - `deals({'value': ']4, 10['}){..}` means `4 < value < 10`
30 | - `deals({'value': '[4, 10['}){..}` means `4 <= value < 10`
31 | - `deals({'value': ']4, 10]'}){..}` means `4 < value <= 10`
32 | - `or`
33 | - `or(ali,fathy)`
34 | - `or(contains(ali), contains(fathy))`
35 | - `and`
36 | - `and(contains(ali), ~alii)`
37 |
--------------------------------------------------------------------------------
/docs/Installation.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | - CRM is a [Flask](flask.pocoo.org/) app
4 | - We have 2 main modes to install and run the application ```Development``` & ```Production``` modes
5 | Depending on the mode you're running requirements may change (a bit) not dramatically
6 |
7 | ### Prepare Your virtual environment
8 |
9 | - ```virtualenv -p python3 crm_env```
10 | - ```. crm_env/bin/activate```
11 |
12 |
13 | ### Install required packages
14 |
15 | ###### Automatically
16 |
17 | - Use the script ```./prepare.sh``` to install all dependencies
18 | - usage:
19 | ```
20 | ./prepare --prod (Install Production dependencies)
21 | ./prepare --dev (Install Development dependencies)
22 | ```
23 |
24 | ###### Manually
25 |
26 | - Install System level dependencies
27 | - Ubuntu ```apt-get -y < requirements.apt```
28 | - Mac ```xargs brew install < requirements.brew```
29 |
30 | - Install Python level dependencies
31 | - Development environment ```pip3 install -r requirements-testing.pip```
32 | - Production environment ```pip3 install -r requirements.pip```
33 |
34 | - Install Nodejs level dependencies
35 | - Development environment ```cat requirements.npm | sudo xargs npm install -g```
36 | - Production environment ```Nothing yet```
37 |
38 | ### Notes
39 |
40 | - Development mode python packages that are not needed in production:
41 | - ```flask-shell-ipython```
42 | - ```coverage```
43 | - ```nose```
44 | - ```ipdb```
45 |
46 | - Development nodejs packages that are not needed in production:
47 | - ```@2fd/graphdoc``` It's used to re-create graphql API docs from a [graphql](http://graphql.org/learn/) schema file and it's the only
48 | nodejs dependency for now
49 |
--------------------------------------------------------------------------------
/docs/Middlewares.md:
--------------------------------------------------------------------------------
1 | ## Middlewares
2 |
3 | **What is a middleware**
4 |
5 | - A middleware in a web application is a piece of software that runs
6 | before any request in the web application and thus it can do operations
7 | that is global to all/some http actions in your app.
8 | Check [Wikipedia Middleware](https://en.wikipedia.org/wiki/Middleware) for more info
9 |
10 | **How to add a middleware in CRM system**
11 |
12 | - Just add your middleware module inside the package `crm.middlewares`
13 | - write your middleware logic in a function decorated by `@app.before_first_request` and import `flask.request` to get current request
14 | then middleware is exposed automatically
15 | ```python
16 | from crm import app
17 | from flask import request
18 |
19 | @app.before_request
20 | def my_middleware():
21 | pass
22 | ```
23 |
24 | **Example**
25 |
26 | CRM authentication is done through a middleware `crm.middlewares.iyo`, please check [Authentication middleware](AuthenticationMiddleware.md)
27 |
28 |
29 | **How to disable a middleware during development mode**
30 |
31 | Before running app, do `export EXCLUDED_MIDDLEWARES=md1,md2,..` where `EXCLUDED_MIDDLEWARES` takes comma separated list of middleware modulenames in `crm.middlewares` package
32 |
--------------------------------------------------------------------------------
/docs/Models.md:
--------------------------------------------------------------------------------
1 | ## Models
2 |
3 | - All models **Must** be defined under `crm.apps.{my_sub_app}.models` an example is `crm.apps.user.models`
4 | - ِAll models inherit from `db.Model`
5 |
6 | > There's difference between (Many To Many) models and non (Many To Many) models
7 | And we need a way to differentiate between both especially when we export/import DB data
8 | So each category has different Parent class.That says `flask dumpdata` & `flask loaddata` commands
9 | will not work correctly if you're not inheriting from proper parent while defining your model
10 |
11 | - **Many To Many**
12 |
13 | - Must inherit from `crm.db.ManyToManyBaseModel)`
14 | - Primary Key (id) field is integer and auto incremental
15 | - Usually records in these tables are auto created by Flask ORM [SqlAlchemy](https://www.sqlalchemy.org/)
16 | That's why we don't want to alter their creation mechanism.
17 |
18 | ```python
19 | class ContactSubgroup(db.Model, ManyToManyBaseModel):
20 | __tablename__ = "contacts_subgroups"
21 |
22 | subgroup_id = db.Column(
23 | db.String(5),
24 | db.ForeignKey('subgroups.id')
25 | )
26 |
27 | contact_id = db.Column(
28 | db.String(5),
29 | db.ForeignKey("contacts.id")
30 | )
31 | ```
32 |
33 |
34 | - **(Non Many to Many)**
35 |
36 | - Must inherit from `crm.db.BaseModel`
37 | - Primary Key (id) is string of unique 5 characters and assigned by an ORM [SqlAlchemy Event](http://docs.sqlalchemy.org/en/latest/orm/events.html)
38 | > `crm.events.update_auto_fields` registers a `before_flush` event to update id field with a unique ID string
39 | This event finds newly created records and alter their IDS with random ones.
40 |
41 | > Note that newly created tables list in a DB `session.new` doesn't contain any many to many field
42 | so we're sure that only non Many to Many fields are affected
43 |
44 | ```
45 | class Contact(db.Model, BaseModel, RootModel):
46 |
47 | __tablename__ = "contacts"
48 |
49 | firstname = db.Column(
50 | db.String(255),
51 | nullable=False,
52 | index=True
53 | )
54 |
55 | lastname = db.Column(
56 | db.String(255),
57 | default="",
58 | index=True
59 | )
60 | description = db.Column(
61 | db.Text()
62 | )
63 | ```
64 |
65 |
66 | - **Root models**
67 | > We've 7 Root models,
68 | - Company
69 | - Contact
70 | - Deal
71 | - Sprint
72 | - Project
73 | - Organization
74 | - User
75 |
76 | - Root models are these that is exported during `flask dumpdata` command
77 | - A Root model must inherit from `crm.db.RootModel`
78 | - Since only Root models are exported during `flask dumpdata`, all info related to a record from any other non Root
79 | model is contained inside Root model data, that says exported data has duplications but more readable
80 |
--------------------------------------------------------------------------------
/docs/Philosophy.md:
--------------------------------------------------------------------------------
1 | ## Philopsophy
2 |
3 | #### why do we need our own CRM system
4 |
5 | - Managing ITO (Internal Token Offerings) for [Threffoldtoken](https://threefoldtoken.com/login/)
6 | - Easy Authentication & Authorization through [IYO](https://itsyou.online)
7 | - Openness
8 | - All Data should be imported/exported from/into JSON format easily
9 | - Any Data change should be visible instantly in a [Git](https://git-scm.com/) Repo
10 |
--------------------------------------------------------------------------------
/docs/PrefabInstallation.md:
--------------------------------------------------------------------------------
1 | ## Prefab Installation
2 |
3 | You don't need to worry about this section if you're not familiar with [Jumpscale](https://github.com/Jumpscale)
4 |
5 | **Install Bash tools**
6 |
7 |
8 | ```
9 | curl https://raw.githubusercontent.com/Jumpscale/bash/master/install.sh?$RANDOM > /tmp/install.sh;bash /tmp/install.sh
10 | source ~/.bash_profile
11 | ```
12 |
13 | **Build Docker CRM Image**
14 |
15 | ```
16 | ZInstall_crm -p 443 -u "crm.threefoldtoken.com" -o threefold.crm_users -s UrzXJ81iWdckOoOcsBD4Xo9-6ThaMpfOC5sW8apUYwad -i production_crm -e hamdya@greenitglobe.com
17 | ```
18 |
19 | **Build Docker CRM Container**
20 |
21 | ```
22 | ZInstall_crm -p 443 -i production_crm
23 | ZDockerActive -b "jumpscale/js9_docgenerator" -a "-p 443:443 -p 80:80" -c "ZInstall_docgenerator" -i crm_production
24 | ```
25 |
26 | **Access Docker CRM Container**
27 | ```
28 | docker exec -ti crm bash
29 | ```
30 |
31 |
--------------------------------------------------------------------------------
/docs/Production.md:
--------------------------------------------------------------------------------
1 | # Update Production procedures
2 |
3 | - Deploy from ```production``` branch
4 | - After any change to our [graphql](graphql.org/learn/) api, regenerate [graphql](graphql.org/learn/) api usig ```generate_graphql_docs``` then commit new docs
5 |
6 |
7 | - **Postgres**
8 | - Backup postgres database or ensure a recent version of postgres backup
9 |
10 | - **Caddy**
11 | - Update ```caddy``` config with the latest config if any
12 | - stop and start caddy with new config
13 | ```
14 | echo **START**;ulimit -n 8192; /opt/bin/caddy -conf=/opt/cfg/caddy.cfg -agree && echo **OK** || echo **ERROR**
15 | ```
16 | - **CRM**
17 | - check where is ```DATA_DIR``` in ```crm.settings_prod.py``` and backup this DIR
18 | - **`Warning`** ```DATA_DIR``` settings may have been overridden by `export DATA_DIR={custom_path}`
19 |
20 | - make sure you're in production branch and pull new production code
21 | - stop crm
22 | - backup data by running ```flask dumpdata``` and take another copy `DATA_DIR`
23 | - runt script `./prepare.sh --prod` to install all needed requirements or refer to [Installation](Installation.md) page
24 | - run DB migrations ```flask db upgrade``` to run migrations and update Physical DB
25 | - make sure you can dumpdata correctly using ```flask dumpdata``` and take a 3rd copy of data for ```crm.settings.DATA_DIR```
26 | - Run
27 | ```
28 | /opt/code/github/incubaid/crm# echo **START**;cd /opt/code/github/incubaid/crm;export SQLALCHEMY_DATABASE_URI=postgresql://user:pass@host:5432/db;export CACHE_BACKEND_URI=redis://{ip}:{port}/{db_number};export ENV=prod;export FLASK_APP=app.py;flask db upgrade; uwsgi --ini uwsgi.ini && echo **OK** || echo **ERROR**
29 |
30 | ```
31 |
--------------------------------------------------------------------------------
/docs/Structure.md:
--------------------------------------------------------------------------------
1 | ## Project Structure
2 |
3 | **Middlewares**
4 | - Under `crm.middlewares`
5 | - More info about [Middlewares](Middlewares.md)
6 |
7 |
8 | **Custom Command lines**
9 | - Under `crm.cli`
10 | - More info about [Commands](Commands.md)
11 |
12 | **Custom Events**
13 | - Under `crm.events`
14 | - More info about [DB Events](DBEvents.md)
15 |
16 | **Admin application**
17 | - Under `flask.apps.admin`
18 | - More info about [Admin app](AdminInterface.md)
19 |
20 | **Flask sub application**
21 | - Any package under `crm.apps`
22 | - Main modules for any sub app (except the [Admin app](AdminInterface.md)) are:
23 | - `models.py` for models definitions. Please follow [These conventions](Models.md) when defining your own models
24 | - `views.py` for HTTP end points definitions. . Please follow [These conventions](Views.md) when defining your own models
25 | - To add an HTTP end point, you put your code in a function inside `views.py` file in some app decorated by `@app.route`
26 | ```
27 | @app.route('/api', methods=["POST"])
28 | def api():
29 | pass
30 |
31 | ```
32 | - `graphql` package for [graphql](graphql.org/learn/) To expose `graphql` mutations & queries for this sub app.
33 | Please follow [These conventions](GraphqlAdvanced.md)
34 | - Put types in module `types.py`
35 | - Put arguments in module `arguments.py`
36 | - Put queries in module `queries.py`
37 | - Put mutations in module `mutations.py`
38 |
39 |
40 | **Main app**
41 | - [Flask](flask.pocoo.org/) Main entry point is ```app.py``` in the root directory
42 | - It imports actual [Flask](flask.pocoo.org/) app :: `crm.app`
43 | - `crm.app` is defined in `crm/__init__.py` and it's the actual [Flask](flask.pocoo.org/) app
44 | - `crm/__init__.py` contains code to initialize the app and registering all http endpoints for sub applications in
45 | `crm.apps` by importig their `views.py` module if found
46 | - It registers all custom commands by doing ```from crm.cli import *```
47 | - It registers all middlewares by doing ```from crm.middlewares import *```
48 | - It registers all db events by doing ```from crm.events import *```
49 |
--------------------------------------------------------------------------------
/docs/Views.md:
--------------------------------------------------------------------------------
1 | ## Views
2 |
3 | HTTP actions
4 |
5 | - surround an http action with ```@app.route('custom-relative-path')```
6 | - Example:
7 | ```python
8 | from flask.templating import render_template
9 |
10 | from crm import app
11 |
12 | @app.route('/')
13 | def hello_world():
14 | return render_template('home/index.html')
15 | ```
16 |
--------------------------------------------------------------------------------
/docs/assets/addfilter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/assets/addfilter.png
--------------------------------------------------------------------------------
/docs/assets/createview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/assets/createview.png
--------------------------------------------------------------------------------
/docs/assets/detailsview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/assets/detailsview.png
--------------------------------------------------------------------------------
/docs/assets/editview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/assets/editview.png
--------------------------------------------------------------------------------
/docs/assets/extramenu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/assets/extramenu.png
--------------------------------------------------------------------------------
/docs/assets/graphiql_docs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/assets/graphiql_docs.png
--------------------------------------------------------------------------------
/docs/assets/graphql_many.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/assets/graphql_many.png
--------------------------------------------------------------------------------
/docs/assets/graphql_query.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/assets/graphql_query.png
--------------------------------------------------------------------------------
/docs/assets/iyo-settings1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/assets/iyo-settings1.png
--------------------------------------------------------------------------------
/docs/assets/iyo-settings2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/assets/iyo-settings2.png
--------------------------------------------------------------------------------
/docs/assets/listview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/assets/listview.png
--------------------------------------------------------------------------------
/docs/assets/mainmenu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/assets/mainmenu.png
--------------------------------------------------------------------------------
/docs/graphqlapi/assets/code.css:
--------------------------------------------------------------------------------
1 | .code {
2 | background-color: #f6f6f6;
3 | color: #4D4D4C;
4 | border: 1px solid #4D4D4C;
5 | font-size: 14px;
6 | font-family: 'Ubuntu Mono';
7 | cursor: text;
8 | list-style-type: decimal;
9 | border-radius: 0.25rem;
10 | }
11 |
12 | .code .gutter {
13 | background: #f6f6f6;
14 | color: #4D4D4C;
15 | }
16 |
17 | .code .print-margin {
18 | width: 1px;
19 | background: #f6f6f6
20 | }
21 |
22 | .code li {
23 | min-height: 1em;
24 | background: #FFF;
25 | padding: 1px 8px;
26 | }
27 | .code li:hover {
28 | background: #EFEFEF;
29 | }
30 |
31 | .code .tab {
32 | padding-left: 2em;
33 | }
34 |
35 | .code .cursor {
36 | color: #AEAFAD
37 | }
38 |
39 | .code .marker-layer .selection {
40 | background: #D6D6D6
41 | }
42 |
43 | .code.multiselect .selection.start {
44 | box-shadow: 0 0 3px 0px #FFFFFF;
45 | }
46 |
47 | .code .marker-layer .step {
48 | background: rgb(255, 255, 0)
49 | }
50 |
51 | .code .marker-layer .bracket {
52 | margin: -1px 0 0 -1px;
53 | border: 1px solid #D1D1D1
54 | }
55 |
56 | .code .marker-layer .active-line {
57 | background: #EFEFEF
58 | }
59 |
60 | .code .gutter-active-line {
61 | background-color: #dcdcdc
62 | }
63 |
64 | .code .marker-layer .selected-word {
65 | border: 1px solid #D6D6D6
66 | }
67 |
68 | .code .invisible {
69 | color: #D1D1D1
70 | }
71 |
72 | .code .keyword,
73 | .code .meta,
74 | .code .storage,
75 | .code .storage.type,
76 | .code .support.type {
77 | color: #8959A8
78 | }
79 |
80 | .code .keyword.operator {
81 | color: #3E999F
82 | }
83 |
84 | .code .constant.character,
85 | .code .constant.language,
86 | .code .constant.numeric,
87 | .code .keyword.other.unit,
88 | .code .support.constant {
89 | color: #F5871F
90 | }
91 |
92 | .code .constant.other {
93 | color: #666969
94 | }
95 |
96 | .code .invalid {
97 | color: #FFFFFF;
98 | background-color: #C82829
99 | }
100 |
101 | .code .invalid.deprecated {
102 | color: #FFFFFF;
103 | background-color: #8959A8
104 | }
105 |
106 | .code .fold {
107 | background-color: #4271AE;
108 | border-color: #4D4D4C
109 | }
110 |
111 | .code .entity.name.function,
112 | .code .support.function,
113 | .code .variable.parameter,
114 | .code .variable {
115 | color: #4271AE
116 | }
117 |
118 | .code .support.class,
119 | .code .support.type {
120 | color: #C99E00
121 | }
122 |
123 | .code .heading,
124 | .code .markup.heading,
125 | .code .string {
126 | color: #718C00
127 | }
128 |
129 | .code .entity.name.tag,
130 | .code .entity.other.attribute-name,
131 | .code .meta.tag,
132 | .code .string.regexp,
133 | .code .variable {
134 | color: #C82829
135 | }
136 |
137 | .code .comment {
138 | color: #8E908C
139 | }
140 |
141 | .code .indent-guide {
142 | background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bdu3f/BwAlfgctduB85QAAAABJRU5ErkJggg==) right repeat-y
143 | }
--------------------------------------------------------------------------------
/docs/graphqlapi/assets/require-by.css:
--------------------------------------------------------------------------------
1 | div.require-by.anyone,
2 | ul.require-by a {
3 | max-width: 100%;
4 | overflow: hidden;
5 | text-overflow: ellipsis;
6 | white-space: nowrap;
7 | display: block;
8 | }
9 |
10 | div.require-by.anyone {
11 | background-color: #f0f8fc;
12 | border: 1px solid #d8dde6;
13 | color: grey;
14 | padding: 2rem;
15 | text-align: center;
16 | margin: 1rem 0;
17 | border-radius: 0.25rem;
18 | }
19 |
20 | ul.require-by {
21 | margin: 0;
22 | padding: 0;
23 | }
24 |
25 | ul.require-by a {
26 | border-left: .25rem solid transparent;
27 | border-top: 1px solid transparent;
28 | border-bottom: 1px solid transparent;
29 | padding: .5rem 1.5rem;
30 | }
31 |
32 | ul.require-by a:hover {
33 | text-decoration: none;
34 | background-color: #f0f8fc;
35 | border-color: #d8dde6;
36 | border-left-color: #005fb2;
37 | }
38 |
39 | ul.require-by a em {
40 | margin-left: 1rem;
41 | font-size: .75rem;
42 | color: grey;
43 | }
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-Bold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-Bold.eot
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-Bold.woff
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-Bold.woff2
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-BoldItalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-BoldItalic.eot
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-BoldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-BoldItalic.woff
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-BoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-BoldItalic.woff2
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-Italic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-Italic.eot
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-Italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-Italic.woff
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-Italic.woff2
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-Light.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-Light.eot
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-Light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-Light.woff
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-Light.woff2
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-LightItalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-LightItalic.eot
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-LightItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-LightItalic.woff
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-LightItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-LightItalic.woff2
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-Regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-Regular.eot
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-Regular.woff
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-Regular.woff2
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-Thin.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-Thin.eot
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-Thin.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-Thin.woff
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-Thin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-Thin.woff2
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-ThinItalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-ThinItalic.eot
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-ThinItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-ThinItalic.woff
--------------------------------------------------------------------------------
/docs/graphqlapi/fonts/webfonts/SalesforceSans-ThinItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/docs/graphqlapi/fonts/webfonts/SalesforceSans-ThinItalic.woff2
--------------------------------------------------------------------------------
/docs/graphqlapi/scripts/filter-types.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var HIDE_CLASS = 'slds-hide';
3 | var ITEM_CLASS = 'slds-item';
4 |
5 | /**
6 | * @class Item
7 | * @param {HTMLLIElement} li
8 | */
9 | function Item(li) {
10 | this.li = li;
11 | this.type = li.title;
12 | this.typeLowerCase = li.title.toLowerCase();
13 | }
14 |
15 | /**
16 | * @return boolean
17 | */
18 | Item.prototype.contains = function (searchText) {
19 | return this.typeLowerCase.indexOf(searchText) >= 0;
20 | }
21 |
22 | /**
23 | * @return boolean
24 | */
25 | Item.prototype.isHide = function () {
26 | this.li.classList.contains(HIDE_CLASS);
27 | }
28 |
29 | /**
30 | * @return void
31 | */
32 | Item.prototype.hide = function () {
33 | if (!this.isHide())
34 | this.li.classList.add(HIDE_CLASS);
35 | }
36 |
37 | /**
38 | * @return void
39 | */
40 | Item.prototype.show = function () {
41 | this.li.classList.remove(HIDE_CLASS);
42 | }
43 |
44 | /**
45 | * @class ItemList
46 | * @param {Item[]} items
47 | */
48 | function ItemList(items) {
49 | this.items = items;
50 | }
51 |
52 | /**
53 | * @function ItemsList.fromSelector
54 | * @param {string} selector
55 | * @return ItemList
56 | */
57 | ItemList.fromSelector = function (selector) {
58 |
59 | var lis = document.querySelectorAll(selector);
60 | var items = Array.prototype.map.call(lis, function (li) {
61 | return new Item(li);
62 | })
63 |
64 | return new ItemList(items);
65 | }
66 |
67 | /**
68 | * @return void
69 | */
70 | ItemList.prototype.showIfmatch = function (match) {
71 |
72 | match = match.toLowerCase(match);
73 |
74 | this
75 | .items
76 | .forEach(function (item) {
77 | item.contains(match) ?
78 | item.show():
79 | item.hide();
80 | })
81 | }
82 |
83 | /**
84 | * @var {ItemList} items
85 | * @var {HTMLInputElement} input
86 | */
87 | var items = ItemList.fromSelector('nav .slds-navigation-list--vertical li');
88 | var input = document.getElementById('type-search');
89 | var lastMatch = '';
90 |
91 | function onChange() {
92 | if (input.value === lastMatch)
93 | return;
94 |
95 | lastMatch = input.value;
96 | items.showIfmatch(lastMatch);
97 | }
98 |
99 | input.addEventListener('change', onChange);
100 | input.addEventListener('keyup', onChange);
101 | input.addEventListener('mouseup', onChange);
102 | })()
--------------------------------------------------------------------------------
/docs/graphqlapi/scripts/focus-active.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var navScroll = document.getElementById('navication-scroll');
3 | var header = document.querySelector('nav header');
4 | var active = document.querySelector('.slds-is-active a');
5 |
6 | if(active)
7 | navScroll.scrollTop = active.offsetTop - header.offsetHeight - Math.ceil(active.offsetHeight / 2)
8 | })()
--------------------------------------------------------------------------------
/docs/graphqlapi/scripts/toggle-navigation.js:
--------------------------------------------------------------------------------
1 | (function () {
2 |
3 | var ACTIVE_CLASS = 'is-active';
4 | var navigation = document.querySelector('nav');
5 | var toggles = document.querySelectorAll('.js-toggle-navigation');
6 |
7 | function toggleNavigation() {
8 | navigation.classList.contains(ACTIVE_CLASS) ?
9 | navigation.classList.remove(ACTIVE_CLASS) :
10 | navigation.classList.add(ACTIVE_CLASS);
11 | }
12 |
13 | Array.prototype.forEach.call(
14 | toggles,
15 | /**
16 | * @param {HTMLElement} toggle
17 | */
18 | function (toggle) {
19 | toggle.addEventListener('click', toggleNavigation);
20 | }
21 | )
22 |
23 | })()
--------------------------------------------------------------------------------
/docs/graphqlapi/styles/_override.scss:
--------------------------------------------------------------------------------
1 | .slds-scrollable::-webkit-scrollbar {
2 | width: 10px;
3 | height: 10px
4 | }
5 |
6 | .slds-scrollable::-webkit-scrollbar:window-inactive {
7 | opacity: 0
8 | }
9 |
10 | .slds-scrollable::-webkit-scrollbar-thumb {
11 | background: #e0e5ee;
12 | border-radius: .5rem;
13 | box-shadow: #a8b7c7 0 0 0 1px inset
14 | }
15 |
16 | .slds-scrollable::-webkit-scrollbar-track {
17 | background: #a8b7c7
18 | }
19 |
20 | .slds-scrollable--y {
21 | -webkit-overflow-scrolling: touch;
22 | max-height: 100%;
23 | overflow: hidden;
24 | overflow-y: auto
25 | }
26 |
27 | .slds-scrollable--y::-webkit-scrollbar {
28 | width: 10px;
29 | height: 10px
30 | }
31 |
32 | .slds-scrollable--y::-webkit-scrollbar:window-inactive {
33 | opacity: 0
34 | }
35 |
36 | .slds-scrollable--y::-webkit-scrollbar-thumb {
37 | background: #e0e5ee;
38 | border-radius: .5rem;
39 | box-shadow: #a8b7c7 0 0 0 1px inset
40 | }
41 |
42 | .slds-scrollable--y::-webkit-scrollbar-track {
43 | background: #a8b7c7
44 | }
45 |
46 | .slds-scrollable--x {
47 | -webkit-overflow-scrolling: touch;
48 | max-width: 100%;
49 | overflow: hidden;
50 | overflow-x: auto
51 | }
52 |
53 | .slds-scrollable--x::-webkit-scrollbar {
54 | width: 10px;
55 | height: 10px
56 | }
57 |
58 | .slds-scrollable--x::-webkit-scrollbar:window-inactive {
59 | opacity: 0
60 | }
61 |
62 | .slds-scrollable--x::-webkit-scrollbar-thumb {
63 | background: #e0e5ee;
64 | border-radius: .5rem;
65 | box-shadow: #a8b7c7 0 0 0 1px inset
66 | }
67 |
68 | .slds-scrollable--x::-webkit-scrollbar-track {
69 | background: #a8b7c7
70 | }
71 |
72 | .slds-scrollable--x::-webkit-scrollbar-track,
73 | .slds-scrollable--y::-webkit-scrollbar-track {
74 | background: rgba(255, 255, 255, 0.1);
75 | }
76 |
77 | .slds-scrollable--x::-webkit-scrollbar-thumb,
78 | .slds-scrollable--y::-webkit-scrollbar-thumb {
79 | background: rgba(0, 0, 0, 0.1);
80 | box-shadow: none;
81 | }
82 |
83 | .material-icons.slds-button__icon {
84 | font-size: .875rem;
85 | vertical-align: text-bottom;
86 | }
--------------------------------------------------------------------------------
/frontend/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-runtime"],
12 | "env": {
13 | "test": {
14 | "presets": ["env", "stage-2"],
15 | "plugins": ["istanbul"]
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/frontend/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/frontend/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | config/*.js
3 |
--------------------------------------------------------------------------------
/frontend/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // https://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | parser: 'babel-eslint',
6 | parserOptions: {
7 | sourceType: 'module'
8 | },
9 | env: {
10 | browser: true,
11 | },
12 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md
13 | extends: 'standard',
14 | // required to lint *.vue files
15 | plugins: [
16 | 'html'
17 | ],
18 | // add your custom rules here
19 | 'rules': {
20 | // allow paren-less arrow functions
21 | 'arrow-parens': 0,
22 | // allow async-await
23 | 'generator-star-spacing': 0,
24 | // allow debugger during development
25 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | test/unit/coverage
8 |
9 | # Editor directories and files
10 | .idea
11 | .vscode
12 | *.suo
13 | *.ntvs*
14 | *.njsproj
15 | *.sln
16 |
--------------------------------------------------------------------------------
/frontend/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | // to edit target browsers: use "browserslist" field in package.json
6 | "autoprefixer": {}
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # frontend
2 |
3 | > A Vue.js project
4 |
5 | ## Build Setup
6 |
7 | ``` bash
8 | # install dependencies
9 | npm install
10 |
11 | # serve with hot reload at localhost:8080
12 | npm run dev
13 |
14 | # build for production with minification
15 | npm run build
16 |
17 | # build for production and view the bundle analyzer report
18 | npm run build --report
19 |
20 | # run unit tests
21 | npm run unit
22 |
23 | # run all tests
24 | npm test
25 | ```
26 |
27 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).
28 |
--------------------------------------------------------------------------------
/frontend/build/build.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | require('./check-versions')()
3 |
4 | process.env.NODE_ENV = 'production'
5 |
6 | const ora = require('ora')
7 | const rm = require('rimraf')
8 | const path = require('path')
9 | const chalk = require('chalk')
10 | const webpack = require('webpack')
11 | const config = require('../config')
12 | const webpackConfig = require('./webpack.prod.conf')
13 |
14 | const spinner = ora('building for production...')
15 | spinner.start()
16 |
17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
18 | if (err) throw err
19 | webpack(webpackConfig, function (err, stats) {
20 | spinner.stop()
21 | if (err) throw err
22 | process.stdout.write(stats.toString({
23 | colors: true,
24 | modules: false,
25 | children: false,
26 | chunks: false,
27 | chunkModules: false
28 | }) + '\n\n')
29 |
30 | // if (stats.hasErrors()) {
31 | // console.log(chalk.red(' Build failed with errors.\n'))
32 | // process.exit(1)
33 | // }
34 |
35 | console.log(chalk.cyan(' Build complete.\n'))
36 | console.log(chalk.yellow(
37 | ' Tip: built files are meant to be served over an HTTP server.\n' +
38 | ' Opening index.html over file:// won\'t work.\n'
39 | ))
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/frontend/build/check-versions.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const chalk = require('chalk')
3 | const semver = require('semver')
4 | const packageConfig = require('../package.json')
5 | const shell = require('shelljs')
6 | function exec (cmd) {
7 | return require('child_process').execSync(cmd).toString().trim()
8 | }
9 |
10 | const versionRequirements = [
11 | {
12 | name: 'node',
13 | currentVersion: semver.clean(process.version),
14 | versionRequirement: packageConfig.engines.node
15 | }
16 | ]
17 |
18 | if (shell.which('npm')) {
19 | versionRequirements.push({
20 | name: 'npm',
21 | currentVersion: exec('npm --version'),
22 | versionRequirement: packageConfig.engines.npm
23 | })
24 | }
25 |
26 | module.exports = function () {
27 | const warnings = []
28 | for (let i = 0; i < versionRequirements.length; i++) {
29 | const mod = versionRequirements[i]
30 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
31 | warnings.push(mod.name + ': ' +
32 | chalk.red(mod.currentVersion) + ' should be ' +
33 | chalk.green(mod.versionRequirement)
34 | )
35 | }
36 | }
37 |
38 | if (warnings.length) {
39 | console.log('')
40 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
41 | console.log()
42 | for (let i = 0; i < warnings.length; i++) {
43 | const warning = warnings[i]
44 | console.log(' ' + warning)
45 | }
46 | console.log()
47 | process.exit(1)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/frontend/build/dev-client.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | 'use strict'
3 | require('eventsource-polyfill')
4 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
5 |
6 | hotClient.subscribe(function (event) {
7 | if (event.action === 'reload') {
8 | window.location.reload()
9 | }
10 | })
11 |
--------------------------------------------------------------------------------
/frontend/build/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const config = require('../config')
4 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
5 |
6 | exports.assetsPath = function (_path) {
7 | const assetsSubDirectory = process.env.NODE_ENV === 'production'
8 | ? config.build.assetsSubDirectory
9 | : config.dev.assetsSubDirectory
10 | return path.posix.join(assetsSubDirectory, _path)
11 | }
12 |
13 | exports.cssLoaders = function (options) {
14 | options = options || {}
15 |
16 | const cssLoader = {
17 | loader: 'css-loader',
18 | options: {
19 | minimize: process.env.NODE_ENV === 'production',
20 | sourceMap: options.sourceMap
21 | }
22 | }
23 |
24 | // generate loader string to be used with extract text plugin
25 | function generateLoaders (loader, loaderOptions) {
26 | const loaders = [cssLoader]
27 | if (loader) {
28 | loaders.push({
29 | loader: loader + '-loader',
30 | options: Object.assign({}, loaderOptions, {
31 | sourceMap: options.sourceMap
32 | })
33 | })
34 | }
35 |
36 | // Extract CSS when that option is specified
37 | // (which is the case during production build)
38 | if (options.extract) {
39 | return ExtractTextPlugin.extract({
40 | use: loaders,
41 | fallback: 'vue-style-loader'
42 | })
43 | } else {
44 | return ['vue-style-loader'].concat(loaders)
45 | }
46 | }
47 |
48 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
49 | return {
50 | css: generateLoaders(),
51 | postcss: generateLoaders(),
52 | less: generateLoaders('less'),
53 | sass: generateLoaders('sass', { indentedSyntax: true }),
54 | scss: generateLoaders('sass'),
55 | stylus: generateLoaders('stylus'),
56 | styl: generateLoaders('stylus')
57 | }
58 | }
59 |
60 | // Generate loaders for standalone style files (outside of .vue)
61 | exports.styleLoaders = function (options) {
62 | const output = []
63 | const loaders = exports.cssLoaders(options)
64 | for (const extension in loaders) {
65 | const loader = loaders[extension]
66 | output.push({
67 | test: new RegExp('\\.' + extension + '$'),
68 | use: loader
69 | })
70 | }
71 | return output
72 | }
73 |
--------------------------------------------------------------------------------
/frontend/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const utils = require('./utils')
3 | const config = require('../config')
4 | const isProduction = process.env.NODE_ENV === 'production'
5 |
6 | module.exports = {
7 | loaders: utils.cssLoaders({
8 | sourceMap: isProduction
9 | ? config.build.productionSourceMap
10 | : config.dev.cssSourceMap,
11 | extract: isProduction
12 | }),
13 | transformToRequire: {
14 | video: 'src',
15 | source: 'src',
16 | img: 'src',
17 | image: 'xlink:href'
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const config = require('../config')
5 | const vueLoaderConfig = require('./vue-loader.conf')
6 |
7 | function resolve (dir) {
8 | return path.join(__dirname, '..', dir)
9 | }
10 |
11 | module.exports = {
12 | entry: {
13 | app: './src/main.js'
14 | },
15 | output: {
16 | path: config.build.assetsRoot,
17 | filename: '[name].js',
18 | publicPath: process.env.NODE_ENV === 'production'
19 | ? config.build.assetsPublicPath
20 | : config.dev.assetsPublicPath
21 | },
22 | resolve: {
23 | extensions: ['.js', '.vue', '.json'],
24 | alias: {
25 | 'vue$': 'vue/dist/vue.esm.js',
26 | '@': resolve('src'),
27 | }
28 | },
29 | module: {
30 | rules: [
31 | {
32 | test: /\.(js|vue)$/,
33 | loader: 'eslint-loader',
34 | enforce: 'pre',
35 | include: [resolve('src'), resolve('test')],
36 | options: {
37 | formatter: require('eslint-friendly-formatter')
38 | }
39 | },
40 | {
41 | test: /\.vue$/,
42 | loader: 'vue-loader',
43 | options: vueLoaderConfig
44 | },
45 | {
46 | test: /\.js$/,
47 | loader: 'babel-loader',
48 | include: [resolve('src'), resolve('test')]
49 | },
50 | {
51 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
52 | loader: 'url-loader',
53 | options: {
54 | limit: 10000,
55 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
56 | }
57 | },
58 | {
59 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
60 | loader: 'url-loader',
61 | options: {
62 | limit: 10000,
63 | name: utils.assetsPath('media/[name].[hash:7].[ext]')
64 | }
65 | },
66 | {
67 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
68 | loader: 'url-loader',
69 | options: {
70 | limit: 10000,
71 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
72 | }
73 | }
74 | ]
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/frontend/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const utils = require('./utils')
3 | const webpack = require('webpack')
4 | const config = require('../config')
5 | const merge = require('webpack-merge')
6 | const baseWebpackConfig = require('./webpack.base.conf')
7 | const HtmlWebpackPlugin = require('html-webpack-plugin')
8 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
9 |
10 | // add hot-reload related code to entry chunks
11 | Object.keys(baseWebpackConfig.entry).forEach(function (name) {
12 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
13 | })
14 |
15 | module.exports = merge(baseWebpackConfig, {
16 | module: {
17 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
18 | },
19 | // cheap-module-eval-source-map is faster for development
20 | devtool: '#cheap-module-eval-source-map',
21 | plugins: [
22 | new webpack.DefinePlugin({
23 | 'process.env': config.dev.env
24 | }),
25 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
26 | new webpack.HotModuleReplacementPlugin(),
27 | new webpack.NoEmitOnErrorsPlugin(),
28 | // https://github.com/ampedandwired/html-webpack-plugin
29 | new HtmlWebpackPlugin({
30 | filename: 'index.html',
31 | template: 'index.html',
32 | inject: true
33 | }),
34 | new FriendlyErrorsPlugin()
35 | ]
36 | })
37 |
--------------------------------------------------------------------------------
/frontend/build/webpack.test.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // This is the webpack config used for unit tests.
3 |
4 | const utils = require('./utils')
5 | const webpack = require('webpack')
6 | const merge = require('webpack-merge')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 |
9 | const webpackConfig = merge(baseWebpackConfig, {
10 | // use inline sourcemap for karma-sourcemap-loader
11 | module: {
12 | rules: utils.styleLoaders()
13 | },
14 | devtool: '#inline-source-map',
15 | resolveLoader: {
16 | alias: {
17 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option
18 | // see discussion at https://github.com/vuejs/vue-loader/issues/724
19 | 'scss-loader': 'sass-loader'
20 | }
21 | },
22 | plugins: [
23 | new webpack.DefinePlugin({
24 | 'process.env': require('../config/test.env')
25 | })
26 | ]
27 | })
28 |
29 | // no need for app entry during tests
30 | delete webpackConfig.entry
31 |
32 | module.exports = webpackConfig
33 |
--------------------------------------------------------------------------------
/frontend/config/dev.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const prodEnv = require('./prod.env')
4 |
5 | module.exports = merge(prodEnv, {
6 | NODE_ENV: '"development"'
7 | })
8 |
--------------------------------------------------------------------------------
/frontend/config/index.js:
--------------------------------------------------------------------------------
1 |
2 | 'use strict'
3 | // Template version: 1.1.3
4 | // see http://vuejs-templates.github.io/webpack for documentation.
5 |
6 | const path = require('path')
7 |
8 | module.exports = {
9 | build: {
10 | env: require('./prod.env'),
11 | index: path.resolve(__dirname, '../dist/index.html'),
12 | assetsRoot: path.resolve(__dirname, '../dist'),
13 | assetsSubDirectory: 'static',
14 | assetsPublicPath: '/',
15 | productionSourceMap: true,
16 | // Gzip off by default as many popular static hosts such as
17 | // Surge or Netlify already gzip all static assets for you.
18 | // Before setting to `true`, make sure to:
19 | // npm install --save-dev compression-webpack-plugin
20 | productionGzip: false,
21 | productionGzipExtensions: ['js', 'css'],
22 | // Run the build command with an extra argument to
23 | // View the bundle analyzer report after build finishes:
24 | // `npm run build --report`
25 | // Set to `true` or `false` to always turn it on or off
26 | bundleAnalyzerReport: process.env.npm_config_report
27 | },
28 | dev: {
29 | env: require('./dev.env'),
30 | port: process.env.PORT || 8080,
31 | autoOpenBrowser: true,
32 | assetsSubDirectory: 'static',
33 | assetsPublicPath: '/',
34 | proxyTable: {},
35 | // CSS Sourcemaps off by default because relative paths are "buggy"
36 | // with this option, according to the CSS-Loader README
37 | // (https://github.com/webpack/css-loader#sourcemaps)
38 | // In our experience, they generally work as expected,
39 | // just be aware of this issue when enabling this option.
40 | cssSourceMap: false
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/frontend/config/prod.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | NODE_ENV: '"production"'
4 | }
5 |
--------------------------------------------------------------------------------
/frontend/config/test.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const devEnv = require('./dev.env')
4 |
5 | module.exports = merge(devEnv, {
6 | NODE_ENV: '"testing"'
7 | })
8 |
--------------------------------------------------------------------------------
/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | frontend
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/frontend/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Home
6 | Reports
7 |
8 |
9 |
10 |
11 |
12 |
17 |
18 |
28 |
--------------------------------------------------------------------------------
/frontend/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/frontend/src/assets/logo.png
--------------------------------------------------------------------------------
/frontend/src/axioshelpers.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | export const queryAllDeals = `
4 | query allDeals {
5 | deals {
6 | edges{
7 | node{
8 | uid,
9 | name,
10 | amount,
11 | dealState,
12 | contact {
13 | firstname
14 | lastname
15 | }
16 | }
17 | }
18 | }
19 | }
20 | `
21 |
22 | export function getAllDeals() {
23 | return axios.post(`http://27c9db6b.ngrok.io/api`, {'query': queryAllDeals }, {'headers':{'Content-Type':'application/json'}})
24 | }
--------------------------------------------------------------------------------
/frontend/src/components/Deal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{deal.uid}} / {{ deal.name }} ( {{deal.amount}} ) : {{deal.dealState}} {{deal.contact['firstname'] + " " + deal.contact['lastname']}}
5 |
6 |
7 |
8 |
9 |
23 |
--------------------------------------------------------------------------------
/frontend/src/components/DealsList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
List of Deals
4 |
Still loading
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | TOTAL: {{total}}
15 | TOTAL PENDING: {{totalpending}}
16 | TOTAL CLOSED: {{totalclosed}}
17 |
18 |
19 |
20 |
21 |
62 |
63 |
64 |
82 |
--------------------------------------------------------------------------------
/frontend/src/components/DealsPending.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
List of Pending Deals
4 |
Still loading
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | TOTAL of pending: {{total}}
14 |
15 |
16 |
17 |
18 |
51 |
52 |
53 |
71 |
--------------------------------------------------------------------------------
/frontend/src/components/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
22 |
23 |
24 |
43 |
--------------------------------------------------------------------------------
/frontend/src/components/Reports.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 | All Deals
5 | All pending deals
6 |
7 |
8 |
9 |
10 |
11 |
12 |
20 |
21 |
22 |
41 |
--------------------------------------------------------------------------------
/frontend/src/main.js:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue'
4 | import App from './App'
5 | import router from './router'
6 | import axios from 'axios';
7 | Vue.config.productionTip = false
8 |
9 |
10 | /* eslint-disable no-new */
11 | const crmapp = new Vue({
12 | el: '#app',
13 | router,
14 | template: ' ',
15 | components: { App }
16 | })
17 | console.log(crmapp)
18 |
--------------------------------------------------------------------------------
/frontend/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import Reports from '@/components/Reports'
4 | import Home from '@/components/Home'
5 | import DealsList from '@/components/DealsList'
6 | import PendingDeals from '@/components/DealsPending'
7 |
8 |
9 | Vue.use(Router)
10 |
11 | export default new Router({
12 | routes: [
13 | {
14 | path: '/',
15 | name: 'Home',
16 | component: Home
17 | },
18 | {
19 | path:'/deals',
20 | name: 'DealsList',
21 | component: DealsList
22 | },
23 | {
24 | path:'/reports',
25 | name: 'Reports',
26 | component: Reports
27 | },
28 | { path:'/pendingdeals',
29 | name: 'PendingDeals',
30 | component: PendingDeals
31 | }
32 |
33 | ]
34 | })
35 |
--------------------------------------------------------------------------------
/frontend/test/unit/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | },
5 | "globals": {
6 | "expect": true,
7 | "sinon": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/frontend/test/unit/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | Vue.config.productionTip = false
4 |
5 | // require all test files (files that ends with .spec.js)
6 | const testsContext = require.context('./specs', true, /\.spec$/)
7 | testsContext.keys().forEach(testsContext)
8 |
9 | // require all src files except main.js for coverage.
10 | // you can also change this to match only the subset of files that
11 | // you want coverage for.
12 | const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/)
13 | srcContext.keys().forEach(srcContext)
14 |
--------------------------------------------------------------------------------
/frontend/test/unit/karma.conf.js:
--------------------------------------------------------------------------------
1 | // This is a karma config file. For more details see
2 | // http://karma-runner.github.io/0.13/config/configuration-file.html
3 | // we are also using it with karma-webpack
4 | // https://github.com/webpack/karma-webpack
5 |
6 | var webpackConfig = require('../../build/webpack.test.conf')
7 |
8 | module.exports = function (config) {
9 | config.set({
10 | // to run in additional browsers:
11 | // 1. install corresponding karma launcher
12 | // http://karma-runner.github.io/0.13/config/browsers.html
13 | // 2. add it to the `browsers` array below.
14 | browsers: ['PhantomJS'],
15 | frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],
16 | reporters: ['spec', 'coverage'],
17 | files: ['./index.js'],
18 | preprocessors: {
19 | './index.js': ['webpack', 'sourcemap']
20 | },
21 | webpack: webpackConfig,
22 | webpackMiddleware: {
23 | noInfo: true
24 | },
25 | coverageReporter: {
26 | dir: './coverage',
27 | reporters: [
28 | { type: 'lcov', subdir: '.' },
29 | { type: 'text-summary' }
30 | ]
31 | }
32 | })
33 | }
34 |
--------------------------------------------------------------------------------
/frontend/test/unit/specs/Hello.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import HelloWorld from '@/components/HelloWorld'
3 |
4 | describe('HelloWorld.vue', () => {
5 | it('should render correct contents', () => {
6 | const Constructor = Vue.extend(HelloWorld)
7 | const vm = new Constructor().$mount()
8 | expect(vm.$el.querySelector('.hello h1').textContent)
9 | .to.equal('Welcome to Your Vue.js App')
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/prepare.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | usage(){
4 | echo "Usage:"
5 | echo -e "\t./prepare --prod (Install Production dependencies)"
6 | echo -e "\t./prepare --dev (Install Development dependencies)"
7 | echo ""
8 | exit 1
9 | }
10 |
11 | environment=""
12 |
13 |
14 | if [ "$1" != "" ]; then
15 | if [ "$1" == "--prod" ];then
16 | environment="prod"
17 | elif [ "$1" == "--dev" ];then
18 | environment="dev"
19 | else
20 | usage
21 | fi
22 |
23 | else
24 | usage
25 | fi
26 |
27 |
28 |
29 | echo '************************************'
30 | echo 'Installing System-Level Dependencies'
31 | echo '************************************'
32 | echo ''
33 |
34 | if [ $(uname) == 'Linux' ];then
35 | cat requirements.apt | xargs sudo apt-get install -y
36 | sudo curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
37 | else
38 | xargs brew install < requirements.brew
39 | fi
40 |
41 | clear
42 |
43 | echo '************************************'
44 | echo 'Installing Python-Level Dependencies'
45 | echo '************************************'
46 | echo ''
47 |
48 | if [ "$environment" == "prod" ]; then
49 | pip3 install -r requirements.pip
50 | else
51 | pip3 install -r requirements-testing.pip
52 | fi
53 |
54 | clear
55 |
56 | echo '************************************'
57 | echo 'Installing Nodejs-Level Dependencies'
58 | echo '************************************'
59 | echo ''
60 |
61 | if [ "$environment" == "prod" ]; then
62 | echo -e 'No production level npm packages required\n'
63 | else
64 | cat requirements.npm | sudo xargs npm install -g
65 | fi
66 |
67 | echo 'DONE!'
68 |
--------------------------------------------------------------------------------
/requirements-dev.apt:
--------------------------------------------------------------------------------
1 | python3-pip
2 | python3-dev
3 | libffi-dev
4 | nodejs
5 | redis-server
6 | postgresql
7 | postgresql-contrib
8 | curl
9 |
--------------------------------------------------------------------------------
/requirements-testing.pip:
--------------------------------------------------------------------------------
1 | alembic==0.9.5
2 | certifi==2017.7.27.1
3 | cffi==1.11.0
4 | chardet==3.0.4
5 | click==6.7
6 | coverage==4.4.2
7 | decorator==4.1.2
8 | ecdsa==0.13
9 | Faker==0.7.18
10 | Flask==0.12.2
11 | Flask-Admin==1.5.0
12 | Flask-Cache==0.13.1
13 | Flask-GraphQL==1.4.1
14 | Flask-Migrate==2.1.0
15 | Flask-Misaka==0.4.1
16 | Flask-Script==2.0.5
17 | flask-shell-ipython==0.3.0
18 | Flask-SQLAlchemy==2.2
19 | future==0.16.0
20 | graphene==2.0.dev20170802065539
21 | graphene-sqlalchemy==2.0.dev2017083101
22 | graphql-core==2.0.dev20170801051721
23 | graphql-relay==0.4.5
24 | idna==2.6
25 | inbox.py==0.0.6
26 | infinity==1.4
27 | intervals==0.8.0
28 | ipython==6.2.1
29 | ipython-genutils==0.2.0
30 | iso8601==0.1.12
31 | itsdangerous==0.24
32 | jedi==0.11.0
33 | Jinja2==2.9.6
34 | Logbook==1.1.0
35 | Mako==1.0.7
36 | MarkupSafe==1.0
37 | misaka==2.1.0
38 | nose==1.3.7
39 | parso==0.1.0
40 | pexpect==4.2.1
41 | pickleshare==0.7.4
42 | pkg-resources==0.0.0
43 | promise==2.1
44 | prompt-toolkit==1.0.15
45 | psycopg2==2.7.3.1
46 | ptyprocess==0.5.2
47 | pyblake2==0.9.3
48 | pycparser==2.18
49 | pycrypto==2.6.1
50 | Pygments==2.2.0
51 | python-dateutil==2.6.1
52 | python-editor==1.0.3
53 | python-http-client==3.0.0
54 | python-jose==1.4.0
55 | redis==2.10.6
56 | requests==2.18.4
57 | rq==0.9.2
58 | sendgrid==5.3.0
59 | simplegeneric==0.8.1
60 | singledispatch==3.4.0.3
61 | six==1.11.0
62 | SQLAlchemy==1.1.13
63 | SQLAlchemy-Utils==0.32.16
64 | traitlets==4.3.2
65 | typing==3.6.2
66 | ujson==1.35
67 | urllib3==1.22
68 | uWSGI==2.0.15
69 | validators==0.12.0
70 | wcwidth==0.1.7
71 | Werkzeug==0.12.2
72 | WTForms==2.1
73 | WTForms-Alchemy==0.16.5
74 | WTForms-Components==0.10.3
75 |
--------------------------------------------------------------------------------
/requirements.apt:
--------------------------------------------------------------------------------
1 | python3-dev
2 | libffi-dev
3 |
--------------------------------------------------------------------------------
/requirements.brew:
--------------------------------------------------------------------------------
1 | libffi
2 | python3
3 | node
--------------------------------------------------------------------------------
/requirements.npm:
--------------------------------------------------------------------------------
1 | @2fd/graphdoc
2 |
--------------------------------------------------------------------------------
/requirements.pip:
--------------------------------------------------------------------------------
1 | alembic==0.9.5
2 | certifi==2017.7.27.1
3 | cffi==1.11.0
4 | chardet==3.0.4
5 | click==6.7
6 | decorator==4.1.2
7 | ecdsa==0.13
8 | Faker==0.7.18
9 | Flask==0.12.2
10 | Flask-Admin==1.5.0
11 | Flask-Cache==0.13.1
12 | Flask-GraphQL==1.4.1
13 | Flask-Migrate==2.1.0
14 | Flask-Misaka==0.4.1
15 | Flask-Script==2.0.5
16 | flask-shell-ipython==0.3.0
17 | Flask-SQLAlchemy==2.2
18 | future==0.16.0
19 | graphene==2.0.dev20170802065539
20 | graphene-sqlalchemy==2.0.dev2017083101
21 | graphql-core==2.0.dev20170801051721
22 | graphql-relay==0.4.5
23 | idna==2.6
24 | inbox.py==0.0.6
25 | infinity==1.4
26 | intervals==0.8.0
27 | ipython==6.2.1
28 | ipython-genutils==0.2.0
29 | iso8601==0.1.12
30 | itsdangerous==0.24
31 | jedi==0.11.0
32 | Jinja2==2.9.6
33 | Logbook==1.1.0
34 | Mako==1.0.7
35 | MarkupSafe==1.0
36 | misaka==2.1.0
37 | parso==0.1.0
38 | pexpect==4.2.1
39 | pickleshare==0.7.4
40 | promise==2.1
41 | prompt-toolkit==1.0.15
42 | psycopg2==2.7.3.1
43 | ptyprocess==0.5.2
44 | pyblake2==0.9.3
45 | pycparser==2.18
46 | pycrypto==2.6.1
47 | Pygments==2.2.0
48 | python-dateutil==2.6.1
49 | python-editor==1.0.3
50 | python-http-client==3.0.0
51 | python-jose==1.4.0
52 | redis==2.10.6
53 | requests==2.18.4
54 | rq==0.9.2
55 | sendgrid==5.3.0
56 | simplegeneric==0.8.1
57 | singledispatch==3.4.0.3
58 | six==1.11.0
59 | SQLAlchemy==1.1.13
60 | SQLAlchemy-Utils==0.32.16
61 | traitlets==4.3.2
62 | typing==3.6.2
63 | ujson==1.35
64 | urllib3==1.22
65 | uWSGI==2.0.15
66 | validators==0.12.0
67 | wcwidth==0.1.7
68 | Werkzeug==0.12.2
69 | WTForms==2.1
70 | WTForms-Alchemy==0.16.5
71 | WTForms-Components==0.10.3
72 | inbox.py==0.0.6
73 |
--------------------------------------------------------------------------------
/run_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | nosetests --with-coverage --cover-package=crm --cover-html --cover-erase
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | setup(
4 | name='crm',
5 | packages=['crm'],
6 | include_package_data=True,
7 | install_requires=[
8 | 'flask',
9 | 'uwsgi',
10 | ],
11 | )
12 |
--------------------------------------------------------------------------------
/specs/AlertManagement.md:
--------------------------------------------------------------------------------
1 |
2 | ## principles
3 |
4 | - part of CRM app
5 |
6 | ## model
7 |
8 | source
9 | - uid
10 | - title
11 | - description (markdown)
12 | - comments
13 | - itenvironment (link to itenvironment obj if it exists, see assetmgmt)
14 | - project (link to project object)
15 | - links (list of link objects)
16 |
17 |
18 | alert
19 | - uid
20 | - title
21 | - source (link to source)
22 | - source_id #whatever id used on source to identify where the issue comes from (recommend dot notation)
23 | - content (markdown)
24 | - category (dot notation, freely to be chosen by source)
25 | - parentalert (if this is dependent on other alert)
26 | - deviceuid (is assetmgmt used)
27 | - componentuid (is assetmgmt used)
28 | - starttime (auto calculated by system)
29 | - closetime (auto calculated by system)
30 | - state: new, confirmed, closed
31 | - urgency: critical, urgent, normal, minor
32 | - data (for e.g. toml, json or yaml structured content)
33 | - escalationlevel (yellow, orrange, red, green)
34 | - alert_profile
35 | - comments
36 | - tasks
37 | - links
38 | - owner
39 | #can be more than 1 at same time
40 | - type_down: bool (if there is downtime)
41 | - type_performance: bool
42 | - type_security: book
43 | - type_instability: bool
44 | - type_ui :userinterface
45 | - type_possibledataloss
46 |
47 | alert_profile
48 | - uid
49 | - name
50 | - description (markdown)
51 | - configuration: is toml config, depends on type of profile, what this configuration is
52 |
--------------------------------------------------------------------------------
/specs/Beta2Test.md:
--------------------------------------------------------------------------------
1 |
2 | ## what
3 |
4 | describe some use cases for testing our beta2
5 |
6 | ## test 1
7 |
8 | - add task to user/contact/deal/project/sprint/company
9 | - be able to assign 1 user to a task (from user table)
10 | - make sure this task is visible in details view
11 | - make sure we can query the task from task filter list
12 |
13 | ## test 2
14 |
15 | - make sure user can see his assigned tasks & filter on user/contact/deal/project/sprint/company (linked to task)
16 | - this needs to be easy for a user
17 |
18 | ## test 3
19 |
20 | - everyone logs in over IYO, each modified object needs to show author (original & modified) so we know who changed & when
21 | - this needs to be visible in each object in detail mode
22 |
23 | ## test 4
24 |
25 | - test messages, comments & links to user/contact/deal/project/sprint/company
26 | - in detail view on comments we need to see author & moddate (so in relation to last change)
27 |
28 | ## test 5
29 |
30 | - export all data
31 | - in each json there needs to be subobj: tasks, messages, links, comments
32 | - remove the DB
33 | - import all data back
34 | - crm needs to have all data
35 |
36 | ## test 6
37 |
38 | - create python script which on server works with the flask objects & does some manipulations against postgresql
39 |
40 | ## test 10
41 |
42 | - create a python test program, is linked to a subgroup which has rights to crm
43 | - from this test program do a graphql query
44 |
--------------------------------------------------------------------------------
/specs/MailinMailout.md:
--------------------------------------------------------------------------------
1 | ## mail in
2 |
3 | - use python mailserver which processes all incoming mails & attaches them to appropriate object
4 | - e.g. https://github.com/kennethreitz/inbox.py
5 | - mail addresses used will be
6 | - $uid_$objtype@$maildomain e.g. 4jd3_contact@main.threefoldtoken.com
7 | - support@$maildomain will be attached to project: support as ticket
8 | - only recognized senders will be allowed to send
9 | - the author field is used to show where mail comes from (who is originator)
10 | - if author not recognized then we send message back to see we don't recognize you in our system please send email to support@$maildomain to get help
11 | - attachments will be stored in $webdir/files/$blakehash.$extension & linked into the message (markdown)
12 |
13 | ## mail out
14 |
15 | - depending the object we decide to who to send emails e.g. project will send to all users & contacts & companies attached to project, ...
16 | - if the destination field was used before sending then use the email addr as stated there
17 | - we need a state field to know what the state of the email is:
18 | - tosend, failed, ...
19 |
20 |
21 |
--------------------------------------------------------------------------------
/specs/Redis.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | - when delete/change/new for a data object
4 | - put in redis
5 | - hset: $nameofobj(table):$uid -> value: the toml which is the dataobj
6 | - queue:
7 | - msgpack list:
8 | - user_name
9 | - user_email
10 | - nameofobj e.g. task
11 | - uid
12 | - epoch
13 | - so we know what to process from hset
14 | - a separate python process will
15 | - write data to git structure & commit with username
16 |
--------------------------------------------------------------------------------
/specs/Requirements.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | - use as much as possible the tools (lots of autogeneration possible)
4 | - keep everything as simple as possible
5 | - the UID is always the 4 char generated code (lower case number/letter e.g. 5bhs), check dublicates, generate otherone if duplicate
6 | - hosted behind caddy (can do the IYO integration & pass the info)
7 |
--------------------------------------------------------------------------------
/specs/beta2_basic_test_usecases.md:
--------------------------------------------------------------------------------
1 |
2 | ## what
3 |
4 | describe some use cases for testing our beta2
5 |
6 | ## test 1
7 |
8 | - add task to user/contact/deal/project/sprint/company
9 | - be able to assign 1 user to a task (from user table)
10 | - make sure this task is visible in details view
11 | - make sure we can query the task from task filter list
12 |
13 | ## test 2
14 |
15 | - make sure user can see his assigned tasks & filter on user/contact/deal/project/sprint/company (linked to task)
16 | - this needs to be easy for a user
17 |
18 | ## test 3
19 |
20 | - everyone logs in over IYO, each modified object needs to show author (original & modified) so we know who changed & when
21 | - this needs to be visible in each object in detail mode
22 |
23 | ## test 4
24 |
25 | - test messages, comments & links to user/contact/deal/project/sprint/company
26 | - in detail view on comments we need to see author & moddate (so in relation to last change)
27 |
28 | ## test 5
29 |
30 | - export all data
31 | - in each json there needs to be subobj: tasks, messages, links, comments
32 | - remove the DB
33 | - import all data back
34 | - crm needs to have all data
35 |
36 | ## test 6
37 |
38 | - create python script which on server works with the flask objects & does some manipulations against postgresql
39 |
40 | ## test 10
41 |
42 | - create a python test program, is linked to a subgroup which has rights to crm
43 | - from this test program do a graphql query
44 |
45 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Incubaid/crm/5f68ebb3236e1dfeb8378e11591b865a89004bac/tests/__init__.py
--------------------------------------------------------------------------------
/tests/base_tests.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 | import tempfile
4 | import sys
5 |
6 | # crm_base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
7 | # sys.path.insert(0, crm_base)
8 | import crm
9 |
10 |
11 |
12 |
13 | class BaseTestCase(unittest.TestCase):
14 | """
15 | Base testcase
16 | """
17 |
18 | def setUp(self):
19 | """
20 | Setup
21 | """
22 | self.db_fd, crm.app.config['DATABASE'] = tempfile.mkstemp()
23 | crm.app.testing = True
24 | self.app = crm.app.test_client()
25 |
26 |
27 | def tearDown(self):
28 | """
29 | Teardown
30 | """
31 | os.close(self.db_fd)
32 | os.unlink(crm.app.config['DATABASE'])
33 |
34 |
35 | if __name__ == '__main__':
36 | unittest.main()
--------------------------------------------------------------------------------
/tests/test_home.py:
--------------------------------------------------------------------------------
1 | """
2 | Tests for home module
3 | """
4 | import unittest
5 | from tests.base_tests import BaseTestCase
6 |
7 | class HomeTest(BaseTestCase):
8 | """
9 | Test for home module
10 | """
11 | def test_home(self):
12 | """
13 | Test home page
14 | """
15 | rv = self.app.get('/')
16 | assert rv.status_code == 200
17 | assert b'Quick links' in rv.data
18 |
19 |
20 | if __name__ == '__main__':
21 | unittest.main()
--------------------------------------------------------------------------------
/utils/google_spreadsheet_example.gs:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | How to apply:
4 |
5 | Edit -> Current project's trigger then (setFilter, on change)
6 | Resource -> Advanced google services ->
7 |
8 | */
9 |
10 | function setFilter() {
11 |
12 | var ss = SpreadsheetApp.getActiveSpreadsheet();
13 | var import = ss.getSheetByName('IMPORT')
14 | var list = ss.getSheetByName('List')
15 |
16 | list.clear()
17 |
18 | // Copy All data
19 | var data = import.getRange('A:Z').getValues()
20 | for (var i=0; i < data.length; i++){
21 | list.appendRow(data[i])
22 | }
23 |
24 | // Apply filter to list
25 |
26 | var request = {
27 | "setBasicFilter": {
28 | "filter": {
29 | "range" : {
30 | sheetId: list.getSheetId()
31 | },
32 |
33 | "criteria": {
34 | 4 : { 'hiddenValues': ['AMBASSADOR', 'ITFT']},
35 | 5 : {'hiddenValues': ['NEW', 'CLOSED']}
36 | }
37 | }
38 | }
39 | };
40 |
41 | // Apply filter
42 | var data = Sheets.Spreadsheets.batchUpdate({'requests': [request]}, ss.getId());
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/uwsgi.ini:
--------------------------------------------------------------------------------
1 | [uwsgi]
2 | http = 0.0.0.0:5000
3 | module = app:app
4 | processes = 4
5 | threads = 2
6 | stats = 127.0.0.1:9191
7 | die-on-term = true
--------------------------------------------------------------------------------