def compare_io_with_mc(io_file_name, mc_file_name):
    """
    Takes a IdrottOnline file and converts into a My Club Import Excel file
    """
    # IO Dataframe
    io_export_df = _read_io_file(io_file_name)
    stats("Antal medlemmar i IO: {} ({})".format(str(len(io_export_df)), Path(io_file_name).name))

    # MC Dataframe
    mc_export_df = _read_mc_file(mc_file_name)
    stats("Antal medlemmar i MC: {} ({})".format(str(len(mc_export_df)), Path(mc_file_name).name))


    """
    My Club import columns
    'Förnamn',
    'Efternamn',
    'Adress',
    'Postnummer',
    'Postadress',
    'Personnummer',
    'Hemtelefon medlem',
    'Hemtelefon kontaktperson1',
    'Hemtelefon kontaktperson2',
    'Mobiltelefon medlem',
    'Mobiltelefon kontaktperson1',
    'Mobiltelefon kontaktperson2',
    'Epost medlem',
    'Epost kontaktperson1',
    'Epost kontaktperson2',
    'Lag',
    'Medlemstyp',
    'Kön',
    'Förnamn kontaktperson1',
    'Efternamn kontaktperson1',
    'Förnamn kontaktperson2',
    'Efternamn kontaktperson2',
    'Extra 1',
    'Extra 2',
    'Extra 3',
    'Extra 4',
    'Extra 5',
    """
    pass
Пример #2
0
def sync_last_ones(mc_file_name, mc_invoice_file, io_file_name):
    """
    Export last ones from My Club to IO
    """
    # Get data from latest MC export
    mc_read_df = _read_mc_file(mc_file_name)
    stats("Antal inlästa från MC: {:>4} ({})".format(str(len(mc_read_df)), Path(mc_file_name).name))

    # Invoice info from My Club
    mc_invoice_df = pd.read_excel(mc_invoice_file,  
        dtype = {'MedlemsID': 'string'}, 
        usecols=['MedlemsID','Avgift','Summa','Summa betalt',
            'Familjemedlem 1','Familjemedlem 2','Familjemedlem 3','Familjemedlem 4','Familjemedlem 5','Familjemedlem 6'])
    stats("Antal fakturor i MC:  {} ({})".format(str(len(mc_invoice_df)), Path(mc_invoice_file).name))
    # Merge in invoice details
    # Added later as special column
    #mc_read_df = mc_read_df.merge(mc_invoice_df, on='MedlemsID', how='left', suffixes=(None,'_inv'), validate = "one_to_one")
    mc_read_df = mc_read_df.merge(mc_invoice_df, on='MedlemsID', how='left', suffixes=(None,'_inv'), validate = "1:m")

    # Get data from latest IO export
    io_read_df = _read_io_file(io_file_name)
    # print(io_read_df.columns)
    stats("Antal inlästa från IO: {:>4} ({})".format(str(len(io_read_df)), Path(io_file_name).name))

    # Get MedlemsID from IO - if we can find it
    io_read_df['MC_MedlemsID'] = [search_medlemsid_from_io(comment, medlemsnr)
        for comment, medlemsnr 
        in zip (io_read_df['Övrig medlemsinfo'], io_read_df['Medlemsnr.'])]

    # Stats about personnummer
    stats("Antal med ofullständiga personnummer I MC: {:>4} st".format(str(len(mc_read_df[mc_read_df['Personnummer'].str.len() == 8]))))
    stats("Antal med ofullständiga personnummer I IO: {:>4} st".format(str(len(io_read_df[io_read_df['Födelsedat./Personnr.'].str.len() == 8]))))
    stats("Antal med  fullständiga personnummer I MC: {:>4} st".format(str(len(mc_read_df[mc_read_df['Personnummer'].str.len() == 13]))))
    stats("Antal med  fullständiga personnummer I IO: {:>4} st".format(str(len(io_read_df[io_read_df['Födelsedat./Personnr.'].str.len() == 13]))))

    # Merge on all 'Personnummer','Förnamn','Efternamn'
    print("MC " + "> Merge (MedlemsID) <".center(40, "-") + " IO")
    merged_df = pd.merge(mc_read_df, io_read_df,
        left_on = ['MedlemsID'],
        right_on = ['MC_MedlemsID'],
        #left_on = ['Personnummer','Förnamn','Efternamn'],
        #right_on = ['Födelsedat./Personnr.','Förnamn','Efternamn'],
        how = 'left',
        #suffixes = ('_mc','_io'),
        suffixes = ('','_io'),
        indicator = True)
    stats("Antal efter merge: {:>8}".format(str(len(merged_df))))
    stats("Antal lika (på personnummer): {:>12} (fullst. + icke fullständiga)".format(str(len(merged_df))))

    # Number of members with complete personnummer
    stats("Antal med  fullständiga personnummer: {:>4}".format(str(len(merged_df[merged_df['Personnummer'].str.len() == 13]))))
    stats("Antal med ofullständiga personnummer: {:>4}".format(str(len(merged_df[merged_df['Personnummer'].str.len() == 8]))))

    # Filter only on members with incomplete 'personnummer'
    print(" Filter: Endast ofullständiga ".center(46, "-"))
    #merged_df = merged_df[merged_df['Personnummer'].str.len() == 8]
    #stats("Antal med ofullständiga personnummer: {:>4}".format(str(len(merged_df))))

    stats("Antal endast i MC: {:>8}".format(str(len(merged_df.loc[merged_df['_merge'] == 'left_only' ]))))
    stats("Antal endast i IO: {:>8}".format(str(len(merged_df.loc[merged_df['_merge'] == 'right_only' ]))))
    stats("Antal i båda:      {:>8}".format(str(len(merged_df.loc[merged_df['_merge'] == 'both' ]))))

    # Filter away those originating from MC (= have group "MC_Import")
    #merged_df = merged_df[merged_df['Grupp/Lag/Arbetsrum/Familj'].str.contains('MC_Import', na="") != True]
    #stats("Antal utan MC-grupper i IO: {:>3}".format(str(len(merged_df))))

    # Convert to import format

    # IO Import columns - for ref
    io_import_cols = ['Prova-på','Förnamn','Alt. förnamn','Efternamn','Kön','Nationalitet','IdrottsID','Födelsedat./Personnr.','Telefon mobil',
        'E-post kontakt','Kontaktadress - c/o adress','Kontaktadress - Gatuadress','Kontaktadress - Postnummer','Kontaktadress - Postort',
        'Kontaktadress - Land','Arbetsadress - c/o adress','Arbetsadress - Gatuadress','Arbetsadress - Postnummer','Arbetsadress - Postort','Arbetsadress - Land',
        'Telefon bostad','Telefon arbete','E-post privat','E-post arbete','Medlemsnr.','Medlem sedan','Medlem t.o.m.','Övrig medlemsinfo',
        'Familj','Fam.Admin','Lägg till GruppID','Ta bort GruppID']

    # 1. Convert all MC members to IO Import format
    #mc_in_io_format_df = _convert_mc_to_io_format(io_import_cols, merged_df, timestamp)
    mc_in_io_format_df = pd.DataFrame(columns=io_import_cols)
