├── .devcontainer ├── devcontainer.json └── docker-compose.yml ├── .github ├── crm_logo.png ├── helper │ └── update_pot_file.sh ├── logo.png ├── logo.svg ├── screenshots │ ├── CallLog.png │ ├── CallUI.png │ ├── EmailTemplate.png │ ├── FrappeCRMHeroImage.png │ ├── LeadList.png │ ├── LeadPage.png │ └── MainDealPage.png └── workflows │ ├── builds.yml │ ├── ci.yml │ ├── generate-pot-file.yml │ ├── on_release.yml │ └── release_notes.yml ├── .gitignore ├── .gitmodules ├── .mergify.yml ├── .releaserc ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── crm ├── __init__.py ├── api │ ├── __init__.py │ ├── activities.py │ ├── auth.py │ ├── comment.py │ ├── contact.py │ ├── demo.py │ ├── doc.py │ ├── notifications.py │ ├── onboarding.py │ ├── session.py │ ├── settings.py │ ├── todo.py │ ├── views.py │ └── whatsapp.py ├── config │ └── __init__.py ├── crowdin.yml ├── fcrm │ ├── __init__.py │ ├── doctype │ │ ├── __init__.py │ │ ├── crm_call_log │ │ │ ├── __init__.py │ │ │ ├── crm_call_log.js │ │ │ ├── crm_call_log.json │ │ │ ├── crm_call_log.py │ │ │ └── test_crm_call_log.py │ │ ├── crm_communication_status │ │ │ ├── __init__.py │ │ │ ├── crm_communication_status.js │ │ │ ├── crm_communication_status.json │ │ │ ├── crm_communication_status.py │ │ │ └── test_crm_communication_status.py │ │ ├── crm_contacts │ │ │ ├── __init__.py │ │ │ ├── crm_contacts.json │ │ │ └── crm_contacts.py │ │ ├── crm_deal │ │ │ ├── __init__.py │ │ │ ├── api.py │ │ │ ├── crm_deal.js │ │ │ ├── crm_deal.json │ │ │ ├── crm_deal.py │ │ │ └── test_crm_deal.py │ │ ├── crm_deal_status │ │ │ ├── __init__.py │ │ │ ├── crm_deal_status.js │ │ │ ├── crm_deal_status.json │ │ │ ├── crm_deal_status.py │ │ │ └── test_crm_deal_status.py │ │ ├── crm_dropdown_item │ │ │ ├── __init__.py │ │ │ ├── crm_dropdown_item.json │ │ │ └── crm_dropdown_item.py │ │ ├── crm_exotel_settings │ │ │ ├── __init__.py │ │ │ ├── crm_exotel_settings.js │ │ │ ├── crm_exotel_settings.json │ │ │ ├── crm_exotel_settings.py │ │ │ └── test_crm_exotel_settings.py │ │ ├── crm_fields_layout │ │ │ ├── __init__.py │ │ │ ├── crm_fields_layout.js │ │ │ ├── crm_fields_layout.json │ │ │ ├── crm_fields_layout.py │ │ │ └── test_crm_fields_layout.py │ │ ├── crm_form_script │ │ │ ├── __init__.py │ │ │ ├── crm_form_script.js │ │ │ ├── crm_form_script.json │ │ │ ├── crm_form_script.py │ │ │ └── test_crm_form_script.py │ │ ├── crm_global_settings │ │ │ ├── __init__.py │ │ │ ├── crm_global_settings.js │ │ │ ├── crm_global_settings.json │ │ │ ├── crm_global_settings.py │ │ │ └── test_crm_global_settings.py │ │ ├── crm_holiday │ │ │ ├── __init__.py │ │ │ ├── crm_holiday.json │ │ │ └── crm_holiday.py │ │ ├── crm_holiday_list │ │ │ ├── __init__.py │ │ │ ├── crm_holiday_list.js │ │ │ ├── crm_holiday_list.json │ │ │ ├── crm_holiday_list.py │ │ │ └── test_crm_holiday_list.py │ │ ├── crm_industry │ │ │ ├── __init__.py │ │ │ ├── crm_industry.js │ │ │ ├── crm_industry.json │ │ │ ├── crm_industry.py │ │ │ └── test_crm_industry.py │ │ ├── crm_invitation │ │ │ ├── __init__.py │ │ │ ├── crm_invitation.js │ │ │ ├── crm_invitation.json │ │ │ ├── crm_invitation.py │ │ │ └── test_crm_invitation.py │ │ ├── crm_lead │ │ │ ├── __init__.py │ │ │ ├── api.py │ │ │ ├── crm_lead.js │ │ │ ├── crm_lead.json │ │ │ ├── crm_lead.py │ │ │ └── test_crm_lead.py │ │ ├── crm_lead_source │ │ │ ├── __init__.py │ │ │ ├── crm_lead_source.js │ │ │ ├── crm_lead_source.json │ │ │ ├── crm_lead_source.py │ │ │ └── test_crm_lead_source.py │ │ ├── crm_lead_status │ │ │ ├── __init__.py │ │ │ ├── crm_lead_status.js │ │ │ ├── crm_lead_status.json │ │ │ ├── crm_lead_status.py │ │ │ └── test_crm_lead_status.py │ │ ├── crm_notification │ │ │ ├── __init__.py │ │ │ ├── crm_notification.js │ │ │ ├── crm_notification.json │ │ │ ├── crm_notification.py │ │ │ └── test_crm_notification.py │ │ ├── crm_organization │ │ │ ├── __init__.py │ │ │ ├── crm_organization.js │ │ │ ├── crm_organization.json │ │ │ ├── crm_organization.py │ │ │ └── test_crm_organization.py │ │ ├── crm_product │ │ │ ├── __init__.py │ │ │ ├── crm_product.js │ │ │ ├── crm_product.json │ │ │ ├── crm_product.py │ │ │ └── test_crm_product.py │ │ ├── crm_products │ │ │ ├── __init__.py │ │ │ ├── crm_products.json │ │ │ └── crm_products.py │ │ ├── crm_service_day │ │ │ ├── __init__.py │ │ │ ├── crm_service_day.json │ │ │ └── crm_service_day.py │ │ ├── crm_service_level_agreement │ │ │ ├── __init__.py │ │ │ ├── crm_service_level_agreement.js │ │ │ ├── crm_service_level_agreement.json │ │ │ ├── crm_service_level_agreement.py │ │ │ ├── test_crm_service_level_agreement.py │ │ │ └── utils.py │ │ ├── crm_service_level_priority │ │ │ ├── __init__.py │ │ │ ├── crm_service_level_priority.js │ │ │ ├── crm_service_level_priority.json │ │ │ ├── crm_service_level_priority.py │ │ │ └── test_crm_service_level_priority.py │ │ ├── crm_status_change_log │ │ │ ├── __init__.py │ │ │ ├── crm_status_change_log.json │ │ │ └── crm_status_change_log.py │ │ ├── crm_task │ │ │ ├── __init__.py │ │ │ ├── crm_task.js │ │ │ ├── crm_task.json │ │ │ ├── crm_task.py │ │ │ └── test_crm_task.py │ │ ├── crm_telephony_agent │ │ │ ├── __init__.py │ │ │ ├── crm_telephony_agent.js │ │ │ ├── crm_telephony_agent.json │ │ │ ├── crm_telephony_agent.py │ │ │ └── test_crm_telephony_agent.py │ │ ├── crm_telephony_phone │ │ │ ├── __init__.py │ │ │ ├── crm_telephony_phone.json │ │ │ └── crm_telephony_phone.py │ │ ├── crm_territory │ │ │ ├── __init__.py │ │ │ ├── crm_territory.js │ │ │ ├── crm_territory.json │ │ │ ├── crm_territory.py │ │ │ └── test_crm_territory.py │ │ ├── crm_twilio_settings │ │ │ ├── __init__.py │ │ │ ├── crm_twilio_settings.js │ │ │ ├── crm_twilio_settings.json │ │ │ ├── crm_twilio_settings.py │ │ │ └── test_crm_twilio_settings.py │ │ ├── crm_view_settings │ │ │ ├── __init__.py │ │ │ ├── crm_view_settings.js │ │ │ ├── crm_view_settings.json │ │ │ ├── crm_view_settings.py │ │ │ └── test_crm_view_settings.py │ │ ├── erpnext_crm_settings │ │ │ ├── __init__.py │ │ │ ├── erpnext_crm_settings.js │ │ │ ├── erpnext_crm_settings.json │ │ │ ├── erpnext_crm_settings.py │ │ │ └── test_erpnext_crm_settings.py │ │ ├── fcrm_note │ │ │ ├── __init__.py │ │ │ ├── fcrm_note.js │ │ │ ├── fcrm_note.json │ │ │ ├── fcrm_note.py │ │ │ └── test_fcrm_note.py │ │ └── fcrm_settings │ │ │ ├── __init__.py │ │ │ ├── fcrm_settings.js │ │ │ ├── fcrm_settings.json │ │ │ ├── fcrm_settings.py │ │ │ └── test_fcrm_settings.py │ └── workspace │ │ └── frappe_crm │ │ └── frappe_crm.json ├── hooks.py ├── install.py ├── integrations │ ├── __init__.py │ ├── api.py │ ├── exotel │ │ └── handler.py │ └── twilio │ │ ├── api.py │ │ ├── twilio_handler.py │ │ └── utils.py ├── locale │ ├── ar.po │ ├── bs.po │ ├── de.po │ ├── eo.po │ ├── es.po │ ├── fa.po │ ├── fr.po │ ├── hr.po │ ├── hu.po │ ├── main.pot │ ├── pl.po │ ├── pt.po │ ├── ru.po │ ├── sv.po │ ├── th.po │ ├── tr.po │ └── zh.po ├── modules.txt ├── overrides │ ├── contact.py │ └── email_template.py ├── patches.txt ├── patches │ └── v1_0 │ │ ├── __init__.py │ │ ├── create_default_fields_layout.py │ │ ├── create_default_scripts.py │ │ ├── create_default_sidebar_fields_layout.py │ │ ├── create_email_template_custom_fields.py │ │ ├── move_crm_note_data_to_fcrm_note.py │ │ ├── move_twilio_agent_to_telephony_agent.py │ │ ├── rename_twilio_settings_to_crm_twilio_settings.py │ │ ├── update_deal_quick_entry_layout.py │ │ └── update_layouts_to_new_format.py ├── public │ ├── .gitkeep │ ├── images │ │ ├── desk.png │ │ ├── logo.png │ │ └── logo.svg │ ├── manifest │ │ ├── apple-icon-180.png │ │ ├── apple-splash-1125-2436.jpg │ │ ├── apple-splash-1136-640.jpg │ │ ├── apple-splash-1170-2532.jpg │ │ ├── apple-splash-1179-2556.jpg │ │ ├── apple-splash-1242-2208.jpg │ │ ├── apple-splash-1242-2688.jpg │ │ ├── apple-splash-1284-2778.jpg │ │ ├── apple-splash-1290-2796.jpg │ │ ├── apple-splash-1334-750.jpg │ │ ├── apple-splash-1488-2266.jpg │ │ ├── apple-splash-1536-2048.jpg │ │ ├── apple-splash-1620-2160.jpg │ │ ├── apple-splash-1640-2360.jpg │ │ ├── apple-splash-1668-2224.jpg │ │ ├── apple-splash-1668-2388.jpg │ │ ├── apple-splash-1792-828.jpg │ │ ├── apple-splash-2048-1536.jpg │ │ ├── apple-splash-2048-2732.jpg │ │ ├── apple-splash-2160-1620.jpg │ │ ├── apple-splash-2208-1242.jpg │ │ ├── apple-splash-2224-1668.jpg │ │ ├── apple-splash-2266-1488.jpg │ │ ├── apple-splash-2360-1640.jpg │ │ ├── apple-splash-2388-1668.jpg │ │ ├── apple-splash-2436-1125.jpg │ │ ├── apple-splash-2532-1170.jpg │ │ ├── apple-splash-2556-1179.jpg │ │ ├── apple-splash-2688-1242.jpg │ │ ├── apple-splash-2732-2048.jpg │ │ ├── apple-splash-2778-1284.jpg │ │ ├── apple-splash-2796-1290.jpg │ │ ├── apple-splash-640-1136.jpg │ │ ├── apple-splash-750-1334.jpg │ │ ├── apple-splash-828-1792.jpg │ │ ├── manifest-icon-192.maskable.png │ │ └── manifest-icon-512.maskable.png │ └── videos │ │ ├── changeDealStatus.mov │ │ └── convertToDeal.mov ├── templates │ ├── __init__.py │ ├── emails │ │ └── crm_invitation.html │ └── pages │ │ └── __init__.py ├── uninstall.py ├── utils │ └── __init__.py └── www │ ├── __init__.py │ └── crm.py ├── crowdin.yml ├── docker ├── docker-compose.yml └── init.sh ├── frontend ├── .gitignore ├── .prettierrc.json ├── README.md ├── components.d.ts ├── index.html ├── package.json ├── postcss.config.js ├── public │ └── favicon.png ├── src │ ├── App.vue │ ├── components │ │ ├── Activities │ │ │ ├── Activities.vue │ │ │ ├── ActivityHeader.vue │ │ │ ├── AllModals.vue │ │ │ ├── AttachmentArea.vue │ │ │ ├── AudioPlayer.vue │ │ │ ├── CallArea.vue │ │ │ ├── CommentArea.vue │ │ │ ├── DataFields.vue │ │ │ ├── EmailArea.vue │ │ │ ├── EmailContent.vue │ │ │ ├── NoteArea.vue │ │ │ ├── PlaybackSpeedOption.vue │ │ │ ├── TaskArea.vue │ │ │ ├── WhatsAppArea.vue │ │ │ └── WhatsAppBox.vue │ │ ├── Apps.vue │ │ ├── AssignTo.vue │ │ ├── AttachmentItem.vue │ │ ├── BrandLogo.vue │ │ ├── ColumnSettings.vue │ │ ├── CommentBox.vue │ │ ├── CommunicationArea.vue │ │ ├── Controls │ │ │ ├── FormattedInput.vue │ │ │ ├── Grid.vue │ │ │ ├── GridFieldsEditorModal.vue │ │ │ ├── GridRowFieldsModal.vue │ │ │ ├── GridRowModal.vue │ │ │ ├── ImageUploader.vue │ │ │ ├── Link.vue │ │ │ ├── MultiSelectEmailInput.vue │ │ │ ├── Password.vue │ │ │ └── TableMultiselectInput.vue │ │ ├── CountUpTimer.vue │ │ ├── CustomActions.vue │ │ ├── DropdownItem.vue │ │ ├── EmailEditor.vue │ │ ├── ErrorPage.vue │ │ ├── FadedScrollableDiv.vue │ │ ├── FieldLayout │ │ │ ├── Column.vue │ │ │ ├── Field.vue │ │ │ ├── FieldLayout.vue │ │ │ └── Section.vue │ │ ├── FieldLayoutEditor.vue │ │ ├── FilesUploader │ │ │ ├── FilesUploader.vue │ │ │ ├── FilesUploaderArea.vue │ │ │ └── filesUploaderHandler.ts │ │ ├── Filter.vue │ │ ├── GroupBy.vue │ │ ├── Icon.vue │ │ ├── IconPicker.vue │ │ ├── Icons │ │ │ ├── ActivityIcon.vue │ │ │ ├── AddressIcon.vue │ │ │ ├── AppsIcon.vue │ │ │ ├── ArrowUpRightIcon.vue │ │ │ ├── AscendingIcon.vue │ │ │ ├── AttachmentIcon.vue │ │ │ ├── AvatarIcon.vue │ │ │ ├── CRMLogo.vue │ │ │ ├── CalendarIcon.vue │ │ │ ├── CameraIcon.vue │ │ │ ├── CertificateIcon.vue │ │ │ ├── CheckCircleIcon.vue │ │ │ ├── CheckIcon.vue │ │ │ ├── CollapseSidebar.vue │ │ │ ├── ColumnsIcon.vue │ │ │ ├── CommentIcon.vue │ │ │ ├── ContactIcon.vue │ │ │ ├── ContactsIcon.vue │ │ │ ├── ConvertIcon.vue │ │ │ ├── DashboardIcon.vue │ │ │ ├── DealsIcon.vue │ │ │ ├── DeclinedCallIcon.vue │ │ │ ├── DesendingIcon.vue │ │ │ ├── DetailsIcon.vue │ │ │ ├── DialpadIcon.vue │ │ │ ├── DocumentIcon.vue │ │ │ ├── DotIcon.vue │ │ │ ├── DoubleCheckIcon.vue │ │ │ ├── DragIcon.vue │ │ │ ├── DragVerticalIcon.vue │ │ │ ├── DuplicateIcon.vue │ │ │ ├── DurationIcon.vue │ │ │ ├── ERPNextIcon.vue │ │ │ ├── EditIcon.vue │ │ │ ├── Email2Icon.vue │ │ │ ├── EmailAtIcon.vue │ │ │ ├── EmailIcon.vue │ │ │ ├── ExportIcon.vue │ │ │ ├── ExternalLinkIcon.vue │ │ │ ├── FileAudioIcon.vue │ │ │ ├── FileIcon.vue │ │ │ ├── FileImageIcon.vue │ │ │ ├── FileSpreadsheetIcon.vue │ │ │ ├── FileTextIcon.vue │ │ │ ├── FileTypeIcon.vue │ │ │ ├── FileVideoIcon.vue │ │ │ ├── FilterIcon.vue │ │ │ ├── FrappeCloudIcon.vue │ │ │ ├── GenderIcon.vue │ │ │ ├── GoogleIcon.vue │ │ │ ├── GroupByIcon.vue │ │ │ ├── HeartIcon.vue │ │ │ ├── HelpIcon.vue │ │ │ ├── InboundCallIcon.vue │ │ │ ├── InboxIcon.vue │ │ │ ├── IndicatorIcon.vue │ │ │ ├── InviteIcon.vue │ │ │ ├── KanbanIcon.vue │ │ │ ├── LeadsIcon.vue │ │ │ ├── LightningIcon.vue │ │ │ ├── LinkIcon.vue │ │ │ ├── ListIcon.vue │ │ │ ├── LoadingIndicator.vue │ │ │ ├── MarkAsDoneIcon.vue │ │ │ ├── MaximizeIcon.vue │ │ │ ├── MenuIcon.vue │ │ │ ├── MinimizeIcon.vue │ │ │ ├── MissedCallIcon.vue │ │ │ ├── MoneyIcon.vue │ │ │ ├── MuteIcon.vue │ │ │ ├── NoteIcon.vue │ │ │ ├── NotificationsIcon.vue │ │ │ ├── OrganizationsIcon.vue │ │ │ ├── OutboundCallIcon.vue │ │ │ ├── PauseIcon.vue │ │ │ ├── PhoneIcon.vue │ │ │ ├── PinIcon.vue │ │ │ ├── PlayIcon.vue │ │ │ ├── PlaybackSpeedIcon.vue │ │ │ ├── QuickFilterIcon.vue │ │ │ ├── ReactIcon.vue │ │ │ ├── RefreshIcon.vue │ │ │ ├── ReloadIcon.vue │ │ │ ├── ReplyAllIcon.vue │ │ │ ├── ReplyIcon.vue │ │ │ ├── RightSideLayoutIcon.vue │ │ │ ├── SelectIcon.vue │ │ │ ├── SettingsIcon.vue │ │ │ ├── SmileIcon.vue │ │ │ ├── SortIcon.vue │ │ │ ├── SquareAsterisk.vue │ │ │ ├── StepsIcon.vue │ │ │ ├── SuccessIcon.vue │ │ │ ├── TaskIcon.vue │ │ │ ├── TaskPriorityIcon.vue │ │ │ ├── TaskStatusIcon.vue │ │ │ ├── TelegramIcon.vue │ │ │ ├── TerritoryIcon.vue │ │ │ ├── UnpinIcon.vue │ │ │ ├── VolumnHighIcon.vue │ │ │ ├── VolumnLowIcon.vue │ │ │ ├── WebsiteIcon.vue │ │ │ └── WhatsAppIcon.vue │ │ ├── Kanban │ │ │ ├── KanbanSettings.vue │ │ │ └── KanbanView.vue │ │ ├── LayoutHeader.vue │ │ ├── Layouts │ │ │ ├── AppHeader.vue │ │ │ ├── AppSidebar.vue │ │ │ ├── DesktopLayout.vue │ │ │ └── MobileLayout.vue │ │ ├── ListBulkActions.vue │ │ ├── ListViews │ │ │ ├── CallLogsListView.vue │ │ │ ├── ContactsListView.vue │ │ │ ├── DealsListView.vue │ │ │ ├── EmailTemplatesListView.vue │ │ │ ├── LeadsListView.vue │ │ │ ├── ListRows.vue │ │ │ ├── OrganizationsListView.vue │ │ │ └── TasksListView.vue │ │ ├── Mobile │ │ │ ├── MobileAppHeader.vue │ │ │ └── MobileSidebar.vue │ │ ├── Modals │ │ │ ├── AboutModal.vue │ │ │ ├── AddressModal.vue │ │ │ ├── AssignmentModal.vue │ │ │ ├── CallLogDetailModal.vue │ │ │ ├── CallLogModal.vue │ │ │ ├── ContactModal.vue │ │ │ ├── CreateDocumentModal.vue │ │ │ ├── DataFieldsModal.vue │ │ │ ├── DealModal.vue │ │ │ ├── EditValueModal.vue │ │ │ ├── EmailTemplateModal.vue │ │ │ ├── EmailTemplateSelectorModal.vue │ │ │ ├── GlobalModals.vue │ │ │ ├── LeadModal.vue │ │ │ ├── NoteModal.vue │ │ │ ├── OrganizationModal.vue │ │ │ ├── QuickEntryModal.vue │ │ │ ├── SidePanelModal.vue │ │ │ ├── TaskModal.vue │ │ │ ├── ViewModal.vue │ │ │ └── WhatsappTemplateSelectorModal.vue │ │ ├── MultiActionButton.vue │ │ ├── MultipleAvatar.vue │ │ ├── NestedPopover.vue │ │ ├── Notifications.vue │ │ ├── QuickFilterField.vue │ │ ├── Resizer.vue │ │ ├── SLASection.vue │ │ ├── Section.vue │ │ ├── Settings │ │ │ ├── ERPNextSettings.vue │ │ │ ├── EmailAccountCard.vue │ │ │ ├── EmailAccountList.vue │ │ │ ├── EmailAdd.vue │ │ │ ├── EmailConfig.vue │ │ │ ├── EmailEdit.vue │ │ │ ├── EmailProviderIcon.vue │ │ │ ├── GeneralSettings.vue │ │ │ ├── InviteMemberPage.vue │ │ │ ├── ProfileImageEditor.vue │ │ │ ├── ProfileSettings.vue │ │ │ ├── Settings.vue │ │ │ ├── SettingsPage.vue │ │ │ ├── TelephonySettings.vue │ │ │ ├── WhatsAppSettings.vue │ │ │ └── emailConfig.js │ │ ├── SidePanelLayout.vue │ │ ├── SidePanelLayoutEditor.vue │ │ ├── SidebarLink.vue │ │ ├── SortBy.vue │ │ ├── Telephony │ │ │ ├── CallUI.vue │ │ │ ├── ExotelCallUI.vue │ │ │ ├── TaskPanel.vue │ │ │ └── TwilioCallUI.vue │ │ ├── UserAvatar.vue │ │ ├── UserDropdown.vue │ │ ├── ViewBreadcrumbs.vue │ │ ├── ViewControls.vue │ │ └── frappe-ui │ │ │ ├── Autocomplete.vue │ │ │ ├── Dropdown.vue │ │ │ └── Popover.vue │ ├── composables │ │ ├── document.js │ │ ├── frappecloud.js │ │ ├── settings.js │ │ ├── twilio.js │ │ └── useActiveTabManager.js │ ├── data │ │ ├── document.js │ │ └── script.js │ ├── images │ │ ├── frappe-mail.svg │ │ ├── gmail.png │ │ ├── outlook.png │ │ ├── sendgrid.png │ │ ├── sparkpost.webp │ │ ├── yahoo.png │ │ └── yandex.png │ ├── index.css │ ├── main.js │ ├── pages │ │ ├── CallLogs.vue │ │ ├── Contact.vue │ │ ├── Contacts.vue │ │ ├── Dashboard.vue │ │ ├── Deal.vue │ │ ├── Deals.vue │ │ ├── EmailTemplate.vue │ │ ├── EmailTemplates.vue │ │ ├── InvalidPage.vue │ │ ├── Lead.vue │ │ ├── Leads.vue │ │ ├── MobileContact.vue │ │ ├── MobileDeal.vue │ │ ├── MobileLead.vue │ │ ├── MobileNotification.vue │ │ ├── MobileOrganization.vue │ │ ├── Notes.vue │ │ ├── Organization.vue │ │ ├── Organizations.vue │ │ ├── Tasks.vue │ │ └── Welcome.vue │ ├── router.js │ ├── socket.js │ ├── stores │ │ ├── global.js │ │ ├── meta.js │ │ ├── notifications.js │ │ ├── organizations.js │ │ ├── session.js │ │ ├── settings.js │ │ ├── statuses.js │ │ ├── theme.js │ │ ├── user.js │ │ ├── users.js │ │ └── views.js │ ├── telemetry.ts │ ├── translation.js │ ├── types.ts │ └── utils │ │ ├── callLog.js │ │ ├── dialogs.jsx │ │ ├── index.js │ │ ├── numberFormat.js │ │ └── view.js ├── tailwind.config.js └── vite.config.js ├── package.json ├── pyproject.toml ├── scripts └── init.sh └── yarn.lock /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Frappe Bench", 3 | "forwardPorts": [8000, 9000, 6787], 4 | "remoteUser": "frappe", 5 | "settings": { 6 | "terminal.integrated.defaultProfile.linux": "bash", 7 | "debug.node.autoAttach": "disabled" 8 | }, 9 | "dockerComposeFile": "./docker-compose.yml", 10 | "service": "frappe", 11 | "workspaceFolder": "/workspace/frappe-bench", 12 | "postCreateCommand": "bash /workspace/scripts/init.sh", 13 | "shutdownAction": "stopCompose", 14 | "extensions": [ 15 | "ms-python.python", 16 | "ms-vscode.live-server", 17 | "grapecity.gc-excelviewer", 18 | "mtxr.sqltools", 19 | "visualstudioexptteam.vscodeintellicode" 20 | ] 21 | } -------------------------------------------------------------------------------- /.devcontainer/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | mariadb: 4 | image: mariadb:10.6 5 | command: 6 | - --character-set-server=utf8mb4 7 | - --collation-server=utf8mb4_unicode_ci 8 | - --skip-character-set-client-handshake 9 | - --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6 10 | environment: 11 | MYSQL_ROOT_PASSWORD: 123 12 | volumes: 13 | - mariadb-data:/var/lib/mysql 14 | 15 | # Enable PostgreSQL only if you use it, see development/README.md for more information. 16 | # postgresql: 17 | # image: postgres:11.8 18 | # environment: 19 | # POSTGRES_PASSWORD: 123 20 | # volumes: 21 | # - postgresql-data:/var/lib/postgresql/data 22 | 23 | redis-cache: 24 | image: redis:alpine 25 | 26 | redis-queue: 27 | image: redis:alpine 28 | 29 | redis-socketio: 30 | image: redis:alpine 31 | 32 | frappe: 33 | image: frappe/bench:latest 34 | command: sleep infinity 35 | environment: 36 | - SHELL=/bin/bash 37 | volumes: 38 | - ..:/workspace:cached 39 | working_dir: /workspace/frappe-bench 40 | ports: 41 | - 8000-8005:8000-8005 42 | - 9000-9005:9000-9005 43 | 44 | volumes: 45 | mariadb-data: 46 | postgresql-data: -------------------------------------------------------------------------------- /.github/crm_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/.github/crm_logo.png -------------------------------------------------------------------------------- /.github/helper/update_pot_file.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | cd ~ || exit 4 | 5 | echo "Setting Up Bench..." 6 | 7 | pip install frappe-bench 8 | bench -v init frappe-bench --skip-assets --skip-redis-config-generation --python "$(which python)" --frappe-branch "${BASE_BRANCH}" 9 | cd ./frappe-bench || exit 10 | 11 | echo "Get FCRM..." 12 | bench get-app --skip-assets crm "${GITHUB_WORKSPACE}" 13 | 14 | echo "Generating POT file..." 15 | bench generate-pot-file --app crm 16 | 17 | cd ./apps/crm || exit 18 | 19 | echo "Configuring git user..." 20 | git config user.email "developers@erpnext.com" 21 | git config user.name "frappe-pr-bot" 22 | 23 | echo "Setting the correct git remote..." 24 | # Here, the git remote is a local file path by default. Let's change it to the upstream repo. 25 | git remote set-url upstream https://github.com/frappe/crm.git 26 | 27 | echo "Creating a new branch..." 28 | isodate=$(date -u +"%Y-%m-%d") 29 | branch_name="pot_${BASE_BRANCH}_${isodate}" 30 | git checkout -b "${branch_name}" 31 | 32 | echo "Commiting changes..." 33 | git add crm/locale/main.pot 34 | git commit -m "chore: update POT file" 35 | 36 | gh auth setup-git 37 | git push -u upstream "${branch_name}" 38 | 39 | echo "Creating a PR..." 40 | gh pr create --fill --base "${BASE_BRANCH}" --head "${branch_name}" -R frappe/crm -------------------------------------------------------------------------------- /.github/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/.github/logo.png -------------------------------------------------------------------------------- /.github/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.github/screenshots/CallLog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/.github/screenshots/CallLog.png -------------------------------------------------------------------------------- /.github/screenshots/CallUI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/.github/screenshots/CallUI.png -------------------------------------------------------------------------------- /.github/screenshots/EmailTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/.github/screenshots/EmailTemplate.png -------------------------------------------------------------------------------- /.github/screenshots/FrappeCRMHeroImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/.github/screenshots/FrappeCRMHeroImage.png -------------------------------------------------------------------------------- /.github/screenshots/LeadList.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/.github/screenshots/LeadList.png -------------------------------------------------------------------------------- /.github/screenshots/LeadPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/.github/screenshots/LeadPage.png -------------------------------------------------------------------------------- /.github/screenshots/MainDealPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/.github/screenshots/MainDealPage.png -------------------------------------------------------------------------------- /.github/workflows/generate-pot-file.yml: -------------------------------------------------------------------------------- 1 | name: Regenerate POT file (translatable strings) 2 | on: 3 | schedule: 4 | # 9:30 UTC => 3 PM IST Sunday 5 | - cron: "30 9 * * 0" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | regenerate-pot-file: 10 | name: Regenerate POT file 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | branch: ["develop"] 16 | permissions: 17 | contents: write 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | with: 23 | ref: ${{ matrix.branch }} 24 | 25 | - name: Setup Python 26 | uses: actions/setup-python@v5 27 | with: 28 | python-version: "3.12" 29 | 30 | - name: Run script to update POT file 31 | run: | 32 | bash ${GITHUB_WORKSPACE}/.github/helper/update_pot_file.sh 33 | env: 34 | GH_TOKEN: ${{ secrets.RELEASE_TOKEN }} 35 | BASE_BRANCH: ${{ matrix.branch }} -------------------------------------------------------------------------------- /.github/workflows/on_release.yml: -------------------------------------------------------------------------------- 1 | name: Generate Semantic Release 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | jobs: 8 | release: 9 | name: Release 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout Entire Repository 13 | uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | persist-credentials: false 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: 20 21 | - name: Setup dependencies 22 | run: | 23 | npm install @semantic-release/git @semantic-release/exec --no-save 24 | - name: Create Release 25 | env: 26 | GH_TOKEN: ${{ secrets.RELEASE_TOKEN }} 27 | GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} 28 | GIT_AUTHOR_NAME: "Frappe PR Bot" 29 | GIT_AUTHOR_EMAIL: "developers@frappe.io" 30 | GIT_COMMITTER_NAME: "Frappe PR Bot" 31 | GIT_COMMITTER_EMAIL: "developers@frappe.io" 32 | run: npx semantic-release -------------------------------------------------------------------------------- /.github/workflows/release_notes.yml: -------------------------------------------------------------------------------- 1 | # This action: 2 | # 3 | # 1. Generates release notes using github API. 4 | # 2. Strips unnecessary info like chore/style etc from notes. 5 | # 3. Updates release info. 6 | 7 | name: 'Release Notes' 8 | 9 | on: 10 | workflow_dispatch: 11 | inputs: 12 | tag_name: 13 | description: 'Tag of release like v1.0.0' 14 | required: true 15 | type: string 16 | release: 17 | types: [released] 18 | 19 | permissions: 20 | contents: read 21 | 22 | jobs: 23 | regen-notes: 24 | name: 'Regenerate release notes' 25 | runs-on: ubuntu-latest 26 | 27 | steps: 28 | - name: Update notes 29 | run: | 30 | NEW_NOTES=$(gh api --method POST -H "Accept: application/vnd.github+json" /repos/frappe/crm/releases/generate-notes -f tag_name=$RELEASE_TAG \ 31 | | jq -r '.body' \ 32 | | sed -E '/^\* (chore|ci|test|docs|style)/d' \ 33 | | sed -E 's/by @mergify //' 34 | ) 35 | RELEASE_ID=$(gh api -H "Accept: application/vnd.github+json" /repos/frappe/crm/releases/tags/$RELEASE_TAG | jq -r '.id') 36 | gh api --method PATCH -H "Accept: application/vnd.github+json" /repos/frappe/crm/releases/$RELEASE_ID -f body="$NEW_NOTES" 37 | 38 | env: 39 | GH_TOKEN: ${{ secrets.RELEASE_TOKEN }} 40 | RELEASE_TAG: ${{ github.event.inputs.tag_name || github.event.release.tag_name }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.egg-info 4 | *.swp 5 | __pycache__ 6 | dev-dist 7 | tags 8 | node_modules 9 | crm/public/frontend 10 | frontend/yarn.lock 11 | crm/www/crm.html 12 | build 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "frappe-ui"] 2 | path = frappe-ui 3 | url = https://github.com/frappe/frappe-ui 4 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: Auto-close PRs on stable branch 3 | conditions: 4 | - and: 5 | - and: 6 | - author!=shariquerik 7 | - author!=frappe-pr-bot 8 | - author!=mergify[bot] 9 | - or: 10 | - base=main 11 | actions: 12 | close: 13 | comment: 14 | message: | 15 | @{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on develop branch. 16 | 17 | - name: backport to develop 18 | conditions: 19 | - label="backport develop" 20 | actions: 21 | backport: 22 | branches: 23 | - develop 24 | assignees: 25 | - "{{ author }}" 26 | 27 | - name: backport to main-hotfix 28 | conditions: 29 | - label="backport main-hotfix" 30 | actions: 31 | backport: 32 | branches: 33 | - main-hotfix 34 | assignees: 35 | - "{{ author }}" 36 | 37 | - name: backport to main 38 | conditions: 39 | - label="backport main" 40 | actions: 41 | backport: 42 | branches: 43 | - main 44 | assignees: 45 | - "{{ author }}" 46 | -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": ["main"], 3 | "plugins": [ 4 | "@semantic-release/commit-analyzer", { 5 | "preset": "angular" 6 | }, 7 | "@semantic-release/release-notes-generator", 8 | [ 9 | "@semantic-release/exec", { 10 | "prepareCmd": 'sed -ir "s/[0-9]*\.[0-9]*\.[0-9]*/${nextRelease.version}/" crm/__init__.py' 11 | } 12 | ], 13 | [ 14 | "@semantic-release/git", { 15 | "assets": ["crm/__init__.py"], 16 | "message": "chore(release): Bumped to Version ${nextRelease.version}" 17 | } 18 | ], 19 | "@semantic-release/github" 20 | ] 21 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.defaultInterpreterPath": "frappe-bench/env/bin/python", 3 | "debug.node.autoAttach": "disabled", 4 | "sqltools.connections": [ 5 | { 6 | "mysqlOptions": { 7 | "authProtocol": "default" 8 | }, 9 | "previewLimit": 50, 10 | "server": "mariadb", 11 | "port": 3306, 12 | "driver": "MariaDB", 13 | "name": "MariaDB", 14 | "username": "root", 15 | "password": "123" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /crm/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | __version__ = "2.0.0-dev" 3 | __title__ = "Frappe CRM" 4 | 5 | -------------------------------------------------------------------------------- /crm/api/auth.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | 3 | @frappe.whitelist(allow_guest=True) 4 | def oauth_providers(): 5 | from frappe.utils.html_utils import get_icon_html 6 | from frappe.utils.password import get_decrypted_password 7 | from frappe.utils.oauth import get_oauth2_authorize_url, get_oauth_keys 8 | 9 | out = [] 10 | providers = frappe.get_all( 11 | "Social Login Key", 12 | filters={"enable_social_login": 1}, 13 | fields=["name", "client_id", "base_url", "provider_name", "icon"], 14 | order_by="name", 15 | ) 16 | 17 | for provider in providers: 18 | client_secret = get_decrypted_password("Social Login Key", provider.name, "client_secret") 19 | if not client_secret: 20 | continue 21 | 22 | icon = None 23 | if provider.icon: 24 | if provider.provider_name == "Custom": 25 | icon = get_icon_html(provider.icon, small=True) 26 | else: 27 | icon = f"{provider.provider_name}" 28 | 29 | if provider.client_id and provider.base_url and get_oauth_keys(provider.name): 30 | out.append( 31 | { 32 | "name": provider.name, 33 | "provider_name": provider.provider_name, 34 | "auth_url": get_oauth2_authorize_url(provider.name, "/crm"), 35 | "icon": icon, 36 | } 37 | ) 38 | return out -------------------------------------------------------------------------------- /crm/api/demo.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | from frappe import _ 3 | from frappe.auth import LoginManager 4 | 5 | 6 | @frappe.whitelist(allow_guest=True) 7 | def login(): 8 | if not frappe.conf.demo_username or not frappe.conf.demo_password: 9 | return 10 | frappe.local.response["redirect_to"] = "/crm" 11 | login_manager = LoginManager() 12 | login_manager.authenticate(frappe.conf.demo_username, frappe.conf.demo_password) 13 | login_manager.post_login() 14 | frappe.local.response["type"] = "redirect" 15 | frappe.local.response["location"] = frappe.local.response["redirect_to"] 16 | 17 | 18 | def validate_reset_password(doc, event): 19 | if frappe.conf.demo_username and frappe.session.user == frappe.conf.demo_username: 20 | frappe.throw( 21 | _("Password cannot be reset by Demo User {}").format(frappe.bold(frappe.conf.demo_username)), 22 | frappe.PermissionError, 23 | ) 24 | 25 | 26 | def validate_user(doc, event): 27 | if frappe.conf.demo_username and frappe.session.user == frappe.conf.demo_username and doc.new_password: 28 | frappe.throw( 29 | _("Password cannot be reset by Demo User {}").format(frappe.bold(frappe.conf.demo_username)), 30 | frappe.PermissionError, 31 | ) 32 | -------------------------------------------------------------------------------- /crm/api/onboarding.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | 3 | 4 | @frappe.whitelist() 5 | def get_first_lead(): 6 | lead = frappe.get_all( 7 | "CRM Lead", 8 | filters={"converted": 0}, 9 | fields=["name"], 10 | order_by="creation", 11 | limit=1, 12 | ) 13 | return lead[0].name if lead else None 14 | 15 | 16 | @frappe.whitelist() 17 | def get_first_deal(): 18 | deal = frappe.get_all( 19 | "CRM Deal", 20 | fields=["name"], 21 | order_by="creation", 22 | limit=1, 23 | ) 24 | return deal[0].name if deal else None 25 | -------------------------------------------------------------------------------- /crm/api/session.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | 3 | 4 | @frappe.whitelist() 5 | def get_users(): 6 | users = frappe.qb.get_query( 7 | "User", 8 | fields=[ 9 | "name", 10 | "email", 11 | "enabled", 12 | "user_image", 13 | "first_name", 14 | "last_name", 15 | "full_name", 16 | "user_type", 17 | ], 18 | order_by="full_name asc", 19 | distinct=True, 20 | ).run(as_dict=1) 21 | 22 | for user in users: 23 | if frappe.session.user == user.name: 24 | user.session_user = True 25 | 26 | user.is_manager = "Sales Manager" in frappe.get_roles(user.name) or user.name == "Administrator" 27 | 28 | user.is_agent = frappe.db.exists("CRM Telephony Agent", {"user": user.name}) 29 | 30 | return users 31 | 32 | 33 | @frappe.whitelist() 34 | def get_organizations(): 35 | organizations = frappe.qb.get_query( 36 | "CRM Organization", 37 | fields=["*"], 38 | order_by="name asc", 39 | distinct=True, 40 | ).run(as_dict=1) 41 | 42 | return organizations 43 | -------------------------------------------------------------------------------- /crm/api/views.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | from pypika import Criterion 3 | 4 | 5 | @frappe.whitelist() 6 | def get_views(doctype): 7 | View = frappe.qb.DocType("CRM View Settings") 8 | query = ( 9 | frappe.qb.from_(View) 10 | .select("*") 11 | .where(Criterion.any([View.user == "", View.user == frappe.session.user])) 12 | ) 13 | if doctype: 14 | query = query.where(View.dt == doctype) 15 | views = query.run(as_dict=True) 16 | return views 17 | -------------------------------------------------------------------------------- /crm/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/config/__init__.py -------------------------------------------------------------------------------- /crm/crowdin.yml: -------------------------------------------------------------------------------- 1 | files: 2 | - source: /crm/locale/main.pot 3 | translation: /crm/locale/%two_letters_code%.po 4 | pull_request_title: "chore: sync translations from crowdin" 5 | pull_request_labels: 6 | - translation 7 | commit_message: "chore: %language% translations" 8 | append_commit_message: false -------------------------------------------------------------------------------- /crm/fcrm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_call_log/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_call_log/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_call_log/crm_call_log.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("CRM Call Log", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_call_log/test_crm_call_log.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMCallLog(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_communication_status/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_communication_status/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_communication_status/crm_communication_status.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("CRM Communication Status", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_communication_status/crm_communication_status.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "autoname": "field:status", 5 | "creation": "2023-12-13 13:25:07.213100", 6 | "doctype": "DocType", 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "status" 10 | ], 11 | "fields": [ 12 | { 13 | "fieldname": "status", 14 | "fieldtype": "Data", 15 | "in_list_view": 1, 16 | "label": "Status", 17 | "reqd": 1, 18 | "unique": 1 19 | } 20 | ], 21 | "index_web_pages_for_search": 1, 22 | "links": [], 23 | "modified": "2024-01-19 21:55:17.952032", 24 | "modified_by": "Administrator", 25 | "module": "FCRM", 26 | "name": "CRM Communication Status", 27 | "naming_rule": "By fieldname", 28 | "owner": "Administrator", 29 | "permissions": [ 30 | { 31 | "create": 1, 32 | "delete": 1, 33 | "email": 1, 34 | "export": 1, 35 | "print": 1, 36 | "read": 1, 37 | "report": 1, 38 | "role": "Sales User", 39 | "share": 1, 40 | "write": 1 41 | }, 42 | { 43 | "create": 1, 44 | "delete": 1, 45 | "email": 1, 46 | "export": 1, 47 | "print": 1, 48 | "read": 1, 49 | "report": 1, 50 | "role": "Sales Manager", 51 | "share": 1, 52 | "write": 1 53 | } 54 | ], 55 | "quick_entry": 1, 56 | "sort_field": "modified", 57 | "sort_order": "DESC", 58 | "states": [] 59 | } -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_communication_status/crm_communication_status.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class CRMCommunicationStatus(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_communication_status/test_crm_communication_status.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMCommunicationStatus(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_contacts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_contacts/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_contacts/crm_contacts.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class CRMContacts(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_deal/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_deal/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_deal/test_crm_deal.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMDeal(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_deal_status/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_deal_status/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_deal_status/crm_deal_status.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("CRM Deal Status", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_deal_status/crm_deal_status.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class CRMDealStatus(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_deal_status/test_crm_deal_status.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMDealStatus(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_dropdown_item/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_dropdown_item/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_dropdown_item/crm_dropdown_item.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class CRMDropdownItem(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_exotel_settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_exotel_settings/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_exotel_settings/crm_exotel_settings.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("CRM Exotel Settings", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_exotel_settings/crm_exotel_settings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | import frappe 5 | import requests 6 | from frappe import _ 7 | from frappe.model.document import Document 8 | 9 | 10 | class CRMExotelSettings(Document): 11 | def validate(self): 12 | self.verify_credentials() 13 | 14 | def verify_credentials(self): 15 | if self.enabled: 16 | response = requests.get( 17 | "https://{subdomain}/v1/Accounts/{sid}".format( 18 | subdomain=self.subdomain, sid=self.account_sid 19 | ), 20 | auth=(self.api_key, self.get_password("api_token")), 21 | ) 22 | if response.status_code != 200: 23 | frappe.throw( 24 | _(f"Please enter valid exotel Account SID, API key & API token: {response.reason}"), 25 | title=_("Invalid credentials"), 26 | ) 27 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_exotel_settings/test_crm_exotel_settings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import IntegrationTestCase, UnitTestCase 6 | 7 | # On IntegrationTestCase, the doctype test records and all 8 | # link-field test record dependencies are recursively loaded 9 | # Use these module variables to add/remove to/from that list 10 | EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] 11 | IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] 12 | 13 | 14 | class UnitTestCRMExotelSettings(UnitTestCase): 15 | """ 16 | Unit tests for CRMExotelSettings. 17 | Use this class for testing individual functions and methods. 18 | """ 19 | 20 | pass 21 | 22 | 23 | class IntegrationTestCRMExotelSettings(IntegrationTestCase): 24 | """ 25 | Integration tests for CRMExotelSettings. 26 | Use this class for testing interactions between multiple components. 27 | """ 28 | 29 | pass 30 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_fields_layout/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_fields_layout/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("CRM Fields Layout", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_fields_layout/test_crm_fields_layout.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMFieldsLayout(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_form_script/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_form_script/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_form_script/crm_form_script.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | import frappe 5 | from frappe import _ 6 | from frappe.model.document import Document 7 | 8 | 9 | class CRMFormScript(Document): 10 | def validate(self): 11 | in_user_env = not ( 12 | frappe.flags.in_install 13 | or frappe.flags.in_patch 14 | or frappe.flags.in_test 15 | or frappe.flags.in_fixtures 16 | ) 17 | if in_user_env and not self.is_new() and self.is_standard and not frappe.conf.developer_mode: 18 | # only enabled can be changed for standard form scripts 19 | if self.has_value_changed("enabled"): 20 | enabled_value = self.enabled 21 | self.reload() 22 | self.enabled = enabled_value 23 | else: 24 | frappe.throw(_("You need to be in developer mode to edit a Standard Form Script")) 25 | 26 | def get_form_script(dt, view="Form"): 27 | """Returns the form script for the given doctype""" 28 | FormScript = frappe.qb.DocType("CRM Form Script") 29 | query = ( 30 | frappe.qb.from_(FormScript) 31 | .select("script") 32 | .where(FormScript.dt == dt) 33 | .where(FormScript.view == view) 34 | .where(FormScript.enabled == 1) 35 | ) 36 | 37 | doc = query.run(as_dict=True) 38 | if doc: 39 | return [d.script for d in doc] if len(doc) > 1 else doc[0].script 40 | else: 41 | return None 42 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_form_script/test_crm_form_script.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMFormScript(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_global_settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_global_settings/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_global_settings/crm_global_settings.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("CRM Global Settings", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_global_settings/crm_global_settings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class CRMGlobalSettings(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_global_settings/test_crm_global_settings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import IntegrationTestCase, UnitTestCase 6 | 7 | 8 | # On IntegrationTestCase, the doctype test records and all 9 | # link-field test record dependencies are recursively loaded 10 | # Use these module variables to add/remove to/from that list 11 | EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] 12 | IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] 13 | 14 | 15 | class UnitTestCRMGlobalSettings(UnitTestCase): 16 | """ 17 | Unit tests for CRMGlobalSettings. 18 | Use this class for testing individual functions and methods. 19 | """ 20 | 21 | pass 22 | 23 | 24 | class IntegrationTestCRMGlobalSettings(IntegrationTestCase): 25 | """ 26 | Integration tests for CRMGlobalSettings. 27 | Use this class for testing interactions between multiple components. 28 | """ 29 | 30 | pass 31 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_holiday/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_holiday/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_holiday/crm_holiday.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "creation": "2023-12-14 11:16:15.476366", 5 | "doctype": "DocType", 6 | "editable_grid": 1, 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "date", 10 | "column_break_xzyo", 11 | "weekly_off", 12 | "section_break_zenz", 13 | "description" 14 | ], 15 | "fields": [ 16 | { 17 | "fieldname": "date", 18 | "fieldtype": "Date", 19 | "in_list_view": 1, 20 | "label": "Date", 21 | "reqd": 1 22 | }, 23 | { 24 | "fieldname": "column_break_xzyo", 25 | "fieldtype": "Column Break" 26 | }, 27 | { 28 | "default": "0", 29 | "fieldname": "weekly_off", 30 | "fieldtype": "Check", 31 | "label": "Weekly Off" 32 | }, 33 | { 34 | "fieldname": "section_break_zenz", 35 | "fieldtype": "Section Break" 36 | }, 37 | { 38 | "fieldname": "description", 39 | "fieldtype": "Text Editor", 40 | "in_list_view": 1, 41 | "label": "Description", 42 | "reqd": 1 43 | } 44 | ], 45 | "index_web_pages_for_search": 1, 46 | "istable": 1, 47 | "links": [], 48 | "modified": "2023-12-14 11:17:41.745419", 49 | "modified_by": "Administrator", 50 | "module": "FCRM", 51 | "name": "CRM Holiday", 52 | "owner": "Administrator", 53 | "permissions": [], 54 | "sort_field": "modified", 55 | "sort_order": "DESC", 56 | "states": [] 57 | } -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_holiday/crm_holiday.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class CRMHoliday(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_holiday_list/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_holiday_list/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_holiday_list/crm_holiday_list.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("CRM Holiday List", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_holiday_list/crm_holiday_list.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class CRMHolidayList(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_holiday_list/test_crm_holiday_list.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMHolidayList(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_industry/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_industry/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_industry/crm_industry.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("CRM Industry", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_industry/crm_industry.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class CRMIndustry(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_industry/test_crm_industry.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMIndustry(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_invitation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_invitation/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_invitation/crm_invitation.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on("CRM Invitation", { 5 | refresh(frm) { 6 | if (frm.doc.status != "Accepted") { 7 | frm.add_custom_button(__("Accept Invitation"), () => { 8 | return frm.call("accept_invitation"); 9 | }); 10 | } 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_invitation/test_crm_invitation.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMInvitation(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_lead/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_lead/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_lead/api.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | 3 | from crm.api.doc import get_assigned_users, get_fields_meta 4 | from crm.fcrm.doctype.crm_form_script.crm_form_script import get_form_script 5 | 6 | 7 | @frappe.whitelist() 8 | def get_lead(name): 9 | lead = frappe.get_doc("CRM Lead", name) 10 | lead.check_permission("read") 11 | 12 | lead = lead.as_dict() 13 | 14 | lead["fields_meta"] = get_fields_meta("CRM Lead") 15 | lead["_form_script"] = get_form_script("CRM Lead") 16 | lead["_assign"] = get_assigned_users("CRM Lead", lead.name) 17 | return lead 18 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_lead/test_crm_lead.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMLead(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_lead_source/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_lead_source/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_lead_source/crm_lead_source.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("CRM Lead Source", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_lead_source/crm_lead_source.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class CRMLeadSource(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_lead_source/test_crm_lead_source.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMLeadSource(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_lead_status/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_lead_status/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_lead_status/crm_lead_status.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("CRM Lead Status", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_lead_status/crm_lead_status.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class CRMLeadStatus(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_lead_status/test_crm_lead_status.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMLeadStatus(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_notification/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_notification/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_notification/crm_notification.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("CRM Notification", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_notification/crm_notification.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | import frappe 5 | from frappe import _ 6 | from frappe.model.document import Document 7 | 8 | 9 | class CRMNotification(Document): 10 | def on_update(self): 11 | if self.to_user: 12 | frappe.publish_realtime("crm_notification", user= self.to_user) 13 | 14 | def notify_user(args): 15 | """ 16 | Notify the assigned user 17 | """ 18 | args = frappe._dict(args) 19 | if args.owner == args.assigned_to: 20 | return 21 | 22 | values = frappe._dict( 23 | doctype="CRM Notification", 24 | from_user=args.owner, 25 | to_user=args.assigned_to, 26 | type=args.notification_type, 27 | message=args.message, 28 | notification_text=args.notification_text, 29 | notification_type_doctype=args.reference_doctype, 30 | notification_type_doc=args.reference_docname, 31 | reference_doctype=args.redirect_to_doctype, 32 | reference_name=args.redirect_to_docname, 33 | ) 34 | 35 | if frappe.db.exists("CRM Notification", values): 36 | return 37 | frappe.get_doc(values).insert(ignore_permissions=True) 38 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_notification/test_crm_notification.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMNotification(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_organization/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_organization/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_organization/crm_organization.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("CRM Organization", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_organization/crm_organization.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class CRMOrganization(Document): 9 | @staticmethod 10 | def default_list_data(): 11 | columns = [ 12 | { 13 | 'label': 'Organization', 14 | 'type': 'Data', 15 | 'key': 'organization_name', 16 | 'width': '16rem', 17 | }, 18 | { 19 | 'label': 'Website', 20 | 'type': 'Data', 21 | 'key': 'website', 22 | 'width': '14rem', 23 | }, 24 | { 25 | 'label': 'Industry', 26 | 'type': 'Link', 27 | 'key': 'industry', 28 | 'options': 'CRM Industry', 29 | 'width': '14rem', 30 | }, 31 | { 32 | 'label': 'Annual Revenue', 33 | 'type': 'Currency', 34 | 'key': 'annual_revenue', 35 | 'width': '14rem', 36 | }, 37 | { 38 | 'label': 'Last Modified', 39 | 'type': 'Datetime', 40 | 'key': 'modified', 41 | 'width': '8rem', 42 | }, 43 | ] 44 | rows = [ 45 | "name", 46 | "organization_name", 47 | "organization_logo", 48 | "website", 49 | "industry", 50 | "currency", 51 | "annual_revenue", 52 | "modified", 53 | ] 54 | return {'columns': columns, 'rows': rows} 55 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_organization/test_crm_organization.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMOrganization(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_product/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_product/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_product/crm_product.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on("CRM Product", { 5 | product_code: function (frm) { 6 | if (!frm.doc.product_name) 7 | frm.set_value("product_name", frm.doc.product_code); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_product/crm_product.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class CRMProduct(Document): 9 | def validate(self): 10 | self.set_product_name() 11 | 12 | def set_product_name(self): 13 | if not self.product_name: 14 | self.product_name = self.product_code 15 | else: 16 | self.product_name = self.product_name.strip() 17 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_product/test_crm_product.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import IntegrationTestCase, UnitTestCase 6 | 7 | # On IntegrationTestCase, the doctype test records and all 8 | # link-field test record dependencies are recursively loaded 9 | # Use these module variables to add/remove to/from that list 10 | EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] 11 | IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] 12 | 13 | 14 | class UnitTestCRMProduct(UnitTestCase): 15 | """ 16 | Unit tests for CRMProduct. 17 | Use this class for testing individual functions and methods. 18 | """ 19 | 20 | pass 21 | 22 | 23 | class IntegrationTestCRMProduct(IntegrationTestCase): 24 | """ 25 | Integration tests for CRMProduct. 26 | Use this class for testing interactions between multiple components. 27 | """ 28 | 29 | pass 30 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_products/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_products/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_service_day/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_service_day/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_service_day/crm_service_day.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "creation": "2023-12-04 16:07:20.400084", 5 | "doctype": "DocType", 6 | "editable_grid": 1, 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "workday", 10 | "section_break_uegc", 11 | "start_time", 12 | "column_break_maie", 13 | "end_time" 14 | ], 15 | "fields": [ 16 | { 17 | "fieldname": "workday", 18 | "fieldtype": "Select", 19 | "in_list_view": 1, 20 | "label": "Workday", 21 | "options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday", 22 | "reqd": 1 23 | }, 24 | { 25 | "fieldname": "section_break_uegc", 26 | "fieldtype": "Section Break" 27 | }, 28 | { 29 | "fieldname": "start_time", 30 | "fieldtype": "Time", 31 | "in_list_view": 1, 32 | "label": "Start Time", 33 | "reqd": 1 34 | }, 35 | { 36 | "fieldname": "column_break_maie", 37 | "fieldtype": "Column Break" 38 | }, 39 | { 40 | "fieldname": "end_time", 41 | "fieldtype": "Time", 42 | "in_list_view": 1, 43 | "label": "End Time", 44 | "reqd": 1 45 | } 46 | ], 47 | "index_web_pages_for_search": 1, 48 | "istable": 1, 49 | "links": [], 50 | "modified": "2023-12-04 16:09:22.928308", 51 | "modified_by": "Administrator", 52 | "module": "FCRM", 53 | "name": "CRM Service Day", 54 | "owner": "Administrator", 55 | "permissions": [], 56 | "sort_field": "modified", 57 | "sort_order": "DESC", 58 | "states": [] 59 | } -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_service_day/crm_service_day.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class CRMServiceDay(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_service_level_agreement/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_service_level_agreement/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_service_level_agreement/crm_service_level_agreement.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on("CRM Service Level Agreement", { 5 | validate(frm) { 6 | let default_priority_count = 0; 7 | frm.doc.priorities.forEach(function (row) { 8 | if (row.default_priority) { 9 | default_priority_count++; 10 | } 11 | }); 12 | if (default_priority_count > 1) { 13 | frappe.throw( 14 | __("There can only be one default priority in Priorities table") 15 | ); 16 | } 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_service_level_agreement/test_crm_service_level_agreement.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMServiceLevelAgreement(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_service_level_priority/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_service_level_priority/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_service_level_priority/crm_service_level_priority.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("CRM Service Level Priority", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_service_level_priority/crm_service_level_priority.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class CRMServiceLevelPriority(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_service_level_priority/test_crm_service_level_priority.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMServiceLevelPriority(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_status_change_log/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_status_change_log/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_task/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_task/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_task/crm_task.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("CRM Task", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_task/test_crm_task.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMTask(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_telephony_agent/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_telephony_agent/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("CRM Telephony Agent", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | import frappe 5 | from frappe import _ 6 | from frappe.model.document import Document 7 | 8 | 9 | class CRMTelephonyAgent(Document): 10 | def validate(self): 11 | self.set_primary() 12 | 13 | def set_primary(self): 14 | # Used to set primary mobile no. 15 | if len(self.phone_nos) == 0: 16 | self.mobile_no = "" 17 | return 18 | 19 | is_primary = [phone.number for phone in self.phone_nos if phone.get("is_primary")] 20 | 21 | if len(is_primary) > 1: 22 | frappe.throw( 23 | _("Only one {0} can be set as primary.").format(frappe.bold(frappe.unscrub("mobile_no"))) 24 | ) 25 | 26 | primary_number_exists = False 27 | for d in self.phone_nos: 28 | if d.get("is_primary") == 1: 29 | primary_number_exists = True 30 | self.mobile_no = d.number 31 | break 32 | 33 | if not primary_number_exists: 34 | self.mobile_no = "" 35 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_telephony_agent/test_crm_telephony_agent.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import IntegrationTestCase, UnitTestCase 6 | 7 | # On IntegrationTestCase, the doctype test records and all 8 | # link-field test record dependencies are recursively loaded 9 | # Use these module variables to add/remove to/from that list 10 | EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] 11 | IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] 12 | 13 | 14 | class UnitTestCRMTelephonyAgent(UnitTestCase): 15 | """ 16 | Unit tests for CRMTelephonyAgent. 17 | Use this class for testing individual functions and methods. 18 | """ 19 | 20 | pass 21 | 22 | 23 | class IntegrationTestCRMTelephonyAgent(IntegrationTestCase): 24 | """ 25 | Integration tests for CRMTelephonyAgent. 26 | Use this class for testing interactions between multiple components. 27 | """ 28 | 29 | pass 30 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_telephony_phone/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_telephony_phone/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_telephony_phone/crm_telephony_phone.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "creation": "2025-01-19 13:57:01.702519", 5 | "doctype": "DocType", 6 | "editable_grid": 1, 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "number", 10 | "is_primary" 11 | ], 12 | "fields": [ 13 | { 14 | "fieldname": "number", 15 | "fieldtype": "Data", 16 | "in_list_view": 1, 17 | "label": "Number", 18 | "reqd": 1 19 | }, 20 | { 21 | "default": "0", 22 | "fieldname": "is_primary", 23 | "fieldtype": "Check", 24 | "in_list_view": 1, 25 | "label": "Is Primary" 26 | } 27 | ], 28 | "index_web_pages_for_search": 1, 29 | "istable": 1, 30 | "links": [], 31 | "modified": "2025-01-19 13:58:59.063775", 32 | "modified_by": "Administrator", 33 | "module": "FCRM", 34 | "name": "CRM Telephony Phone", 35 | "owner": "Administrator", 36 | "permissions": [], 37 | "sort_field": "creation", 38 | "sort_order": "DESC", 39 | "states": [] 40 | } -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_telephony_phone/crm_telephony_phone.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class CRMTelephonyPhone(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_territory/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_territory/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_territory/crm_territory.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("CRM Territory", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_territory/crm_territory.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class CRMTerritory(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_territory/test_crm_territory.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMTerritory(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_twilio_settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_twilio_settings/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_twilio_settings/crm_twilio_settings.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("CRM Twilio Settings", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_twilio_settings/test_crm_twilio_settings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMTwilioSettings(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_view_settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/crm_view_settings/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_view_settings/crm_view_settings.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("CRM View Settings", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/crm_view_settings/test_crm_view_settings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestCRMViewSettings(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/erpnext_crm_settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/erpnext_crm_settings/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on("ERPNext CRM Settings", { 5 | refresh(frm) { 6 | if (!frm.doc.enabled) return; 7 | frm.add_custom_button(__("Reset ERPNext Form Script"), () => { 8 | frappe.confirm( 9 | __( 10 | "Are you sure you want to reset 'Create Quotation from CRM Deal' Form Script?" 11 | ), 12 | () => frm.trigger("reset_erpnext_form_script") 13 | ); 14 | }); 15 | }, 16 | async reset_erpnext_form_script(frm) { 17 | let script = await frm.call("reset_erpnext_form_script"); 18 | script.message && 19 | frappe.msgprint(__("Form Script updated successfully")); 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/erpnext_crm_settings/test_erpnext_crm_settings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestERPNextCRMSettings(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/fcrm_note/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/fcrm_note/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/fcrm_note/fcrm_note.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("FCRM Note", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/fcrm_note/fcrm_note.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class FCRMNote(Document): 9 | @staticmethod 10 | def default_list_data(): 11 | rows = [ 12 | "name", 13 | "title", 14 | "content", 15 | "reference_doctype", 16 | "reference_docname", 17 | "owner", 18 | "modified", 19 | ] 20 | return {'columns': [], 'rows': rows} 21 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/fcrm_note/test_fcrm_note.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestFCRMNote(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/fcrm_settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/fcrm/doctype/fcrm_settings/__init__.py -------------------------------------------------------------------------------- /crm/fcrm/doctype/fcrm_settings/fcrm_settings.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on("FCRM Settings", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | restore_defaults: function (frm) { 9 | let message = __( 10 | "This will restore (if not exist) all the default statuses, custom fields and layouts. Delete & Restore will delete default layouts and then restore them." 11 | ); 12 | let d = new frappe.ui.Dialog({ 13 | title: __("Restore Defaults"), 14 | primary_action_label: __("Restore"), 15 | primary_action: () => { 16 | frm.call("restore_defaults", { force: false }, () => { 17 | frappe.show_alert({ 18 | message: __( 19 | "Default statuses, custom fields and layouts restored successfully." 20 | ), 21 | indicator: "green", 22 | }); 23 | }); 24 | d.hide(); 25 | }, 26 | secondary_action_label: __("Delete & Restore"), 27 | secondary_action: () => { 28 | frm.call("restore_defaults", { force: true }, () => { 29 | frappe.show_alert({ 30 | message: __( 31 | "Default statuses, custom fields and layouts restored successfully." 32 | ), 33 | indicator: "green", 34 | }); 35 | }); 36 | d.hide(); 37 | }, 38 | }); 39 | d.show(); 40 | d.set_message(message); 41 | }, 42 | }); 43 | -------------------------------------------------------------------------------- /crm/fcrm/doctype/fcrm_settings/test_fcrm_settings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests import UnitTestCase 6 | 7 | 8 | class TestFCRMSettings(UnitTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /crm/integrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/integrations/__init__.py -------------------------------------------------------------------------------- /crm/integrations/twilio/utils.py: -------------------------------------------------------------------------------- 1 | from frappe.utils import get_url 2 | 3 | 4 | def get_public_url(path: str | None = None): 5 | return get_url().split(":8", 1)[0] + path 6 | 7 | 8 | def merge_dicts(d1: dict, d2: dict): 9 | """Merge dicts of dictionaries. 10 | >>> merge_dicts( 11 | {'name1': {'age': 20}, 'name2': {'age': 30}}, 12 | {'name1': {'phone': '+xxx'}, 'name2': {'phone': '+yyy'}, 'name3': {'phone': '+zzz'}} 13 | ) 14 | ... {'name1': {'age': 20, 'phone': '+xxx'}, 'name2': {'age': 30, 'phone': '+yyy'}} 15 | """ 16 | return {k: {**v, **d2.get(k, {})} for k, v in d1.items()} 17 | 18 | -------------------------------------------------------------------------------- /crm/modules.txt: -------------------------------------------------------------------------------- 1 | FCRM -------------------------------------------------------------------------------- /crm/overrides/contact.py: -------------------------------------------------------------------------------- 1 | # import frappe 2 | from frappe import _ 3 | from frappe.contacts.doctype.contact.contact import Contact 4 | 5 | 6 | class CustomContact(Contact): 7 | @staticmethod 8 | def default_list_data(): 9 | columns = [ 10 | { 11 | 'label': 'Name', 12 | 'type': 'Data', 13 | 'key': 'full_name', 14 | 'width': '17rem', 15 | }, 16 | { 17 | 'label': 'Email', 18 | 'type': 'Data', 19 | 'key': 'email_id', 20 | 'width': '12rem', 21 | }, 22 | { 23 | 'label': 'Phone', 24 | 'type': 'Data', 25 | 'key': 'mobile_no', 26 | 'width': '12rem', 27 | }, 28 | { 29 | 'label': 'Organization', 30 | 'type': 'Data', 31 | 'key': 'company_name', 32 | 'width': '12rem', 33 | }, 34 | { 35 | 'label': 'Last Modified', 36 | 'type': 'Datetime', 37 | 'key': 'modified', 38 | 'width': '8rem', 39 | }, 40 | ] 41 | rows = [ 42 | "name", 43 | "full_name", 44 | "company_name", 45 | "email_id", 46 | "mobile_no", 47 | "modified", 48 | "image", 49 | ] 50 | return {'columns': columns, 'rows': rows} 51 | -------------------------------------------------------------------------------- /crm/overrides/email_template.py: -------------------------------------------------------------------------------- 1 | # import frappe 2 | from frappe import _ 3 | from frappe.email.doctype.email_template.email_template import EmailTemplate 4 | 5 | 6 | class CustomEmailTemplate(EmailTemplate): 7 | @staticmethod 8 | def default_list_data(): 9 | columns = [ 10 | { 11 | 'label': 'Name', 12 | 'type': 'Data', 13 | 'key': 'name', 14 | 'width': '17rem', 15 | }, 16 | { 17 | 'label': 'Subject', 18 | 'type': 'Data', 19 | 'key': 'subject', 20 | 'width': '12rem', 21 | }, 22 | { 23 | 'label': 'Enabled', 24 | 'type': 'Check', 25 | 'key': 'enabled', 26 | 'width': '6rem', 27 | }, 28 | { 29 | 'label': 'Doctype', 30 | 'type': 'Link', 31 | 'key': 'reference_doctype', 32 | 'width': '12rem', 33 | }, 34 | { 35 | 'label': 'Last Modified', 36 | 'type': 'Datetime', 37 | 'key': 'modified', 38 | 'width': '8rem', 39 | }, 40 | ] 41 | rows = [ 42 | "name", 43 | "enabled", 44 | "use_html", 45 | "reference_doctype", 46 | "subject", 47 | "response", 48 | "response_html", 49 | "modified", 50 | ] 51 | return {'columns': columns, 'rows': rows} 52 | -------------------------------------------------------------------------------- /crm/patches.txt: -------------------------------------------------------------------------------- 1 | [pre_model_sync] 2 | # Patches added in this section will be executed before doctypes are migrated 3 | # Read docs to understand patches: https://frappeframework.com/docs/v14/user/en/database-migrations 4 | crm.patches.v1_0.move_crm_note_data_to_fcrm_note 5 | crm.patches.v1_0.rename_twilio_settings_to_crm_twilio_settings 6 | 7 | [post_model_sync] 8 | # Patches added in this section will be executed after doctypes are migrated 9 | crm.patches.v1_0.create_email_template_custom_fields 10 | crm.patches.v1_0.create_default_fields_layout #22/01/2025 11 | crm.patches.v1_0.create_default_sidebar_fields_layout 12 | crm.patches.v1_0.update_deal_quick_entry_layout 13 | crm.patches.v1_0.update_layouts_to_new_format 14 | crm.patches.v1_0.move_twilio_agent_to_telephony_agent 15 | crm.patches.v1_0.create_default_scripts -------------------------------------------------------------------------------- /crm/patches/v1_0/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/patches/v1_0/__init__.py -------------------------------------------------------------------------------- /crm/patches/v1_0/create_default_fields_layout.py: -------------------------------------------------------------------------------- 1 | from crm.install import add_default_fields_layout 2 | 3 | 4 | def execute(): 5 | add_default_fields_layout() 6 | -------------------------------------------------------------------------------- /crm/patches/v1_0/create_default_scripts.py: -------------------------------------------------------------------------------- 1 | from crm.install import add_default_scripts 2 | 3 | 4 | def execute(): 5 | add_default_scripts() 6 | -------------------------------------------------------------------------------- /crm/patches/v1_0/create_email_template_custom_fields.py: -------------------------------------------------------------------------------- 1 | 2 | from crm.install import add_email_template_custom_fields 3 | 4 | def execute(): 5 | add_email_template_custom_fields() -------------------------------------------------------------------------------- /crm/patches/v1_0/move_crm_note_data_to_fcrm_note.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | from frappe.model.rename_doc import rename_doc 3 | 4 | 5 | def execute(): 6 | 7 | if not frappe.db.exists("DocType", "FCRM Note"): 8 | frappe.flags.ignore_route_conflict_validation = True 9 | rename_doc("DocType", "CRM Note", "FCRM Note") 10 | frappe.flags.ignore_route_conflict_validation = False 11 | 12 | frappe.reload_doctype("FCRM Note", force=True) 13 | 14 | if frappe.db.exists("DocType", "FCRM Note") and frappe.db.count("FCRM Note") > 0: 15 | return 16 | 17 | notes = frappe.db.sql("SELECT * FROM `tabCRM Note`", as_dict=True) 18 | if notes: 19 | for note in notes: 20 | doc = frappe.get_doc({ 21 | "doctype": "FCRM Note", 22 | "creation": note.get("creation"), 23 | "modified": note.get("modified"), 24 | "modified_by": note.get("modified_by"), 25 | "owner": note.get("owner"), 26 | "title": note.get("title"), 27 | "content": note.get("content"), 28 | "reference_doctype": note.get("reference_doctype"), 29 | "reference_docname": note.get("reference_docname"), 30 | }) 31 | doc.db_insert() -------------------------------------------------------------------------------- /crm/patches/v1_0/move_twilio_agent_to_telephony_agent.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | 3 | 4 | def execute(): 5 | if not frappe.db.exists("DocType", "CRM Telephony Agent"): 6 | frappe.reload_doctype("CRM Telephony Agent", force=True) 7 | 8 | if frappe.db.exists("DocType", "Twilio Agents") and frappe.db.count("Twilio Agents") == 0: 9 | return 10 | 11 | agents = frappe.db.sql("SELECT * FROM `tabTwilio Agents`", as_dict=True) 12 | if agents: 13 | for agent in agents: 14 | doc = frappe.get_doc( 15 | { 16 | "doctype": "CRM Telephony Agent", 17 | "creation": agent.get("creation"), 18 | "modified": agent.get("modified"), 19 | "modified_by": agent.get("modified_by"), 20 | "owner": agent.get("owner"), 21 | "user": agent.get("user"), 22 | "twilio_number": agent.get("twilio_number"), 23 | "user_name": agent.get("user_name"), 24 | "twilio": True, 25 | } 26 | ) 27 | doc.db_insert() 28 | -------------------------------------------------------------------------------- /crm/patches/v1_0/rename_twilio_settings_to_crm_twilio_settings.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | from frappe.model.rename_doc import rename_doc 3 | 4 | 5 | def execute(): 6 | if frappe.db.exists("DocType", "Twilio Settings"): 7 | frappe.flags.ignore_route_conflict_validation = True 8 | rename_doc("DocType", "Twilio Settings", "CRM Twilio Settings") 9 | frappe.flags.ignore_route_conflict_validation = False 10 | 11 | frappe.reload_doctype("CRM Twilio Settings", force=True) 12 | 13 | if frappe.db.exists("__Auth", {"doctype": "Twilio Settings"}): 14 | Auth = frappe.qb.DocType("__Auth") 15 | result = frappe.qb.from_(Auth).select("*").where(Auth.doctype == "Twilio Settings").run(as_dict=True) 16 | 17 | for row in result: 18 | frappe.qb.into(Auth).insert( 19 | "CRM Twilio Settings", "CRM Twilio Settings", row.fieldname, row.password, row.encrypted 20 | ).run() 21 | -------------------------------------------------------------------------------- /crm/patches/v1_0/update_deal_quick_entry_layout.py: -------------------------------------------------------------------------------- 1 | import json 2 | import frappe 3 | 4 | def execute(): 5 | if not frappe.db.exists("CRM Fields Layout", "CRM Deal-Quick Entry"): 6 | return 7 | 8 | deal = frappe.db.get_value("CRM Fields Layout", "CRM Deal-Quick Entry", "layout") 9 | 10 | layout = json.loads(deal) 11 | for section in layout: 12 | if section.get("label") in ["Select Organization", "Organization Details", "Select Contact", "Contact Details"]: 13 | section["editable"] = False 14 | 15 | frappe.db.set_value("CRM Fields Layout", "CRM Deal-Quick Entry", "layout", json.dumps(layout)) -------------------------------------------------------------------------------- /crm/public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/.gitkeep -------------------------------------------------------------------------------- /crm/public/images/desk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/images/desk.png -------------------------------------------------------------------------------- /crm/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/images/logo.png -------------------------------------------------------------------------------- /crm/public/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /crm/public/manifest/apple-icon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-icon-180.png -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-1125-2436.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-1125-2436.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-1136-640.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-1136-640.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-1170-2532.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-1170-2532.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-1179-2556.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-1179-2556.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-1242-2208.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-1242-2208.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-1242-2688.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-1242-2688.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-1284-2778.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-1284-2778.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-1290-2796.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-1290-2796.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-1334-750.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-1334-750.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-1488-2266.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-1488-2266.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-1536-2048.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-1536-2048.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-1620-2160.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-1620-2160.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-1640-2360.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-1640-2360.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-1668-2224.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-1668-2224.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-1668-2388.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-1668-2388.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-1792-828.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-1792-828.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-2048-1536.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-2048-1536.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-2048-2732.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-2048-2732.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-2160-1620.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-2160-1620.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-2208-1242.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-2208-1242.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-2224-1668.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-2224-1668.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-2266-1488.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-2266-1488.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-2360-1640.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-2360-1640.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-2388-1668.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-2388-1668.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-2436-1125.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-2436-1125.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-2532-1170.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-2532-1170.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-2556-1179.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-2556-1179.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-2688-1242.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-2688-1242.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-2732-2048.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-2732-2048.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-2778-1284.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-2778-1284.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-2796-1290.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-2796-1290.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-640-1136.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-640-1136.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-750-1334.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-750-1334.jpg -------------------------------------------------------------------------------- /crm/public/manifest/apple-splash-828-1792.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/apple-splash-828-1792.jpg -------------------------------------------------------------------------------- /crm/public/manifest/manifest-icon-192.maskable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/manifest-icon-192.maskable.png -------------------------------------------------------------------------------- /crm/public/manifest/manifest-icon-512.maskable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/manifest/manifest-icon-512.maskable.png -------------------------------------------------------------------------------- /crm/public/videos/changeDealStatus.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/videos/changeDealStatus.mov -------------------------------------------------------------------------------- /crm/public/videos/convertToDeal.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/public/videos/convertToDeal.mov -------------------------------------------------------------------------------- /crm/templates/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/templates/__init__.py -------------------------------------------------------------------------------- /crm/templates/emails/crm_invitation.html: -------------------------------------------------------------------------------- 1 |

