Merge pull request #837 from frappe/revert-836-invite-member-fix-3

This commit is contained in:
Shariq Ansari 2025-05-20 13:54:44 +05:30 committed by GitHub
commit cd8dd683fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 19 additions and 179 deletions

View File

@ -123,12 +123,6 @@ def invite_by_email(emails: str, role: str):
for email in to_invite: for email in to_invite:
frappe.get_doc(doctype="CRM Invitation", email=email, role=role).insert(ignore_permissions=True) frappe.get_doc(doctype="CRM Invitation", email=email, role=role).insert(ignore_permissions=True)
return {
"existing_members": existing_members,
"existing_invites": existing_invites,
"to_invite": to_invite,
}
@frappe.whitelist() @frappe.whitelist()
def get_file_uploader_defaults(doctype: str): def get_file_uploader_defaults(doctype: str):

View File

@ -65,7 +65,7 @@
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2025-05-19 17:57:24.610295", "modified": "2024-09-16 19:40:19.340948",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "FCRM", "module": "FCRM",
"name": "CRM Form Script", "name": "CRM Form Script",
@ -83,19 +83,9 @@
"role": "Sales Manager", "role": "Sales Manager",
"share": 1, "share": 1,
"write": 1 "write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"share": 1
} }
], ],
"row_format": "Dynamic",
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [] "states": []
} }

View File