#    mc_in_io_format_df['Prova-på'] = mc_export_df['']  # Not used in MC?
    mc_in_io_format_df['Förnamn'] = merged_df['Förnamn']
#    mc_in_io_format_df['Alt. förnamn'] = mc_export_df['']  # Found none in MC
    mc_in_io_format_df['Efternamn'] = merged_df['Efternamn']
    mc_in_io_format_df['Kön'] = merged_df['Kön (flicka/pojke)']
    mc_in_io_format_df['Nationalitet'] = merged_df['Nationalitet'].replace('SE','Sverige')
#    mc_in_io_format_df['IdrottsID'] = mc_export_df[''] 
    mc_in_io_format_df['Födelsedat./Personnr.'] = merged_df['Personnummer'] #.astype('string').apply(convert_personnummer) 
    mc_in_io_format_df['Telefon mobil'] = merged_df['Mobiltelefon']
    mc_in_io_format_df['E-post kontakt'] = merged_df['E-post'] 
    mc_in_io_format_df['Kontaktadress - c/o adress'] = merged_df['c/o']
    mc_in_io_format_df['Kontaktadress - Gatuadress'] = merged_df['Adress']
    mc_in_io_format_df['Kontaktadress - Postnummer'] = merged_df['Postnummer'].astype('string').apply(convert_postnr)
    mc_in_io_format_df['Kontaktadress - Postort'] = merged_df['Postort']
    mc_in_io_format_df['Kontaktadress - Land'] = merged_df['Land'].apply(convert_countrycode)
#    mc_in_io_format_df['Arbetsadress - c/o adress'] = mc_export_df['']
#    mc_in_io_format_df['Arbetsadress - Gatuadress'] = mc_export_df['']
#    mc_in_io_format_df['Arbetsadress - Postnummer'] = mc_export_df['']
#    mc_in_io_format_df['Arbetsadress - Postort'] = mc_export_df['']
#    mc_in_io_format_df['Arbetsadress - Land'] = mc_export_df['']
    mc_in_io_format_df['Telefon bostad'] = merged_df['Hemtelefon']
    mc_in_io_format_df['Telefon arbete'] = merged_df['Arbetstelefon']
#    mc_in_io_format_df['E-post privat'] = mc_export_df['Kontakt 1 epost']
#    mc_in_io_format_df['E-post arbete'] = mc_export_df['']
    
    mc_in_io_format_df['Medlem sedan'] = merged_df['Datum registrerad']
    mc_in_io_format_df['MC_Senast ändrad'] = merged_df['Senast ändrad']
#    mc_in_io_format_df['Medlem t.o.m.'] = mc_export_df['']
    mc_in_io_format_df['Övrig medlemsinfo'] = merged_df['Kommentar'].astype('string').apply(clean_pii_comments) # Special handling - not for all clubs
    # Add special info to 'Övrig medlemsinfo' - MC MedlemsInfo and execution time
    mc_in_io_format_df['Övrig medlemsinfo'] = [add_comment_info(comment, member_id, timestamp)
        for comment, member_id
        in zip(mc_in_io_format_df['Övrig medlemsinfo'] , merged_df['MedlemsID'])]

#   mc_in_io_format_df['Familj'] = mc_export_df['Familj']
#   mc_in_io_format_df['Fam.Admin'] = mc_export_df[''] 
    mc_in_io_format_df['Lägg till GruppID'] = merged_df['Grupper'].apply(convert_mc_groups_to_io_groups) 
    # Also - add special columns as groupIDs
    mc_in_io_format_df['Lägg till GruppID'] = [concat_special_cols(groups, cirkusutb, frisksportlofte, hedersmedlem, ingen_tidning, frisksportutb, trampolinutb, avgift) 
        for groups, cirkusutb, frisksportlofte, hedersmedlem, ingen_tidning, frisksportutb, trampolinutb, avgift
        in zip(mc_in_io_format_df['Lägg till GruppID'], merged_df['Cirkusledarutbildning'], merged_df['Frisksportlöfte'], 
            merged_df['Hedersmedlem'], merged_df['Ingen tidning tack'], merged_df['Frisksportutbildning'], 
            merged_df['Trampolinutbildning'], merged_df['Avgift'])]
    # Also - add family info as groups
    # 2020-11-15 Disabled - since IO does not handle this according to documentation...
    if False:
        merged_df['Familj'] = merged_df['Familj'].apply(mc_family_to_id)
        mc_in_io_format_df['Lägg till GruppID'] = [concat_group_id(groups, family_id) 
            for groups, family_id 
            in zip(mc_in_io_format_df['Lägg till GruppID'], merged_df['Familj'])]

    # Extra info
    mc_in_io_format_df['_merge'] = merged_df['_merge']

    # Add missing, neccessary for import, columns - as nan
    mc_in_io_format_df[['Prova-på', 'Ta bort GruppID']] = np.nan
    #merged_df[['Prova-på', 'Ta bort GruppID']] = np.nan
    #print(merged_df['Grupper'].head())

    # Req. for incomplete personnummer:
    # "namn, födelsedatum (år, månad, dag), kön, nationalitet och minst en angiven adress"

    last_filename = path_out + timestamp + '_last-5_import.xlsx'
    save_file(last_filename, mc_in_io_format_df)
    #print(mc_in_io_format_df)
    #save_file(last_filename, merged_df)
    stats("Antal kvar att uppdatera: {:>5} ({})".format(str(len(mc_in_io_format_df)), Path(last_filename).name))
