diff --git a/src/main/java/seedu/address/logic/commands/DeleteMeetingCommand.java b/src/main/java/seedu/address/logic/commands/DeleteMeetingCommand.java new file mode 100644 index 00000000000..cfea0d704a9 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteMeetingCommand.java @@ -0,0 +1,117 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Client; +import seedu.address.model.person.Lead; +import seedu.address.model.person.Person; + +/** + * Deletes a meeting identified using it's displayed index from the address book. + */ +public class DeleteMeetingCommand extends Command { + + public static final String COMMAND_WORD = "deletemeeting"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the meeting identified by the index number used in the displayed meeting list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_MEETING_SUCCESS = "Deleted Meeting: %1$s from Person %2$s"; + + private final Index targetIndex; + + public DeleteMeetingCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + /** + * Deletes the meeting from the person. + * @param personToDeleteMeeting Person to delete meeting from. + * @return Person with meeting deleted. + */ + private static Person deleteMeeting(Person personToDeleteMeeting) { + requireNonNull(personToDeleteMeeting); + assert personToDeleteMeeting.isLead() || personToDeleteMeeting.isClient(); + + if (personToDeleteMeeting.getMeetingTime().isEmpty()) { + return personToDeleteMeeting; + } + + Person personWithMeetingDeleted; + + if (personToDeleteMeeting.isClient()) { + personWithMeetingDeleted = new Client( + personToDeleteMeeting.getName(), + personToDeleteMeeting.getPhone(), + personToDeleteMeeting.getEmail(), + personToDeleteMeeting.getAddress(), + Optional.empty(), + personToDeleteMeeting.getTags()); + } else { + // If person is not Client, person is a Lead + assert personToDeleteMeeting.isLead(); + Lead leadWithMeetingDeleted = (Lead) personToDeleteMeeting; + personWithMeetingDeleted = new Lead( + leadWithMeetingDeleted.getName(), + leadWithMeetingDeleted.getPhone(), + leadWithMeetingDeleted.getEmail(), + leadWithMeetingDeleted.getAddress(), + leadWithMeetingDeleted.getKeyMilestone(), + Optional.empty(), + leadWithMeetingDeleted.getTags()); + } + + return personWithMeetingDeleted; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person personToDeleteMeeting = lastShownList.get(targetIndex.getZeroBased()); + Person personWithMeetingDeleted = deleteMeeting(personToDeleteMeeting); + model.setPerson(personToDeleteMeeting, personWithMeetingDeleted); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format(MESSAGE_DELETE_MEETING_SUCCESS, + personToDeleteMeeting.getMeetingTimeString(), + targetIndex.getOneBased())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeleteMeetingCommand)) { + return false; + } + + DeleteMeetingCommand otherDeleteMeetingCommand = (DeleteMeetingCommand) other; + return targetIndex.equals(otherDeleteMeetingCommand.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 8121dc888f3..f368d4c7d48 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -15,6 +15,7 @@ import seedu.address.logic.commands.ConvertClientToLeadCommand; import seedu.address.logic.commands.ConvertLeadToClientCommand; import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteMeetingCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; import seedu.address.logic.commands.FindCommand; @@ -71,6 +72,9 @@ public Command parseCommand(String userInput) throws ParseException { case DeleteCommand.COMMAND_WORD: return new DeleteCommandParser().parse(arguments); + case DeleteMeetingCommand.COMMAND_WORD: + return new DeleteMeetingCommandParser().parse(arguments); + case ClearCommand.COMMAND_WORD: return new ClearCommand(); diff --git a/src/main/java/seedu/address/logic/parser/DeleteMeetingCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteMeetingCommandParser.java new file mode 100644 index 00000000000..def3dedc869 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteMeetingCommandParser.java @@ -0,0 +1,29 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.DeleteMeetingCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteMeetingCommand object + */ +public class DeleteMeetingCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteMeetingCommand + * and returns a DeleteMeetingCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public DeleteMeetingCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteMeetingCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteMeetingCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json index 6d7ec18fb4e..e0d0e1c343e 100644 --- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json @@ -7,7 +7,7 @@ "type": "client", "address" : "123, Jurong West Ave 6, #08-111", "keyMilestone": null, - "meetingTime" : null, + "meetingTime" : "12/12/2023 12:00", "tags" : [ "friends" ] }, { "name" : "Benson Meier", @@ -43,7 +43,7 @@ "type": "lead", "keyMilestone": "01/12/2023", "address" : "michegan ave", - "meetingTime" : null, + "meetingTime" : "12/12/2023 12:00", "tags" : [] }, { "name" : "Fiona Kunz", diff --git a/src/test/java/seedu/address/logic/commands/DeleteMeetingCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteMeetingCommandTest.java new file mode 100644 index 00000000000..913c5131a28 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/DeleteMeetingCommandTest.java @@ -0,0 +1,167 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.Messages; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.Person; +import seedu.address.testutil.PersonBuilder; + +/** + * Contains integration tests (interaction with the Model) and unit tests for + * {@code DeleteMeetingCommand}. + */ +public class DeleteMeetingCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() { + Person personToDeleteMeeting = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person personWithMeetingDeleted = new PersonBuilder(personToDeleteMeeting) + .withMeetingTime(null).buildClient(); // First person in typical address book without meeting time + + DeleteMeetingCommand deleteMeetingCommand = new DeleteMeetingCommand(INDEX_FIRST_PERSON); + + String expectedMessage = String.format(DeleteMeetingCommand.MESSAGE_DELETE_MEETING_SUCCESS, + personToDeleteMeeting.getMeetingTimeString(), INDEX_FIRST_PERSON.getOneBased()); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.setPerson(personToDeleteMeeting, personWithMeetingDeleted); + + assertCommandSuccess(deleteMeetingCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Index outOfBoundIndex = Index.fromOneBased( + model.getFilteredPersonList().size() + 1); // Last person in typical address book + DeleteMeetingCommand deleteMeetingCommand = new DeleteMeetingCommand(outOfBoundIndex); + + CommandTestUtil.assertCommandFailure(deleteMeetingCommand, model, + Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + @Test + public void execute_validIndexFilteredList_success() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + Person personToDeleteMeeting = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person personWithMeetingDeleted = new PersonBuilder(personToDeleteMeeting) + .withMeetingTime(null).buildClient(); // First person in typical address book without meeting time + + DeleteMeetingCommand deleteMeetingCommand = new DeleteMeetingCommand(INDEX_FIRST_PERSON); + + String expectedMessage = String.format(DeleteMeetingCommand.MESSAGE_DELETE_MEETING_SUCCESS, + personToDeleteMeeting.getMeetingTimeString(), INDEX_FIRST_PERSON.getOneBased()); + + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.setPerson(personToDeleteMeeting, personWithMeetingDeleted); + + assertCommandSuccess(deleteMeetingCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + Index outOfBoundIndex = INDEX_SECOND_PERSON; // Second person in typical address book + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); + + DeleteMeetingCommand deleteMeetingCommand = new DeleteMeetingCommand(outOfBoundIndex); + + CommandTestUtil.assertCommandFailure(deleteMeetingCommand, model, + Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + @Test + public void deleteMeeting_personIsLead() { + Index indexFirstLead = Index.fromOneBased(5); + Person leadToDeleteMeeting = model.getFilteredPersonList().get(indexFirstLead.getZeroBased()); + assertTrue(leadToDeleteMeeting.isLead()); + + Person leadWithMeetingDeleted = new PersonBuilder(leadToDeleteMeeting) + .withMeetingTime(null).buildLead(); // First lead in typical address book without meeting time + + DeleteMeetingCommand deleteMeetingCommand = new DeleteMeetingCommand(indexFirstLead); + + try { + deleteMeetingCommand.execute(model); + } catch (Exception e) { + fail(); + } + + Person expectedLead = model.getFilteredPersonList().get(indexFirstLead.getZeroBased()); + + assertTrue(expectedLead.isLead()); + assertEquals(expectedLead, leadWithMeetingDeleted); + } + + @Test + public void deleteMeeting_personNoMeetingTime_returnsPerson() { + Index indexPersonWithNoMeeting = Index.fromOneBased(3); // Third person in typical address book + Person personToDeleteMeeting = model.getFilteredPersonList() + .get(indexPersonWithNoMeeting.getZeroBased()); + + assertTrue(personToDeleteMeeting.getMeetingTime().isEmpty()); + + Person personWithMeetingDeleted = new PersonBuilder(personToDeleteMeeting) + .withMeetingTime(null).buildClient(); + assertEquals(personToDeleteMeeting, personWithMeetingDeleted); + + DeleteMeetingCommand deleteMeetingCommand = new DeleteMeetingCommand(indexPersonWithNoMeeting); + + try { + deleteMeetingCommand.execute(model); + } catch (Exception e) { + fail(); + } + + Person expectedPerson = model.getFilteredPersonList().get(indexPersonWithNoMeeting.getZeroBased()); + assertEquals(expectedPerson, personWithMeetingDeleted); + } + + @Test + public void equals() { + DeleteMeetingCommand deleteMeetingFirstCommand = new DeleteMeetingCommand(INDEX_FIRST_PERSON); + DeleteMeetingCommand deleteMeetingSecondCommand = new DeleteMeetingCommand(INDEX_SECOND_PERSON); + + // same object -> returns true + assertTrue(deleteMeetingFirstCommand.equals(deleteMeetingFirstCommand)); + + // same values -> returns true + DeleteMeetingCommand deleteMeetingFirstCommandCopy = new DeleteMeetingCommand(INDEX_FIRST_PERSON); + assertTrue(deleteMeetingFirstCommand.equals(deleteMeetingFirstCommandCopy)); + + // different types -> returns false + assertFalse(deleteMeetingFirstCommand.equals(1)); + + // null -> returns false + assertFalse(deleteMeetingFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(deleteMeetingFirstCommand.equals(deleteMeetingSecondCommand)); + } + + @Test + public void toStringMethod() { + Index targetIndex = Index.fromOneBased(1); + DeleteMeetingCommand deleteMeetingCommand = new DeleteMeetingCommand(targetIndex); + String expectedString = DeleteMeetingCommand.class.getCanonicalName() + "{targetIndex=" + targetIndex + "}"; + assertTrue(deleteMeetingCommand.toString().equals(expectedString)); + } +} diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java index 921c8426e39..299fc7a8d89 100644 --- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/EditCommandTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MEETING_TIME_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; @@ -37,7 +38,7 @@ public class EditCommandTest { @Test public void execute_allFieldsSpecifiedUnfilteredList_success() { - Person editedPerson = new PersonBuilder().buildClient(); + Person editedPerson = new PersonBuilder().withMeetingTime(VALID_MEETING_TIME_AMY).buildClient(); EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build(); EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor); diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java index ca36dc729dd..59771816997 100644 --- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java @@ -20,6 +20,7 @@ import seedu.address.logic.commands.AddLeadCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteMeetingCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.EditCommand.EditLeadDescriptor; import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; @@ -74,6 +75,14 @@ public void parseCommand_delete() throws Exception { assertEquals(new DeleteCommand(INDEX_FIRST_PERSON), command); } //todo: test failed maybe because meeting time is null, should be fixed after merging master + + @Test + public void parseCommand_deleteMeeting() throws Exception { + DeleteMeetingCommand command = (DeleteMeetingCommand) parser.parseCommand( + DeleteMeetingCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); + assertEquals(new DeleteMeetingCommand(INDEX_FIRST_PERSON), command); + } + @Test public void parseCommand_edit_withClient() throws Exception { Client client = new PersonBuilder().buildClient(); diff --git a/src/test/java/seedu/address/logic/parser/DeleteMeetingCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteMeetingCommandParserTest.java new file mode 100644 index 00000000000..59895eddb8b --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/DeleteMeetingCommandParserTest.java @@ -0,0 +1,32 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.DeleteMeetingCommand; + +public class DeleteMeetingCommandParserTest { + + private DeleteMeetingCommandParser parser = new DeleteMeetingCommandParser(); + + @Test + public void parse_validArgs_returnsDeleteMeetingCommand() { + assertParseSuccess(parser, "1", new DeleteMeetingCommand(INDEX_FIRST_PERSON)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteMeetingCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_emptyArgs_throwsParseException() { + assertParseFailure(parser, "", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteMeetingCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java index 87861a64f82..90b791ffa4a 100644 --- a/src/test/java/seedu/address/testutil/TypicalPersons.java +++ b/src/test/java/seedu/address/testutil/TypicalPersons.java @@ -31,7 +31,8 @@ public class TypicalPersons { public static final Client ALICE = new PersonBuilder().withName("Alice Pauline") .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com") - .withPhone("94351253").withTags("friends").buildClient(); + .withPhone("94351253").withTags("friends") + .withMeetingTime("12/12/2023 12:00").buildClient(); public static final Client BENSON = new PersonBuilder().withName("Benson Meier") .withAddress("311, Clementi Ave 2, #02-25") .withEmail("johnd@example.com").withPhone("98765432") @@ -46,7 +47,8 @@ public class TypicalPersons { public static final Client DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533") .withEmail("cornelia@example.com").withAddress("10th street").withTags("friends").buildClient(); public static final Lead ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224") - .withEmail("werner@example.com").withAddress("michegan ave").withKeyMilestone("01/12/2023").buildLead(); + .withEmail("werner@example.com").withAddress("michegan ave").withKeyMilestone("01/12/2023") + .withMeetingTime("12/12/2023 12:00").buildLead(); public static final Lead FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427") .withEmail("lydia@example.com").withAddress("little tokyo").withKeyMilestone("01/12/2023").buildLead(); public static final Lead GEORGE = new PersonBuilder().withName("George Best").withPhone("9482442")