La MFI tiene ~ 100 oficinas (~ 70 son sucursales) y desea migrar 5 de estas sucursales a mifos.
La MFI tiene x cantidad de personal, de los cuales y son oficiales de crédito, y z son otros miembros del personal, como el ingreso de datos, el gerente, etc.
MFI tiene 1 producto de préstamo (un préstamo individual aplicable a clientes, 1-24 cuotas, cuotas semanales)
No hay centros como tales.
¿Número de grupos por rama?
¿Número de clientes por grupo?
Grupo con muchos clientes.
Clientes con préstamos individuales y cuentas de ahorro.
Campos personalizados para información del cliente
Se toma la decisión de ingresar manualmente el personal, las oficinas, los datos de referencia (propósitos del préstamo),
Se toma la decisión de utilizar un enfoque de "balance de apertura". Básicamente para una sucursal y un oficial de campo específicos, si ese oficial de campo se está reuniendo con un grupo especial el lunes. Una vez que termina el día, se captura una hoja de cálculo con todos los detalles del préstamo para cada miembro cliente del grupo y se realiza un seguimiento de los siguientes detalles:
Monto del préstamo original
no de cuotas
Fecha de desembolso
Primera fecha de pago
Fecha de entrega actual
Monto pagado hasta la fecha (Principio + Interés)
Ciclo de prestamo
Propósito del prestamo
Esta información luego se extrae de la hoja de cálculo de la plantilla y (posiblemente se transforma de alguna manera) y se carga en mifos.
¿Consolidación? ¿Pregunto a ryan de nuevo?
La historia pasada de los préstamos no se captura.
Desde un esquema de mifos en blanco:
update personnel set password=0x22648ca42330a561964dbe32f54e6a04a49d03f203ca64b2515338d9 where personnel_id=1; |
Después de hacer un análisis en el mfi, puede ser posible juntar los archivos de configuración de la aplicación:
En este punto, debería poder iniciar la aplicación mifos contra el esquema de mifos. Como resultado, la base de datos en la actualidad tiene la siguiente información:
La ejecución de la siguiente consulta le mostrará los datos de referencia existentes
SELECT l.lookup_id, le.entity_name, l.lookup_name, le.description FROM lookup_value l INNER JOIN lookup_entity le ON l.entity_id=le.entity_id order by l.entity_id, l.lookup_id; |
Los datos de referencia se rellenan aplicando base-data.sql y deben existir para que la aplicación funcione.
Data Type | entity_id | Options |
---|---|---|
ClientStatus | 1 | Pending Approval, active, on hold, cancelled, closed |
GroupStatus | 2 | Partial, Pending, active, on hold, cancelled, closed |
AccountStates | 5 | partial, pending, approved, disbursed to LO, active-good-standing, active-bad-standing, closed-obligations-met, closed-written-off, closed-rescheduled, cancelled |
PovertyStatus | 10 | poor, very poor, non poor |
Gender | 16 | male, female |
PersonnelLevels | 30 | loan officer, non loan officer |
SpouseFather | 52 | spouse, father, mother, child |
CustomerStatus | 53 | active, inactive |
PersonnelStatus | 56 | active, inactive |
Language | 74 | English, Spanish, French, etc |
LivingStatus | 92 | Together, Not Together |
Datos de referencia que son opcionales y generalmente diferentes en un cliente por cliente
Data Type | entity_id | Options |
---|---|---|
Salutation | 15 | Mr., Mrs. |
MaritalStatus | 17 | single, married, seperated, widow |
Citizenship | 18 | Roman Catholic,Protestant Baptist Pentecostal,Born Again Christian,Seventh Day Adventist,Iglesia Ni Kristo,Jehovahs Witness,Latter Day Saints,Islam,Others |
Ethinicity | 19 | Yes, No |
EducationLevel | 20 | Elementary, High School, College, Vocational, None |
BusinessActivities | 21 | BuyAndSell, Vending, SariSariStore, Retailing, Dressmaking, Garments, Handicrafts, FoodProcessing, SoapMaking, PerfumeMaking, DishwashingDetergentMaking, BeadsAccessoriesMaking, Tailoring, Upholstery, Vulcanizing, DoormatMaking, TricycleOperator |
PersonnelTitles | 29 | Program Officer, Program Unit Supervisor, Branch Accountant, Branch Accountant Assistant, Branch Manager |
NOTA: La ciudadanía se usa para rastrear la religión.
NOTA: ¿La etinicidad se usa para rastrear?
You should restart the application after inserting in custom reference data as lookup values are cached on start up. |
Script SQL para insertar en datos de referencia específicos del cliente
-- Entity: Salutation insert into lookup_value(lookup_id,entity_id,lookup_name) values (2000, 15, 'Salutation-Mr'), (2001, 15, 'Salutation-Mrs'); insert into lookup_value_locale(lookup_value_id,locale_id,lookup_id,lookup_value) values (2000,1, 2000,'Mr'), (2001,1, 2001,'Mrs'); -- Entity: MaritalStatus insert into lookup_value(lookup_id,entity_id,lookup_name) values (2002, 17, 'MaritalStatus-Single'), (2003, 17, 'MaritalStatus-Married'), (2004, 17, 'MaritalStatus-Seperated'), (2005, 17, 'MaritalStatus-Widow'); insert into lookup_value_locale(lookup_value_id,locale_id,lookup_id,lookup_value) values (2002, 1, 2002, 'Single'), (2003, 1, 2003, 'Married'), (2004, 1, 2004, 'Seperated'), (2005, 1, 2005, 'Widow'); -- Entity: Citizenship insert into lookup_value(lookup_id,entity_id,lookup_name) values (2006, 18, 'Citizenship-RomanCatholic'), (2007, 18, 'Citizenship-ProtestantBaptistPentecostal'), (2008, 18, 'Citizenship-BornAgainChristian'), (2009, 18, 'Citizenship-SeventhDayAdventist'), (2010, 18, 'Citizenship-IglesiaNiKristo'), (2011, 18, 'Citizenship-JehovahsWitness'), (2012, 18, 'Citizenship-LatterDaySaints'), (2013, 18, 'Citizenship-Islam'), (2014, 18, 'Citizenship-Others'); insert into lookup_value_locale(lookup_value_id,locale_id,lookup_id,lookup_value) values (2006, 1, 2006, 'Roman Catholic'), (2007, 1, 2007, 'Protestant Baptist Pentecostal'), (2008, 1, 2008, 'Born Again Christian'), (2009, 1, 2009, 'Seventh Day Adventist'), (2010, 1, 2010, 'Iglesia Ni Kristo'), (2011, 1, 2011, 'Jehovahs Witness'), (2012, 1, 2012, 'Latter Day Saints'), (2013, 1, 2013, 'Islam'), (2014, 1, 2014, 'Others'); -- Entity: Ethinicity insert into lookup_value(lookup_id,entity_id,lookup_name) values (2015, 19, 'Ethinicity-Yes'), (2016, 19, 'Ethinicity-No'); insert into lookup_value_locale(lookup_value_id,locale_id,lookup_id,lookup_value) values (2015, 1, 2015, 'Yes'), (2016, 1, 2016, 'No'); -- Entity: EducationLevel insert into lookup_value(lookup_id,entity_id,lookup_name) values (2017, 20, 'EducationLevel-Elementary'), (2018, 20, 'EducationLevel-High School'), (2019, 20, 'EducationLevel-College'), (2020, 20, 'EducationLevel-Vocational'), (2021, 20, 'EducationLevel-None'); insert into lookup_value_locale(lookup_value_id,locale_id,lookup_id,lookup_value) values (2017, 1, 2017, 'Elementary'), (2018, 1, 2018, 'High School'), (2019, 1, 2019, 'College'), (2020, 1, 2020, 'Vocational'), (2021, 1, 2021, 'None'); -- Entity: PersonnelTitles insert into lookup_value(lookup_id,entity_id,lookup_name) values (2022, 29, 'PersonnelTitles-ProgramOfficer'), (2023, 29, 'PersonnelTitles-ProgramUnitSupervisor'), (2024, 29, 'PersonnelTitles-BranchAccountant'), (2025, 29, 'PersonnelTitles-BranchAccountantAssistant'), (2026, 29, 'PersonnelTitles-BranchManager'); insert into lookup_value_locale(lookup_value_id,locale_id,lookup_id,lookup_value) values (2022, 1, 2022, 'Program Officer'), (2023, 1, 2023, 'Program Unit Supervisor'), (2024, 1, 2024, 'Branch Accountant'), (2025, 1, 2025, 'Branch Accountant Assistant'), (2026, 1, 2026, 'Branch Manager'); -- Entity: BusinessActivities insert into lookup_value(lookup_id,entity_id,lookup_name) values (2027, 21, 'BusinessActivities-BuyAndSell'), (2028, 21, 'BusinessActivities-Vending'), (2029, 21, 'BusinessActivities-SariSariStore'), (2030, 21, 'BusinessActivities-Retailing'), (2031, 21, 'BusinessActivities-Dressmaking'), (2032, 21, 'BusinessActivities-Garments'), (2033, 21, 'BusinessActivities-Handicrafts'), (2034, 21, 'BusinessActivities-FoodProcessing'), (2035, 21, 'BusinessActivities-SoapMaking'), (2036, 21, 'BusinessActivities-PerfumeMaking'), (2037, 21, 'BusinessActivities-DishwashingDetergentMaking'), (2038, 21, 'BusinessActivities-BeadsAccessoriesMaking'), (2039, 21, 'BusinessActivities-Tailoring'), (2040, 21, 'BusinessActivities-Upholstery'), (2041, 21, 'BusinessActivities-Vulcanizing'), (2042, 21, 'BusinessActivities-DoormatMaking'), (2043, 21, 'BusinessActivities-TricycleOperator'); insert into lookup_value_locale(lookup_value_id,locale_id,lookup_id,lookup_value) values (2027, 1, 2027, 'Buy And Sell'), (2028, 1, 2028, 'Vending'), (2029, 1, 2029, 'Sari Sari Store'), (2030, 1, 2030, 'Retailing'), (2031, 1, 2031, 'Dressmaking'), (2032, 1, 2032, 'Garments'), (2033, 1, 2033, 'Handicrafts'), (2034, 1, 2034, 'Food Processing'), (2035, 1, 2035, 'Soap Making'), (2036, 1, 2036, 'Perfume Making'), (2037, 1, 2037, 'Dishwashing Detergent Making'), (2038, 1, 2038, 'Beads Accessories Making'), (2039, 1, 2039, 'Tailoring'), (2040, 1, 2040, 'Upholstery'), (2041, 1, 2041, 'Vulcanizing'), (2042, 1, 2042, 'Doormat Making'), (2043, 1, 2043, 'Tricycle Operator'); |
Después de asegurar la configuración, los datos de referencia se configuran correctamente para la aplicación, es hora de agregar elementos organizativos que son fundamentales para el funcionamiento de la aplicación mifos.
NOTA: Solo creé los primeros 11 hasta Valenzuela, ya que quiero migrar los datos de esta rama a mifos.
Manually migrating group data cannot be done correctly without modifying the system date where the application is running or by somehow modifying the data directly on the database after the group has being created and activated. |
Build flexibility into mifos services and API to allow groups to be created and activated with an activation date in the past. |
package demo.migration; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import org.joda.time.DateTime; import org.joda.time.LocalDate; import org.mifos.application.admin.servicefacade.AdminServiceFacade; import org.mifos.application.admin.servicefacade.OfficeServiceFacade; import org.mifos.application.admin.servicefacade.PersonnelServiceFacade; import org.mifos.application.master.business.MifosCurrency; import org.mifos.application.meeting.util.helpers.MeetingType; import org.mifos.application.meeting.util.helpers.RecurrenceType; import org.mifos.application.servicefacade.GroupServiceFacade; import org.mifos.application.servicefacade.SavingsServiceFacade; import org.mifos.calendar.DayOfWeek; import org.mifos.customers.util.helpers.CustomerStatus; import org.mifos.dto.domain.AddressDto; import org.mifos.dto.domain.ApplicableAccountFeeDto; import org.mifos.dto.domain.GroupCreationDetail; import org.mifos.dto.domain.MeetingDetailsDto; import org.mifos.dto.domain.MeetingDto; import org.mifos.dto.domain.MeetingRecurrenceDto; import org.mifos.dto.domain.MeetingTypeDto; import org.mifos.dto.screen.ProductDisplayDto; import org.mifos.framework.hibernate.helper.StaticHibernateUtil; import org.mifos.framework.util.helpers.Money; import org.mifos.security.AuthenticationAuthorizationServiceFacade; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class KMBIMigrationDemo { public static void main(String[] args) { System.setProperty("mifos.mode", "main"); String[] configLocations = new String[2]; configLocations[0] = "classpath:/org/mifos/config/resources/applicationContext.xml"; configLocations[1] = "classpath:META-INF/spring/QuestionnaireContext.xml"; // NOTE: Questionaire is coupled with applicationContext due to // QuestionaireMigration effort. ApplicationContext applicationContext = new ClassPathXmlApplicationContext(configLocations); // NOTE: need to ensure hibernateUtil is initialised StaticHibernateUtil.initialize(); // NOTE: need to set default currency final MifosCurrency RUPEE = new MifosCurrency((short) 2, "RUPEE", BigDecimal.valueOf(1.0), "INR"); Money.setDefaultCurrency(RUPEE); System.out.println("fetching service"); GroupServiceFacade groupServiceFacade = applicationContext.getBean(GroupServiceFacade.class); AuthenticationAuthorizationServiceFacade authenticationAuthorizationServiceFacade = applicationContext.getBean(AuthenticationAuthorizationServiceFacade.class); System.out.println("found"); // authenticate first of all authenticationAuthorizationServiceFacade.reloadUserDetailsForSecurityContext("mifos"); String displayName = "VAL B-211"; Short loanOfficerId = Short.valueOf("2"); // hardcoded id of ALDRIN HONDANTE Short officeId = Short.valueOf("11"); // hardcoded office id of Valenzuela Short customerStatus = CustomerStatus.GROUP_ACTIVE.getValue(); boolean trained = true; DateTime trainedOn = new DateTime().withDate(2010, 11, 30); DateTime mfiJoiningDate = new DateTime().withDate(2010, 12, 06); DateTime activationDate = new DateTime().withDate(2010, 12, 06); GroupCreationDetail groupCreationDetail = createGroupDto(displayName, loanOfficerId, officeId, customerStatus, trained, trainedOn, mfiJoiningDate, activationDate); LocalDate meetingStartDate = new LocalDate(new DateTime().withDate(2010, 12, 06)); Integer dayOfWeek = DayOfWeek.monday(); Integer recurrenceTypeId = RecurrenceType.WEEKLY.getValue().intValue(); Integer every = Integer.valueOf(1); MeetingDto meeting = createMeetingDto(meetingStartDate, dayOfWeek, recurrenceTypeId, every); // create group ( groupServiceFacade.createNewGroup(groupCreationDetail, meeting); } private static MeetingDto createMeetingDto(LocalDate meetingStartDate, Integer dayOfWeek, Integer recurrenceTypeId, Integer every) { String meetingPlace = "SANTULAN MALABON CITY"; MeetingTypeDto meetingType = new MeetingTypeDto(MeetingType.CUSTOMER_MEETING.getValue().intValue(), "group meeting", "some description of meeting"); String recurrenceName = "weekly"; Integer dayNumber = Integer.valueOf(0); Integer weekOfMonth = null; MeetingRecurrenceDto recurrenceDetails = new MeetingRecurrenceDto(dayNumber, weekOfMonth, dayOfWeek); MeetingDetailsDto meetingDetailsDto = new MeetingDetailsDto(recurrenceTypeId, recurrenceName, every, recurrenceDetails); MeetingDto meeting = new MeetingDto(meetingStartDate, meetingPlace, meetingType, meetingDetailsDto); return meeting; } private static GroupCreationDetail createGroupDto(String displayName, Short loanOfficerId, Short officeId, Short customerStatus, boolean trained, DateTime trainedOn, DateTime mfiJoiningDate, DateTime activationDate) { String externalId = null; AddressDto addressDto = null; List<ApplicableAccountFeeDto> feesToApply = new ArrayList<ApplicableAccountFeeDto>(); String parentSystemId = null; GroupCreationDetail groupCreationDetail = new GroupCreationDetail(displayName, externalId, addressDto, loanOfficerId, feesToApply, customerStatus, trained, trainedOn, parentSystemId, officeId, mfiJoiningDate, activationDate); return groupCreationDetail; } } |
La reunión se crea para cada lunes, semanalmente.
En la tabla de clientes, la entrada para grupo tiene los siguientes detalles de interés:
display name | trained | trained_date | created_date | created_by | mfijoining_date | customer_activation_date |
---|---|---|---|---|---|---|
VAL B-211 | YES | 30-11-2010 | 19-01-2011 | 1 | 06-Dec-2010 | 06-Dec-2010 |
En la tabla de programación de clientes, se crean diez programaciones de clientes desde el 06 de diciembre de 2010 hasta el 14 de febrero de 2011.
At present, only ten customer schedules are created when customer is activated and each night a batch job checks to see if more should be added. If the activation date is further than 10 of meeting periods away from today then there won't be a customer schedule existing for a meeting that should occur today or in the future until a batch job runs after midnight. So we should also check to see if customer activation date is in past and if so, create customer schedules from that date up to todays date, and then also create the 10 extra customer schedules for future meeting dates. |
Manually migrating client data cannot be done correctly without modifying the system date where the application is running or by somehow modifying the data directly on the database after the group has being created and activated. |
Build flexibility into mifos services and API to allow groups to be created and activated with an activation date in the past. |
package demo.migration; import java.io.InputStream; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.joda.time.DateTime; import org.joda.time.LocalDate; import org.mifos.application.admin.servicefacade.AdminServiceFacade; import org.mifos.application.admin.servicefacade.OfficeServiceFacade; import org.mifos.application.admin.servicefacade.PersonnelServiceFacade; import org.mifos.application.master.business.MifosCurrency; import org.mifos.application.meeting.util.helpers.MeetingType; import org.mifos.application.meeting.util.helpers.RecurrenceType; import org.mifos.application.servicefacade.ClientServiceFacade; import org.mifos.application.servicefacade.GroupServiceFacade; import org.mifos.application.servicefacade.SavingsServiceFacade; import org.mifos.application.util.helpers.YesNoFlag; import org.mifos.calendar.DayOfWeek; import org.mifos.customers.client.business.NameType; import org.mifos.customers.util.helpers.CustomerStatus; import org.mifos.dto.domain.AddressDto; import org.mifos.dto.domain.ApplicableAccountFeeDto; import org.mifos.dto.domain.ClientCreationDetail; import org.mifos.dto.domain.GroupCreationDetail; import org.mifos.dto.domain.MeetingDetailsDto; import org.mifos.dto.domain.MeetingDto; import org.mifos.dto.domain.MeetingRecurrenceDto; import org.mifos.dto.domain.MeetingTypeDto; import org.mifos.dto.domain.SavingsDetailDto; import org.mifos.dto.screen.ClientFamilyDetailDto; import org.mifos.dto.screen.ClientNameDetailDto; import org.mifos.dto.screen.ClientPersonalDetailDto; import org.mifos.dto.screen.ProductDisplayDto; import org.mifos.framework.hibernate.helper.StaticHibernateUtil; import org.mifos.framework.util.helpers.Money; import org.mifos.security.AuthenticationAuthorizationServiceFacade; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class KMBIMigrationDemo { public static void main(String[] args) { System.setProperty("mifos.mode", "main"); String[] configLocations = new String[2]; configLocations[0] = "classpath:/org/mifos/config/resources/applicationContext.xml"; configLocations[1] = "classpath:META-INF/spring/QuestionnaireContext.xml"; // NOTE: Questionaire is coupled with applicationContext due to // QuestionaireMigration effort. ApplicationContext applicationContext = new ClassPathXmlApplicationContext(configLocations); // NOTE: need to ensure hibernateUtil is initialised StaticHibernateUtil.initialize(); // NOTE: need to set default currency final MifosCurrency RUPEE = new MifosCurrency((short) 2, "RUPEE", BigDecimal.valueOf(1.0), "INR"); Money.setDefaultCurrency(RUPEE); System.out.println("fetching service"); ClientServiceFacade clientServiceFacade = applicationContext.getBean(ClientServiceFacade.class); AuthenticationAuthorizationServiceFacade authenticationAuthorizationServiceFacade = applicationContext.getBean(AuthenticationAuthorizationServiceFacade.class); System.out.println("found"); // authenticate first of all authenticationAuthorizationServiceFacade.reloadUserDetailsForSecurityContext("mifos"); List<Short> selectedSavingProducts = new ArrayList<Short>(); String clientName = "CARMELITA DAGALLA ALCARAZ"; Short clientStatus = CustomerStatus.CLIENT_ACTIVE.getValue(); boolean trained = true; Date trainedOn = new DateTime().withDate(2010, 11, 30).toDate(); Date mfiJoiningDate = new DateTime().withDate(2010, 12, 06).toDate(); LocalDate activationDate = new LocalDate(mfiJoiningDate); String externalId = null; AddressDto address = new AddressDto("48M. SANTOS ST. SANTULAN", null, null, "MALABON CITY", null, "PHILIPPINES", "1470", null); Short formedBy = null; Date dateOfBirth = new DateTime().withDate(1954, 5, 14).toDate(); String governmentId = null; Short groupFlag = YesNoFlag.YES.getValue(); Integer salutation = Integer.valueOf(2001); //Mrs. see custom reference-data for salutations String firstName = "CARMELITA"; String middleName = "DAGALLA"; String lastName = "ALCARAZ"; String secondLastName = null; ClientNameDetailDto clientNameDetailDto = new ClientNameDetailDto(NameType.CLIENT.getValue(), salutation, firstName, middleName, lastName, secondLastName); Integer ethinicity = Integer.valueOf(19); // no Integer citizenship = Integer.valueOf(2006); // roman catholic Integer handicapped = null; Integer businessActivities = Integer.valueOf(2029); // sari sari Integer maritalStatus = Integer.valueOf(2005); // widow Integer educationLevel = Integer.valueOf(2018); // high school Short numChildren = Short.valueOf("1"); Short gender = Short.valueOf("50"); // female Short povertyStatus = null; ClientPersonalDetailDto clientPersonalDetailDto = new ClientPersonalDetailDto(ethinicity, citizenship, handicapped, businessActivities, maritalStatus, educationLevel, numChildren, gender, povertyStatus); ClientNameDetailDto spouseFatherName = null; InputStream picture = null; List<ApplicableAccountFeeDto> feesToApply = new ArrayList<ApplicableAccountFeeDto>(); String parentGroupId = "5"; List<ClientNameDetailDto> familyNames = new ArrayList<ClientNameDetailDto>(); List<ClientFamilyDetailDto> familyDetails = new ArrayList<ClientFamilyDetailDto>(); Short loanOfficerId = null; // 2 inherit from parent Short officeId = null; // 11 inherit from parent ClientCreationDetail client = new ClientCreationDetail(selectedSavingProducts, clientName, clientStatus, mfiJoiningDate, externalId, address, formedBy, dateOfBirth, governmentId, trained, trainedOn, groupFlag, clientNameDetailDto, clientPersonalDetailDto, spouseFatherName, picture, feesToApply, parentGroupId, familyNames, familyDetails, loanOfficerId, officeId, activationDate); MeetingDto meeting = null; // will inherit meeting of parent group. List<SavingsDetailDto> savings = new ArrayList<SavingsDetailDto>(); clientServiceFacade.createNewClient(client, meeting, savings); } } |
La reunión se crea para cada lunes, semanalmente.
En la tabla de clientes, la entrada para el cliente tiene los siguientes detalles de interés:
display name | trained | trained_date | created_date | created_by | mfijoining_date | customer_activation_date |
---|---|---|---|---|---|---|
CARMELITA DAGALLA ALCARAZ | YES | 30-11-2010 | 19-01-2011 | 1 | 06-Dec-2010 | 06-Dec-2010 |
En la tabla de programación de clientes, se crean diez programaciones de clientes desde el 06 de diciembre de 2010 hasta el 14 de febrero de 2011.
At present, only ten customer schedules are created when customer is activated and each night a batch job checks to see if more should be added. If the activation date is further than 10 of meeting periods away from today then there won't be a customer schedule existing for a meeting that should occur today or in the future until a batch job runs after midnight. So we should also check to see if customer activation date is in past and if so, create customer schedules from that date up to todays date, and then also create the 10 extra customer schedules for future meeting dates. |