Пример #3
0
def from_mc_to_io(mc_file_name, mc_invoice_file, io_file_name):
    """
    Takes a My Club All members file and converts to IdrottOnline Import Excel
    """
    # My Club Dataframe
    mc_export_df = _read_mc_file(mc_file_name)
    stats("Antal medlemmar i MC: {} ({})".format(str(len(mc_export_df)), Path(mc_file_name).name))

    # Invoice info from My Club
    mc_invoice_df = pd.read_excel(mc_invoice_file, 
        usecols=['MedlemsID','Avgift','Summa','Summa betalt',
            'Familjemedlem 1','Familjemedlem 2','Familjemedlem 3','Familjemedlem 4','Familjemedlem 5','Familjemedlem 6'])
    stats("Antal fakturor i MC: {} ({})".format(str(len(mc_invoice_df)), Path(mc_invoice_file).name))
    # Merge in invoice details
    # Added later as special column
    # TODO Only for family head and not each person
    mc_export_df = mc_export_df.merge(mc_invoice_df, on='MedlemsID', how='left', suffixes=(None,'_inv'), validate = "one_to_one")

    # IO Import columns - for ref
    io_import_cols = ['Prova-på','Förnamn','Alt. förnamn','Efternamn','Kön','Nationalitet','IdrottsID','Födelsedat./Personnr.','Telefon mobil',
        'E-post kontakt','Kontaktadress - c/o adress','Kontaktadress - Gatuadress','Kontaktadress - Postnummer','Kontaktadress - Postort',
        'Kontaktadress - Land','Arbetsadress - c/o adress','Arbetsadress - Gatuadress','Arbetsadress - Postnummer','Arbetsadress - Postort','Arbetsadress - Land',
        'Telefon bostad','Telefon arbete','E-post privat','E-post arbete','Medlemsnr.','Medlem sedan','Medlem t.o.m.','Övrig medlemsinfo',
        'Familj','Fam.Admin','Lägg till GruppID','Ta bort GruppID']

    # 1. Convert all MC members to IO Import format
    mc_in_io_format_df = _convert_mc_to_io_format(io_import_cols, mc_export_df, timestamp)
   
    # Export-4 last
    # Filter out only thos in group "Remaining migration"
    #mc_in_io_format_df['MissedGroup'] = np.where(mc_export_df['Grupper'].str.contains('Remaining migration'), 'True', 'False') 
    #mc_in_io_format_df = mc_in_io_format_df[mc_in_io_format_df.MissedGroup == "True"]

#    mc_in_io_format_df['Ta bort GruppID'] = mc_export_df[''] 

    # 2. Compare MC data with current IO data
    # Todo
    #comp_df = mc_in_io_format_df[['Förnamn','Alt. förnamn','Efternamn','Födelsedat./Personnr.','Kön','Nationalitet','Telefon mobil','E-post kontakt',
    #    'Kontaktadress - c/o adress','Kontaktadress - Gatuadress','Kontaktadress - Postnummer','Kontaktadress - Postort','Kontaktadress - Land',
    #    'Arbetsadress - c/o adress','Arbetsadress - Gatuadress','Arbetsadress - Postnummer','Arbetsadress - Postort','Arbetsadress - Land',
    #    'Telefon bostad','Telefon arbete','E-post privat','E-post arbete','Övrig medlemsinfo','Familj','Fam.Admin','Medlem sedan','Medlem t.o.m.']].compare(
    #        io_current_df[['Förnamn','Alt. förnamn','Efternamn','Födelsedat./Personnr.','Kön','Nationalitet','Telefon mobil','E-post kontakt',
    #    'Kontaktadress - c/o adress','Kontaktadress - Gatuadress','Kontaktadress - Postnummer','Kontaktadress - Postort','Kontaktadress - Land',
    #    'Arbetsadress - c/o adress','Arbetsadress - Gatuadress','Arbetsadress - Postnummer','Arbetsadress - Postort','Arbetsadress - Land',
    #    'Telefon bostad','Telefon arbete','E-post privat','E-post arbete','Övrig medlemsinfo','Familj','Fam.Admin','Medlem sedan','Medlem t.o.m.']])

    #df1 = mc_in_io_format_df[['Förnamn','Efternamn','Födelsedat./Personnr.','Kön','Medlem sedan']].copy()
    #print(df1.axes)
    #df2 = io_current_df[['Förnamn','Efternamn','Födelsedat./Personnr.','Kön','Medlem sedan']].copy()
    #print(df2.axes)
    #comp_df = df1.compare(df2)
    #save_file('/usr/src/app/files/' + date_today + '_mc-io_comparison.xlsx', comp_df)
    
    # 3. Save file with all members from MC in correct format (still need to cross check with IO!)
    save_file(path + timestamp + '_all_mc_in_io_format.xlsx', mc_in_io_format_df)
    stats("Sparat: " + path + timestamp + '_all_mc_in_io_format.xlsx')

    # 4. Merge
    
    # Current members in IdrottOnline
    io_current_df = pd.read_excel(io_file_name, 
        usecols=['Födelsedat./Personnr.'],
        dtype = {
        'Telefon mobil': 'string', 'Telefon bostad': 'string', 'Telefon arbete': object, 'Medlemsnr.': 'string'},
        converters = {'E-post kontakt':normalize_email, 'E-post privat':normalize_email, 'E-post arbete':normalize_email}) # IO columns
    stats("Antal medlemmar i IO: " + str(len(io_current_df)) + " (" + Path(io_file_name).name + ")")

    # For import
    # TODO Remove this later!
    # This is already handled by how = 'left' below - so we can assign 'Medlemsnr.' without risking overwrite
    # mc_in_io_format_df['Medlemsnr.'] = mc_export_df['MedlemsID'] 
    # TODO Remove this later!
    # Filter - only non-existing in IO (solved by how = 'left')
    # Label: 'Export-1' - For members updated '2020-11-16_01.20'
    #for_io_import_df = pd.merge(mc_in_io_format_df, io_current_df,
    #                 on = 'Födelsedat./Personnr.',
    #                 how = 'left',
    #                 suffixes = ('_mc','_io'),
    #                 indicator = True)

    # Label: 'Export-2' - Current members in IO updated in IO with new data from MC
    for_io_import_df = pd.merge(mc_in_io_format_df, io_current_df,
                     on = 'Födelsedat./Personnr.',
                     how = 'right',
                     suffixes = ('_mc','_io'),
                     indicator = True)

    # Filter - only with full personnummer
    for_io_import_df = for_io_import_df[for_io_import_df['Födelsedat./Personnr.'].str.len() > 8]

    # Filter - only MC
    for_io_import_df = for_io_import_df[for_io_import_df['_merge'] == "right_only" ]

    for_io_import_file = path + timestamp + '_for_io_import.xlsx'
    stats("Antal för import till IO:   " + str(len(for_io_import_df)) + " (" + Path(for_io_import_file).name + ")")
    stats("Enbart i MC: " + str(len(for_io_import_df.loc[for_io_import_df['_merge'] == 'left_only' ])))
    stats("I både MC och IO: " + str(len(for_io_import_df.loc[for_io_import_df['_merge'] == 'both' ])))
    stats("Antal med endast födelsedatum: " + str(len(for_io_import_df[for_io_import_df['Födelsedat./Personnr.'].str.len() == 8])))
    stats("Antal med fullt personnummer:  " + str(len(for_io_import_df[for_io_import_df['Födelsedat./Personnr.'].str.len() > 8])))
    save_file(for_io_import_file, for_io_import_df)
    stats("Sparat: " + for_io_import_file)
