diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..d81e8dd Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 6c01878..88a60bd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ build/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/** !**/src/test/** +bin/ ### STS ### .apt_generated diff --git a/bin/main/application.properties b/bin/main/application.properties deleted file mode 100644 index 8b13789..0000000 --- a/bin/main/application.properties +++ /dev/null @@ -1 +0,0 @@ - diff --git a/bin/main/com/invoicetracker/InvoiceTrackerApplication.class b/bin/main/com/invoicetracker/InvoiceTrackerApplication.class deleted file mode 100644 index 065aa49..0000000 Binary files a/bin/main/com/invoicetracker/InvoiceTrackerApplication.class and /dev/null differ diff --git a/bin/main/com/invoicetracker/models/User.class b/bin/main/com/invoicetracker/models/User.class deleted file mode 100644 index b230602..0000000 Binary files a/bin/main/com/invoicetracker/models/User.class and /dev/null differ diff --git a/bin/test/com/invoicetracker/InvoiceTrackerApplicationTests.class b/bin/test/com/invoicetracker/InvoiceTrackerApplicationTests.class deleted file mode 100644 index b28de05..0000000 Binary files a/bin/test/com/invoicetracker/InvoiceTrackerApplicationTests.class and /dev/null differ diff --git a/build.gradle b/build.gradle index 5344326..42d0444 100644 --- a/build.gradle +++ b/build.gradle @@ -25,6 +25,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-rest' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.data:spring-data-rest-hal-browser' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.h2database:h2' testImplementation('org.springframework.boot:spring-boot-starter-test') { diff --git a/build.zip b/build.zip new file mode 100644 index 0000000..7e89624 Binary files /dev/null and b/build.zip differ diff --git a/java-json.jar b/java-json.jar new file mode 100644 index 0000000..2f211e3 Binary files /dev/null and b/java-json.jar differ diff --git a/src/main/java/com/invoicetracker/Populator.java b/src/main/java/com/invoicetracker/Populator.java new file mode 100644 index 0000000..c931c90 --- /dev/null +++ b/src/main/java/com/invoicetracker/Populator.java @@ -0,0 +1,185 @@ +package com.invoicetracker; + +import java.time.LocalDate; +import javax.annotation.Resource; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; +import com.invoicetracker.models.Contractor; +import com.invoicetracker.models.Invoice; +import com.invoicetracker.models.ServiceItem; +import com.invoicetracker.repositories.ContractorRepository; +import com.invoicetracker.repositories.InvoiceRepository; +import com.invoicetracker.repositories.ServiceItemRepository; + +@Component +public class Populator implements CommandLineRunner { + + @Resource + ServiceItemRepository serviceItemRepo; + + @Resource + InvoiceRepository invoiceRepo; + + @Resource + ContractorRepository contractorRepo; + + @Override + public void run(String... args) throws Exception { + + /* + * Contractor_A has 5 invoices called invoice_A1...A5 each invoice has 1 to 4 + * service items + */ + Contractor contractor_A = new Contractor(); + contractor_A.setEmail("JohnSmith@gmail.com"); + contractor_A.setAddressLineOne("Center St"); + contractor_A.setAddressLineTwo("Suite A"); + contractor_A.setCity("Cityville"); + contractor_A.setState("Alabama"); + contractor_A.setZip("90210"); + contractor_A.setCountry("USA"); + contractor_A.setPhoneNumber("(555) 555-5555"); + contractor_A.setFirstName("Andy"); + contractor_A.setLastName("Amoray"); + contractor_A.setPayPalId("AndyAmorayPayPal"); + contractorRepo.save(contractor_A); + + /********* Next invoice and service items *************/ + { + Invoice invoice_A1 = new Invoice(contractor_A); + LocalDate dateInvoice_A1 = LocalDate.of(2020, 03, 28); + invoice_A1.setDateOfInvoice(dateInvoice_A1); + invoice_A1.setInvoiceNote("Students did a great job applying themselves durring both sessions, I am very impressed with the progress!"); + invoice_A1.setIsPaid(false); + invoiceRepo.save(invoice_A1); + contractorRepo.save(contractor_A); + + ServiceItem serviceItem_A1 = new ServiceItem(invoice_A1); + LocalDate dateServiceItem_A1 = LocalDate.of(2020, 03, 11); + serviceItem_A1.setDateOfService(dateServiceItem_A1); + serviceItem_A1.setAmountDue(100); + serviceItem_A1.setServiceDescription("Tutored Alley"); + serviceItemRepo.save(serviceItem_A1); + + ServiceItem serviceItem_A2 = new ServiceItem(invoice_A1); + LocalDate dateServiceItem_A2 = LocalDate.of(2020, 02, 01); + serviceItem_A2.setDateOfService(dateServiceItem_A2); + serviceItem_A2.setAmountDue(150); + serviceItem_A2.setServiceDescription("Tutored Allen"); + serviceItemRepo.save(serviceItem_A2); + } + /********* Next invoice and service items *************/ + { + Invoice invoice_A2 = new Invoice(contractor_A); + LocalDate dateInvoice_A2 = LocalDate.of(2020, 03, 11); + invoice_A2.setDateOfInvoice(dateInvoice_A2); + invoice_A2.setInvoiceNote("Students did a great job applying themselves durring both sessions, I am very impressed with the progress!"); + invoice_A2.setIsPaid(false); + invoiceRepo.save(invoice_A2); + + ServiceItem serviceItem_A3 = new ServiceItem(invoice_A2); + LocalDate dateServiceItem_A3 = LocalDate.of(2020, 02, 12); + serviceItem_A3.setDateOfService(dateServiceItem_A3); + serviceItem_A3.setAmountDue(200); + serviceItem_A3.setServiceDescription("Tutored Billy"); + serviceItemRepo.save(serviceItem_A3); + + ServiceItem serviceItem_A4 = new ServiceItem(invoice_A2); + LocalDate dateServiceItem_A4 = LocalDate.of(2020, 02, 02); + serviceItem_A4.setDateOfService(dateServiceItem_A4); + serviceItem_A4.setAmountDue(250); + serviceItem_A4.setServiceDescription("Tutored Bobo"); + serviceItemRepo.save(serviceItem_A4); + + ServiceItem serviceItem_A5 = new ServiceItem(invoice_A2); + LocalDate dateServiceItem_A5 = LocalDate.of(2020, 02, 01); + serviceItem_A5.setDateOfService(dateServiceItem_A5); + serviceItem_A5.setAmountDue(350); + serviceItem_A5.setServiceDescription("Tutored Bathsheba"); + serviceItemRepo.save(serviceItem_A5); + } + /********* Next invoice and service items *************/ + { + Invoice invoice_A3 = new Invoice(contractor_A); + LocalDate dateInvoice_A3 = LocalDate.of(2020, 03, 11); + invoice_A3.setDateOfInvoice(dateInvoice_A3); + invoice_A3.setInvoiceNote("Contractor A's Third Invoice"); + invoice_A3.setIsPaid(false); + invoiceRepo.save(invoice_A3); + + ServiceItem serviceItem_A6 = new ServiceItem(invoice_A3); + LocalDate dateServiceItem_A6 = LocalDate.of(2020, 02, 12); + serviceItem_A6.setDateOfService(dateServiceItem_A6); + serviceItem_A6.setAmountDue(200); + serviceItem_A6.setServiceDescription("Tutored Chanel"); + serviceItemRepo.save(serviceItem_A6); + + ServiceItem serviceItem_A7 = new ServiceItem(invoice_A3); + LocalDate dateServiceItem_A7 = LocalDate.of(2020, 02, 02); + serviceItem_A7.setDateOfService(dateServiceItem_A7); + serviceItem_A7.setAmountDue(250); + serviceItem_A7.setServiceDescription("Tutored Chira"); + serviceItemRepo.save(serviceItem_A7); + + ServiceItem serviceItem_A8 = new ServiceItem(invoice_A3); + LocalDate dateServiceItem_A8 = LocalDate.of(2020, 02, 01); + serviceItem_A8.setDateOfService(dateServiceItem_A8); + serviceItem_A8.setAmountDue(350); + serviceItem_A8.setServiceDescription("Tutored Carton'O"); + serviceItemRepo.save(serviceItem_A8); + } + /********* Next invoice and service items *************/ + { + Invoice invoice_A4 = new Invoice(contractor_A); + LocalDate dateInvoice_A4 = LocalDate.of(2020, 03, 11); + invoice_A4.setDateOfInvoice(dateInvoice_A4); + invoice_A4.setInvoiceNote("Contractor A's Fourth Invoice"); + invoice_A4.setIsPaid(false); + invoiceRepo.save(invoice_A4); + + ServiceItem serviceItem_A9 = new ServiceItem(invoice_A4); + LocalDate dateServiceItem_A9 = LocalDate.of(2020, 02, 12); + serviceItem_A9.setDateOfService(dateServiceItem_A9); + serviceItem_A9.setAmountDue(200); + serviceItem_A9.setServiceDescription("Tutored David"); + serviceItemRepo.save(serviceItem_A9); + + ServiceItem serviceItem_A10 = new ServiceItem(invoice_A4); + LocalDate dateServiceItem_A10 = LocalDate.of(2020, 02, 02); + serviceItem_A10.setDateOfService(dateServiceItem_A10); + serviceItem_A10.setAmountDue(250); + serviceItem_A10.setServiceDescription("Tutored Donna"); + serviceItemRepo.save(serviceItem_A10); + + ServiceItem serviceItem_A11 = new ServiceItem(invoice_A4); + LocalDate dateServiceItem_A11 = LocalDate.of(2020, 02, 01); + serviceItem_A11.setDateOfService(dateServiceItem_A11); + serviceItem_A11.setAmountDue(350); + serviceItem_A11.setServiceDescription("Tutored Dauphny"); + serviceItemRepo.save(serviceItem_A11); + + ServiceItem serviceItem_A12 = new ServiceItem(invoice_A4); + LocalDate dateServiceItem_A12 = LocalDate.of(2020, 02, 01); + serviceItem_A12.setDateOfService(dateServiceItem_A12); + serviceItem_A12.setAmountDue(350); + serviceItem_A12.setServiceDescription("Tutored Le'Don"); + serviceItemRepo.save(serviceItem_A12); + } + /********* Next invoice and service items *************/ + { + Invoice invoice_A5 = new Invoice(contractor_A); + LocalDate dateInvoice_A5 = LocalDate.of(2020, 01, 11); + invoice_A5.setDateOfInvoice(dateInvoice_A5); + invoice_A5.setInvoiceNote("Contractor A's Fith Invoice"); + invoice_A5.setIsPaid(false); + invoiceRepo.save(invoice_A5); + + ServiceItem serviceItem_A13 = new ServiceItem(invoice_A5); + LocalDate dateServiceItem_A13 = LocalDate.of(2020, 02, 12); + serviceItem_A13.setDateOfService(dateServiceItem_A13); + serviceItem_A13.setAmountDue(200); + serviceItem_A13.setServiceDescription("Tutored Erin"); + serviceItemRepo.save(serviceItem_A13); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/invoicetracker/controllers/ContractorController.java b/src/main/java/com/invoicetracker/controllers/ContractorController.java new file mode 100644 index 0000000..4569d34 --- /dev/null +++ b/src/main/java/com/invoicetracker/controllers/ContractorController.java @@ -0,0 +1,89 @@ +package com.invoicetracker.controllers; + +import java.util.Collection; +import javax.annotation.Resource; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import com.invoicetracker.models.Contractor; +import com.invoicetracker.models.Invoice; +import com.invoicetracker.repositories.ContractorRepository; +import com.invoicetracker.repositories.InvoiceRepository; + +@RequestMapping("/contractor") +@Controller +public class ContractorController { + + @Resource + private ContractorRepository contractorRepo; + + @Resource + private InvoiceRepository invoiceRepo; + + @GetMapping("/create-new-invoice/{contractorId}") + private String createInvoice(@PathVariable(value = "contractorId") long contractorId, Model model) { + Contractor contractor = contractorRepo.findById(contractorId).get(); + model.addAttribute("contractor", contractor); + return "create-invoice"; + } + + @GetMapping("/update-profile/{contractorId}") + private String updateProfile(@PathVariable(value = "contractorId") long contractorId, Model model) { + Contractor contractor = contractorRepo.findById(contractorId).get(); + model.addAttribute("contractor", contractor); + return "profile"; + } + + + @GetMapping("/view-existing-invoice/{contractorId}/{invoiceId}") + private String viewInvoice(@PathVariable(value = "contractorId") long contractorId, + @PathVariable(value = "invoiceId") long invoiceId, Model model) { + + Contractor contractor = contractorRepo.findById(contractorId).get(); + Invoice invoice = invoiceRepo.findById(invoiceId).get(); + + model.addAttribute("contractor", contractor); + model.addAttribute("invoice", invoice); + + return "view-invoice"; + } + + @GetMapping("/search-invoice-list/{contractorId}") + private String viewInvoiceList(@PathVariable(value = "contractorId") long contractorId, Model model) { + + Contractor contractor = contractorRepo.findById(contractorId).get(); + Collection invoices = contractor.getInvoices(); + model.addAttribute("invoices", invoices); + model.addAttribute("contractor", contractor); + + return "search-invoice-list"; + } + + /* + * TODO Needs Code Review: + * I do not think a @PutMapping is right, but it is working. + */ + @PutMapping("/mark-invoice-paid/{invoiceId}") + private void markInvoicePaid(@PathVariable(value = "invoiceId") long invoiceId){ + + Invoice invoiceToMarkPaid = invoiceRepo.findById(invoiceId).get(); + invoiceToMarkPaid.setIsPaid(true); + invoiceRepo.save(invoiceToMarkPaid); + } + + /* + * TODO Needs Code Review: + * I do not think a @PutMapping is right, but it is working. + */ + @PutMapping("/mark-invoice-sent/{invoiceId}") + private void markInvoiceSent(@PathVariable(value = "invoiceId") long invoiceId){ + + Invoice invoiceToSend = invoiceRepo.findById(invoiceId).get(); + invoiceToSend.setIsSent(true); + invoiceRepo.save(invoiceToSend); + } + +} \ No newline at end of file diff --git a/src/main/java/com/invoicetracker/controllers/InvoiceController.java b/src/main/java/com/invoicetracker/controllers/InvoiceController.java new file mode 100644 index 0000000..fc2ff14 --- /dev/null +++ b/src/main/java/com/invoicetracker/controllers/InvoiceController.java @@ -0,0 +1,69 @@ +package com.invoicetracker.controllers; + +import java.time.LocalDate; + +import javax.annotation.Resource; + +import org.json.JSONArray; +import org.json.JSONException; +//the jar file for this import is on the desktop +// source of file is found https://stackoverflow.com/questions/8997598/importing-json-into-an-eclipse-project +import org.json.JSONObject; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import com.invoicetracker.models.Invoice; +import com.invoicetracker.models.ServiceItem; +import com.invoicetracker.repositories.ContractorRepository; +import com.invoicetracker.repositories.InvoiceRepository; +import com.invoicetracker.repositories.ServiceItemRepository; + +@CrossOrigin +@Controller +public class InvoiceController { + + @Resource + private InvoiceRepository invoiceRepo; + + @Resource + private ServiceItemRepository serviceItemRepo; + + @Resource + private ContractorRepository contractorItemRepo; + + @RequestMapping("submit-invoice") + private void createNewInvoice(@RequestBody String body) throws JSONException { + + /* Create the invoice */ + JSONObject newInvoice = new JSONObject(body); + + String dateOfInvoice = newInvoice.getJSONObject("invoiceNumbersJson").getString("invoiceDate"); + LocalDate localDate = LocalDate.parse(dateOfInvoice); + Invoice invoice = new Invoice(localDate); + + int invoiceNumber = newInvoice.getJSONObject("invoiceNumbersJson").getInt("invoiceNumber"); + invoice.setInvoiceNumber(invoiceNumber); + invoiceRepo.save(invoice); + + /* Add Service Items */ + JSONArray serviceItemsArray = newInvoice.getJSONArray("invoiceArray"); + + for(int i = 0; i < serviceItemsArray.length(); i++) { + + JSONObject aServiceItem = (JSONObject) serviceItemsArray.get(2); + String serviceDate = aServiceItem.getString("serviceDate"); + LocalDate localServiceDate = LocalDate.parse(serviceDate); + ServiceItem newServiceItem = new ServiceItem(localServiceDate, invoice); + serviceItemRepo.save(newServiceItem); + } + + contractorItemRepo.findById(27L).get().addInvoice(invoice); + + } + +} diff --git a/src/main/java/com/invoicetracker/controllers/InvoiceRestController.java b/src/main/java/com/invoicetracker/controllers/InvoiceRestController.java new file mode 100644 index 0000000..9ed30fc --- /dev/null +++ b/src/main/java/com/invoicetracker/controllers/InvoiceRestController.java @@ -0,0 +1,22 @@ +package com.invoicetracker.controllers; + +import javax.annotation.Resource; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import com.invoicetracker.models.Invoice; +import com.invoicetracker.repositories.InvoiceRepository; + +@RestController +public class InvoiceRestController { + + @Resource + private InvoiceRepository invoiceRepo; + + @GetMapping("/api/invoice/{id}") + public Invoice retrieveInvoice(@PathVariable Long id) { + return invoiceRepo.findById(id).get(); + } +} diff --git a/src/main/java/com/invoicetracker/controllers/LoginController.java b/src/main/java/com/invoicetracker/controllers/LoginController.java new file mode 100644 index 0000000..24ba5f2 --- /dev/null +++ b/src/main/java/com/invoicetracker/controllers/LoginController.java @@ -0,0 +1,16 @@ +package com.invoicetracker.controllers; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@RequestMapping("/login") +@Controller +public class LoginController { + + @GetMapping("") + public String login() { + + return "contractor-login"; + } +} diff --git a/src/main/java/com/invoicetracker/models/Contractor.java b/src/main/java/com/invoicetracker/models/Contractor.java new file mode 100644 index 0000000..fac7953 --- /dev/null +++ b/src/main/java/com/invoicetracker/models/Contractor.java @@ -0,0 +1,118 @@ +package com.invoicetracker.models; + +import java.util.Collection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +@Entity +public class Contractor extends User { + + /************************ Field Values ****************/ + + @Id + @GeneratedValue + private long id; + + private String firstName; + private String lastName; + private String payPalId; + private int currentInvoiceNumber = 1000; + + @JsonIgnore + @OneToMany(mappedBy = "contractor") + private Collection invoices; + + /************************ Getters and Setters ****************/ + + public long getId() { + return id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getPayPalId() { + return payPalId; + } + + public void setPayPalId(String payPalId) { + this.payPalId = payPalId; + } + + public int getCurrentInvoiceNumber() { + return currentInvoiceNumber; + } + + public void incrementCurrentInvoiceNumber() { + this.currentInvoiceNumber++; + } + + public Collection getInvoices() { + return invoices; + } + + /************************ Methods ****************/ + + public void addInvoice(Invoice newInvoice) { + + getInvoices().add(newInvoice); + newInvoice.setContractor(this); + } + + public void removeInvoice(Invoice invoiceToRemove) { + + getInvoices().remove(invoiceToRemove); + invoiceToRemove.setContractor(null); + } + + /************************ Constructors ****************/ + + public Contractor() { + } + + public Contractor(String firstName) { + this.firstName = firstName; + } + + /************************ Overrides ****************/ + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + (int) (id ^ (id >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + Contractor other = (Contractor) obj; + if (id != other.id) + return false; + return true; + } + +} diff --git a/src/main/java/com/invoicetracker/models/Invoice.java b/src/main/java/com/invoicetracker/models/Invoice.java new file mode 100644 index 0000000..901bc5c --- /dev/null +++ b/src/main/java/com/invoicetracker/models/Invoice.java @@ -0,0 +1,217 @@ +package com.invoicetracker.models; + +import java.text.NumberFormat; +import java.time.LocalDate; +import java.util.Collection; +import java.util.Locale; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +@Entity +public class Invoice { + + /************************ Field Values ****************/ + + @Id + @GeneratedValue + private long id; + + private LocalDate dateOfInvoice; + private int invoiceNumber; + private float totalAmountDue; + private String invoiceNote; + private boolean isPaid; + private boolean isSent; + + //@JsonIgnore + @ManyToOne + private Contractor contractor; + + @OneToMany(mappedBy = "invoice") + private Collection serviceItems; + + /************************ Getters and Setter ****************/ + public long getId() { + return id; + } + + public LocalDate getDateOfInvoice() { + return dateOfInvoice; + } + + public void setDateOfInvoice(LocalDate dateOfInvoice) { + this.dateOfInvoice = dateOfInvoice; + } + + public int getInvoiceNumber() { + return invoiceNumber; + } + + public void setInvoiceNumber(int invoiceNumber) { + this.invoiceNumber = invoiceNumber; + } + + public float getTotalAmountDue() { + return totalAmountDue; + } + + public void setTotalAmountDue(float totalAmountDue) { + this.totalAmountDue = totalAmountDue; + } + + public String getInvoiceNote() { + return invoiceNote; + } + + public void setInvoiceNote(String invoiceNote) { + this.invoiceNote = invoiceNote; + } + + public boolean getIsPaid() { + return isPaid; + } + + public void setIsPaid(boolean isPaid) { + this.isPaid = isPaid; + } + + public boolean getIsSent() { + return isSent; + } + + public void setIsSent(boolean isSent) { + this.isSent = isSent; + } + + public Contractor getContractor() { + return contractor; + } + + public void setContractor(Contractor contractor) { + this.contractor = contractor; + } + + public Collection getServiceItems() { + return serviceItems; + } + + public void setServiceItems(Collection serviceItems) { + this.serviceItems = serviceItems; + } + + /************************ Constructors ****************/ + + public Invoice() { + } + + public Invoice(Contractor contractor) { + + this.contractor = contractor; + this.contractor.incrementCurrentInvoiceNumber(); + this.invoiceNumber = contractor.getCurrentInvoiceNumber(); + } + + /************************ Methods ****************/ + + public void removeServiceItem(ServiceItem serviceItemToRemove) { + + getServiceItems().remove(serviceItemToRemove); + serviceItemToRemove.setInvoice(null); + } + + public String getTotalAmountDueAsCurrencyString() { + + float amount = calculateTotalAmountDueFromAllServiceItemsOnInvoice(); + NumberFormat format = NumberFormat.getCurrencyInstance(Locale.US); + String currency = format.format(amount); + // TODO: Requires Code Review + // I am auto down casting here, format expects a double...Is that bad? + + return currency; + } + + public float calculateTotalAmountDueFromAllServiceItemsOnInvoice() { + + float runningTotal = 0; + + for (ServiceItem item : this.serviceItems) { + runningTotal += item.getAmountDue(); + } + + return runningTotal; + } + + // TODO This function is an abomination and needs fixed. + public String getCustomerNamePreviewAsString() { + + /* Test and then remove this */ + // ArrayList serviceItemsArray = new + // ArrayList<>(this.serviceItems); + String customerNames = ""; + + int count = 0; + for (ServiceItem serviceItem : serviceItems) { + + if (count == 0) { + customerNames += serviceItem.getServiceDescription(); + } + if (count == 1) { + customerNames += ", " + serviceItem.getServiceDescription(); + } + if (count == 2) { + customerNames += " ... plus " + (Math.abs(serviceItems.size() - 2)) + " more"; + } + + count++; + } + + return customerNames; + } + + public String getPaymentStatus() { + + String currentPaymentStatus; + + if(isPaid) { + currentPaymentStatus = "Paid"; + } else if(isSent) { + currentPaymentStatus = "Sent"; + } else { + currentPaymentStatus = "Not sent"; + } + + return currentPaymentStatus; + } + + /************************ Overrides ****************/ + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (int) (id ^ (id >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Invoice other = (Invoice) obj; + if (id != other.id) + return false; + return true; + } + + + +} \ No newline at end of file diff --git a/src/main/java/com/invoicetracker/models/ServiceItem.java b/src/main/java/com/invoicetracker/models/ServiceItem.java new file mode 100644 index 0000000..896bf71 --- /dev/null +++ b/src/main/java/com/invoicetracker/models/ServiceItem.java @@ -0,0 +1,114 @@ +package com.invoicetracker.models; + +import java.text.NumberFormat; +import java.time.LocalDate; +import java.util.Locale; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +@Entity +public class ServiceItem { + + /************************ Field Values ****************/ + + @Id + @GeneratedValue + private long id; + + private LocalDate dateOfService; + private float amountDue; + private String serviceDescription; + + @JsonIgnore + @ManyToOne + private Invoice invoice; + + /************************ Getters and Setters ****************/ + + public long getId() { + return id; + } + + public LocalDate getDateOfService() { + return dateOfService; + } + + public void setDateOfService(LocalDate dateOfService) { + this.dateOfService = dateOfService; + } + + public float getAmountDue() { + return amountDue; + } + + public void setAmountDue(float amountDue) { + this.amountDue = amountDue; + } + + public String getServiceDescription() { + return serviceDescription; + } + + public void setServiceDescription(String serviceDescription) { + this.serviceDescription = serviceDescription; + } + + public Invoice getInvoice() { + return invoice; + } + + public void setInvoice(Invoice invoice) { + this.invoice = invoice; + } + + /********************* Constructors ****************/ + + public ServiceItem() { + } + + public ServiceItem(Invoice invoice) { + this.invoice = invoice; + } + + /************************ Methods ****************/ + + public Object getAmountDueAsCurrencyString() { + NumberFormat format = NumberFormat.getCurrencyInstance(Locale.US); + String currency = format.format(amountDue); + // TODO: Requires Code Review + // I am auto down casting here, format expects a double...Is that bad? + + return currency; + } + + /************************ Overrides ****************/ + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (int) (id ^ (id >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ServiceItem other = (ServiceItem) obj; + if (id != other.id) + return false; + return true; + } + + +} \ No newline at end of file diff --git a/src/main/java/com/invoicetracker/models/User.java b/src/main/java/com/invoicetracker/models/User.java index c329674..f43f386 100644 --- a/src/main/java/com/invoicetracker/models/User.java +++ b/src/main/java/com/invoicetracker/models/User.java @@ -1,14 +1,23 @@ package com.invoicetracker.models; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; + +@Entity +@Inheritance(strategy = InheritanceType.JOINED) public abstract class User { + + /************************ Field Values ****************/ - /************************ Field Values****************/ + @Id + @GeneratedValue + private long id; /* Contact Info */ - private String firstName; - private String lastName; - private String businessName; private String email; /* Address */ @@ -18,31 +27,13 @@ public abstract class User { private String state; private String zip; private String country; + private String phoneNumber; + /************************ Getters and Setters ****************/ - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public String getBusinessName() { - return businessName; - } - - public void setBusinessName(String businessName) { - this.businessName = businessName; + public long getId() { + return id; } public String getEmail() { @@ -101,4 +92,44 @@ public void setCountry(String country) { this.country = country; } -} + public String getPhoneNumber() { + return phoneNumber; + } + + public void setPhoneNumber(String phoneNumber) { + this.phoneNumber = phoneNumber; + } + + /************************ Constructors ****************/ + + protected User() {} + + protected User(String email) { + this.email = email; + } + + /************************ Overrides ****************/ + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (int) (id ^ (id >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + User other = (User) obj; + if (id != other.id) + return false; + return true; + } + +} \ No newline at end of file diff --git a/src/main/java/com/invoicetracker/repositories/ContractorRepository.java b/src/main/java/com/invoicetracker/repositories/ContractorRepository.java new file mode 100644 index 0000000..92ef18b --- /dev/null +++ b/src/main/java/com/invoicetracker/repositories/ContractorRepository.java @@ -0,0 +1,11 @@ +package com.invoicetracker.repositories; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import com.invoicetracker.models.Contractor; + +@Repository +public interface ContractorRepository extends CrudRepository { + +} diff --git a/src/main/java/com/invoicetracker/repositories/InvoiceRepository.java b/src/main/java/com/invoicetracker/repositories/InvoiceRepository.java new file mode 100644 index 0000000..0000f78 --- /dev/null +++ b/src/main/java/com/invoicetracker/repositories/InvoiceRepository.java @@ -0,0 +1,9 @@ +package com.invoicetracker.repositories; + +import org.springframework.data.repository.CrudRepository; + +import com.invoicetracker.models.Invoice; + +public interface InvoiceRepository extends CrudRepository { + +} diff --git a/src/main/java/com/invoicetracker/repositories/ServiceItemRepository.java b/src/main/java/com/invoicetracker/repositories/ServiceItemRepository.java new file mode 100644 index 0000000..a94e917 --- /dev/null +++ b/src/main/java/com/invoicetracker/repositories/ServiceItemRepository.java @@ -0,0 +1,9 @@ +package com.invoicetracker.repositories; + +import org.springframework.data.repository.CrudRepository; + +import com.invoicetracker.models.ServiceItem; + +public interface ServiceItemRepository extends CrudRepository { + +} diff --git a/src/main/package.json b/src/main/package.json new file mode 100644 index 0000000..67cbdb9 --- /dev/null +++ b/src/main/package.json @@ -0,0 +1,12 @@ +{ + "name": "main", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b13789..72fc330 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1 @@ - +spring.jpa.show-sql=true \ No newline at end of file diff --git a/src/main/resources/static/css/footer.css b/src/main/resources/static/css/footer.css new file mode 100644 index 0000000..fb7f4dd --- /dev/null +++ b/src/main/resources/static/css/footer.css @@ -0,0 +1,14 @@ +#footer_section { + display: flex; + font-family: sans-serif; + line-height: 1.618em; + width: 100%; + justify-content: center; + align-items: center; + font-style: italic; +} + +img#logo { + width: 75px; + margin-bottom: 0.3em; +} \ No newline at end of file diff --git a/src/main/resources/static/css/invoice-creation-view.css b/src/main/resources/static/css/invoice-creation-view.css new file mode 100644 index 0000000..69b35c8 --- /dev/null +++ b/src/main/resources/static/css/invoice-creation-view.css @@ -0,0 +1,208 @@ +*{ + font-family: sans-serif; + font-size: large; +} + +html { + color: #2E343F; + background-color: #4f5357; +} + +p { + border: rgb(255, 255, 255) solid 2px; +} + +body { + background-color: #f2f2f2; + margin: 1em 1em; + border: 5px solid #3a9ac1; + border-radius: 1em 1em 1em 1em; + padding: 10px; + display: flex; + flex-direction: column; + justify-content: center; + align-content: space-around; +} + +/* Logo */ +header { + /* font-size: 2em; + font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif; */ + margin-top: 2%; + margin-left: 2%; + margin-right: auto; + border: #3a9ac1 solid 5px; + border-radius: 5em; + width: 280px; + height: 130px; + display: flex; + justify-content: center; + background-color: #4f5357; + color: #3a9ac1; +} + +body h2 { + margin-left: 65%; + font-size: 4em; +} + +p{ + display: flex; + justify-content: center; + align-items: center; + background-color: whitesmoke; +} +/* container hierarchy is dertermined outside in */ +.container { + margin: auto; + padding: 2%; + display: flex; + flex-direction: column; + width: 90%; +} + +input { + width: 80%; +} + +#topContainer, #middleContainer { + padding: 1em; + display: flex; + + flex-direction: row; + +} + +/* applies to top and middle containers sub containers */ +.container3 { + padding: .25em; + display: flex; + flex-direction: column; + width: 100%; +} + +#invoiceIdandDateContainer { + align-items: flex-end; + justify-content: space-around; +} + +#invoiceIdandDateContainer div{ + display: flex; + padding: .25em; +} + +.container4 input{ + margin-left: 10%; + width: 8.5em; +} + +/* separates the right hand side of the upper section */ +.container4 { + margin: 0.5em; + width: 30%; + height: 1.5em; + display: flex; + justify-content: flex-end; + align-items: center; +} + +.container4b { + width: 30%; + display: flex; + flex-direction: column; + justify-content: flex-start; +} + +/* paid button */ +#invoicePaid { + display: flex; + flex-direction: column; +} + +#invoicePaid button { + color: black; + background-color: red; +} + + +/* .container5 is for lower section */ +.container5 { + margin: 0%; + padding: 0%; + display: flex; + flex-direction: row; + justify-content: center; +} + +#serviceLables label { + display: flex; + justify-content: space-evenly; + margin: 0%; + padding: 0%; + width: 16.66%; + height: 1.5em; +} + +#serviceInfo { + display: flex; + flex-wrap: wrap; +} + +.serviceInfoSet { + display: flex; + flex-wrap: wrap; +} + +#serviceInfo div input { + margin: 0%; + padding: 0%; + width: 16.22%; + height: 1.5em; +} + +#totalsBeforeNotes{ + margin-left: auto; + margin-right: 3%; +} + +#totalAmountDueLabel{ + width: 500px; +} + +#totalAmountDue{ + width: 300px; + height: 1.5em; + border: solid black 3px; +} + +/* buttons */ + +button { + width: 120px; + margin: auto; + margin-top: .5em; + padding: .25; + border-radius: .5em .5em; +} + +#addServBtn { + margin-right: 0%; + display: flex; + flex-direction: row; + justify-self: flex-end; +} + +#deleteServBtn { + margin-left: 0%; + margin-right: -2em; + margin-top: 0em; + width: 2em; +} + +#submitBtn { + + background-color: #4f5357; + color: #3a9ac1; + font-size: larger; + font-style: italic; +} diff --git a/src/main/resources/static/css/login.css b/src/main/resources/static/css/login.css new file mode 100644 index 0000000..377cc7c --- /dev/null +++ b/src/main/resources/static/css/login.css @@ -0,0 +1,114 @@ + * { + font-family: sans-serif; + line-height: 1.618em; + box-sizing: border-box; + margin: 0; + padding: 0; + font-size: large; + } + + html, body { + background-color: #EDE7F6; + height: 100%; + } + + body { + display: grid; + grid-template-columns: [left] 1fr [login-start] minmax(200px, 600px) [login-stop] 1fr [right]; + grid-template-rows: [top] auto [row1-end] auto [row2-end] 1fr [row3-end] auto [bottom]; + } + + body { + background-image: url(/images/login_background3.png); + /* Full height */ + height: 100%; + /* Center and scale the image nicely */ + background-position: center; + background-repeat: no-repeat; + background-size: cover; + } + + #header_section { + grid-column: login-start / login-stop; + grid-row: top / row1-end; + } + + #form_section { + grid-column: login-start / login-stop; + grid-row: row1-end / row2-end; + } + + #footer_section { + grid-column: login-start / login-stop; + grid-row: row3-end / bottom; + } + + form { + background-color: #ffffff; + box-shadow: 0px 0px 10px 2px rgba(0, 0, 0, 0.75); + border-radius: 1em; + /* margin: 2em; */ + } + + input[type=text], input[type=password] { + width: 100%; + padding: 12px 20px; + margin: 8px 0; + display: inline-block; + border: 1px solid rgb(2, 12, 4); + box-sizing: border-box; + border-radius: 1em; + } + + button { + font-size: 2em; + background-color: #4f5357; + color: #3a9ac1; + padding: 7px 20px; + margin: auto; + border: none; + cursor: pointer; + width: 100%; + border-radius: 1em; + } + + button:hover { + opacity: 0.8; + } + + .imgcontainer { + text-align: center; + margin: 12px 6px; + /* margin for the space of the container */ + } + + .container { + text-align: justify; + padding: 10px 50px; + } + + span.psw { + float: left; + padding-top: 8px; + } + + span.sup { + float: right; + padding-top: 8px; + } + + /* styles for span and cancel button on extra small screens */ + + @media screen and (max-width: 100px) { + span.psw { + display: block; + float: none; + } + span.sup { + display: block; + float: none; + } + /* .cancelbtn { + width: 100%; + } */ + } \ No newline at end of file diff --git a/src/main/resources/static/css/navbar.css b/src/main/resources/static/css/navbar.css new file mode 100644 index 0000000..c3834f6 --- /dev/null +++ b/src/main/resources/static/css/navbar.css @@ -0,0 +1,37 @@ +.navbar { + display: flex; + justify-content: center; + font-family: sans-serif; + line-height: 1.618em; + width: 100%; + background-color: #555; +} + +.navbar a { + padding-top: 1em; + padding-left: 1em; + padding-right: 1em; + color: white; + text-decoration: none; + font-size: larger; +} + +.navbar a:hover { + background-color: #000; +} + +img.logo { + width: 5%; + margin-right: 10rem; +} + +.active { + background-color: #3a9ac1; +} + +@media screen and (max-width: 500px) { + .navbar a { + float: none; + display: block; + } +} \ No newline at end of file diff --git a/src/main/resources/static/css/profile.css b/src/main/resources/static/css/profile.css new file mode 100644 index 0000000..61e3c26 --- /dev/null +++ b/src/main/resources/static/css/profile.css @@ -0,0 +1,107 @@ +* { + font-family: sans-serif; + line-height: 1.618em; + box-sizing: border-box; + margin: 0; + padding: 0; + font-size: large; +} + +button { + line-height: 1.618em; + font-size: large; +} + +h1 { + font-size: 3em; + font-weight: bold; +} + +html, body { + background-color: #EDE7F6; + height: 100%; +} + +body { + display: grid; + grid-template-columns: [left] 1fr [profile-start] minmax(200px, 8fr) [profile-stop] 1fr [right]; + grid-template-rows: [top] auto [row1-end] auto [row2-end] auto [row3-end] 1fr [row4-end] auto [bottom]; +} + +#nav_section { + grid-column: left / right; + grid-row: top / span 1; + margin-bottom: 1em; +} + +#header_section { + grid-column: profile-start / profile-stop; + grid-row: row1-end / row2-end; +} + +#profile_section { + margin-top: 15px; + grid-column: profile-start / profile-stop; + grid-row: row2-end / row3-end; + align-self: end; + box-shadow: 0px 0px 10px 2px rgba(0, 0, 0, 0.75); +} + +.collapsible { + background-color: white; + border: 2px solid #3a9ac1; + color: black; + border-radius: 1em; + padding: 20px 20px; + margin-bottom: 15px; + text-align: center; + text-decoration: none; + display: block; + font-size: 16px; +} + +.divider { + border-radius: 1em; + background: white; + margin: auto; + width: 100%; +} + +.profile-settings, .name-row { + border-radius: 1em; + margin: 1em; +} + +.profile-settings input[type=text], input[type=password], select { + font-family: sans-serif; + line-height: 1.618em; + width: 50%; + padding: 12px 20px; + margin: 8px 0; + display: inline-block; + border: 2px solid #1d2951; + border-radius: 1em; + box-sizing: border-box; +} + +input[type=submit] { + width: 20%; + background-color: lightgray; + color: black; + padding: 12px 20px; + margin: 8px 0; + border: none; + border-radius: 1em; + cursor: pointer; +} + +input[type=submit]:hover { + background-color: darkgrey; +} + +/*********************** Footer Section ************************/ + +#footer_section { + grid-column: profile-start / profile-stop; + grid-row: row4-end / bottom; +} \ No newline at end of file diff --git a/src/main/resources/static/css/search-invoices.css b/src/main/resources/static/css/search-invoices.css new file mode 100644 index 0000000..aa9b2d8 --- /dev/null +++ b/src/main/resources/static/css/search-invoices.css @@ -0,0 +1,130 @@ +* { + font-family: sans-serif; + line-height: 1.618em; + box-sizing: border-box; + margin: 0; + padding: 0; + font-size: large; +} + +h1 { + font-size: 3em; +} + +html { + background-color: #EDE7F6; + height: 100%; +} + +body { + display: grid; + grid-template-columns: [left] 1fr [table-start] minmax(200px, 8fr) [table-stop] 1fr [right]; + grid-template-rows: [top] auto [row1-end] auto [row2-end] auto [row3-end] 1fr [row4-end] auto [bottom]; + height: 100%; +} + +#box_shadow { + grid-column: table-start / table-stop; + grid-row: row2-end / row3-end; + background-color: white; + z-index: -1; + margin-top: 10px; + border-radius: 1em; + box-shadow: 0px 0px 10px 2px rgba(0, 0, 0, 0.75); + align-self: stretch; +} + +/*********************** Nav Section ************************/ + +#nav_section { + grid-column: left / right; + grid-row: top / span 1; + margin-bottom: 1em; +} + +nav { + display: flex; + flex-flow: row; + justify-content: center; + background: #2E343F; +} + +/*********************** Header Section ************************/ + +#header_section { + grid-column: table-start / table-stop; + grid-row: row1-end / row2-end; +} + +/*********************** Table Section / Invoice List ************************/ + +#table_section { + grid-column: table-start / table-stop; + grid-row: row2-end / row3-end; + align-self: end; +} + +table { + border-collapse: collapse; + width: 100%; + background-color: #ffffff; + border-radius: 1em; + box-shadow: 0px 0px 10px 2px rgba(0, 0, 0, 0.75); +} + +tbody tr, td { + border: 2px solid #2E343F; +} + +th, td { + padding: 15px; + text-align: left; +} + +tbody tr:nth-child(0) { + font-size: 50px; +} + +thead { + background-color: #2E343F; + color: #ffffff; +} + +tfoot { + background-color: #2E343F; +} + +thead th:first-child { + border-radius: 15px 0 0 0; +} + +thead th:last-child { + border-radius: 0 15px 0 0; +} + +thead th:only-child { + border-radius: 15px 15px 0 0; +} + +tfoot th:first-child { + border-radius: 0 0 0 15px; +} + +tfoot th:last-child { + border-radius: 0 0 15px 0; +} + +tfoot th:only-child { + border-radius: 0 0 15px 15px; +} + +tbody tr:nth-child(even) { + background: #3a9ac1; +} + +/*********************** Footer Section ************************/ + +#footer_section { + grid-column: table-start / table-stop; + grid-row: row4-end / bottom; +} \ No newline at end of file diff --git a/src/main/resources/static/css/single-invoice.css b/src/main/resources/static/css/single-invoice.css new file mode 100644 index 0000000..ce8ac99 --- /dev/null +++ b/src/main/resources/static/css/single-invoice.css @@ -0,0 +1,267 @@ +* { + font-family: sans-serif; + box-sizing: border-box; + margin: 0; + padding: 0; + font-size: large; +} + +h1 { + font-size: 3em; + line-height: 1.618em; +} + +h2 { + font-size: 1.5em; + line-height: 1.618em; +} + +html { + background-color: #EDE7F6; +} + +body { + display: grid; + grid-template-columns: [left] 0.4fr [invoice-start] minmax(200px, 1fr) [invoice-stop] 0.4fr [right]; + grid-template-rows: repeat(8, auto) 90px [bottom]; +} + +.darker { + font-weight: bold; +} + +.lighter { + font-weight: lighter; +} + +#box_shadow { + grid-column: invoice-start / invoice-stop; + grid-row: 3 / 8; + z-index: -1; + border-radius: 1em; + box-shadow: 0 0px 10px 2px rgba(148, 142, 142, 0.31); +} + +/************************** NavBar ****************************/ + +#nav_section { + grid-column: left / right; + grid-row: 1 / 2; + margin-bottom: 1em; +} + +/************************** Invoice Actions ****************************/ + +#invoice_actions { + grid-column: invoice-start / invoice-stop; + grid-row: 2 / 3; + padding: 1.5rem; +} + +#invoice_actions { + display: flex; + justify-content: space-between; +} + +#invoice_actions>button { + color: #4d6575; + background: rgba(0, 0, 0, 0); + border: 1px solid #b2c2cd; + padding: 8px 20px; + vertical-align: middle; + cursor: pointer; + border-radius: 1em; +} + +#invoice_actions>button:hover { + color: #4d6575; + background: rgba(0, 0, 0, 0); + border: 1px solid #2d6f9b; + padding: 8px 20px; + vertical-align: middle; + cursor: pointer; + border-radius: 1em; +} + +#paid_stamp { + display: none; + grid-column: invoice-start / invoice-stop; + grid-row: 3 / 4; + padding: 2rem 2rem 0 60%; + z-index: 1; +} + +#sent_stamp { + display: none; + grid-column: invoice-start / invoice-stop; + grid-row: 3 / 4; + padding: 2rem 2rem 0 60%; + z-index: 1; +} + +/************************** Contractor Info ****************************/ + +#contractor_info { + grid-column: invoice-start / invoice-stop; + grid-row: 3 / 4; + background: #ffffff; + padding: 1.5rem; + border-top-left-radius: 1rem; + border-top-right-radius: 1rem; + line-height: 1.2em; +} + +hr { + margin-top: 1rem; +} + +/************************** Invoice Info ****************************/ + +#invoice_info { + grid-column: invoice-start / invoice-stop; + grid-row: 4 / 5; + background: #ffffff; + padding: 2rem; +} + +#invoice_info div { + display: flex; + justify-content: flex-end; + align-items: baseline; +} + +#invoice_info p { + padding-right: 1rem; +} + +.amount_due { + background-color: #b2c2cd; + padding-right: 0; + padding-left: 1rem; +} + +#invoice_amount_due { + padding-left: 1rem; + border-top-left-radius: 1rem; + border-bottom-left-radius: 1rem; +} + +/************************** ServiceItem Info ****************************/ + +#serviceItem_info { + grid-column: invoice-start / invoice-stop; + grid-row: 5 / 6; + background: #c3d7df; + justify-items: start; +} + +table { + width: 100%; + border-collapse: collapse; + border-spacing: 0; +} + +tbody tr, td { + border: 2px solid rgb(121, 122, 123); +} + +th { + padding: 15px; + text-align: center; +} + +td { + padding: 15px; +} + +tr #service_date { + text-align: center; +} + +tr #service_price { + text-align: right; + padding-right: 3rem; +} + +.twenty_five { + width: 25%; +} + +#serviceItem_info .container { + display: flex; + line-height: 1.6em; + padding-top: .5rem; + padding-bottom: 1rem; + padding-right: 3rem; + justify-content: flex-end; +} + +#total_amount_due { + padding-right: 1rem; + padding-left: 1rem; + border-top-left-radius: 1rem; + border-bottom-left-radius: 1rem; +} + +.underline { + text-decoration-line: underline; +} + +/************************** Notes ****************************/ + +#notes { + grid-column: invoice-start / invoice-stop; + grid-row: 6 / 7; + background: #ffffff; + padding: 2rem 1.5rem 14rem 1.5rem; +} + +#notes { + display: flex; + flex-direction: column; +} + +/************************** Invoice Footer ****************************/ + +#invoice_footer { + grid-column: invoice-start / invoice-stop; + grid-row: 7 / 8; + background: #ffffff; + padding: 1.5rem; + border-bottom-left-radius: 1rem; + border-bottom-right-radius: 1rem; +} + +#invoice_footer { + display: flex; + justify-content: center; + font-style: italic; +} + +/************************** Footer Section ****************************/ + +#footer_section { + grid-column: invoice-start / invoice-stop; + grid-row: 9 / span 1; +} + +/************************** CSS For Printing ****************************/ + +@media print { + body { + display: grid; + grid-template-columns: 0.1fr [invoice-start] minmax(200px, 8fr) [invoice-stop] 0.1fr; + } + #invoice_actions, #box_shadow, #nav_section, #footer_section { + display: none; + } + #contractor_info, #invoice_info, #serviceItem_info, #total_amount_due, #notes, #invoice_footer * { + visibility: visible; + } + #notes { + padding: 1rem 1.5rem 5rem 1.5rem; + } + #invoice_footer { + padding: 0.5rem; + } +} \ No newline at end of file diff --git a/src/main/resources/static/images/login_background.jpg b/src/main/resources/static/images/login_background.jpg new file mode 100644 index 0000000..1115ce6 Binary files /dev/null and b/src/main/resources/static/images/login_background.jpg differ diff --git a/src/main/resources/static/images/login_background2.jpg b/src/main/resources/static/images/login_background2.jpg new file mode 100644 index 0000000..ee8da20 Binary files /dev/null and b/src/main/resources/static/images/login_background2.jpg differ diff --git a/src/main/resources/static/images/login_background3.png b/src/main/resources/static/images/login_background3.png new file mode 100644 index 0000000..081604a Binary files /dev/null and b/src/main/resources/static/images/login_background3.png differ diff --git a/src/main/resources/static/images/paid_stamp.png b/src/main/resources/static/images/paid_stamp.png new file mode 100644 index 0000000..8492200 Binary files /dev/null and b/src/main/resources/static/images/paid_stamp.png differ diff --git a/src/main/resources/static/images/paid_stamp_transparent.png b/src/main/resources/static/images/paid_stamp_transparent.png new file mode 100644 index 0000000..01ea0bc Binary files /dev/null and b/src/main/resources/static/images/paid_stamp_transparent.png differ diff --git a/src/main/resources/static/images/projo.png b/src/main/resources/static/images/projo.png new file mode 100644 index 0000000..eb0c3f4 Binary files /dev/null and b/src/main/resources/static/images/projo.png differ diff --git a/src/main/resources/static/images/sent_stamp.jpg b/src/main/resources/static/images/sent_stamp.jpg new file mode 100644 index 0000000..18a431b Binary files /dev/null and b/src/main/resources/static/images/sent_stamp.jpg differ diff --git a/src/main/resources/static/images/sent_stamp.png b/src/main/resources/static/images/sent_stamp.png new file mode 100644 index 0000000..8bcd623 Binary files /dev/null and b/src/main/resources/static/images/sent_stamp.png differ diff --git a/src/main/resources/static/js/api/api-actions.js b/src/main/resources/static/js/api/api-actions.js new file mode 100644 index 0000000..d8ec32f --- /dev/null +++ b/src/main/resources/static/js/api/api-actions.js @@ -0,0 +1,12 @@ +/** + * Boiler Plate + */ +function postRequest(location, requestBody) { + fetch(location, { + method: "POST", + body: JSON.stringify(requestBody) + }) + .then(response => response.json()) + .catch(err => console.log(err)) +} + diff --git a/src/main/resources/static/js/components/app.js b/src/main/resources/static/js/components/app.js new file mode 100644 index 0000000..643bc9b --- /dev/null +++ b/src/main/resources/static/js/components/app.js @@ -0,0 +1,8 @@ +function submitInvoiceButton() { + + const invoiceJSON = submitInvoice() + postRequest('http://localhost:8080/submit-invoice', invoiceJSON) + + console.log(JSON.stringify(invoiceJSON)); + +} \ No newline at end of file diff --git a/src/main/resources/static/js/invoice-creation-view.js b/src/main/resources/static/js/invoice-creation-view.js new file mode 100644 index 0000000..eb33023 --- /dev/null +++ b/src/main/resources/static/js/invoice-creation-view.js @@ -0,0 +1,49 @@ +// contractor view functions +var totalServs = 1; +var counter = 1; +var limit = 15; + +function addService(divName) { + if (totalServs == limit) { + alert("You have reached the service limit.") + } + else { + var newServ = document.createElement('div'); + // newServ.outerHTML = `
` + counter++; + newServ.innerHTML = ` + + + + + + + `; + newServ.id = counter; + document.getElementById(divName).appendChild(newServ); + newServ.className = 'serviceInfoSet'; + totalServs++; + } +} + +function deleteServ(id) { + var target = document.getElementById(id); + target.parentElement.removeChild(target); + console.log("firing"); + totalServs--; +} + +// agency view functions +var paid = 'no'; + +function payInvoice(divName) { + var color = document.getElementById('payBtn'); + if (paid == 'yes') { + alert("Invoice has already been paid.") + } + else { + paid = 'yes'; + color.style.backgroundColor = "green"; + + } +} \ No newline at end of file diff --git a/src/main/resources/static/js/invoice-json.js b/src/main/resources/static/js/invoice-json.js new file mode 100644 index 0000000..ab05d51 --- /dev/null +++ b/src/main/resources/static/js/invoice-json.js @@ -0,0 +1,110 @@ +function submitInvoice() { + + // ***information from the upper half of the html doc*** + + var contractorName = document.getElementById('contractorName').value; + var contractorAddress = document.getElementById('contractorAddress').value; + var contractorPhone = document.getElementById('contractorPhone').value + var contractorJson = { + 'contractorName': contractorName, + 'contractorAddress': contractorAddress, + 'contractorPhone': contractorPhone + } + //console.log(contractorJson); + + var invoiceNumber = document.getElementById('invoiceNumber').value; + var invoiceDate = document.getElementById('invoiceDate').value; + var invoiceTerm = document.getElementById('invoiceTerm').value; + var invoiceNumbersJson = { + 'invoiceNumber': invoiceNumber, + 'invoiceDate': invoiceDate, + 'invoiceTerm': invoiceTerm + } + //console.log(invoiceNumbersJson); + + var agencyName = document.getElementById('agencyName').value; + var agencyAddress = document.getElementById('agencyAddress').value; + var agencyPhone = document.getElementById('agencyPhone').value; + var agencyJson = { + 'agencyName': agencyName, + 'agencyAddress': agencyAddress, + 'agencyPhone': agencyPhone + } + //console.log(agencyJson); + + // ***information from the lower half of the html doc*** + + var invoiceArray = []; + //console.log(invoiceArray); + + var serviceInfoSets = document.getElementsByClassName('serviceInfoSet'); + for (let infoSet of serviceInfoSets) { + // console.log(infoSet.id); + var innerInvoiceId = infoSet.id; + var clientName; + var serviceName; + var serviceDate; + var serviceTime; + var hourlyRate; + var amountDue; + + + var values = infoSet.getElementsByClassName('info'); + for (let value of values) { + tempValue = value.value; + if (value.id == 'clientName') { + clientName = tempValue; + // console.log('test1'); + } + if (value.id == 'serviceName') { + serviceName = tempValue; + // console.log('test2'); + } + if (value.id == 'serviceDate') { + serviceDate = tempValue; + // console.log('test3'); + } + if (value.id == 'serviceTime') { + serviceTime = tempValue; + // console.log('test4'); + } + if (value.id == 'hourlyRate') { + hourlyRate = tempValue; + //console.log('test5'); + } + if (value.id == 'amountDue') { + amountDue = tempValue; + // console.log('test6'); + } + } + + //console.log(innerInvoiceId, clientName, serviceName, serviceDate, serviceTime, hourlyRate, amountDue); + + var infoSetValues = { + 'innerInvoiceId': innerInvoiceId, + 'clientName': clientName, + 'serviceName': serviceName, + 'serviceDate': serviceDate, + 'serviceTime': serviceTime, + 'hourlyRate': hourlyRate, + 'amountDue': amountDue + } + + // console.log(infoSetValues); + invoiceArray.push(infoSetValues); + + } + + //the preperation of the json data for transport + + var invoiceJson = { + contractorJson, + invoiceNumbersJson, + agencyJson, + invoiceArray + } + // console.log(JSON.stringify(invoiceJson)); + return invoiceJson; + +} + diff --git a/src/main/resources/static/js/login.js b/src/main/resources/static/js/login.js new file mode 100644 index 0000000..c7b6cb6 --- /dev/null +++ b/src/main/resources/static/js/login.js @@ -0,0 +1,7 @@ + +// let loginButton = document.getElementById("login_button"); +// loginButton.addEventListener("click", loginRedirect) + +function loginRedirect() { + window.location.href = "/contractor/search-invoice-list/1"; +} \ No newline at end of file diff --git a/src/main/resources/static/js/profile.js b/src/main/resources/static/js/profile.js new file mode 100644 index 0000000..2bc08e3 --- /dev/null +++ b/src/main/resources/static/js/profile.js @@ -0,0 +1,18 @@ +function proJoFunction(id) { + var x = document.getElementById(id); + if (x.className.indexOf("w3-show") == -1) { + x.className += " w3-show"; + } else { + x.className = x.className.replace(" w3-show", ""); + } +} + +function validateForm() { + var x = document.forms["personal"]["name"].value; + if (x == "") { + alert("Name must be filled out."); + return false; + } +} + + diff --git a/src/main/resources/static/js/single-invoice.js b/src/main/resources/static/js/single-invoice.js new file mode 100644 index 0000000..49ec85e --- /dev/null +++ b/src/main/resources/static/js/single-invoice.js @@ -0,0 +1,96 @@ + +alreadyPaidActions() +alreadySentActions() + +//TODO: This function should get worked into separate functions. +function alreadyPaidActions() { + + let isPaid = document.getElementById("is_paid").value; + if (isPaid === "true") { + //Hide Payment button and show Paid stamp + let paidImage = document.getElementById("paid_stamp") + paidImage.style.display = "block" + let paidButton = document.getElementById("payBtn") + paidButton.style.display = "none" + + //Hide Sent Button and Sent Image + let sentImage = document.getElementById("sent_stamp") + sentImage.style.display = "none" + let sentButton = document.getElementById("sentBtn") + sentButton.style.display = "none" + } +} + +function alreadySentActions() { + + let isSent = document.getElementById("is_sent").value; + let isPaid = document.getElementById("is_paid").value; + if (isSent === "true" && isPaid === "false") { + //Display Sent Stamp and hide sent button + let sentImage = document.getElementById("sent_stamp") + sentImage.style.display = "block" + let sentButton = document.getElementById("sentBtn") + sentButton.style.display = "none" + } +} + +/*********************** Mark Invoice Paid ******************************/ + +//TODO: This function should get worked into separate functions. +function markAsPaid() { + + //Hide Payment button and show Paid stamp + let paidImage = document.getElementById("paid_stamp") + paidImage.style.display = "block" + let paidButton = document.getElementById("payBtn") + paidButton.style.display = "none" + + //Hide Sent Button and Sent Image + let sentImage = document.getElementById("sent_stamp") + sentImage.style.display = "none" + let sentButton = document.getElementById("sentBtn") + sentButton.style.display = "none" + + markInvoicePaid() +} + +/* +TODO: This function need a code reveiew. +It works, but it's not right but I am not sure what right looks like. +*/ +function markInvoicePaid() { + let invoiceId = document.getElementById('invoice_id').value; + putRequest('http://localhost:8080/contractor/mark-invoice-paid/' + invoiceId) +} + +/*********************** Mark Invoice Sent ******************************/ + +//TODO: This functions needs worked into separate functions. +function markAsSent() { + + //Display Sent Stamp and hide sent button + let sentImage = document.getElementById("sent_stamp") + sentImage.style.display = "block" + let sentButton = document.getElementById("sentBtn") + sentButton.style.display = "none" + + markInvoiceSent() +} + +function markInvoiceSent() { + let invoiceId = document.getElementById('invoice_id').value; + putRequest('http://localhost:8080/contractor/mark-invoice-sent/' + invoiceId) +} + + +function putRequest(location, requestBody) { + fetch(location, { + method: "PUT" + }) +} + +/*********************** Printing Invoice ******************************/ + +function printInvoice() { + window.print(); +} diff --git a/src/main/resources/templates/contractor-login.html b/src/main/resources/templates/contractor-login.html new file mode 100644 index 0000000..dfc23d2 --- /dev/null +++ b/src/main/resources/templates/contractor-login.html @@ -0,0 +1,40 @@ + + + + + + ProJo Login, welcome! + + + + + + +
+ +
+ + +
+
+ + + + + + + +
+
+ + +
+ Powered by + +
+ + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/create-invoice.html b/src/main/resources/templates/create-invoice.html new file mode 100644 index 0000000..616b561 --- /dev/null +++ b/src/main/resources/templates/create-invoice.html @@ -0,0 +1,120 @@ + + + + + + + + + ProJo Invoice + + + + + + + + + +
+ + + + ProJo +
+

INVOICE

+ +
+
+
+
+ + + + + + +
+
+
+ + +
+
+ + +
+
+
+
+
+ + + + + + +
+
+
+ + +
+
+
+
+ + + +
+
+ + + + + + +
+ +
+
+ + + + + + + +
+
+ +
+ +

0.0

+
+ + + +
+
+ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/profile.html b/src/main/resources/templates/profile.html new file mode 100644 index 0000000..2406ed0 --- /dev/null +++ b/src/main/resources/templates/profile.html @@ -0,0 +1,129 @@ + + + + + + + Update Profile + + + + + + + + + + + + + +
+

Update Profile

+
+ + +
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+

Notification Preference:

+
+ +
+ +
+ +
+ +
+ +
+
+ +
+
+
+
+ + + + + +
+ Powered by + +
+ + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/raw-login.html b/src/main/resources/templates/raw-login.html new file mode 100644 index 0000000..ae640ec --- /dev/null +++ b/src/main/resources/templates/raw-login.html @@ -0,0 +1,126 @@ + + + + + + Welcome, ProJo! + + + + + + +

Welcome, ProJo!

+ +
+
+ + +
+ +
+ + + + + + + + +
+ +
+ + Forgot password? + Don't have an account? Sign Up! +
+
+ + + + \ No newline at end of file diff --git a/src/main/resources/templates/search-invoice-list.html b/src/main/resources/templates/search-invoice-list.html new file mode 100644 index 0000000..4826bd0 --- /dev/null +++ b/src/main/resources/templates/search-invoice-list.html @@ -0,0 +1,87 @@ + + + + + + + My Invoices + + + + + + + + + + + + + +
+
+

My Invoices

+
+
+ + +
+ + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Invoice #DateService ItemsTotal Amount DueStatus
 
+ +
+
+ + + + + +
+ Powered by + +
+ + + + + \ No newline at end of file diff --git a/src/main/resources/templates/view-invoice.html b/src/main/resources/templates/view-invoice.html new file mode 100644 index 0000000..569328d --- /dev/null +++ b/src/main/resources/templates/view-invoice.html @@ -0,0 +1,128 @@ + + + + + + + ProJo Invoice + + + + + + + + + + +
+ +
+ +
+
+ + + + +
+ +
+

INVOICE

+

+

+

+

+

+

 

+

+

+
+
+ +
+ + + +
+

Invoice Number:

+

+
+
+

Invoice Date:

+

+
+
+

Amount Due (USD):

+

+
+
+ +
+ + +   + + + + + + + + + + + + + +
Products / ServicesDate of ServicePrice
+ +
+

Amount Due (USD):

+

+
+
+ +
+

Notes

+

+
+ + + + + + + +
+ Powered by + +
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/FutureTests/InvoiceControllerTest.java b/src/test/java/FutureTests/InvoiceControllerTest.java new file mode 100644 index 0000000..bf46384 --- /dev/null +++ b/src/test/java/FutureTests/InvoiceControllerTest.java @@ -0,0 +1,59 @@ +package FutureTests; + +import static org.mockito.Mockito.when; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.ui.Model; + +import com.invoicetracker.controllers.InvoiceController; +import com.invoicetracker.models.Invoice; +import com.invoicetracker.repositories.InvoiceRepository; + +public class InvoiceControllerTest { + + @InjectMocks + private InvoiceController underTest; + + @Mock + private Invoice invoice; + + @Mock + private InvoiceRepository invoiceRepo; + + @Mock + private Model model; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + +} + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/FutureTests/JPAMappingsTestAgency.java b/src/test/java/FutureTests/JPAMappingsTestAgency.java new file mode 100644 index 0000000..75e49e3 --- /dev/null +++ b/src/test/java/FutureTests/JPAMappingsTestAgency.java @@ -0,0 +1,121 @@ +package FutureTests; + +import java.time.LocalDate; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.greaterThan; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import java.util.Optional; + +import javax.annotation.Resource; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.invoicetracker.models.Agency; +import com.invoicetracker.models.Contractor; +import com.invoicetracker.models.Customer; +import com.invoicetracker.models.Invoice; +import com.invoicetracker.models.ServiceItem; +import com.invoicetracker.repositories.AgencyRepository; +import com.invoicetracker.repositories.ContractorRepository; +import com.invoicetracker.repositories.CustomerRepository; +import com.invoicetracker.repositories.InvoiceRepository; +import com.invoicetracker.repositories.ServiceItemRepository; + +@RunWith(SpringJUnit4ClassRunner.class) +@DataJpaTest +class JPAMappingsTestAgency { + + @Resource + private TestEntityManager entityManager; + + @Resource + private ContractorRepository contractorRepo; + + @Resource + private AgencyRepository agencyRepo; + + @Test + public void shouldSaveAndLoadAgencyBusinessName() { + + // Arrange + Agency agency = agencyRepo.save(new Agency("businessName")); + Long agencyId = agency.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + + Optional result = agencyRepo.findById(agencyId); + agency = result.get(); + + // Assert + assertEquals(agency.getBusinessName(), "businessName"); + } + + @Test + public void shouldGenerateAgencyId() { + + // Arrange + Agency agency = agencyRepo.save(new Agency("businessName")); + Long agencyId = agency.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + + Optional result = agencyRepo.findById(agencyId); + agency = result.get(); + + // Assert + assertThat(agency.getId(), is(greaterThan(0L))); + } + + @Test + public void shouldEstablishContractorToAgencyRelationship() { + // Arrange + Agency agency = agencyRepo.save(new Agency("test")); + Contractor contractor = contractorRepo.save(new Contractor("testContractor", agency)); + + Long contractorId = contractor.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + + Optional result = contractorRepo.findById(contractorId); + contractor = result.get(); + + // Assert + assertEquals(contractor.getAgency(), agency); + + } + + @Test + public void shouldEstablishAgencyToContractorRelationship() { + // Arrange + Contractor contractorOne = contractorRepo.save(new Contractor("testContractorOne")); + Contractor contractorTwo = contractorRepo.save(new Contractor("testContractorTwo")); + Agency agency = agencyRepo.save(new Agency("test", contractorOne, contractorTwo)); + Long agencyId = agency.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + + Optional result = agencyRepo.findById(agencyId); + agency = result.get(); + + // Assert + assertThat(agency.getContractors(), containsInAnyOrder(contractorOne, contractorTwo)); + + } + + +} diff --git a/src/test/java/FutureTests/JPAMappingsTestCustomer.java b/src/test/java/FutureTests/JPAMappingsTestCustomer.java new file mode 100644 index 0000000..01dbe1d --- /dev/null +++ b/src/test/java/FutureTests/JPAMappingsTestCustomer.java @@ -0,0 +1,110 @@ +package FutureTests; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.greaterThan; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import java.time.LocalDate; +import java.util.Optional; + +import javax.annotation.Resource; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; + +import com.invoicetracker.models.Customer; +import com.invoicetracker.models.ServiceItem; +import com.invoicetracker.repositories.ContractorRepository; +import com.invoicetracker.repositories.InvoiceRepository; +import com.invoicetracker.repositories.ServiceItemRepository; + +class JPAMappingsTestCustomer { + + @Resource + private TestEntityManager entityManager; + + @Resource + private ContractorRepository contractorRepo; + + @Resource + private InvoiceRepository invoiceRepo; + + @Resource + private ServiceItemRepository serviceItemRepo; + + @Test + public void shouldGenerateCustomerId() { + // Arrange + Customer customer = customerRepo.save(new Customer("testName")); + long customerId = customer.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + + Optional result = customerRepo.findById(customerId); + customer = result.get(); + + assertThat(customer.getId(), is(greaterThan(0L))); + } + + @Test + public void shouldSaveAndLoadCustomerName() { + // Arrange + Customer customer = customerRepo.save(new Customer("testName")); + long customerId = customer.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + + Optional result = customerRepo.findById(customerId); + customer = result.get(); + + assertEquals(customer.getCustomerName(), "testName"); + } + + @Test + public void shouldEstablishServiceItemToCustomerRelationship() { + // Arrange + Customer customerOne = customerRepo.save(new Customer("testNameOne")); + LocalDate dateOfService = LocalDate.of(2020, 03, 29); + ServiceItem serviceItem = serviceItemRepo.save(new ServiceItem(dateOfService, customerOne)); + long serviceId = serviceItem.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + + Optional result = serviceItemRepo.findById(serviceId); + serviceItem = result.get(); + String customerName = serviceItem.getCustomer().getCustomerName(); + + assertEquals(serviceItem.getCustomer(), customerOne); + } + + @Test + public void shouldEstablishCustomerToServiceItemRelationship() { + // Arrange + LocalDate dateOfServiceOne = LocalDate.of(2020, 03, 29); + LocalDate dateOfServiceTwo = LocalDate.of(2020, 03, 30); + ServiceItem serviceItemOne = serviceItemRepo.save(new ServiceItem(dateOfServiceOne)); + ServiceItem serviceItemTwo = serviceItemRepo.save(new ServiceItem(dateOfServiceTwo)); + Customer customer = customerRepo.save(new Customer("testNameTwo", serviceItemOne, serviceItemTwo)); + long customerId = customer.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + + Optional result = customerRepo.findById(customerId); + customer = result.get(); + + assertThat(customer.getServiceItems(), containsInAnyOrder(serviceItemOne, serviceItemTwo)); + } + + +} diff --git a/src/test/java/com/invoicetracker/ContractorControllerMockMvcTest.java b/src/test/java/com/invoicetracker/ContractorControllerMockMvcTest.java new file mode 100644 index 0000000..47daf6a --- /dev/null +++ b/src/test/java/com/invoicetracker/ContractorControllerMockMvcTest.java @@ -0,0 +1,100 @@ +package com.invoicetracker; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; +import java.util.Optional; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import com.invoicetracker.controllers.ContractorController; +import com.invoicetracker.models.Contractor; +import com.invoicetracker.models.Invoice; +import com.invoicetracker.repositories.ContractorRepository; +import com.invoicetracker.repositories.InvoiceRepository; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(SpringRunner.class) +@WebMvcTest(ContractorController.class) +public class ContractorControllerMockMvcTest { + + @Autowired + private MockMvc mockMvc; + + @InjectMocks + private ContractorController underTest; + + @MockBean + private ContractorRepository contractorRepo; + + @MockBean + private InvoiceRepository invoiceRepo; + + @Mock + private Contractor contractorOne; + + @Mock + private Invoice invoiceOne; + + @Mock + private Invoice invoiceTwo; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mockMvc = MockMvcBuilders.standaloneSetup(underTest).build(); + } + + @Test + public void shouldGetStatusOfOkWhenNavigatingToViewInvoice() throws Exception { + long contractorId = 1; + long invoiceId = 2; + when(contractorRepo.findById(contractorId)).thenReturn(Optional.of(contractorOne)); + when(invoiceRepo.findById(invoiceId)).thenReturn(Optional.of(invoiceOne)); + this.mockMvc.perform(get("/contractor/view-existing-invoice/" + contractorId + "/" + invoiceId)) + .andExpect(status().isOk()).andExpect(view().name("view-invoice")); + } + + @Test + public void shouldGetStatusOfOkWhenNavigatingToSearchInvoiceList() throws Exception { + long contractorId = 1; + when(contractorRepo.findById(contractorId)).thenReturn(Optional.of(contractorOne)); + this.mockMvc.perform(get("/contractor/search-invoice-list/" + contractorId)).andExpect(status().isOk()) + .andExpect(view().name("search-invoice-list")); + } + + @Test + public void shouldGetStatusOfOkWhenNavigatingToUpdateProfile() throws Exception { + long contractorId = 1; + when(contractorRepo.findById(contractorId)).thenReturn(Optional.of(contractorOne)); + this.mockMvc.perform(get("/contractor/update-profile/" + contractorId)).andExpect(status().isOk()) + .andExpect(view().name("profile")); + } + + + /* + * TODO: These tests need fixed. + */ + @Test + public void MarkPaidEndPointWillMarkAnInvoicePaid() throws Exception { + long invoiceId = 1; + this.mockMvc.perform(put("/contractor/mark-invoice-paid/" + invoiceId)).andExpect(status().isOk()); + } + + @Test + public void MarkSentEndPointWillMarkAnInvoiceSent() throws Exception { + long invoiceId = 1; + this.mockMvc.perform(put("/contractor/mark-invoice-sent/" + invoiceId)).andExpect(status().isOk()); + } + +} diff --git a/src/test/java/com/invoicetracker/ContractorTest.java b/src/test/java/com/invoicetracker/ContractorTest.java new file mode 100644 index 0000000..b515a0e --- /dev/null +++ b/src/test/java/com/invoicetracker/ContractorTest.java @@ -0,0 +1,47 @@ +package com.invoicetracker; + +import static org.junit.Assert.assertEquals; +import javax.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import com.invoicetracker.models.Contractor; +import com.invoicetracker.models.Invoice; +import com.invoicetracker.repositories.ContractorRepository; +import com.invoicetracker.repositories.InvoiceRepository; + +@RunWith(SpringJUnit4ClassRunner.class) +@DataJpaTest +class ContractorTest { + + @Resource + private TestEntityManager entityManager; + + @Resource + ContractorRepository contractorRepo; + + @Resource + InvoiceRepository invoiceRepo; + + @Test + void shouldAdd1TocurrentInvoiceNumber() { + //Arrange + Contractor contractor = contractorRepo.save(new Contractor()); + @SuppressWarnings("unused") + Invoice invoiceOne = invoiceRepo.save(new Invoice(contractor)); + @SuppressWarnings("unused") + Invoice invoiceTwo = invoiceRepo.save(new Invoice(contractor)); + contractorRepo.save(contractor); + long contractorId = contractor.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + int result = contractorRepo.findById(contractorId).get().getCurrentInvoiceNumber(); + + assertEquals(1002, result); + } + +} diff --git a/src/test/java/com/invoicetracker/InvoiceTest.java b/src/test/java/com/invoicetracker/InvoiceTest.java new file mode 100644 index 0000000..d65bb97 --- /dev/null +++ b/src/test/java/com/invoicetracker/InvoiceTest.java @@ -0,0 +1,175 @@ +package com.invoicetracker; + +import static org.junit.Assert.assertEquals; +import javax.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import com.invoicetracker.models.Contractor; +import com.invoicetracker.models.Invoice; +import com.invoicetracker.models.ServiceItem; +import com.invoicetracker.repositories.ContractorRepository; +import com.invoicetracker.repositories.InvoiceRepository; +import com.invoicetracker.repositories.ServiceItemRepository; + +@RunWith(SpringJUnit4ClassRunner.class) +@DataJpaTest +class InvoiceTest { + + @Resource + private TestEntityManager entityManager; + + @Resource + ContractorRepository contractorRepo; + + @Resource + ServiceItemRepository serviceItemRepo; + + @Resource + InvoiceRepository invoiceRepo; + + @Test + void shouldSumServiceItemsAmountDue() { + // Arrange + Contractor contractor = contractorRepo.save(new Contractor()); + Invoice invoice = invoiceRepo.save(new Invoice(contractor)); + ServiceItem serviceItemOne = serviceItemRepo.save(new ServiceItem(invoice)); + ServiceItem serviceItemTwo = serviceItemRepo.save(new ServiceItem(invoice)); + serviceItemOne.setAmountDue(300); + serviceItemTwo.setAmountDue(400); + long invoiceId = invoice.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + float result = invoiceRepo.findById(invoiceId).get().calculateTotalAmountDueFromAllServiceItemsOnInvoice(); + + assertEquals(700, result, 0.01); + } + + @Test + void shouldRemoveServiceItem() { + // Arrange + Contractor contractor = contractorRepo.save(new Contractor()); + Invoice invoice = invoiceRepo.save(new Invoice(contractor)); + @SuppressWarnings("unused") + ServiceItem serviceItemOne = serviceItemRepo.save(new ServiceItem(invoice)); + ServiceItem serviceItemTwo = serviceItemRepo.save(new ServiceItem(invoice)); + long invoiceId = invoice.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + Invoice result = invoiceRepo.findById(invoiceId).get(); + result.removeServiceItem(serviceItemTwo); + + assertEquals(1, result.getServiceItems().size()); + } + + @Test + void shouldReturnThreeServiceItemCustomerNamesAsACommaSeparatedString() { + // Arrange + Contractor contractor = contractorRepo.save(new Contractor()); + Invoice invoice = invoiceRepo.save(new Invoice(contractor)); + ServiceItem serviceItemOne = serviceItemRepo.save(new ServiceItem(invoice)); + ServiceItem serviceItemTwo = serviceItemRepo.save(new ServiceItem(invoice)); + serviceItemOne.setServiceDescription("bill"); + serviceItemTwo.setServiceDescription("ted"); + long invoiceId = invoice.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + Invoice result = invoiceRepo.findById(invoiceId).get(); + + assertEquals("bill, ted", result.getCustomerNamePreviewAsString()); + } + + @Test + void shouldClipNumberOfServiceItemCustomerNamesOver3() { + // Arrange + Contractor contractor = contractorRepo.save(new Contractor()); + Invoice invoice = invoiceRepo.save(new Invoice(contractor)); + ServiceItem serviceItemOne = serviceItemRepo.save(new ServiceItem(invoice)); + ServiceItem serviceItemTwo = serviceItemRepo.save(new ServiceItem(invoice)); + ServiceItem serviceItemThree = serviceItemRepo.save(new ServiceItem(invoice)); + ServiceItem serviceItemFour = serviceItemRepo.save(new ServiceItem(invoice)); + serviceItemOne.setServiceDescription("bill"); + serviceItemTwo.setServiceDescription("ted"); + serviceItemThree.setServiceDescription("excellent"); + serviceItemFour.setServiceDescription("Yeah"); + long invoiceId = invoice.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + Invoice result = invoiceRepo.findById(invoiceId).get(); + + assertEquals("bill, ted ... plus 2 more", result.getCustomerNamePreviewAsString()); + } + + @Test + void shouldFormatTotalAmountDueAsCurrency() { + // Arrange + Contractor contractor = contractorRepo.save(new Contractor()); + Invoice invoice = invoiceRepo.save(new Invoice(contractor)); + ServiceItem serviceItemOne = serviceItemRepo.save(new ServiceItem(invoice)); + ServiceItem serviceItemTwo = serviceItemRepo.save(new ServiceItem(invoice)); + serviceItemOne.setAmountDue(300); + serviceItemTwo.setAmountDue(400); + long invoiceId = invoice.getId(); + // Act + entityManager.flush(); + entityManager.clear(); + Invoice result = invoiceRepo.findById(invoiceId).get(); + + assertEquals("$700.00", result.getTotalAmountDueAsCurrencyString()); + } + + @Test + void shouldReturnStringPaidIfInvoiceIsPaid() { + // Arrange + Contractor contractor = contractorRepo.save(new Contractor()); + Invoice invoice = invoiceRepo.save(new Invoice(contractor)); + long invoiceId = invoice.getId(); + // Act + entityManager.flush(); + entityManager.clear(); + Invoice result = invoiceRepo.findById(invoiceId).get(); + result.setIsPaid(true); + + assertEquals("Paid", result.getPaymentStatus()); + } + + @Test + void shouldReturnStringSentIfInvoiceIsSent() { + // Arrange + Contractor contractor = contractorRepo.save(new Contractor()); + Invoice invoice = invoiceRepo.save(new Invoice(contractor)); + long invoiceId = invoice.getId(); + // Act + entityManager.flush(); + entityManager.clear(); + Invoice result = invoiceRepo.findById(invoiceId).get(); + result.setIsSent(true); + + assertEquals("Sent", result.getPaymentStatus()); + } + + @Test + void shouldReturnStringNotSentIfInvoiceIsSent() { + // Arrange + Contractor contractor = contractorRepo.save(new Contractor()); + Invoice invoice = invoiceRepo.save(new Invoice(contractor)); + long invoiceId = invoice.getId(); + // Act + entityManager.flush(); + entityManager.clear(); + Invoice result = invoiceRepo.findById(invoiceId).get(); + + assertEquals("Not sent", result.getPaymentStatus()); + } + +} diff --git a/src/test/java/com/invoicetracker/JPAMappingsTest.java b/src/test/java/com/invoicetracker/JPAMappingsTest.java new file mode 100644 index 0000000..9715c74 --- /dev/null +++ b/src/test/java/com/invoicetracker/JPAMappingsTest.java @@ -0,0 +1,192 @@ +package com.invoicetracker; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.greaterThan; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import java.time.LocalDate; +import java.util.Optional; +import javax.annotation.Resource; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import com.invoicetracker.models.Contractor; +import com.invoicetracker.models.Invoice; +import com.invoicetracker.models.ServiceItem; +import com.invoicetracker.repositories.ContractorRepository; +import com.invoicetracker.repositories.InvoiceRepository; +import com.invoicetracker.repositories.ServiceItemRepository; + +@RunWith(SpringJUnit4ClassRunner.class) +@DataJpaTest +public class JPAMappingsTest { + + @Resource + private TestEntityManager entityManager; + + @Resource + private ContractorRepository contractorRepo; + + @Resource + private InvoiceRepository invoiceRepo; + + @Resource + private ServiceItemRepository serviceItemRepo; + + @Test + public void shouldSaveAndLoadContractorPayPalId() { + // Arrange + Contractor contractor = contractorRepo.save(new Contractor("firstName")); + contractor.setPayPalId("payPalId"); + long contractorId = contractor.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + Optional result = contractorRepo.findById(contractorId); + contractor = result.get(); + + assertEquals(contractor.getPayPalId(), "payPalId"); + + } + + @Test + public void shouldSaveAndLoadContractorFirstName() { + // Arrange + Contractor contractor = contractorRepo.save(new Contractor("firstName")); + long contractorId = contractor.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + Optional result = contractorRepo.findById(contractorId); + contractor = result.get(); + + assertEquals(contractor.getFirstName(), "firstName"); + } + + @Test + public void shouldGenrateContractorId() { + // Arrange + Contractor contractor = contractorRepo.save(new Contractor("firstName")); + long contractorId = contractor.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + Optional result = contractorRepo.findById(contractorId); + contractor = result.get(); + + assertThat(contractor.getId(), is(greaterThan(0L))); + } + + + @Test + public void shouldSaveAndLoadInvoiceDateOfService() { + // Arrange + Contractor contractor = contractorRepo.save(new Contractor()); + Invoice invoice = invoiceRepo.save(new Invoice(contractor)); + LocalDate date = LocalDate.of(2020, 03, 28); + invoice.setDateOfInvoice(date); + Long invoiceId = invoice.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + Optional result = invoiceRepo.findById(invoiceId); + invoice = result.get(); + + assertEquals(invoice.getDateOfInvoice(), date); + } + + @Test + public void shouldGenerateInvoiceId() { + // Arrange + Contractor contractor = contractorRepo.save(new Contractor()); + Invoice invoice = invoiceRepo.save(new Invoice(contractor)); + Long invoiceId = invoice.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + Optional result = invoiceRepo.findById(invoiceId); + invoice = result.get(); + + assertThat(invoice.getId(), is(greaterThan(0L))); + } + + @Test + public void shouldGenerateServiceItemId() { + // Arrange + Contractor contractor = contractorRepo.save(new Contractor()); + Invoice invoice = invoiceRepo.save(new Invoice(contractor)); + ServiceItem serviceItem = serviceItemRepo.save(new ServiceItem(invoice)); + long serviceItemId = serviceItem.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + Optional result = serviceItemRepo.findById(serviceItemId); + serviceItem = result.get(); + + assertThat(serviceItem.getId(), is(greaterThan(0L))); + } + + @Test + public void shouldSaveAndLoadServiceItemDateOfService() { + // Arrange + Contractor contractor = contractorRepo.save(new Contractor()); + Invoice invoice = invoiceRepo.save(new Invoice(contractor)); + ServiceItem serviceItem = serviceItemRepo.save(new ServiceItem(invoice)); + LocalDate date = LocalDate.of(2020, 03, 28); + serviceItem.setDateOfService(date); + long serviceItemId = serviceItem.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + Optional result = serviceItemRepo.findById(serviceItemId); + serviceItem = result.get(); + + assertEquals(serviceItem.getDateOfService(), date); + } + + @Test + public void shouldEstablishInvoiceToServiceItemRelationship() { + // Arrange + Contractor contractor = contractorRepo.save(new Contractor()); + Invoice invoice = invoiceRepo.save(new Invoice(contractor)); + ServiceItem serviceItemOne = serviceItemRepo.save(new ServiceItem(invoice)); + ServiceItem serviceItemTwo = serviceItemRepo.save(new ServiceItem(invoice)); + long invoiceId = invoice.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + Optional result = invoiceRepo.findById(invoiceId); + invoice = result.get(); + + assertThat(invoice.getServiceItems(), containsInAnyOrder(serviceItemOne, serviceItemTwo)); + } + + @Test + public void shouldEstablishContractorToInvoiceRelationship() { + // Arrange + Contractor contractor = contractorRepo.save(new Contractor()); + Invoice invoiceOne = invoiceRepo.save(new Invoice(contractor)); + Invoice invoiceTwo = invoiceRepo.save(new Invoice(contractor)); + long contractorId = contractor.getId(); + + // Act + entityManager.flush(); + entityManager.clear(); + Optional result = contractorRepo.findById(contractorId); + contractor = result.get(); + + assertThat(contractor.getInvoices(), containsInAnyOrder(invoiceOne, invoiceTwo)); + } + +} diff --git a/src/test/java/com/invoicetracker/ServiceItemTest.java b/src/test/java/com/invoicetracker/ServiceItemTest.java new file mode 100644 index 0000000..1f04377 --- /dev/null +++ b/src/test/java/com/invoicetracker/ServiceItemTest.java @@ -0,0 +1,50 @@ +package com.invoicetracker; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.Assert.assertEquals; +import javax.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import com.invoicetracker.models.Contractor; +import com.invoicetracker.models.Invoice; +import com.invoicetracker.models.ServiceItem; +import com.invoicetracker.repositories.ContractorRepository; +import com.invoicetracker.repositories.InvoiceRepository; +import com.invoicetracker.repositories.ServiceItemRepository; + +@RunWith(SpringJUnit4ClassRunner.class) +@DataJpaTest +class ServiceItemTest { + + @Resource + private TestEntityManager entityManager; + + @Resource + ContractorRepository contractorRepo; + + @Resource + ServiceItemRepository serviceItemRepo; + + @Resource + InvoiceRepository invoiceRepo; + + @Test + void shouldFormatAmountDueAsCurrency() { + //Arrange + Contractor contractor = contractorRepo.save(new Contractor()); + Invoice invoice = invoiceRepo.save(new Invoice(contractor)); + ServiceItem serviceItem = serviceItemRepo.save(new ServiceItem(invoice)); + serviceItem.setAmountDue(300); + long serviceItemId = serviceItem.getId(); + // Act + entityManager.flush(); + entityManager.clear(); + ServiceItem result = serviceItemRepo.findById(serviceItemId).get(); + + assertEquals("$300.00", result.getAmountDueAsCurrencyString()); + } + +} diff --git a/src/test/java/com/invoicetracker/loginControllerMockMvcTest.java b/src/test/java/com/invoicetracker/loginControllerMockMvcTest.java new file mode 100644 index 0000000..664e078 --- /dev/null +++ b/src/test/java/com/invoicetracker/loginControllerMockMvcTest.java @@ -0,0 +1,47 @@ +package com.invoicetracker; + +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; + +import org.junit.Before; +import org.junit.jupiter.api.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import com.invoicetracker.controllers.LoginController; + +@RunWith(SpringRunner.class) +@WebMvcTest(LoginController.class) +class loginControllerMockMvcTest { + + @Autowired + private MockMvc mockMvc; + + @InjectMocks + private LoginController underTest; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mockMvc = MockMvcBuilders.standaloneSetup(underTest).build(); + } + + @Test + void shouldGetStatusOfOkWhenNavigatingToLoginScreen() throws Exception { + this.mockMvc.perform(get("/login")).andExpect(status().isOk()); + } + + @Test + void shouldGetViewContratorLoginWhenNavigatingToLoginScreen() throws Exception { + this.mockMvc.perform(get("/login")).andExpect(view().name("contractor-login")); + } + +}