@ -21,7 +21,7 @@ class CRMInvitation(Document):
if frappe.local.dev_server: if frappe.local.dev_server:
print(f"Invite link for {self.email}: {invite_link}") print(f"Invite link for {self.email}: {invite_link}")
title = "Frappe CRM" title = f"Frappe CRM"
template = "crm_invitation" template = "crm_invitation"
frappe.sendmail( frappe.sendmail(
@ -44,24 +44,12 @@ class CRMInvitation(Document):
user = self.create_user_if_not_exists() user = self.create_user_if_not_exists()
user.append_roles(self.role) user.append_roles(self.role)
if self.role == "Sales User":
self.update_module_in_user(user, "FCRM")
user.save(ignore_permissions=True) user.save(ignore_permissions=True)
self.status = "Accepted" self.status = "Accepted"
self.accepted_at = frappe.utils.now() self.accepted_at = frappe.utils.now()
self.save(ignore_permissions=True) self.save(ignore_permissions=True)
def update_module_in_user(self, user, module):
block_modules = frappe.get_all(
"Module Def",
fields=["name as module"],
filters={"name": ["!=", module]},
)
if block_modules:
user.set("block_modules", block_modules)
def create_user_if_not_exists(self): def create_user_if_not_exists(self):
if not frappe.db.exists("User", self.email): if not frappe.db.exists("User", self.email):
first_name = self.email.split("@")[0].title() first_name = self.email.split("@")[0].title()

@ -1 +1 @@
Subproject commit 8b615c0e899d75b99c7d36ec6df97b5d0386b2ca Subproject commit 29307e4fffaacdbb3d9c5d95c5270b2f245a5607

View File

@ -150,8 +150,6 @@ declare module 'vue' {
ListIcon: typeof import('./src/components/Icons/ListIcon.vue')['default'] ListIcon: typeof import('./src/components/Icons/ListIcon.vue')['default']
ListRows: typeof import('./src/components/ListViews/ListRows.vue')['default'] ListRows: typeof import('./src/components/ListViews/ListRows.vue')['default']
LoadingIndicator: typeof import('./src/components/Icons/LoadingIndicator.vue')['default'] LoadingIndicator: typeof import('./src/components/Icons/LoadingIndicator.vue')['default']
LucideCalendar: typeof import('~icons/lucide/calendar')['default']
LucidePlus: typeof import('~icons/lucide/plus')['default']
MarkAsDoneIcon: typeof import('./src/components/Icons/MarkAsDoneIcon.vue')['default'] MarkAsDoneIcon: typeof import('./src/components/Icons/MarkAsDoneIcon.vue')['default']
MaximizeIcon: typeof import('./src/components/Icons/MaximizeIcon.vue')['default'] MaximizeIcon: typeof import('./src/components/Icons/MaximizeIcon.vue')['default']
MenuIcon: typeof import('./src/components/Icons/MenuIcon.vue')['default'] MenuIcon: typeof import('./src/components/Icons/MenuIcon.vue')['default']
@ -175,7 +173,6 @@ declare module 'vue' {
OrganizationsIcon: typeof import('./src/components/Icons/OrganizationsIcon.vue')['default'] OrganizationsIcon: typeof import('./src/components/Icons/OrganizationsIcon.vue')['default']
OrganizationsListView: typeof import('./src/components/ListViews/OrganizationsListView.vue')['default'] OrganizationsListView: typeof import('./src/components/ListViews/OrganizationsListView.vue')['default']
OutboundCallIcon: typeof import('./src/components/Icons/OutboundCallIcon.vue')['default'] OutboundCallIcon: typeof import('./src/components/Icons/OutboundCallIcon.vue')['default']
Password: typeof import('./src/components/Controls/Password.vue')['default']
PauseIcon: typeof import('./src/components/Icons/PauseIcon.vue')['default'] PauseIcon: typeof import('./src/components/Icons/PauseIcon.vue')['default']
PhoneIcon: typeof import('./src/components/Icons/PhoneIcon.vue')['default'] PhoneIcon: typeof import('./src/components/Icons/PhoneIcon.vue')['default']
PinIcon: typeof import('./src/components/Icons/PinIcon.vue')['default'] PinIcon: typeof import('./src/components/Icons/PinIcon.vue')['default']
@ -210,7 +207,6 @@ declare module 'vue' {
SmileIcon: typeof import('./src/components/Icons/SmileIcon.vue')['default'] SmileIcon: typeof import('./src/components/Icons/SmileIcon.vue')['default']
SortBy: typeof import('./src/components/SortBy.vue')['default'] SortBy: typeof import('./src/components/SortBy.vue')['default']
SortIcon: typeof import('./src/components/Icons/SortIcon.vue')['default'] SortIcon: typeof import('./src/components/Icons/SortIcon.vue')['default']
SquareAsterisk: typeof import('./src/components/Icons/SquareAsterisk.vue')['default']
StepsIcon: typeof import('./src/components/Icons/StepsIcon.vue')['default'] StepsIcon: typeof import('./src/components/Icons/StepsIcon.vue')['default']
SuccessIcon: typeof import('./src/components/Icons/SuccessIcon.vue')['default'] SuccessIcon: typeof import('./src/components/Icons/SuccessIcon.vue')['default']
TableMultiselectInput: typeof import('./src/components/Controls/TableMultiselectInput.vue')['default'] TableMultiselectInput: typeof import('./src/components/Controls/TableMultiselectInput.vue')['default']

View File

@ -216,13 +216,6 @@
:options="field.options" :options="field.options"
@change="(e) => fieldChange(e.target.value, field, row)" @change="(e) => fieldChange(e.target.value, field, row)"
/> />
<Password
v-else-if="field.fieldtype === 'Password'"
variant="outline"
:value="row[field.fieldname]"
:disabled="Boolean(field.read_only)"
@change="fieldChange($event.target.value, field, row)"
/>
<FormattedInput <FormattedInput
v-else-if="field.fieldtype === 'Int'" v-else-if="field.fieldtype === 'Int'"
class="[&_input]:text-right" class="[&_input]:text-right"
@ -332,7 +325,6 @@
</template> </template>
<script setup> <script setup>
import Password from '@/components/Controls/Password.vue'
import FormattedInput from '@/components/Controls/FormattedInput.vue' import FormattedInput from '@/components/Controls/FormattedInput.vue'
import GridFieldsEditorModal from '@/components/Controls/GridFieldsEditorModal.vue' import GridFieldsEditorModal from '@/components/Controls/GridFieldsEditorModal.vue'
import GridRowFieldsModal from '@/components/Controls/GridRowFieldsModal.vue' import GridRowFieldsModal from '@/components/Controls/GridRowFieldsModal.vue'

View File

@ -58,21 +58,6 @@
class="p-1.5 max-h-[12rem] overflow-y-auto" class="p-1.5 max-h-[12rem] overflow-y-auto"
static static
> >
<div
v-if="!options.length"
class="flex gap-2 rounded px-2 py-1 text-base text-ink-gray-5"
>
<FeatherIcon
v-if="fetchContacts"
name="search"
class="h-4"
/>
{{
fetchContacts
? __('No results found')
: __('Type an email address to add')
}}
</div>
<ComboboxOption <ComboboxOption
v-for="option in options" v-for="option in options"
:key="option.value" :key="option.value"
@ -152,10 +137,6 @@ const props = defineProps({
type: Function, type: Function,
default: (value) => `${value} is an Invalid value`, default: (value) => `${value} is an Invalid value`,
}, },
fetchContacts: {
type: Boolean,
default: true,
},
}) })
const values = defineModel() const values = defineModel()
@ -210,19 +191,17 @@ const filterOptions = createResource({
}) })
const options = computed(() => { const options = computed(() => {
let searchedContacts = props.fetchContacts ? filterOptions.data : [] let searchedContacts = filterOptions.data || []
if (!searchedContacts?.length && query.value) { if (!searchedContacts.length && query.value) {
searchedContacts.push({ searchedContacts.push({
label: query.value, label: query.value,
value: query.value, value: query.value,
}) })
} }
return searchedContacts || [] return searchedContacts
}) })
function reload(val) { function reload(val) {
if (!props.fetchContacts) return
filterOptions.update({ filterOptions.update({
params: { txt: val }, params: { txt: val },
}) })

View File

@ -1,32 +0,0 @@
<template>
<FormControl
:type="show ? 'text' : 'password'"
:value="modelValue || value"
v-bind="$attrs"
>
<template #suffix>
<Button v-show="showEye" class="!h-4" @click="show = !show">
<FeatherIcon :name="show ? 'eye-off' : 'eye'" class="h-3" />
</Button>
</template>
</FormControl>
</template>
<script setup>
import { FormControl } from 'frappe-ui'
import { ref, computed } from 'vue'
const props = defineProps({
modelValue: {
type: [String, Number],
default: '',
},
value: {
type: [String, Number],
default: '',
},
})
const show = ref(false)
const showEye = computed(() => {
let v = props.modelValue || props.value
return !v?.includes('*')
})
</script>

View File

@ -136,6 +136,7 @@
<DateTimePicker <DateTimePicker
v-else-if="field.fieldtype === 'Datetime'" v-else-if="field.fieldtype === 'Datetime'"
:value="data[field.fieldname]" :value="data[field.fieldname]"
icon-left=""
:formatter="(date) => getFormat(date, '', true, true)" :formatter="(date) => getFormat(date, '', true, true)"
:placeholder="getPlaceholder(field)" :placeholder="getPlaceholder(field)"
input-class="border-none" input-class="border-none"
@ -143,6 +144,7 @@
/> />
<DatePicker <DatePicker
v-else-if="field.fieldtype === 'Date'" v-else-if="field.fieldtype === 'Date'"
icon-left=""
:value="data[field.fieldname]" :value="data[field.fieldname]"
:formatter="(date) => getFormat(date, '', true)" :formatter="(date) => getFormat(date, '', true)"
:placeholder="getPlaceholder(field)" :placeholder="getPlaceholder(field)"
@ -159,18 +161,11 @@
:description="field.description" :description="field.description"
@change="fieldChange($event.target.value, field)" @change="fieldChange($event.target.value, field)"
/> />
<Password
v-else-if="field.fieldtype === 'Password'"
:value="data[field.fieldname]"
:placeholder="getPlaceholder(field)"
:description="field.description"
@change="fieldChange($event.target.value, field)"
/>
<FormattedInput <FormattedInput
v-else-if="field.fieldtype === 'Int'" v-else-if="['Int'].includes(field.fieldtype)"
type="text" type="number"
:placeholder="getPlaceholder(field)" :placeholder="getPlaceholder(field)"
:value="data[field.fieldname] || '0'" :value="data[field.fieldname]"
:disabled="Boolean(field.read_only)" :disabled="Boolean(field.read_only)"
:description="field.description" :description="field.description"
@change="fieldChange($event.target.value, field)" @change="fieldChange($event.target.value, field)"
@ -214,7 +209,6 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import Password from '@/components/Controls/Password.vue'
import FormattedInput from '@/components/Controls/FormattedInput.vue' import FormattedInput from '@/components/Controls/FormattedInput.vue'
import EditIcon from '@/components/Icons/EditIcon.vue' import EditIcon from '@/components/Icons/EditIcon.vue'
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue' import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'

View File

@ -1,19 +0,0 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-square-asterisk-icon lucide-square-asterisk"
>
<rect width="18" height="18" x="3" y="3" rx="2" />
<path d="M12 8v8" />
<path d="m8.5 14 7-4" />
<path d="m8.5 10 7 4" />
</svg>
</template>

View File

@ -150,7 +150,6 @@ import Section from '@/components/Section.vue'
import Email2Icon from '@/components/Icons/Email2Icon.vue' import Email2Icon from '@/components/Icons/Email2Icon.vue'
import PinIcon from '@/components/Icons/PinIcon.vue' import PinIcon from '@/components/Icons/PinIcon.vue'
import UserDropdown from '@/components/UserDropdown.vue' import UserDropdown from '@/components/UserDropdown.vue'
import SquareAsterisk from '@/components/Icons/SquareAsterisk.vue'
import LeadsIcon from '@/components/Icons/LeadsIcon.vue' import LeadsIcon from '@/components/Icons/LeadsIcon.vue'
import DealsIcon from '@/components/Icons/DealsIcon.vue' import DealsIcon from '@/components/Icons/DealsIcon.vue'
import ContactsIcon from '@/components/Icons/ContactsIcon.vue' import ContactsIcon from '@/components/Icons/ContactsIcon.vue'
@ -322,17 +321,6 @@ const showIntermediateModal = ref(false)
const currentStep = ref({}) const currentStep = ref({})
const steps = reactive([ const steps = reactive([
{
name: 'setup_your_password',
title: __('Setup your password'),
icon: markRaw(SquareAsterisk),
completed: false,
onClick: () => {
minimize.value = true
showSettings.value = true
activeSettingsPage.value = 'Profile'
},
},
{ {
name: 'create_first_lead', name: 'create_first_lead',
title: __('Create your first lead'), title: __('Create your first lead'),

View File

@ -20,7 +20,6 @@
:error-message=" :error-message="
(value) => __('{0} is an invalid email address', [value]) (value) => __('{0} is an invalid email address', [value])
" "
:fetchContacts="false"
/> />
</div> </div>
<FormControl <FormControl
@ -128,17 +127,10 @@ const inviteByEmail = createResource({
role: role.value, role: role.value,
} }
}, },
onSuccess(data) { onSuccess() {
if (data?.existing_invites?.length) {
error.value = __('Agent with email {0} already exists', [
data.existing_invites.join(', '),
])
} else {
role.value = 'Sales User'
error.value = null
}
invitees.value = [] invitees.value = []
role.value = 'Sales User'
error.value = null
pendingInvitations.reload() pendingInvitations.reload()
updateOnboardingStep('invite_your_team') updateOnboardingStep('invite_your_team')
}, },

View File

@ -57,7 +57,7 @@
v-model="profile.email" v-model="profile.email"
:disabled="true" :disabled="true"
/> />
<Password <FormControl
class="w-full" class="w-full"
label="Set new password" label="Set new password"
v-model="profile.new_password" v-model="profile.new_password"
@ -77,15 +77,13 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import Password from '@/components/Controls/Password.vue'
import ProfileImageEditor from '@/components/Settings/ProfileImageEditor.vue' import ProfileImageEditor from '@/components/Settings/ProfileImageEditor.vue'
import { usersStore } from '@/stores/users' import { usersStore } from '@/stores/users'
import { Dialog, Avatar, createResource, ErrorMessage, toast } from 'frappe-ui' import { createToast } from '@/utils'
import { useOnboarding } from 'frappe-ui/frappe' import { Dialog, Avatar, createResource, ErrorMessage } from 'frappe-ui'
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
const { getUser, users } = usersStore() const { getUser, users } = usersStore()
const { updateOnboardingStep } = useOnboarding('frappecrm')
const user = computed(() => getUser() || {}) const user = computed(() => getUser() || {})
@ -97,13 +95,6 @@ const error = ref('')
function updateUser() { function updateUser() {
loading.value = true loading.value = true
let passwordUpdated = false
if (profile.value.new_password) {
passwordUpdated = true
}
const fieldname = { const fieldname = {
first_name: profile.value.first_name, first_name: profile.value.first_name,
last_name: profile.value.last_name, last_name: profile.value.last_name,
@ -120,9 +111,6 @@ function updateUser() {
}, },
auto: true, auto: true,
onSuccess: () => { onSuccess: () => {
if (passwordUpdated) {
updateOnboardingStep('setup_your_password')
}
loading.value = false loading.value = false
error.value = '' error.value = ''
profile.value.new_password = '' profile.value.new_password = ''

View File

@ -275,20 +275,11 @@
" "
:disabled="Boolean(field.read_only)" :disabled="Boolean(field.read_only)"
/> />
<Password
v-else-if="field.fieldtype === 'Password'"
class="form-control"
:value="document.doc[field.fieldname]"
:placeholder="field.placeholder"
:debounce="500"
@change.stop="fieldChange($event.target.value, field)"
:disabled="Boolean(field.read_only)"
/>
<FormattedInput <FormattedInput
v-else-if="field.fieldtype === 'Int'" v-else-if="field.fieldtype === 'Int'"
class="form-control" class="form-control"
type="text" type="text"
:value="document.doc[field.fieldname] || '0'" v-model="document.doc[field.fieldname]"
:placeholder="field.placeholder" :placeholder="field.placeholder"
:debounce="500" :debounce="500"
@change.stop="fieldChange($event.target.value, field)" @change.stop="fieldChange($event.target.value, field)"
@ -375,7 +366,6 @@
</template> </template>
<script setup> <script setup>
import Password from '@/components/Controls/Password.vue'
import FormattedInput from '@/components/Controls/FormattedInput.vue' import FormattedInput from '@/components/Controls/FormattedInput.vue'
import Section from '@/components/Section.vue' import Section from '@/components/Section.vue'
import NestedPopover from '@/components/NestedPopover.vue' import NestedPopover from '@/components/NestedPopover.vue'