Пример #4
0
def check_status(mc_file_name, io_file_name):
    """
    Check status between MC and IO

    NOTE! This function is probably not correct all the way to the end righ now
    """
    # Get data from latest MC export
    mc_read_df = _read_mc_file(mc_file_name)
    stats("Antal inlästa från MC: {:>4} ({})".format(str(len(mc_read_df)), Path(mc_file_name).name))

    # Get data from latest IO export
    io_read_df = _read_io_file(io_file_name)
    # print(io_read_df.columns)
    stats("Antal inlästa från IO: {:>4} ({})".format(str(len(io_read_df)), Path(io_file_name).name))

    # Stats about personnummer
    stats("Antal med ofullständiga personnummer I MC: {:>4}".format(str(len(mc_read_df[mc_read_df['Personnummer'].str.len() == 8]))))
    stats("Antal med ofullständiga personnummer I IO: {:>4}".format(str(len(io_read_df[io_read_df['Födelsedat./Personnr.'].str.len() == 8]))))

    merged_df = pd.merge(mc_read_df, io_read_df,
        left_on = ['Personnummer','Förnamn','Efternamn'],
        right_on = ['Födelsedat./Personnr.','Förnamn','Efternamn'],
        how = 'outer',
        suffixes = ('_mc','_io'),
        indicator = True)
    stats("Antal lika (på personnummer): {:>12} (fullst. + icke fullständiga)".format(str(len(merged_df))))

    # Number of members with complete personnummer
    stats("Antal med  fullständiga personnummer: {:>4}".format(str(len(merged_df[merged_df['Personnummer'].str.len() == 13]))))

    # Filter away members with incomplete 'personnummer'
    merged_df = merged_df[merged_df['Personnummer'].str.len() == 8]
    stats("Antal med ofullständiga personnummer: {:>4}".format(str(len(merged_df))))

    # Filter away those originating from MC (= have group "MC_Import")
    #merged_df = merged_df[merged_df['Grupp/Lag/Arbetsrum/Familj'].str.contains('MC_Import', na="") != True]
    #stats("Antal utan MC-grupper i IO: {:>3}".format(str(len(merged_df))))

    # Add missing, neccessary for import, columns - as nan
    merged_df[['Prova-på', 'Ta bort GruppID']] = np.nan

    # Convert MC groups to IO GroupID's
    merged_df['Lägg till GruppID'] = merged_df['Grupper'].apply(convert_mc_groups_to_io_groups) 

    # Add special group 'MC_GruppViaMC'
    # We know that all groups are non-empty, we can just add a the end
    merged_df['Lägg till GruppID'] = merged_df['Lägg till GruppID'].apply(lambda x : x + ', 580242')

    # Retain columns to be used for import only
    # Disable for now
    if False:
        export_cols = ['Prova-på', 'Förnamn', 'Alt. förnamn', 'Efternamn', 'Kön', 'Nationalitet', 'IdrottsID', 
            'Födelsedat./Personnr.', 'Telefon mobil', 'E-post kontakt', 'Kontaktadress - c/o adress', 'Kontaktadress - Gatuadress', 
            'Kontaktadress - Postnummer', 'Kontaktadress - Postort', 'Kontaktadress - Land', 'Arbetsadress - c/o adress', 
            'Arbetsadress - Gatuadress', 'Arbetsadress - Postnummer', 'Arbetsadress - Postort', 'Arbetsadress - Land', 'Telefon bostad', 
            'Telefon arbete', 'E-post privat', 'E-post arbete', 'Medlemsnr.', 'Medlem sedan', 'Medlem t.o.m.', 'Övrig medlemsinfo', 
            'Familj', 'Fam.Admin', 'Lägg till GruppID', 'Ta bort GruppID']
        merged_df = merged_df[export_cols]

    check_status_filename = path + timestamp + '_check_status_update.xlsx'
    save_file(check_status_filename, merged_df)
    stats("Antal kvar att uppdatera: {:>5} ({})".format(str(len(merged_df)), Path(check_status_filename).name))
