1+ import { normalizeEmail } from '@sim/utils/string'
12import { z } from 'zod'
23import {
34 type MemberProfileFields ,
@@ -179,9 +180,17 @@ export function planPeopleImport({
179180 rows : readonly Record < string , string > [ ]
180181 members : readonly PeopleImportMember [ ]
181182} ) : PeopleImportPlan {
182- const byEmail = new Map (
183- members . filter ( ( member ) => member . active ) . map ( ( member ) => [ member . email . toLowerCase ( ) , member ] )
184- )
183+ const byEmail = new Map < string , PeopleImportMember | 'ambiguous' > ( )
184+ for ( const member of members ) {
185+ if ( ! member . active ) continue
186+ const key = normalizeEmail ( member . email )
187+ byEmail . set ( key , byEmail . has ( key ) ? 'ambiguous' : member )
188+ }
189+ /** The one active member an address names; `undefined` when none or several do. */
190+ const memberWith = ( email : string ) => {
191+ const found = byEmail . get ( email )
192+ return found === 'ambiguous' ? undefined : found
193+ }
185194 const managers = new Map (
186195 members . map ( ( member ) => [ member . memberId , member . profile ?. managerMemberId ?? null ] )
187196 )
@@ -193,14 +202,17 @@ export function planPeopleImport({
193202 rows . forEach ( ( row , index ) => {
194203 const rowNumber = index + 2
195204 if ( Object . values ( row ) . every ( ( value ) => ! value ?. trim ( ) ) ) return
196- const email = cell ( row , columns . email ) . toLowerCase ( )
205+ const email = normalizeEmail ( cell ( row , columns . email ) )
197206 const skip = ( message : string ) =>
198207 issues . push ( { row : rowNumber , email : email || null , severity : 'skipped' , message } )
199208
200209 if ( ! emailSchema . safeParse ( email ) . success ) return skip ( 'Enter a valid email address' )
201210 const earlierRow = acceptedRowByEmail . get ( email )
202211 if ( earlierRow !== undefined ) return skip ( `Already listed in row ${ earlierRow } ` )
203- const member = byEmail . get ( email )
212+ if ( byEmail . get ( email ) === 'ambiguous' ) {
213+ return skip ( 'More than one member has this email, so the row was not applied' )
214+ }
215+ const member = memberWith ( email )
204216 if ( ! member ) return skip ( 'Not a member of this organization' )
205217
206218 const fields : Partial < MemberProfileFields > = { }
@@ -228,8 +240,8 @@ export function planPeopleImport({
228240
229241 let warning : string | null = null
230242 if ( columns . managerEmail !== undefined ) {
231- const managerEmail = cell ( row , columns . managerEmail ) . toLowerCase ( )
232- const manager = managerEmail ? byEmail . get ( managerEmail ) : null
243+ const managerEmail = normalizeEmail ( cell ( row , columns . managerEmail ) )
244+ const manager = managerEmail ? memberWith ( managerEmail ) : null
233245 if ( manager ?. memberId === member . memberId ) return skip ( "A person can't be their own manager" )
234246 if ( ! managerEmail ) {
235247 fields . managerMemberId = null
0 commit comments