You have been invited to join Frappe CRM

2 |

3 | Accept Invitation 4 |

5 | -------------------------------------------------------------------------------- /crm/templates/pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/templates/pages/__init__.py -------------------------------------------------------------------------------- /crm/uninstall.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors 2 | # MIT License. See license.txt 3 | import click 4 | import frappe 5 | 6 | 7 | def before_uninstall(): 8 | delete_email_template_custom_fields() 9 | 10 | 11 | def delete_email_template_custom_fields(): 12 | if frappe.get_meta("Email Template").has_field("enabled"): 13 | click.secho("* Uninstalling Custom Fields from Email Template") 14 | 15 | fieldnames = ( 16 | "enabled", 17 | "reference_doctype", 18 | ) 19 | 20 | for fieldname in fieldnames: 21 | frappe.db.delete("Custom Field", {"name": "Email Template-" + fieldname}) 22 | 23 | frappe.clear_cache(doctype="Email Template") 24 | -------------------------------------------------------------------------------- /crm/www/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/crm/www/__init__.py -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | files: 2 | - source: /crm/locale/main.pot 3 | translation: /crm/locale/%two_letters_code%.po 4 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | name: crm 3 | services: 4 | mariadb: 5 | image: mariadb:10.8 6 | command: 7 | - --character-set-server=utf8mb4 8 | - --collation-server=utf8mb4_unicode_ci 9 | - --skip-character-set-client-handshake 10 | - --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6 11 | environment: 12 | MYSQL_ROOT_PASSWORD: 123 13 | volumes: 14 | - mariadb-data:/var/lib/mysql 15 | 16 | redis: 17 | image: redis:alpine 18 | 19 | frappe: 20 | image: frappe/bench:latest 21 | command: bash /workspace/init.sh 22 | environment: 23 | - SHELL=/bin/bash 24 | working_dir: /home/frappe 25 | volumes: 26 | - .:/workspace 27 | ports: 28 | - 8000:8000 29 | - 9000:9000 30 | 31 | volumes: 32 | mariadb-data: -------------------------------------------------------------------------------- /docker/init.sh: -------------------------------------------------------------------------------- 1 | #!bin/bash 2 | 3 | if [ -d "/home/frappe/frappe-bench/apps/frappe" ]; then 4 | echo "Bench already exists, skipping init" 5 | cd frappe-bench 6 | bench start 7 | else 8 | echo "Creating new bench..." 9 | fi 10 | 11 | bench init --skip-redis-config-generation frappe-bench 12 | 13 | cd frappe-bench 14 | 15 | # Use containers instead of localhost 16 | bench set-mariadb-host mariadb 17 | bench set-redis-cache-host redis:6379 18 | bench set-redis-queue-host redis:6379 19 | bench set-redis-socketio-host redis:6379 20 | 21 | # Remove redis, watch from Procfile 22 | sed -i '/redis/d' ./Procfile 23 | sed -i '/watch/d' ./Procfile 24 | 25 | bench get-app crm --branch develop 26 | 27 | bench new-site crm.localhost \ 28 | --force \ 29 | --mariadb-root-password 123 \ 30 | --admin-password admin \ 31 | --no-mariadb-socket 32 | 33 | bench --site crm.localhost install-app crm 34 | bench --site crm.localhost set-config developer_mode 1 35 | bench --site crm.localhost clear-cache 36 | bench --site crm.localhost set-config mute_emails 1 37 | bench use crm.localhost 38 | 39 | bench start -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local -------------------------------------------------------------------------------- /frontend/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crm-ui", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build --base=/assets/crm/frontend/ && yarn copy-html-entry", 9 | "copy-html-entry": "cp ../crm/public/frontend/index.html ../crm/www/crm.html", 10 | "serve": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@tiptap/extension-paragraph": "^2.12.0", 14 | "@twilio/voice-sdk": "^2.10.2", 15 | "@vueuse/integrations": "^10.3.0", 16 | "frappe-ui": "^0.1.145", 17 | "gemoji": "^8.1.0", 18 | "lodash": "^4.17.21", 19 | "mime": "^4.0.1", 20 | "pinia": "^2.0.33", 21 | "socket.io-client": "^4.7.2", 22 | "sortablejs": "^1.15.0", 23 | "vue": "^3.5.13", 24 | "vue-router": "^4.2.2", 25 | "vuedraggable": "^4.1.0" 26 | }, 27 | "devDependencies": { 28 | "@vitejs/plugin-vue": "^4.2.3", 29 | "@vitejs/plugin-vue-jsx": "^3.0.1", 30 | "autoprefixer": "^10.4.14", 31 | "postcss": "^8.4.5", 32 | "tailwindcss": "^3.4.15", 33 | "vite": "^4.4.9", 34 | "vite-plugin-pwa": "^0.15.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /frontend/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/frontend/public/favicon.png -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 36 | -------------------------------------------------------------------------------- /frontend/src/components/Activities/PlaybackSpeedOption.vue: -------------------------------------------------------------------------------- 1 | 15 | 22 | -------------------------------------------------------------------------------- /frontend/src/components/AssignTo.vue: -------------------------------------------------------------------------------- 1 | 19 | 32 | -------------------------------------------------------------------------------- /frontend/src/components/BrandLogo.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /frontend/src/components/Controls/FormattedInput.vue: -------------------------------------------------------------------------------- 1 | 15 | 59 | -------------------------------------------------------------------------------- /frontend/src/components/Controls/ImageUploader.vue: -------------------------------------------------------------------------------- 1 | 27 | 43 | -------------------------------------------------------------------------------- /frontend/src/components/Controls/Password.vue: -------------------------------------------------------------------------------- 1 | 14 | 33 | -------------------------------------------------------------------------------- /frontend/src/components/ErrorPage.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 25 | -------------------------------------------------------------------------------- /frontend/src/components/FieldLayout/Column.vue: -------------------------------------------------------------------------------- 1 | 14 | 21 | -------------------------------------------------------------------------------- /frontend/src/components/FieldLayout/Section.vue: -------------------------------------------------------------------------------- 1 | 31 | 42 | -------------------------------------------------------------------------------- /frontend/src/components/Icon.vue: -------------------------------------------------------------------------------- 1 | 12 | 22 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/ActivityIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/ArrowUpRightIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/AscendingIcon.vue: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/AttachmentIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/AvatarIcon.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/CRMLogo.vue: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/CheckCircleIcon.vue: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/CheckIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/CollapseSidebar.vue: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/ColumnsIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/CommentIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/ContactIcon.vue: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/ContactsIcon.vue: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/DashboardIcon.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/DealsIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/DeclinedCallIcon.vue: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/DesendingIcon.vue: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/DocumentIcon.vue: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/DotIcon.vue: -------------------------------------------------------------------------------- 1 | 12 | 20 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/DoubleCheckIcon.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/DragIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/DragVerticalIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/DuplicateIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/DurationIcon.vue: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/ERPNextIcon.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/EditIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/Email2Icon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/EmailIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/ExportIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/ExternalLinkIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/FileAudioIcon.vue: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/FileIcon.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/FileImageIcon.vue: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/FileSpreadsheetIcon.vue: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/FileTextIcon.vue: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/FileTypeIcon.vue: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/FileVideoIcon.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/FilterIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/FrappeCloudIcon.vue: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/GenderIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/HeartIcon.vue: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/InboxIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/IndicatorIcon.vue: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/KanbanIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/LightningIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/ListIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/LoadingIndicator.vue: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/MarkAsDoneIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/MaximizeIcon.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/MenuIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/MinimizeIcon.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/MoneyIcon.vue: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/NoteIcon.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/PauseIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/PinIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/PlayIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/PlaybackSpeedIcon.vue: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/QuickFilterIcon.vue: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/ReactIcon.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/RefreshIcon.vue: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/ReloadIcon.vue: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/ReplyAllIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/ReplyIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/RightSideLayoutIcon.vue: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/SelectIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/SmileIcon.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/SortIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/SquareAsterisk.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/StepsIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/SuccessIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/TaskPriorityIcon.vue: -------------------------------------------------------------------------------- 1 | 13 | 24 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/TelegramIcon.vue: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/TerritoryIcon.vue: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/UnpinIcon.vue: -------------------------------------------------------------------------------- 1 | 26 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/VolumnHighIcon.vue: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /frontend/src/components/Icons/VolumnLowIcon.vue: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /frontend/src/components/LayoutHeader.vue: -------------------------------------------------------------------------------- 1 | 15 | 24 | -------------------------------------------------------------------------------- /frontend/src/components/Layouts/AppHeader.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | -------------------------------------------------------------------------------- /frontend/src/components/Layouts/DesktopLayout.vue: -------------------------------------------------------------------------------- 1 | 13 | 18 | -------------------------------------------------------------------------------- /frontend/src/components/Layouts/MobileLayout.vue: -------------------------------------------------------------------------------- 1 | 10 | 14 | -------------------------------------------------------------------------------- /frontend/src/components/Mobile/MobileAppHeader.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | -------------------------------------------------------------------------------- /frontend/src/components/Modals/GlobalModals.vue: -------------------------------------------------------------------------------- 1 | 17 | 38 | -------------------------------------------------------------------------------- /frontend/src/components/Settings/ERPNextSettings.vue: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /frontend/src/components/Settings/EmailConfig.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 28 | -------------------------------------------------------------------------------- /frontend/src/components/Settings/EmailProviderIcon.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /frontend/src/components/Settings/WhatsAppSettings.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /frontend/src/components/UserAvatar.vue: -------------------------------------------------------------------------------- 1 | 8 | 21 | -------------------------------------------------------------------------------- /frontend/src/composables/document.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export const showCreateDocumentModal = ref(false) 4 | export const createDocumentDoctype = ref('') 5 | export const createDocumentData = ref({}) 6 | export const createDocumentCallback = ref(null) 7 | 8 | export function createDocument(doctype, obj, close, callback) { 9 | if (doctype) { 10 | close?.() 11 | createDocumentDoctype.value = doctype 12 | createDocumentData.value = obj || {} 13 | createDocumentCallback.value = callback || null 14 | showCreateDocumentModal.value = true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/composables/frappecloud.js: -------------------------------------------------------------------------------- 1 | import { globalStore } from '@/stores/global' 2 | import { createResource } from 'frappe-ui' 3 | import { ref } from 'vue' 4 | 5 | const baseEndpoint = ref('https://frappecloud.com') 6 | const siteName = ref('') 7 | 8 | export const currentSiteInfo = createResource({ 9 | url: 'frappe.integrations.frappe_providers.frappecloud_billing.current_site_info', 10 | cache: 'currentSiteInfo', 11 | onSuccess: (data) => { 12 | baseEndpoint.value = data.base_url 13 | siteName.value = data.site_name 14 | }, 15 | }) 16 | 17 | export const confirmLoginToFrappeCloud = () => { 18 | currentSiteInfo.fetch() 19 | 20 | const { $dialog } = globalStore() 21 | 22 | $dialog({ 23 | title: __('Login to Frappe Cloud?'), 24 | message: __( 25 | 'Are you sure you want to login to your Frappe Cloud dashboard?', 26 | ), 27 | actions: [ 28 | { 29 | label: __('Confirm'), 30 | variant: 'solid', 31 | onClick(close) { 32 | loginToFrappeCloud() 33 | close() 34 | }, 35 | }, 36 | ], 37 | }) 38 | } 39 | 40 | const loginToFrappeCloud = () => { 41 | window.open( 42 | `${baseEndpoint.value}/dashboard/sites/${siteName.value}`, 43 | '_blank', 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /frontend/src/composables/twilio.js: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance } from 'vue' 2 | 3 | export function is_twilio_enabled() { 4 | const app = getCurrentInstance() 5 | return app.appContext.config.globalProperties.is_twilio_enabled 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/images/frappe-mail.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/src/images/gmail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/frontend/src/images/gmail.png -------------------------------------------------------------------------------- /frontend/src/images/outlook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/frontend/src/images/outlook.png -------------------------------------------------------------------------------- /frontend/src/images/sendgrid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/frontend/src/images/sendgrid.png -------------------------------------------------------------------------------- /frontend/src/images/sparkpost.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/frontend/src/images/sparkpost.webp -------------------------------------------------------------------------------- /frontend/src/images/yahoo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/frontend/src/images/yahoo.png -------------------------------------------------------------------------------- /frontend/src/images/yandex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/crm/895da1a8125581e8b9476ace591123ab59d7b3d3/frontend/src/images/yandex.png -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @import "frappe-ui/src/style.css"; 2 | 3 | @layer components { 4 | .prose-f { 5 | @apply break-all 6 | max-w-none 7 | prose 8 | prose-code:break-all 9 | prose-code:whitespace-pre-wrap 10 | prose-img:border 11 | prose-img:rounded-lg 12 | prose-sm 13 | prose-table:table-fixed 14 | prose-td:border 15 | prose-td:border-outline-gray-2 16 | prose-td:p-2 17 | prose-td:relative 18 | prose-th:bg-surface-gray-2 19 | prose-th:border 20 | prose-th:border-outline-gray-2 21 | prose-th:p-2 22 | prose-th:relative; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /frontend/src/pages/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 15 | -------------------------------------------------------------------------------- /frontend/src/pages/EmailTemplate.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /frontend/src/pages/InvalidPage.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /frontend/src/socket.js: -------------------------------------------------------------------------------- 1 | import { io } from 'socket.io-client' 2 | import { socketio_port } from '../../../../sites/common_site_config.json' 3 | import { getCachedListResource } from 'frappe-ui/src/resources/listResource' 4 | import { getCachedResource } from 'frappe-ui/src/resources/resources' 5 | 6 | export function initSocket() { 7 | let host = window.location.hostname 8 | let siteName = window.site_name 9 | let port = window.location.port ? `:${socketio_port}` : '' 10 | let protocol = port ? 'http' : 'https' 11 | let url = `${protocol}://${host}${port}/${siteName}` 12 | 13 | let socket = io(url, { 14 | withCredentials: true, 15 | reconnectionAttempts: 5, 16 | }) 17 | socket.on('refetch_resource', (data) => { 18 | if (data.cache_key) { 19 | let resource = 20 | getCachedResource(data.cache_key) || 21 | getCachedListResource(data.cache_key) 22 | if (resource) { 23 | resource.reload() 24 | } 25 | } 26 | }) 27 | return socket 28 | } -------------------------------------------------------------------------------- /frontend/src/stores/global.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { getCurrentInstance, ref } from 'vue' 3 | 4 | export const globalStore = defineStore('crm-global', () => { 5 | const app = getCurrentInstance() 6 | const { $dialog, $socket } = app.appContext.config.globalProperties 7 | 8 | let callMethod = () => {} 9 | 10 | function setMakeCall(value) { 11 | callMethod = value 12 | } 13 | 14 | function makeCall(number) { 15 | callMethod(number) 16 | } 17 | 18 | return { 19 | $dialog, 20 | $socket, 21 | makeCall, 22 | setMakeCall, 23 | } 24 | }) 25 | -------------------------------------------------------------------------------- /frontend/src/stores/notifications.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { createResource } from 'frappe-ui' 3 | import { computed, ref } from 'vue' 4 | 5 | export const visible = ref(false) 6 | 7 | export const notifications = createResource({ 8 | url: 'crm.api.notifications.get_notifications', 9 | initialData: [], 10 | auto: true, 11 | }) 12 | 13 | export const unreadNotificationsCount = computed( 14 | () => notifications.data?.filter((n) => !n.read).length || 0, 15 | ) 16 | 17 | export const notificationsStore = defineStore('crm-notifications', () => { 18 | const mark_as_read = createResource({ 19 | url: 'crm.api.notifications.mark_as_read', 20 | onSuccess: () => { 21 | mark_as_read.params = {} 22 | notifications.reload() 23 | }, 24 | }) 25 | 26 | function toggle() { 27 | visible.value = !visible.value 28 | } 29 | 30 | function mark_doc_as_read(doc) { 31 | mark_as_read.params = { doc: doc } 32 | mark_as_read.reload() 33 | toggle() 34 | } 35 | 36 | return { 37 | unreadNotificationsCount, 38 | mark_as_read, 39 | mark_doc_as_read, 40 | toggle, 41 | } 42 | }) 43 | -------------------------------------------------------------------------------- /frontend/src/stores/organizations.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { createResource } from 'frappe-ui' 3 | import { reactive } from 'vue' 4 | 5 | export const organizationsStore = defineStore('crm-organizations', () => { 6 | let organizationsByName = reactive({}) 7 | 8 | const organizations = createResource({ 9 | url: 'crm.api.session.get_organizations', 10 | cache: 'organizations', 11 | initialData: [], 12 | auto: true, 13 | transform(organizations) { 14 | for (let organization of organizations) { 15 | organizationsByName[organization.name] = organization 16 | } 17 | return organizations 18 | }, 19 | onError(error) { 20 | if (error && error.exc_type === 'AuthenticationError') { 21 | router.push('/login') 22 | } 23 | }, 24 | }) 25 | 26 | function getOrganization(name) { 27 | return organizationsByName[name] 28 | } 29 | 30 | return { 31 | organizations, 32 | getOrganization, 33 | } 34 | }) 35 | -------------------------------------------------------------------------------- /frontend/src/stores/session.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { createResource } from 'frappe-ui' 3 | import { userResource } from './user' 4 | import router from '@/router' 5 | import { ref, computed } from 'vue' 6 | 7 | export const sessionStore = defineStore('crm-session', () => { 8 | function sessionUser() { 9 | let cookies = new URLSearchParams(document.cookie.split('; ').join('&')) 10 | let _sessionUser = cookies.get('user_id') 11 | if (_sessionUser === 'Guest') { 12 | _sessionUser = null 13 | } 14 | return _sessionUser 15 | } 16 | 17 | let user = ref(sessionUser()) 18 | const isLoggedIn = computed(() => !!user.value) 19 | 20 | const login = createResource({ 21 | url: 'login', 22 | onError() { 23 | throw new Error('Invalid email or password') 24 | }, 25 | onSuccess() { 26 | userResource.reload() 27 | user.value = sessionUser() 28 | login.reset() 29 | router.replace({ path: '/' }) 30 | }, 31 | }) 32 | 33 | const logout = createResource({ 34 | url: 'logout', 35 | onSuccess() { 36 | userResource.reset() 37 | user.value = null 38 | window.location.href = '/login?redirect-to=/crm' 39 | }, 40 | }) 41 | 42 | return { 43 | user, 44 | isLoggedIn, 45 | login, 46 | logout, 47 | } 48 | }) 49 | -------------------------------------------------------------------------------- /frontend/src/stores/settings.js: -------------------------------------------------------------------------------- 1 | import { createDocumentResource } from 'frappe-ui' 2 | import { reactive, ref } from 'vue' 3 | 4 | const settings = ref({}) 5 | const brand = reactive({}) 6 | 7 | const _settings = createDocumentResource({ 8 | doctype: 'FCRM Settings', 9 | name: 'FCRM Settings', 10 | onSuccess: (data) => { 11 | settings.value = data 12 | getSettings().setupBrand() 13 | return data 14 | }, 15 | }) 16 | 17 | export function getSettings() { 18 | function setupBrand() { 19 | brand.name = settings.value?.brand_name 20 | brand.logo = settings.value?.brand_logo 21 | brand.favicon = settings.value?.favicon 22 | } 23 | 24 | return { 25 | _settings, 26 | settings, 27 | brand, 28 | setupBrand, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /frontend/src/stores/theme.js: -------------------------------------------------------------------------------- 1 | import { useStorage } from '@vueuse/core' 2 | 3 | export const theme = useStorage('theme', 'light') 4 | 5 | export function toggleTheme() { 6 | const currentTheme = document.documentElement.getAttribute('data-theme') 7 | theme.value = currentTheme === 'dark' ? 'light' : 'dark' 8 | document.documentElement.setAttribute('data-theme', theme.value) 9 | } 10 | 11 | export function setTheme(value) { 12 | theme.value = value || theme.value 13 | if (['light', 'dark'].includes(theme.value)) { 14 | document.documentElement.setAttribute('data-theme', theme.value) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/stores/user.js: -------------------------------------------------------------------------------- 1 | import router from '@/router' 2 | import { createResource } from 'frappe-ui' 3 | 4 | export const userResource = createResource({ 5 | url: 'frappe.auth.get_logged_user', 6 | cache: 'User', 7 | onError(error) { 8 | if (error && error.exc_type === 'AuthenticationError') { 9 | router.push({ name: 'Home' }) 10 | } 11 | }, 12 | }) 13 | -------------------------------------------------------------------------------- /frontend/src/translation.js: -------------------------------------------------------------------------------- 1 | import { createResource } from 'frappe-ui' 2 | 3 | export default function translationPlugin(app) { 4 | app.config.globalProperties.__ = translate 5 | window.__ = translate 6 | if (!window.translatedMessages) fetchTranslations() 7 | } 8 | 9 | function format(message, replace) { 10 | return message.replace(/{(\d+)}/g, function (match, number) { 11 | return typeof replace[number] != 'undefined' ? replace[number] : match 12 | }) 13 | } 14 | 15 | function translate(message, replace, context = null) { 16 | let translatedMessages = window.translatedMessages || {} 17 | let translatedMessage = '' 18 | 19 | if (context) { 20 | let key = `${message}:${context}` 21 | if (translatedMessages[key]) { 22 | translatedMessage = translatedMessages[key] 23 | } 24 | } 25 | 26 | if (!translatedMessage) { 27 | translatedMessage = translatedMessages[message] || message 28 | } 29 | 30 | const hasPlaceholders = /{\d+}/.test(message) 31 | if (!hasPlaceholders) { 32 | return translatedMessage 33 | } 34 | 35 | return format(translatedMessage, replace) 36 | } 37 | 38 | function fetchTranslations(lang) { 39 | createResource({ 40 | url: 'crm.api.get_translations', 41 | cache: 'translations', 42 | auto: true, 43 | transform: (data) => { 44 | window.translatedMessages = data 45 | }, 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /frontend/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface EmailAccount { 2 | email_account_name: string 3 | email_id: string 4 | service: string 5 | api_key?: string 6 | api_secret?: string 7 | password?: string 8 | frappe_mail_site?: string 9 | enable_outgoing?: boolean 10 | enable_incoming?: boolean 11 | default_outgoing?: boolean 12 | default_incoming?: boolean 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/utils/dialogs.jsx: -------------------------------------------------------------------------------- 1 | import { Dialog, ErrorMessage } from 'frappe-ui' 2 | import { reactive, ref } from 'vue' 3 | 4 | let dialogs = ref([]) 5 | 6 | export let Dialogs = { 7 | name: 'Dialogs', 8 | render() { 9 | return dialogs.value.map((dialog) => ( 10 | (dialog.show = val)} 14 | > 15 | {{ 16 | 'body-content': () => { 17 | return [ 18 | dialog.message && ( 19 |