Пример #5
0
def sync_groups_from_mc_to_io(mc_file_name, mc_invoice_file, io_file_name):
    """
    Sync groupes between MC and IO
    """
    # Get data from latest MC export
    mc_read_df = _read_mc_file(mc_file_name)
    stats("Antal inlästa från MC: {} ({})".format(str(len(mc_read_df)), Path(mc_file_name).name))

    # Invoice info from My Club
    mc_invoice_df = pd.read_excel(mc_invoice_file,  
        dtype = {'MedlemsID': 'string'}, 
        usecols=['MedlemsID','Avgift','Summa','Summa betalt'])
        #usecols=['MedlemsID','Avgift','Summa','Summa betalt',
        #    'Familjemedlem 1','Familjemedlem 2','Familjemedlem 3','Familjemedlem 4','Familjemedlem 5','Familjemedlem 6'])
    stats("Antal fakturor i MC:  {} ({})".format(str(len(mc_invoice_df)), Path(mc_invoice_file).name))
    # Merge in invoice details
    # Added later as special column
    mc_read_df = mc_read_df.merge(mc_invoice_df, on='MedlemsID', how='left', suffixes=(None,'_inv'), validate = "one_to_one")

    # Get data from latest IO export
    io_read_df = _read_io_file(io_file_name)
    # print(io_read_df.columns)
    stats("Antal inlästa från IO: {} ({})".format(str(len(io_read_df)), Path(io_file_name).name))

    # Get MedlemsID from IO - if we can find it
    io_read_df['MC_MedlemsID'] = [search_medlemsid_from_io(comment, medlemsnr)
        for comment, medlemsnr 
        in zip (io_read_df['Övrig medlemsinfo'], io_read_df['Medlemsnr.'])]

    merged_df = pd.merge(mc_read_df, io_read_df,
        left_on = 'MedlemsID',
        right_on = 'MC_MedlemsID',
        #left_on = 'Personnummer',
        #right_on = 'Födelsedat./Personnr.',
        how = 'inner',
        suffixes = ('_mc',''))
    stats("Antal lika (på MedlemsID): {}".format(str(len(merged_df))))

    # Filter away members with incomplete 'personnummer'
    #merged_df = merged_df[merged_df['Personnummer'].str.len() == 13]
    stats("Antal med fullständiga personnummer: {}".format(str(len(merged_df))))

    # Filter away those originating from MC (= have group "MC_Import")
    #merged_df = merged_df[merged_df['Grupp/Lag/Arbetsrum/Familj'].str.contains('MC_Import', na="") != True]
    stats("Antal utan MC-grupper i IO: {}".format(str(len(merged_df))))

    # Add missing, neccessary for import, columns - as nan
    merged_df[['Prova-på', 'Ta bort GruppID']] = np.nan

    # Convert MC groups to IO GroupID's
    #merged_df['Lägg till GruppID'] = merged_df['Grupper'].apply(convert_mc_groups_to_io_groups) 

    # Add special group 'MC_GruppViaMC' 580242
    # Add MC_Alla (580600)
    # We know that all groups are non-empty, we can just add at the end
    # merged_df['Lägg till GruppID'] = merged_df['Lägg till GruppID'].apply(lambda x : x + ', 580600')

    # Also - add ONLY special columns as groupIDs
    # Be sure to uodate via import, DON'T overwrite
    merged_df['Lägg till GruppID'] = [
        concat_special_cols("", cirkusutb, frisksportlofte, hedersmedlem, ingen_tidning, frisksportutb, trampolinutb, avgift) 
                            for cirkusutb, frisksportlofte, hedersmedlem, ingen_tidning, frisksportutb, trampolinutb, avgift
        in zip(merged_df['Cirkusledarutbildning'], 
            merged_df['Frisksportlöfte'], 
            merged_df['Hedersmedlem'], 
            merged_df['Ingen tidning tack'], 
            merged_df['Frisksportutbildning'], 
            merged_df['Trampolinutbildning'], 
            merged_df['Avgift'])]

    # Retain columns to be used for import only
    export_cols = ['Prova-på', 'Förnamn', 'Alt. förnamn', 'Efternamn', 'Kön', 'Nationalitet', 'IdrottsID', 
        'Födelsedat./Personnr.', 'Telefon mobil', 'E-post kontakt', 'Kontaktadress - c/o adress', 'Kontaktadress - Gatuadress', 
        'Kontaktadress - Postnummer', 'Kontaktadress - Postort', 'Kontaktadress - Land', 'Arbetsadress - c/o adress', 
        'Arbetsadress - Gatuadress', 'Arbetsadress - Postnummer', 'Arbetsadress - Postort', 'Arbetsadress - Land', 'Telefon bostad', 
        'Telefon arbete', 'E-post privat', 'E-post arbete', 'Medlemsnr.', 'Medlem sedan', 'Medlem t.o.m.', 'Övrig medlemsinfo', 
        'Familj', 'Fam.Admin', 'Lägg till GruppID', 'Ta bort GruppID']
    merged_df = merged_df[export_cols]

    # sync_groups_filename = path + date_today + '_sync_groups_update.xlsx'
    sync_groups_filename = path_out + timestamp + '_sync_groups_update.xlsx'
    save_file(sync_groups_filename, merged_df)
    stats("Antal att uppdatera i IO: {} ({})".format(str(len(merged_df)), Path(sync_groups_filename).name))
Пример #6
0
def sync_special_fields_from_mc_to_io(mc_file_name, mc_invoice_file, io_file_name):
    """
    Sync forgotten special fileds from MC and IO
    """
    # Get data from latest MC export
    mc_read_df = _read_mc_file(mc_file_name)
    stats("Antal inlästa från MC: {} ({})".format(str(len(mc_read_df)), Path(mc_file_name).name))

    # Invoice info from My Club
    mc_invoice_df = pd.read_excel(mc_invoice_file, 
        usecols=['MedlemsID','Avgift'])
    stats("Antal fakturor i MC:  " + str(len(mc_invoice_df)) + " (" + Path(mc_invoice_file).name + ")")
    # Merge in invoice details
    mc_read_df = mc_read_df.merge(mc_invoice_df, on='MedlemsID', how='left', suffixes=(None,'_inv'), validate = "one_to_one")
    
    # Add missing, neccessary for import, columns - as nan
    mc_read_df[['Prova-på', 'Ta bort GruppID']] = np.nan
    mc_read_df['Lägg till GruppID'] = ""

    # Also - add ONLY special columns as groupIDs
    # Be sure to uodate via import, DON'T overwrite
    mc_read_df['Lägg till GruppID'] = [
        concat_special_cols("", cirkusutb, frisksportlofte, hedersmedlem, ingen_tidning, frisksportutb, trampolinutb, avgift) 
                            for cirkusutb, frisksportlofte, hedersmedlem, ingen_tidning, frisksportutb, trampolinutb, avgift
        in zip(mc_read_df['Cirkusledarutbildning'], 
            mc_read_df['Frisksportlöfte'], 
            mc_read_df['Hedersmedlem'], 
            mc_read_df['Ingen tidning tack'], 
            mc_read_df['Frisksportutbildning'], 
            mc_read_df['Trampolinutbildning'], 
            mc_read_df['Avgift'])]

    # Get data from latest IO export
    io_read_df = _read_io_file(io_file_name)
    # print(io_read_df.columns)
    stats("Antal inlästa från IO: {} ({})".format(str(len(io_read_df)), Path(io_file_name).name))

    merged_df = pd.merge(mc_read_df, io_read_df,
        left_on = 'Personnummer',
        right_on = 'Födelsedat./Personnr.',
        #left_on = ['Personnummer','Förnamn','Efternamn'], # Finns personer med felstavade namn
        #right_on = ['Födelsedat./Personnr.','Förnamn','Efternamn'], # Finns personer med felstavade namn
        how = 'inner',
        suffixes = ('_mc',''))
    stats("Antal match på personnummer/datum: {}".format(str(len(merged_df))))

    # Filter away members with incomplete 'personnummer'
    merged_df = merged_df[merged_df['Personnummer'].str.len() == 13]
    stats("Antal med fullständiga personnummer: {:>4}".format(str(len(merged_df))))

    # Filter away those originating from MC (= have group "MC_Import")
    merged_df = merged_df[merged_df['Grupp/Lag/Arbetsrum/Familj'].str.contains('MC_Import', na="") != True]
    stats("Antal med ursprung från MC: {}".format(str(len(merged_df))))

    # Convert MC groups to IO GroupID's
    # Already done... in earlier export
    # merged_df['Lägg till GruppID'] = merged_df['Grupper'].apply(convert_mc_groups_to_io_groups) 

    # Add special group 'MC_SpecialFält'
    # We know that all groups are non-empty, we can just add a the end
    # TODO Kolla detta
    #merged_df['Lägg till GruppID'] = merged_df['Lägg till GruppID'].apply(lambda x : x + ', 580266')

    # Retain columns to be used for import only
    export_cols = ['Prova-på', 'Förnamn', 'Alt. förnamn', 'Efternamn', 'Kön', 'Nationalitet', 'IdrottsID', 
        'Födelsedat./Personnr.', 'Telefon mobil', 'E-post kontakt', 'Kontaktadress - c/o adress', 'Kontaktadress - Gatuadress', 
        'Kontaktadress - Postnummer', 'Kontaktadress - Postort', 'Kontaktadress - Land', 'Arbetsadress - c/o adress', 
        'Arbetsadress - Gatuadress', 'Arbetsadress - Postnummer', 'Arbetsadress - Postort', 'Arbetsadress - Land', 'Telefon bostad', 
        'Telefon arbete', 'E-post privat', 'E-post arbete', 'Medlemsnr.', 'Medlem sedan', 'Medlem t.o.m.', 'Övrig medlemsinfo', 
        'Familj', 'Fam.Admin', 'Lägg till GruppID', 'Ta bort GruppID']
    merged_df = merged_df[export_cols]

    sync_specials_filename = path_out + timestamp + '_sync_specials_update.xlsx'
    save_file(sync_specials_filename, merged_df)
    stats("Antal att uppdatera i IO: {} ({})".format(str(len(merged_df)), Path(sync_specials_filename).name))
