diff --git a/build.gradle b/build.gradle index 5cd2de53441..ed1c9359026 100644 --- a/build.gradle +++ b/build.gradle @@ -69,6 +69,10 @@ shadowJar { archiveFileName = 'addressbook.jar' } +run { + enableAssertions = true +} + defaultTasks 'clean', 'test' run { diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java index a9b21234c49..50e2eebf552 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/seedu/address/logic/Messages.java @@ -5,6 +5,7 @@ import java.util.stream.Stream; import seedu.address.logic.parser.Prefix; +import seedu.address.model.person.Lead; import seedu.address.model.person.Person; /** @@ -35,6 +36,10 @@ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePref * Formats the {@code person} for display to the user. */ public static String format(Person person) { + if (person.isLead()) { + return format((Lead) person); + } + assert(!person.isLead()); final StringBuilder builder = new StringBuilder(); builder.append(person.getName()) .append("; Phone: ") @@ -50,4 +55,26 @@ public static String format(Person person) { return builder.toString(); } + /** + * Formats the {@code lead} for display to the user. + */ + public static String format(Lead lead) { + assert(lead.isLead()); + final StringBuilder builder = new StringBuilder(); + builder.append(lead.getName()) + .append("; Phone: ") + .append(lead.getPhone()) + .append("; Email: ") + .append(lead.getEmail()) + .append("; Address: ") + .append(lead.getAddress()) + .append("; Key Milestone: ") + .append(lead.getKeyMilestone()) + .append("; Meeting Time: ") + .append(lead.getKeyMilestone()) + .append("; Tags: "); + lead.getTags().forEach(builder::append); + return builder.toString(); + } + } diff --git a/src/main/java/seedu/address/logic/commands/AddLeadCommand.java b/src/main/java/seedu/address/logic/commands/AddLeadCommand.java index 65165faa77a..355d682ca6f 100644 --- a/src/main/java/seedu/address/logic/commands/AddLeadCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddLeadCommand.java @@ -3,6 +3,7 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_KEY_MILESTONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_MEETING_TIME; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; @@ -27,6 +28,7 @@ public class AddLeadCommand extends Command { + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL " + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_KEY_MILESTONE + "KEY_MILESTONE " + PREFIX_MEETING_TIME + "MEETING_TIME " + "[" + PREFIX_TAG + "TAG]...\n" + "Example: " + COMMAND_WORD + " " @@ -34,6 +36,7 @@ public class AddLeadCommand extends Command { + PREFIX_PHONE + "98765432 " + PREFIX_EMAIL + "johnd@example.com " + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_KEY_MILESTONE + "01/12/2023 " + PREFIX_MEETING_TIME + "10/10/2023 14:30 " + PREFIX_TAG + "classmate"; diff --git a/src/main/java/seedu/address/logic/commands/ConvertClientToLeadCommand.java b/src/main/java/seedu/address/logic/commands/ConvertClientToLeadCommand.java index 9de63f7135d..b712bafc8b6 100644 --- a/src/main/java/seedu/address/logic/commands/ConvertClientToLeadCommand.java +++ b/src/main/java/seedu/address/logic/commands/ConvertClientToLeadCommand.java @@ -15,6 +15,7 @@ import seedu.address.model.person.Address; import seedu.address.model.person.Client; import seedu.address.model.person.Email; +import seedu.address.model.person.KeyMilestone; import seedu.address.model.person.Lead; import seedu.address.model.person.MeetingTime; import seedu.address.model.person.Name; @@ -22,6 +23,7 @@ import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; + /** * Converts a Client to a Lead in the address book. */ @@ -65,13 +67,15 @@ public CommandResult execute(Model model) throws CommandException { Phone phone = personToConvert.getPhone(); Email email = personToConvert.getEmail(); Address address = personToConvert.getAddress(); + //todo: temporary fix for keyMilestone + KeyMilestone keyMilestone = null; Set tags = new HashSet<>(personToConvert.getTags()); Optional meetingTime = personToConvert.getMeetingTime(); // TODO: Add more fields from client to lead - Lead convertedLead = new Lead(name, phone, email, address, meetingTime, tags); + Lead convertedLead = new Lead(name, phone, email, address, keyMilestone, meetingTime, tags); model.setPerson(personToConvert, convertedLead); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); 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/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index c23384aa3a3..cd7eadde35c 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -25,6 +25,7 @@ import seedu.address.model.person.Address; import seedu.address.model.person.Client; import seedu.address.model.person.Email; +import seedu.address.model.person.KeyMilestone; import seedu.address.model.person.Lead; import seedu.address.model.person.MeetingTime; import seedu.address.model.person.Name; @@ -32,13 +33,14 @@ import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; + + /** * Edits the details of an existing person in the address book. */ public class EditCommand extends Command { public static final String COMMAND_WORD = "edit"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " + "by the index number used in the displayed person list. " + "Existing values will be overwritten by the input values.\n" @@ -54,6 +56,7 @@ public class EditCommand extends Command { + PREFIX_EMAIL + "johndoe@example.com"; public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; + public static final String MESSAGE_EDIT_LEAD_SUCCESS = "Edited Lead: %1$s"; public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; @@ -86,14 +89,18 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript Optional updatedMeetingTime = editPersonDescriptor.getMeetingTime() .or(personToEdit::getMeetingTime); Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - if (personToEdit.isClient()) { - return new Client(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedMeetingTime, updatedTags); - } else { - return new Lead(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedMeetingTime, updatedTags); + //todo: this temporary fix related to the one in editcommandparser + if (personToEdit.isLead()) { + //keyMilestone will not be updated if editLeadCommand is not used + Lead leadToEdit = (Lead) personToEdit; + KeyMilestone keyMilestone = leadToEdit.getKeyMilestone(); + return new Lead(updatedName, updatedPhone, updatedEmail, updatedAddress, keyMilestone, + updatedMeetingTime, updatedTags); } + return new Client(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedMeetingTime, updatedTags); } + @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); @@ -166,7 +173,6 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setMeetingTime(toCopy.meetingTime); setTags(toCopy.tags); } - /** * Returns true if at least one field is edited. */ @@ -263,4 +269,66 @@ public String toString() { .toString(); } } + + /** + * Stores the details to edit the lead with. Each non-empty field value will replace the + * corresponding field value of the lead. + */ + public static class EditLeadDescriptor extends EditPersonDescriptor { + //This class only used for edit KeyMilestone, for other field of leads like name, editPersonDescriptor + // will be used + private KeyMilestone keyMilestone; + /** + * Copy constructor only for leads. + * A defensive copy of {@code tags} is used internally. + */ + public EditLeadDescriptor(EditLeadDescriptor toCopy) { + super(toCopy); + setKeyMilestone(toCopy.keyMilestone); + } + + public EditLeadDescriptor() { + + } + + @Override + public boolean isAnyFieldEdited() { + return super.isAnyFieldEdited() || CollectionUtil.isAnyNonNull(keyMilestone); + } + + public Optional getKeyMilestone() { + return Optional.ofNullable(keyMilestone); + } + + public void setKeyMilestone(KeyMilestone keyMilestone) { + this.keyMilestone = keyMilestone; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditLeadDescriptor)) { + return false; + } + EditLeadDescriptor otherEditLeadDescriptor = (EditLeadDescriptor) other; + return super.equals(other) && Objects.equals(keyMilestone, otherEditLeadDescriptor.keyMilestone); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("name", super.name) + .add("phone", super.phone) + .add("email", super.email) + .add("address", super.address) + .add("key milestone", keyMilestone) + .add("meeting time", super.meetingTime) + .add("tags", super.tags) + .toString(); + } + } } diff --git a/src/main/java/seedu/address/logic/commands/EditLeadCommand.java b/src/main/java/seedu/address/logic/commands/EditLeadCommand.java new file mode 100644 index 00000000000..850af8b3e17 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/EditLeadCommand.java @@ -0,0 +1,135 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_KEY_MILESTONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MEETING_TIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +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.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.KeyMilestone; +import seedu.address.model.person.Lead; +import seedu.address.model.person.MeetingTime; +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.tag.Tag; + +/** + * Edits the details of an existing lead in the address book. + */ +public class EditLeadCommand extends EditCommand { + + public static final String COMMAND_WORD = "edit"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " + + "by the index number used in the displayed person list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_EMAIL + "EMAIL] " + + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_KEY_MILESTONE + "KEY_MILESTONE] " + + "[" + PREFIX_MEETING_TIME + "MEETING_TIME] " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_PHONE + "91234567 " + + PREFIX_EMAIL + "johndoe@example.com"; + + + private final Index index; + private final EditLeadDescriptor editLeadDescriptor; + + /** + * @param index of the person in the filtered person list to edit + * @param editLeadDescriptor details to edit the lead with + */ + public EditLeadCommand(Index index, EditLeadDescriptor editLeadDescriptor) { + super(index, editLeadDescriptor); + requireNonNull(index); + requireNonNull(editLeadDescriptor); + + this.index = index; + this.editLeadDescriptor = new EditLeadDescriptor(editLeadDescriptor); + } + + + private static Lead createEditedLead(Lead leadToEdit, EditLeadDescriptor editLeadDescriptor) { + assert leadToEdit != null; + + Name updatedName = editLeadDescriptor.getName().orElse(leadToEdit.getName()); + Phone updatedPhone = editLeadDescriptor.getPhone().orElse(leadToEdit.getPhone()); + Email updatedEmail = editLeadDescriptor.getEmail().orElse(leadToEdit.getEmail()); + Address updatedAddress = editLeadDescriptor.getAddress().orElse(leadToEdit.getAddress()); + Set updatedTags = editLeadDescriptor.getTags().orElse(leadToEdit.getTags()); + KeyMilestone updatedKeyMilestone = editLeadDescriptor.getKeyMilestone() + .orElse(leadToEdit.getKeyMilestone()); + Optional updatedMeetingTime = editLeadDescriptor.getMeetingTime() + .or(leadToEdit::getMeetingTime); + + return new Lead(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedKeyMilestone, + updatedMeetingTime, updatedTags); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Lead leadToEdit = (Lead) lastShownList.get(index.getZeroBased()); + assert(leadToEdit.isLead()); + Lead editedLead = createEditedLead((Lead) leadToEdit, editLeadDescriptor); + + if (!leadToEdit.isSamePerson(editedLead) && model.hasPerson(editedLead)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + model.setPerson(leadToEdit, editedLead); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format(MESSAGE_EDIT_LEAD_SUCCESS, Messages.format(editedLead))); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditLeadCommand)) { + return false; + } + + EditLeadCommand otherEditCommand = (EditLeadCommand) other; + return index.equals(otherEditCommand.index) + && editLeadDescriptor.equals(otherEditCommand.editLeadDescriptor); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("index", index) + .add("editLeadDescriptor", editLeadDescriptor) + .toString(); + } + + +} diff --git a/src/main/java/seedu/address/logic/parser/AddLeadCommandParser.java b/src/main/java/seedu/address/logic/parser/AddLeadCommandParser.java index 371232c2d36..7006892e032 100644 --- a/src/main/java/seedu/address/logic/parser/AddLeadCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddLeadCommandParser.java @@ -3,6 +3,7 @@ import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_KEY_MILESTONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_MEETING_TIME; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; @@ -16,6 +17,7 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.Address; import seedu.address.model.person.Email; +import seedu.address.model.person.KeyMilestone; import seedu.address.model.person.Lead; import seedu.address.model.person.MeetingTime; import seedu.address.model.person.Name; @@ -35,26 +37,28 @@ public class AddLeadCommandParser implements Parser { public AddLeadCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, - PREFIX_ADDRESS, PREFIX_MEETING_TIME, PREFIX_TAG); + PREFIX_ADDRESS, PREFIX_KEY_MILESTONE, PREFIX_MEETING_TIME, PREFIX_TAG); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_KEY_MILESTONE, + PREFIX_EMAIL) || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddLeadCommand.MESSAGE_USAGE)); } - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, - PREFIX_ADDRESS, PREFIX_MEETING_TIME); + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_MEETING_TIME, PREFIX_KEY_MILESTONE); Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); + KeyMilestone keyMilestone = ParserUtil.parseKeyMilestone(argMultimap.getValue(PREFIX_KEY_MILESTONE).get()); Optional meetingTime = argMultimap.getValue(PREFIX_MEETING_TIME).isPresent() ? Optional.of(ParserUtil.parseMeetingTime(argMultimap.getValue(PREFIX_MEETING_TIME).get())) : Optional.empty(); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); // TODO: temporary fix, implement add Client and Lead commands - Lead lead = new Lead(name, phone, email, address, meetingTime, tagList); + Lead lead = new Lead(name, phone, email, address, keyMilestone, meetingTime, tagList); return new AddLeadCommand(lead); } 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/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index ca26f7aab7e..35e43589e6d 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -12,6 +12,7 @@ public class CliSyntax { public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); public static final Prefix PREFIX_MEETING_TIME = new Prefix("m/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_KEY_MILESTONE = new Prefix("k/"); public static final Prefix PREFIX_NAME_TP = new Prefix("--name"); public static final Prefix PREFIX_PHONE_TP = new Prefix("--phone"); @@ -19,4 +20,5 @@ public class CliSyntax { public static final Prefix PREFIX_ADDRESS_TP = new Prefix("--address"); public static final Prefix PREFIX_MEETING_TIME_TP = new Prefix("--meeting-time"); public static final Prefix PREFIX_TAG_TP = new Prefix("--tag"); + public static final Prefix PREFIX_KEYMILESTONE_TP = new Prefix("--key"); } 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/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 1306876233a..6b8fec9125f 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -4,6 +4,7 @@ import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_KEY_MILESTONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_MEETING_TIME; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; @@ -16,7 +17,9 @@ import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.EditCommand.EditLeadDescriptor; import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.address.logic.commands.EditLeadCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.tag.Tag; @@ -32,9 +35,10 @@ public class EditCommandParser implements Parser { */ public EditCommand parse(String args) throws ParseException { requireNonNull(args); + boolean isLead = false; ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, - PREFIX_ADDRESS, PREFIX_MEETING_TIME, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_KEY_MILESTONE, PREFIX_MEETING_TIME, PREFIX_TAG); Index index; @@ -44,34 +48,49 @@ public EditCommand parse(String args) throws ParseException { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); } - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_KEY_MILESTONE, PREFIX_MEETING_TIME, PREFIX_ADDRESS); - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); + EditLeadDescriptor editLeadDescriptor = new EditLeadDescriptor(); + if (argMultimap.getValue(PREFIX_KEY_MILESTONE).isPresent()) { + isLead = true; + editLeadDescriptor.setKeyMilestone(ParserUtil.parseKeyMilestone( + argMultimap.getValue(PREFIX_KEY_MILESTONE).get())); + } if (argMultimap.getValue(PREFIX_NAME).isPresent()) { editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + editLeadDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + } if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); + editLeadDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); } if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); + editLeadDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); } if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); + editLeadDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); } if (argMultimap.getValue(PREFIX_MEETING_TIME).isPresent()) { editPersonDescriptor.setMeetingTime( Optional.of(ParserUtil.parseMeetingTime(argMultimap.getValue(PREFIX_MEETING_TIME).get()))); } parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); + parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editLeadDescriptor::setTags); - if (!editPersonDescriptor.isAnyFieldEdited()) { + if (!isLead && !editPersonDescriptor.isAnyFieldEdited()) { throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); + } else if (isLead && !editLeadDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditLeadCommand.MESSAGE_NOT_EDITED); } - - return new EditCommand(index, editPersonDescriptor); + //todo: there is a temporary fix here + //temporary fix to not convert lead to client when editing any lead's field + //EditLead is only used when the keyMilestone is changed + return isLead ? new EditLeadCommand(index, editLeadDescriptor) : new EditCommand(index, editPersonDescriptor); } /** diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 6ee18e8a6b2..80d2db7d380 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -11,6 +11,7 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.Address; import seedu.address.model.person.Email; +import seedu.address.model.person.KeyMilestone; import seedu.address.model.person.MeetingTime; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; @@ -81,6 +82,20 @@ public static Address parseAddress(String address) throws ParseException { return new Address(trimmedAddress); } + /** + * Parses a {@code String keyMilestone} into an {@code KeyMilestone}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code keyMilestone} is invalid. + */ + public static KeyMilestone parseKeyMilestone(String keyMilestone) throws ParseException { + requireNonNull(keyMilestone); + String trimmedKeyMilestone = keyMilestone.trim(); + if (!KeyMilestone.isValidKeyMilestone(trimmedKeyMilestone)) { + throw new ParseException(KeyMilestone.MESSAGE_CONSTRAINTS); + } + return new KeyMilestone(keyMilestone); + } /** * Parses a {@code String email} into an {@code Email}. * Leading and trailing whitespaces will be trimmed. diff --git a/src/main/java/seedu/address/model/person/KeyMilestone.java b/src/main/java/seedu/address/model/person/KeyMilestone.java new file mode 100644 index 00000000000..2e688bdda2b --- /dev/null +++ b/src/main/java/seedu/address/model/person/KeyMilestone.java @@ -0,0 +1,77 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; + +/** + * Represents a lead's key milestone in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidKeyMilestone(String)} + */ +public class KeyMilestone { + public static final String DATE_FORMAT = "dd/MM/uuuu"; + + // Replace uuuu in format to yyyy to not confuse users + public static final String MESSAGE_CONSTRAINTS = + "Key milestone is the date of Lead's milestone moment, should be in the format of " + + DATE_FORMAT.replace("u", "y"); + + public final String value; + + /** + * Constructs an {@code KeyMilestone}. + * + * @param keyMilestone A valid date. + */ + public KeyMilestone(String keyMilestone) { + requireNonNull(keyMilestone); + checkArgument(isValidKeyMilestone(keyMilestone), MESSAGE_CONSTRAINTS); + this.value = keyMilestone; + } + + /** + * Returns if a given string is a valid date. + */ + public static boolean isValidKeyMilestone(String test) { + try { + LocalDate.parse(test, + DateTimeFormatter + .ofPattern(DATE_FORMAT) + .withResolverStyle(ResolverStyle.STRICT)); + return true; + } catch (DateTimeParseException e) { + return false; + } + } + + @Override + public String toString() { + return value; + } + + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof KeyMilestone)) { + return false; + } + + KeyMilestone otherName = (KeyMilestone) other; + return value.equals(otherName.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Lead.java b/src/main/java/seedu/address/model/person/Lead.java index 7cac3b65b4d..4073dd4f4e8 100644 --- a/src/main/java/seedu/address/model/person/Lead.java +++ b/src/main/java/seedu/address/model/person/Lead.java @@ -3,6 +3,7 @@ import java.util.Optional; import java.util.Set; +import seedu.address.commons.util.ToStringBuilder; import seedu.address.model.tag.Tag; /** @@ -10,13 +11,19 @@ */ public class Lead extends Person { public static final String TYPE_LEAD = "lead"; + private final KeyMilestone keyMilestone; /** * Every field must be present and not null. */ public Lead(Name name, Phone phone, Email email, Address address, - Optional meetingTime, Set tags) { + KeyMilestone keyMilestone, Optional meetingTime, Set tags) { super(name, phone, email, new Type(TYPE_LEAD), address, meetingTime, tags); + this.keyMilestone = keyMilestone; + } + + public KeyMilestone getKeyMilestone() { + return this.keyMilestone; } @Override @@ -28,4 +35,19 @@ public boolean isClient() { public boolean isLead() { return true; } + + @Override + public String toString() { + //todo: fix the get + return new ToStringBuilder(this) + .add("name", super.getName()) + .add("phone", super.getPhone()) + .add("email", super.getEmail()) + .add("type", super.getType()) + .add("address", super.getAddress()) + .add("key milestone", this.keyMilestone) + .add("meetingTime", super.getMeetingTime()) + .add("tags", super.getTags()) + .toString(); + } } diff --git a/src/main/java/seedu/address/model/person/MeetingTime.java b/src/main/java/seedu/address/model/person/MeetingTime.java index 4968418d281..5857861d9c6 100644 --- a/src/main/java/seedu/address/model/person/MeetingTime.java +++ b/src/main/java/seedu/address/model/person/MeetingTime.java @@ -11,7 +11,9 @@ */ public class MeetingTime { - public static final String MESSAGE_CONSTRAINTS = "Meeting time should be in the format of " + DATE_TIME_FORMAT; + // Replace uuuu in format to yyyy to not confuse users + public static final String MESSAGE_CONSTRAINTS = "Meeting time should be in the format of " + + DATE_TIME_FORMAT.replace("u", "y"); public final LocalDateTime value; diff --git a/src/main/java/seedu/address/model/person/MeetingTimeFormatter.java b/src/main/java/seedu/address/model/person/MeetingTimeFormatter.java index d297bff94c6..82c21d2fb53 100644 --- a/src/main/java/seedu/address/model/person/MeetingTimeFormatter.java +++ b/src/main/java/seedu/address/model/person/MeetingTimeFormatter.java @@ -3,15 +3,18 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; /** * Helper class for handling meeting time formatting and parsing. */ public class MeetingTimeFormatter { - public static final String DATE_TIME_FORMAT = "dd/MM/yyyy HH:mm"; + public static final String DATE_TIME_FORMAT = "dd/MM/uuuu HH:mm"; private static DateTimeFormatter getFormatter() { - return DateTimeFormatter.ofPattern(DATE_TIME_FORMAT); + return DateTimeFormatter + .ofPattern(DATE_TIME_FORMAT) + .withResolverStyle(ResolverStyle.STRICT); } public static LocalDateTime parse(String meetingTime) { diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 64e22b2b0b1..539da85fb42 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -10,6 +10,7 @@ import seedu.address.model.person.Address; import seedu.address.model.person.Client; import seedu.address.model.person.Email; +import seedu.address.model.person.KeyMilestone; import seedu.address.model.person.Lead; import seedu.address.model.person.MeetingTime; import seedu.address.model.person.Name; @@ -36,13 +37,16 @@ public static Person[] getSamplePersons() { getTagSet("neighbours")), new Lead(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - Optional.of(new MeetingTime("10/10/2023 14:30")), + new KeyMilestone("01/12/2023"), Optional.of(new MeetingTime("10/10/2023 14:30")), getTagSet("family")), new Lead(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), Optional.of(new MeetingTime("10/10/2023 14:30")), + new Address("Blk 47 Tampines Street 20, #17-35"), + new KeyMilestone("01/12/2023"), + Optional.of(new MeetingTime("10/10/2023 14:30")), getTagSet("classmates")), new Lead(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), Optional.of(new MeetingTime("10/10/2023 14:30")), + new Address("Blk 45 Aljunied Street 85, #11-31"), + new KeyMilestone("01/12/2023"), Optional.of(new MeetingTime("10/10/2023 14:30")), getTagSet("colleagues")) }; } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index 6f7ba2f93ea..9aa028a2b38 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -17,6 +17,7 @@ import seedu.address.model.person.Address; import seedu.address.model.person.Client; import seedu.address.model.person.Email; +import seedu.address.model.person.KeyMilestone; import seedu.address.model.person.Lead; import seedu.address.model.person.MeetingTime; import seedu.address.model.person.Name; @@ -29,7 +30,6 @@ * Jackson-friendly version of {@link Person}. */ class JsonAdaptedPerson { - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; private final String name; @@ -37,22 +37,27 @@ class JsonAdaptedPerson { private final String email; private final String type; private final String address; + private String keyMilestone; private String meetingTime; private final List tags = new ArrayList<>(); /** * Constructs a {@code JsonAdaptedPerson} with the given person details. */ + @JsonCreator public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, @JsonProperty("email") String email, @JsonProperty("type") String type, - @JsonProperty("address") String address, @JsonProperty("meetingTime") String meetingTime, + @JsonProperty("address") String address, + @JsonProperty("keyMilestone") String keyMilestone, + @JsonProperty("meetingTime") String meetingTime, @JsonProperty("tags") List tags) { this.name = name; this.phone = phone; this.email = email; this.type = type; this.address = address; + this.keyMilestone = keyMilestone; if (meetingTime != null) { this.meetingTime = meetingTime; } @@ -70,6 +75,10 @@ public JsonAdaptedPerson(Person source) { email = source.getEmail().value; address = source.getAddress().value; type = source.getType().value; + if (type == TYPE_LEAD) { + Lead sourceLead = (Lead) source; + keyMilestone = sourceLead.getKeyMilestone().value; + } meetingTime = source.getMeetingTime().map(MeetingTime::toString).orElse(null); tags.addAll(source.getTags().stream() .map(JsonAdaptedTag::new) @@ -137,7 +146,12 @@ public Person toModelType() throws IllegalValueException { if (type.equals(TYPE_CLIENT)) { return new Client(modelName, modelPhone, modelEmail, modelAddress, modelMeetingTime, modelTags); } else if (type.equals(TYPE_LEAD)) { - return new Lead(modelName, modelPhone, modelEmail, modelAddress, modelMeetingTime, modelTags); + if (!KeyMilestone.isValidKeyMilestone(keyMilestone)) { + throw new IllegalValueException(KeyMilestone.MESSAGE_CONSTRAINTS); + } + final KeyMilestone modelKeyMilestone = new KeyMilestone(keyMilestone); + return new Lead(modelName, modelPhone, modelEmail, modelAddress, modelKeyMilestone, + modelMeetingTime, modelTags); } else { throw new IllegalValueException(Type.MESSAGE_CONSTRAINTS); } diff --git a/src/main/java/seedu/address/ui/ViewWindow.java b/src/main/java/seedu/address/ui/ViewWindow.java index c169cd79e3a..b9e9cc8e174 100644 --- a/src/main/java/seedu/address/ui/ViewWindow.java +++ b/src/main/java/seedu/address/ui/ViewWindow.java @@ -5,10 +5,12 @@ import javafx.fxml.FXML; import javafx.scene.control.Label; +import javafx.scene.image.ImageView; import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; import seedu.address.commons.core.LogsCenter; +import seedu.address.model.person.Lead; import seedu.address.model.person.Person; /** @@ -42,6 +44,16 @@ public class ViewWindow extends UiPart { private Label address; @FXML private Label email; + + @FXML + private Label keyMilestone; + + @FXML + private ImageView keyMilestoneIcon; + + @FXML + private Label keyMilestoneTitle; + @FXML private HBox meetingTimeBox; @@ -59,7 +71,7 @@ public class ViewWindow extends UiPart { public ViewWindow(Person person, int displayedIndex) { super(FXML); this.person = person; - //id.setText(displayedIndex + "."); + //id.setText(displayedIndex + ".") name.setText(person.getName().fullName); phone.setText(person.getPhone().value); address.setText(person.getAddress().value); @@ -69,6 +81,18 @@ public ViewWindow(Person person, int displayedIndex) { } else { meetingTime.setText(""); meetingTimeBox.setVisible(false); + if (person.isLead()) { + Lead convertedPerson = (Lead) person; + keyMilestoneIcon.setVisible(true); + keyMilestone.setManaged(true); + keyMilestoneTitle.setManaged(true); + //the key milestone title is added here to make it disappear entirely + keyMilestone.setText(convertedPerson.getKeyMilestone().value); + } else { + //keyMilestone should be at the last line of the ui, if not the empty line will be obvious + keyMilestoneIcon.setVisible(false); + keyMilestone.setManaged(false); + keyMilestoneTitle.setManaged(false); } Label label = new Label(person.getType().value); if (person.isClient()) { diff --git a/src/main/resources/images/milestone.png b/src/main/resources/images/milestone.png new file mode 100644 index 00000000000..79bf6e2d726 Binary files /dev/null and b/src/main/resources/images/milestone.png differ diff --git a/src/main/resources/view/ViewWindow.fxml b/src/main/resources/view/ViewWindow.fxml index b32aaca8ea5..87509247ae5 100644 --- a/src/main/resources/view/ViewWindow.fxml +++ b/src/main/resources/view/ViewWindow.fxml @@ -94,6 +94,20 @@