{dialog.message}

20 | ), 21 | dialog.html && ( 22 |
23 | ), 24 | , 25 | ] 26 | }, 27 | }} 28 |
29 | )) 30 | }, 31 | } 32 | 33 | export function createDialog(dialogOptions) { 34 | let dialog = reactive(dialogOptions) 35 | dialog.key = 'dialog-' + dialogs.value.length 36 | dialog.show = false 37 | setTimeout(() => { 38 | dialog.show = true 39 | }, 0) 40 | dialogs.value.push(dialog) 41 | } 42 | -------------------------------------------------------------------------------- /frontend/src/utils/view.js: -------------------------------------------------------------------------------- 1 | import ListIcon from '@/components/Icons/ListIcon.vue' 2 | import GroupByIcon from '@/components/Icons/GroupByIcon.vue' 3 | import KanbanIcon from '@/components/Icons/KanbanIcon.vue' 4 | import { viewsStore } from '@/stores/views' 5 | import { markRaw } from 'vue' 6 | 7 | const { getView: getViewDetails } = viewsStore() 8 | 9 | function standardView(type) { 10 | let types = { 11 | list: { 12 | label: __('List'), 13 | icon: markRaw(ListIcon), 14 | }, 15 | group_by: { 16 | label: __('Group By'), 17 | icon: markRaw(GroupByIcon), 18 | }, 19 | kanban: { 20 | label: __('Kanban'), 21 | icon: markRaw(KanbanIcon), 22 | }, 23 | } 24 | 25 | return types[type] 26 | } 27 | 28 | export function getView(view, type, doctype) { 29 | let viewType = type || 'list' 30 | let viewDetails = getViewDetails(view, viewType, doctype) 31 | if (viewDetails && !viewDetails.icon) { 32 | viewDetails.icon = standardView(viewType).icon 33 | } 34 | return viewDetails || standardView(viewType) 35 | } 36 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | import frappeUIPreset from 'frappe-ui/src/tailwind/preset' 2 | 3 | export default { 4 | presets: [frappeUIPreset], 5 | content: [ 6 | './index.html', 7 | './src/**/*.{vue,js,ts,jsx,tsx}', 8 | './node_modules/frappe-ui/src/**/*.{vue,js,ts,jsx,tsx}', 9 | '../node_modules/frappe-ui/src/**/*.{vue,js,ts,jsx,tsx}', 10 | './node_modules/frappe-ui/frappe/**/*.{vue,js,ts,jsx,tsx}', 11 | '../node_modules/frappe-ui/frappe/**/*.{vue,js,ts,jsx,tsx}', 12 | ], 13 | safelist: [{ pattern: /!(text|bg)-/, variants: ['hover', 'active'] }], 14 | theme: { 15 | extend: {}, 16 | }, 17 | plugins: [], 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "workspaces": ["frontend", "frappe-ui"], 5 | "scripts": { 6 | "postinstall": "cd frontend && yarn install", 7 | "dev": "cd frontend && yarn dev", 8 | "build": "cd frontend && yarn build", 9 | "disable-workspaces": "sed -i '' 's/\"workspaces\"/\"aworkspaces\"/g' package.json", 10 | "enable-workspaces": "sed -i '' 's/\"aworkspaces\"/\"workspaces\"/g' package.json && rm -rf node_modules ./frontend/node_modules/ frappe-ui/node_modules/ && yarn install", 11 | "upgrade-frappeui": "cd frontend && yarn add frappe-ui@latest && cd ..", 12 | "disable-workspaces-and-upgrade-frappeui": "yarn disable-workspaces && yarn upgrade-frappeui" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /scripts/init.sh: -------------------------------------------------------------------------------- 1 | #!bin/bash 2 | 3 | set -e 4 | 5 | if [[ -f "/workspaces/frappe_codespace/frappe-bench/apps/frappe" ]] 6 | then 7 | echo "Bench already exists, skipping init" 8 | exit 0 9 | fi 10 | 11 | rm -rf /workspaces/frappe_codespace/.git 12 | 13 | source /home/frappe/.nvm/nvm.sh 14 | nvm alias default 18 15 | nvm use 18 16 | 17 | echo "nvm use 18" >> ~/.bashrc 18 | cd /workspace 19 | 20 | bench init \ 21 | --ignore-exist \ 22 | --skip-redis-config-generation \ 23 | frappe-bench 24 | 25 | cd frappe-bench 26 | 27 | # Use containers instead of localhost 28 | bench set-mariadb-host mariadb 29 | bench set-redis-cache-host redis-cache:6379 30 | bench set-redis-queue-host redis-queue:6379 31 | bench set-redis-socketio-host redis-socketio:6379 32 | 33 | # Remove redis from Procfile 34 | sed -i '/redis/d' ./Procfile 35 | 36 | 37 | bench new-site dev.localhost \ 38 | --mariadb-root-password 123 \ 39 | --admin-password admin \ 40 | --no-mariadb-socket 41 | 42 | bench --site dev.localhost set-config developer_mode 1 43 | bench --site dev.localhost clear-cache 44 | bench use dev.localhost 45 | bench get-app crm 46 | bench --site dev.localhost install-app crm --------------------------------------------------------------------------------