Пример #7
0
def update_medlemsid_in_io(mc_file_name, io_file_name):
    """
    Goal: Update all members with MC MedlemsID, in IO
    """
    # My Club Dataframe
    mc_read_df = _read_mc_file(mc_file_name)
    stats("Antal medlemmar i MC: {} ({})".format(str(len(mc_read_df)), Path(mc_file_name).name))

    # IO Import columns - for ref
    io_import_cols = ['Prova-på','Förnamn','Alt. förnamn','Efternamn','Kön','Nationalitet','IdrottsID','Födelsedat./Personnr.','Telefon mobil',
        'E-post kontakt','Kontaktadress - c/o adress','Kontaktadress - Gatuadress','Kontaktadress - Postnummer','Kontaktadress - Postort',
        'Kontaktadress - Land','Arbetsadress - c/o adress','Arbetsadress - Gatuadress','Arbetsadress - Postnummer','Arbetsadress - Postort','Arbetsadress - Land',
        'Telefon bostad','Telefon arbete','E-post privat','E-post arbete','Medlemsnr.','Medlem sedan','Medlem t.o.m.','Övrig medlemsinfo',
        'Familj','Fam.Admin','Lägg till GruppID','Ta bort GruppID']

    # Convert all MC members to IO Import format
    for_io_import_df = _convert_mc_to_io_format(io_import_cols, mc_read_df, timestamp)
    stats("Antal medlemmar för IO: {}".format(len(for_io_import_df)))

    # Get IO dataframe
    io_read_df = _read_io_file(io_file_name)
    stats("Antal medlemmar i IO: {} ({})".format(str(len(io_read_df)), Path(io_file_name).name))

    # Get MedlemsID from IO - if we can find it
    io_read_df['MC_MedlemsID'] = [search_medlemsid_from_io(comment, medlemsnr)
        for comment, medlemsnr 
        in zip (io_read_df['Övrig medlemsinfo'], io_read_df['Medlemsnr.'])]

    # Filter away members that already have MedlemsID
    io_read_df = io_read_df[io_read_df['MC_MedlemsID'].isnull()]
    stats("Antal medlemmar utan MedlemsID i IO: {}".format(str(len(io_read_df))))

    # Skip for now
    if False:
        save_file(path + timestamp + '_membersid_before_io_import.xlsx', io_read_df)
        stats("Sparat: " + path + timestamp + '_membersid_before_io_import.xlsx')
        print(io_read_df.head())
        return


    # Map person to person between MC <-> IO
    merged_df = pd.merge(mc_read_df, io_read_df,
                     #on = 'Födelsedat./Personnr.',
                     left_on = 'Personnummer',
                     right_on = 'Födelsedat./Personnr.',
                     how = 'inner',
                     suffixes = ('_mc',''),
                     indicator = True)
    stats("Antal mergade medlemmar: {}".format(len(merged_df)))

    # Add MedlemsID from MC for new import
    merged_df['Övrig medlemsinfo'] = [add_comment_info(comment, member_id, date_today)
        for comment, member_id
        in zip(merged_df['Övrig medlemsinfo'] , merged_df['MedlemsID'])]

    # Set "Medlemsnr." to "MedlemsID" if not already set
    merged_df['Medlemsnr.'] = np.where(pd.isna(merged_df['Medlemsnr.']), merged_df['MedlemsID'], merged_df['Medlemsnr.'])

    # Export in "import" format
    io_import_cols = ['Typ','Målsman','Förnamn','Alt. förnamn','Efternamn','IdrottsID','Födelsedat./Personnr.','Kön','Nationalitet','Telefon mobil','E-post kontakt','Kontaktadress - c/o adress','Kontaktadress - Gatuadress','Kontaktadress - Postnummer','Kontaktadress - Postort','Kontaktadress - Land','Folkbokföring - c/o adress','Folkbokföring - Gatuadress','Folkbokföring - Postnummer','Folkbokföring - Postort','Folkbokföring - Land','Folkbokföring - Kommunkod','Folkbokföring - Kommun','Arbetsadress - c/o adress','Arbetsadress - Gatuadress','Arbetsadress - Postnummer','Arbetsadress - Postort','Arbetsadress - Land','Telefon bostad','Telefon arbete','E-post privat','E-post arbete','Roller','Behörighet','Övrig medlemsinfo','Grupp/Lag/Arbetsrum/Familj','Familj','Fam.Admin','Medlemsnr.','Medlem sedan','Medlem t.o.m.','Organisation','Registreringsdatum','Avslutningsdatum']
    io_for_import_df = merged_df.loc[:, io_import_cols]
    stats("Antal medlemmar for IO import: {}".format(len(io_for_import_df)))

    save_file(path + timestamp + '_membersid_for_io_import.xlsx', io_for_import_df)
    stats("Sparat: " + path + timestamp + '_membersid_for_io_import.xlsx')
    # print(for_io_import_df.head())

    disabledBelow1 = True
    if disabledBelow1:
        return

    # Done?
   
    # Export-4 last
    # Filter out only thos in group "Remaining migration"
    #mc_in_io_format_df['MissedGroup'] = np.where(mc_export_df['Grupper'].str.contains('Remaining migration'), 'True', 'False') 
    #mc_in_io_format_df = mc_in_io_format_df[mc_in_io_format_df.MissedGroup == "True"]

#    mc_in_io_format_df['Ta bort GruppID'] = mc_export_df[''] 

    # 2. Compare MC data with current IO data
    # Todo
    #comp_df = mc_in_io_format_df[['Förnamn','Alt. förnamn','Efternamn','Födelsedat./Personnr.','Kön','Nationalitet','Telefon mobil','E-post kontakt',
    #    'Kontaktadress - c/o adress','Kontaktadress - Gatuadress','Kontaktadress - Postnummer','Kontaktadress - Postort','Kontaktadress - Land',
    #    'Arbetsadress - c/o adress','Arbetsadress - Gatuadress','Arbetsadress - Postnummer','Arbetsadress - Postort','Arbetsadress - Land',
    #    'Telefon bostad','Telefon arbete','E-post privat','E-post arbete','Övrig medlemsinfo','Familj','Fam.Admin','Medlem sedan','Medlem t.o.m.']].compare(
    #        for_io_import_df[['Förnamn','Alt. förnamn','Efternamn','Födelsedat./Personnr.','Kön','Nationalitet','Telefon mobil','E-post kontakt',
    #    'Kontaktadress - c/o adress','Kontaktadress - Gatuadress','Kontaktadress - Postnummer','Kontaktadress - Postort','Kontaktadress - Land',
    #    'Arbetsadress - c/o adress','Arbetsadress - Gatuadress','Arbetsadress - Postnummer','Arbetsadress - Postort','Arbetsadress - Land',
    #    'Telefon bostad','Telefon arbete','E-post privat','E-post arbete','Övrig medlemsinfo','Familj','Fam.Admin','Medlem sedan','Medlem t.o.m.']])

    #df1 = mc_in_io_format_df[['Förnamn','Efternamn','Födelsedat./Personnr.','Kön','Medlem sedan']].copy()
    #print(df1.axes)
    #df2 = for_io_import_df[['Förnamn','Efternamn','Födelsedat./Personnr.','Kön','Medlem sedan']].copy()
    #print(df2.axes)
    #comp_df = df1.compare(df2)
    #save_file('/usr/src/app/files/' + date_today + '_mc-io_comparison.xlsx', comp_df)
    

    # 3. Save file with all members from MC in correct format (still need to cross check with IO!)
    save_file(path + timestamp + '_all_mc_in_io_format.xlsx', for_io_import_df)
    stats("Sparat: " + path + timestamp + '_all_mc_in_io_format.xlsx')

    # 4. Merge
    
    # Current members in IdrottOnline
    for_io_import_df = pd.read_excel(io_file_name, 
        usecols=['Födelsedat./Personnr.'],
        dtype = {
        'Telefon mobil': 'string', 'Telefon bostad': 'string', 'Telefon arbete': object, 'Medlemsnr.': 'string'},
        converters = {'E-post kontakt':normalize_email, 'E-post privat':normalize_email, 'E-post arbete':normalize_email}) # IO columns
    stats("Antal medlemmar i IO: " + str(len(for_io_import_df)) + " (" + Path(io_file_name).name + ")")

    # TODO: Finish below
    disabledBelow2 = True
    if disabledBelow2:
        return

    # For import
    # TODO Remove this later!
    # This is already handled by how = 'left' below - so we can assign 'Medlemsnr.' without risking overwrite
    # mc_in_io_format_df['Medlemsnr.'] = mc_export_df['MedlemsID'] 
    # TODO Remove this later!
    # Filter - only non-existing in IO (solved by how = 'left')
    # Label: 'Export-1' - For members updated '2020-11-16_01.20'
    #for_io_import_df = pd.merge(mc_in_io_format_df, for_io_import_df,
    #                 on = 'Födelsedat./Personnr.',
    #                 how = 'left',
    #                 suffixes = ('_mc','_io'),
    #                 indicator = True)


    # Label: 'Export-2' - Current members in IO updated in IO with new data from MC
    for_io_import_df = pd.merge(mc_read_df, for_io_import_df,
                    #on = 'Födelsedat./Personnr.',
                    on = 'Personnummer',
                    how = 'right',
                    suffixes = ('_mc','_io'),
                    indicator = True)

    # Filter - only with full personnummer
    for_io_import_df = for_io_import_df[for_io_import_df['Födelsedat./Personnr.'].str.len() > 8]

    # Filter - only MC
    for_io_import_df = for_io_import_df[for_io_import_df['_merge'] == "right_only" ]

    for_io_import_file = path + timestamp + '_for_io_import.xlsx'
    stats("Antal för import till IO:   " + str(len(for_io_import_df)) + " (" + Path(for_io_import_file).name + ")")
    stats("Enbart i MC: " + str(len(for_io_import_df.loc[for_io_import_df['_merge'] == 'left_only' ])))
    stats("I både MC och IO: " + str(len(for_io_import_df.loc[for_io_import_df['_merge'] == 'both' ])))
    stats("Antal med endast födelsedatum: " + str(len(for_io_import_df[for_io_import_df['Födelsedat./Personnr.'].str.len() == 8])))
    stats("Antal med fullt personnummer:  " + str(len(for_io_import_df[for_io_import_df['Födelsedat./Personnr.'].str.len() > 8])))
    save_file(for_io_import_file, for_io_import_df)
    stats("Sparat: " + for_io_import_file)
Пример #8
0
def compare_mc_and_io(mc_file_name, io_file_name):
    """
    Compare members from MC and IO
    """
    # Get "important" data from latest MC export
    # Note! Doesn't read all columns...
    mc_read_df = _read_mc_file(mc_file_name)
    stats("Antal inlästa från MC: {:>4} ({})".format(str(len(mc_read_df)), Path(mc_file_name).name))

    def print_mc_group_stats(label, extra = None):
        # Make label regexp safe
        slabel = label.replace("(", r"\(")
        slabel = slabel.replace(")", r"\)")
        if extra:
            print("{:<30} {:>3} st ({})".format(label, len(mc_read_df.loc[mc_read_df['Grupper'].str.contains(slabel) == True ]), extra))
        else:
            print("{:<30} {:>3} st".format(label, len(mc_read_df.loc[mc_read_df['Grupper'].str.contains(slabel) == True ])))

    print_mc_group_stats('Medlemmar')
    print_mc_group_stats('Senior')
    #print_mc_group_stats('Uppdatering till fullt personnummer')
    print_mc_group_stats('Orientering')
    #print_mc_group_stats('Remaining migration 3')
    print_mc_group_stats('Skidor')
    print_mc_group_stats('Huvudsektion')
    print_mc_group_stats('styrelsen')
    print_mc_group_stats('Fotboll')
    print_mc_group_stats('Trampolin (SACRO)')
    print_mc_group_stats('Volleyboll')
    #print_mc_group_stats('Remaining migration')
    print_mc_group_stats('MTB')
    print_mc_group_stats('Skateboard (Chillskate)')
    print_mc_group_stats('Innebandy')
    #print_mc_group_stats('Remaining migration 2')

    column_stats('Frisksportlöfte', mc_read_df)
    column_stats('Hedersmedlem', mc_read_df)
    column_stats('Ingen tidning tack', mc_read_df)
    column_stats('Trampolinutbildning', mc_read_df)
    column_stats('Frisksportutbildning', mc_read_df)

    stats(" S**t MC ".center(40,"-"))
    stats("")

    # Get data from latest IO export (and convert some cols to IO format)
    # Note! Doesn't read all columns...
    io_read_df = _read_io_file(io_file_name, compare_io_columns)
    stats("Antal inlästa från IO: {:>4} ({})".format(len(io_read_df), Path(io_file_name).name))

    # Filter only with MC_Alla
    #io_read_df = io_read_df[io_read_df['Grupp/Lag/Arbetsrum/Familj'].str.contains("MC_Alla", na=False)]
    #stats("Antal medlemmar med MC_Alla i IO: {}".format(len(io_read_df)))

    # Extract MC MedlemsID for all IO members
    io_read_df['MC_MedlemsID'] = [search_medlemsid_from_io(comment, medlemsnr)
        for comment, medlemsnr 
        in zip (io_read_df['Övrig medlemsinfo'], io_read_df['Medlemsnr.'])]
    # Set correct dtype
    io_read_df['MC_MedlemsID'] = io_read_df['MC_MedlemsID'].astype('string')

    def print_io_group_stats(label, extra = None):
        if extra:
            print("{:<30} {:>3} st ({})".format(label, len(io_read_df.loc[io_read_df['Grupp/Lag/Arbetsrum/Familj'].str.contains(label) == True ]), extra))
        else:
            print("{:<30} {:>3} st".format(label, len(io_read_df.loc[io_read_df['Grupp/Lag/Arbetsrum/Familj'].str.contains(label) == True ])))

    print_io_group_stats('Styrelse SFK')
    print_io_group_stats('Senior')
    print_io_group_stats('Sektion Innebandy')
    print_io_group_stats('Sektion MTB')
    print_io_group_stats('MC_Alla')
    print_io_group_stats('MC_Cirkusledarutbildning')
    print_io_group_stats('MC_Fotboll')
    print_io_group_stats('MC_FrisksportlöfteJa')
    print_io_group_stats('MC_FrisksportlöfteNej')
    print_io_group_stats('MC_FrisksportutbildningBasic')
    print_io_group_stats('MC_FrisksportutbildningSteg1')
    print_io_group_stats('MC_GruppViaMC', "Ignorera")
    print_io_group_stats('MC_Hedersmedlem')
    print_io_group_stats('MC_Huvudsektion')
    print_io_group_stats('MC_Import')
    print_io_group_stats('MC_IngenTidning')
    print_io_group_stats('MC_Innebandy')
    print_io_group_stats('MC_Medlemmar', "Ignorera. Tas bort?")
    print_io_group_stats('MC_Medlemsavgift_2020')
    print_io_group_stats('MC_MTB')
    print_io_group_stats('MC_OfullstPnr', "Ignorera")
    print_io_group_stats('MC_OL')
    print_io_group_stats('MC_SACRO')
    print_io_group_stats('MC_Skate')
    print_io_group_stats('MC_Skidor')
    print_io_group_stats('MC_TrampolinutbildningSteg1')
    print_io_group_stats('MC_TrampolinutbildningSteg2')
    print_io_group_stats('MC_Uppdaterad', "Ignorera")
    print_io_group_stats('MC_Volleyboll')

    #io_groups = io_read_df['Grupp/Lag/Arbetsrum/Familj']
    #io_each_group = io_groups.str.split(', ', expand=True)
    #print(io_each_group)

    # MC_FrisksportlöfteJa - 579061
    # merged_df = merged_df[merged_df['Grupp/Lag/Arbetsrum/Familj'].str.contains('MC_Import', na="") != True]
    #stats("Friskportslöfte =  Ja i IO: {:>8}".format(str(len(io_read_df.loc[io_read_df['Grupp/Lag/Arbetsrum/Familj'] == 'Ja' ]))))
    # MC_FrisksportlöfteNej - 579062
    #stats("Friskportslöfte = Nej i IO: {:>8}".format(str(len(io_read_df.loc[io_read_df['Grupp/Lag/Arbetsrum/Familj'] == 'Nej' ]))))

    stats("MC " + "> Merge (outer: personnummer) <".center(40, "-") + " IO")
    merged_df = pd.merge(mc_read_df, io_read_df,
        left_on = 'MedlemsID',
        right_on = 'MC_MedlemsID',
        how = 'outer',
        suffixes = ('_mc','_io'),
        indicator = True)
    stats("Antal efter merge: {:>8}".format(str(len(merged_df))))
    stats("Antal lika (på personnummer): {:>12} (fullst. + icke fullständiga)".format(str(len(merged_df))))

    stats("Antal endast i MC: {:>8}".format(str(len(merged_df.loc[merged_df['_merge'] == 'left_only' ]))))
    stats("Antal endast i IO: {:>8}".format(str(len(merged_df.loc[merged_df['_merge'] == 'right_only' ]))))
    stats("Antal i båda:      {:>8}".format(str(len(merged_df.loc[merged_df['_merge'] == 'both' ]))))

    merged_filename = path_out + timestamp + '_comparison_merge_report.xlsx'
    save_file(merged_filename, merged_df)
    stats("Saving report: {}".format(merged_filename))

    return

    # Add group MC_Alla to everyone in MC and now also IO
    for_mc_alla_df = pd.merge(mc_read_df, io_read_df,
        left_on = 'Personnummer',
        right_on = 'Födelsedat./Personnr.',
        #left_on = ['Personnummer','Förnamn','Efternamn'],
        #right_on = ['Födelsedat./Personnr.','Förnamn','Efternamn'],
        how = 'inner',
        suffixes = ('_mc','_io'))

    mc_alla_filename = path_out + timestamp + '_mc_alla_report.xlsx'