diff --git a/src/main/java/de/deadlocker8/budgetmaster/controller/PaymentController.java b/src/main/java/de/deadlocker8/budgetmaster/controller/PaymentController.java index 35e2fab30e5063a10935d66e60dabe75294c4c50..7fbacc6987e82355653c3f9a49c0c4337e55a30e 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/controller/PaymentController.java +++ b/src/main/java/de/deadlocker8/budgetmaster/controller/PaymentController.java @@ -4,6 +4,10 @@ import de.deadlocker8.budgetmaster.entities.CategoryType; import de.deadlocker8.budgetmaster.entities.Payment; import de.deadlocker8.budgetmaster.entities.Settings; import de.deadlocker8.budgetmaster.entities.Tag; +import de.deadlocker8.budgetmaster.repeating.RepeatingOption; +import de.deadlocker8.budgetmaster.repeating.endoption.RepeatingEnd; +import de.deadlocker8.budgetmaster.repeating.endoption.RepeatingEndAfterXTimes; +import de.deadlocker8.budgetmaster.repeating.modifier.*; import de.deadlocker8.budgetmaster.repositories.*; import de.deadlocker8.budgetmaster.services.HelpersService; import de.deadlocker8.budgetmaster.services.PaymentService; @@ -15,6 +19,7 @@ import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; +import tools.Localization; import java.util.ArrayList; import java.util.List; @@ -99,10 +104,14 @@ public class PaymentController extends BaseController } @RequestMapping(value = "/payments/newPayment", method = RequestMethod.POST) - public String post(Model model, @ModelAttribute("NewPayment") Payment payment, BindingResult bindingResult, @RequestParam(value = "isPayment", required = false) boolean isPayment) + public String post(Model model, @ModelAttribute("NewPayment") Payment payment, BindingResult bindingResult, + @RequestParam(value = "isPayment", required = false) boolean isPayment, + @RequestParam(value = "enableRepeating", required = false) boolean enableRepeating, + @RequestParam(value = "repeatingModifierNumber", required = false) int repeatingModifierNumber, + @RequestParam(value = "repeatingModifierType", required = false) String repeatingModifierType) { - PaymentValidator userValidator = new PaymentValidator(); - userValidator.validate(payment, bindingResult); + PaymentValidator paymentValidator = new PaymentValidator(); + paymentValidator.validate(payment, bindingResult); if(bindingResult.hasErrors()) { @@ -133,6 +142,31 @@ public class PaymentController extends BaseController } } + RepeatingOption repeatingOption = null; + if(enableRepeating) + { + RepeatingModifier repeatingModifier = null; + RepeatingModifierType type = RepeatingModifierType.getByLocalization(repeatingModifierType); + switch(type) + { + case DAYS: + repeatingModifier = new RepeatingModifierDays(repeatingModifierNumber); + break; + case MONTHS: + repeatingModifier = new RepeatingModifierMonths(repeatingModifierNumber); + break; + case YEARS: + repeatingModifier = new RepeatingModifierYears(repeatingModifierNumber); + break; + } + + //TODO + RepeatingEnd repeatingEnd = new RepeatingEndAfterXTimes(3); + + repeatingOption = new RepeatingOption(payment.getDate(), repeatingModifier, repeatingEnd); + } + payment.setRepeatingOption(repeatingOption); + paymentRepository.save(payment); } diff --git a/src/main/java/de/deadlocker8/budgetmaster/entities/Payment.java b/src/main/java/de/deadlocker8/budgetmaster/entities/Payment.java index fe2b64e451a71e42b34aab4b775d1bbfe3de7580..e23962908843e98a7963f7696b420bd08a307b45 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/entities/Payment.java +++ b/src/main/java/de/deadlocker8/budgetmaster/entities/Payment.java @@ -31,7 +31,7 @@ public class Payment @ManyToMany(cascade = CascadeType.ALL) private List<Tag> tags; - @OneToOne(optional = true, cascade = CascadeType.ALL) + @ManyToOne(optional = true, cascade = CascadeType.ALL) private RepeatingOption repeatingOption; public Payment() diff --git a/src/main/java/de/deadlocker8/budgetmaster/repeating/RepeatingOption.java b/src/main/java/de/deadlocker8/budgetmaster/repeating/RepeatingOption.java index 6103032c0cdd75c310cb359dcf9f9236dd08df90..61a387432c09895bb907219005053fd9de36d830 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/repeating/RepeatingOption.java +++ b/src/main/java/de/deadlocker8/budgetmaster/repeating/RepeatingOption.java @@ -1,5 +1,6 @@ package de.deadlocker8.budgetmaster.repeating; +import de.deadlocker8.budgetmaster.entities.Payment; import de.deadlocker8.budgetmaster.repeating.endoption.RepeatingEnd; import de.deadlocker8.budgetmaster.repeating.modifier.RepeatingModifier; import org.joda.time.DateTime; @@ -25,6 +26,9 @@ public class RepeatingOption @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) private RepeatingEnd endOption; + @OneToMany(mappedBy = "repeatingOption", fetch = FetchType.LAZY) + private List<Payment> referringPayments; + public RepeatingOption(DateTime startDate, RepeatingModifier modifier, RepeatingEnd endOption) { this.startDate = startDate; @@ -32,6 +36,8 @@ public class RepeatingOption this.endOption = endOption; } + public RepeatingOption() {} + public Integer getID() { return ID; @@ -72,6 +78,16 @@ public class RepeatingOption this.endOption = endOption; } + public List<Payment> getReferringPayments() + { + return referringPayments; + } + + public void setReferringPayments(List<Payment> referringPayments) + { + this.referringPayments = referringPayments; + } + public List<DateTime> getRepeatingDates(DateTime dateFetchLimit) { List<DateTime> dates = new ArrayList<>(); diff --git a/src/main/java/de/deadlocker8/budgetmaster/repeating/endoption/RepeatingEndAfterXTimes.java b/src/main/java/de/deadlocker8/budgetmaster/repeating/endoption/RepeatingEndAfterXTimes.java index 574ae9b369512410e14c082a86cb5287220ca7dc..9f5b100871bfee979bd9c2de43a8329dfc7d1406 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/repeating/endoption/RepeatingEndAfterXTimes.java +++ b/src/main/java/de/deadlocker8/budgetmaster/repeating/endoption/RepeatingEndAfterXTimes.java @@ -15,6 +15,8 @@ public class RepeatingEndAfterXTimes extends RepeatingEnd this.times = times; } + public RepeatingEndAfterXTimes() {} + @Override @Transient public boolean isEndReached(List<DateTime> dates) diff --git a/src/main/java/de/deadlocker8/budgetmaster/repeating/endoption/RepeatingEndDate.java b/src/main/java/de/deadlocker8/budgetmaster/repeating/endoption/RepeatingEndDate.java index 50fde395c2c3dd3af9130ff93b011f1b5923aa58..131b9f9e5fd52b3337182f9203a2e2b92100da31 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/repeating/endoption/RepeatingEndDate.java +++ b/src/main/java/de/deadlocker8/budgetmaster/repeating/endoption/RepeatingEndDate.java @@ -17,6 +17,8 @@ public class RepeatingEndDate extends RepeatingEnd this.endDate = endDate; } + public RepeatingEndDate() {} + @Override @Transient public boolean isEndReached(List<DateTime> dates) diff --git a/src/main/java/de/deadlocker8/budgetmaster/repeating/modifier/RepeatingModifier.java b/src/main/java/de/deadlocker8/budgetmaster/repeating/modifier/RepeatingModifier.java index 830b590534d57985e57e928447cffc1b59c4a6fc..2eb7d633241b2af5c004be4e12781405052a89f3 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/repeating/modifier/RepeatingModifier.java +++ b/src/main/java/de/deadlocker8/budgetmaster/repeating/modifier/RepeatingModifier.java @@ -15,11 +15,17 @@ public abstract class RepeatingModifier Integer quantity; - RepeatingModifier(int quantity) + @Transient + private String localizationKey; + + RepeatingModifier(int quantity, String localizationKey) { this.quantity = quantity; + this.localizationKey = localizationKey; } + RepeatingModifier() {} + public Integer getID() { return ID; @@ -30,6 +36,26 @@ public abstract class RepeatingModifier this.ID = ID; } + public Integer getQuantity() + { + return quantity; + } + + public void setQuantity(Integer quantity) + { + this.quantity = quantity; + } + + public String getLocalizationKey() + { + return localizationKey; + } + + public void setLocalizationKey(String localizationKey) + { + this.localizationKey = localizationKey; + } + @Transient public abstract DateTime getNextDate(DateTime lastDate); } \ No newline at end of file diff --git a/src/main/java/de/deadlocker8/budgetmaster/repeating/modifier/RepeatingModifierDays.java b/src/main/java/de/deadlocker8/budgetmaster/repeating/modifier/RepeatingModifierDays.java index dd62a5280116522e7fafd1ab8d7792fd83a22952..df1fb82b67360b92bfd99a5c879f864aef436090 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/repeating/modifier/RepeatingModifierDays.java +++ b/src/main/java/de/deadlocker8/budgetmaster/repeating/modifier/RepeatingModifierDays.java @@ -9,9 +9,11 @@ public class RepeatingModifierDays extends RepeatingModifier { public RepeatingModifierDays(int numberOfDays) { - super(numberOfDays); + super(numberOfDays, "repeating.modifier.days"); } + public RepeatingModifierDays() {} + @Override public DateTime getNextDate(DateTime lastDate) { diff --git a/src/main/java/de/deadlocker8/budgetmaster/repeating/modifier/RepeatingModifierMonths.java b/src/main/java/de/deadlocker8/budgetmaster/repeating/modifier/RepeatingModifierMonths.java index f58acb0446c45bebafb664daa0613c36b1d0da25..b9869f4d13f1f0275b137db31ce853c203a9859c 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/repeating/modifier/RepeatingModifierMonths.java +++ b/src/main/java/de/deadlocker8/budgetmaster/repeating/modifier/RepeatingModifierMonths.java @@ -9,9 +9,11 @@ public class RepeatingModifierMonths extends RepeatingModifier { public RepeatingModifierMonths(int numberOfmonths) { - super(numberOfmonths); + super(numberOfmonths, "repeating.modifier.months"); } + public RepeatingModifierMonths() {} + @Override public DateTime getNextDate(DateTime lastDate) { diff --git a/src/main/java/de/deadlocker8/budgetmaster/repeating/modifier/RepeatingModifierType.java b/src/main/java/de/deadlocker8/budgetmaster/repeating/modifier/RepeatingModifierType.java new file mode 100644 index 0000000000000000000000000000000000000000..a72b9c2c5ab46cc669d0e63ddbafac8588c0c736 --- /dev/null +++ b/src/main/java/de/deadlocker8/budgetmaster/repeating/modifier/RepeatingModifierType.java @@ -0,0 +1,43 @@ +package de.deadlocker8.budgetmaster.repeating.modifier; + +import tools.Localization; + +public enum RepeatingModifierType +{ + DAYS("repeating.modifier.days"), + MONTHS("repeating.modifier.months"), + YEARS("repeating.modifier.years"); + + private String localizationKey; + + RepeatingModifierType(String localizationKey) + { + this.localizationKey = localizationKey; + } + + public String getLocalizationKey() + { + return localizationKey; + } + + public static RepeatingModifierType getByLocalization(String localizedName) + { + for(RepeatingModifierType type : values()) + { + if(Localization.getString(type.getLocalizationKey()).equals(localizedName)) + { + return type; + } + } + + return null; + } + + @Override + public String toString() + { + return "RepeatingModifierType{" + + "localizationKey='" + localizationKey + '\'' + + '}'; + } +} diff --git a/src/main/java/de/deadlocker8/budgetmaster/repeating/modifier/RepeatingModifierYears.java b/src/main/java/de/deadlocker8/budgetmaster/repeating/modifier/RepeatingModifierYears.java index 3e268acf9c808677c0295cbadc428847545d6ff6..f5140fdc8ad53f2ca60f003d3c15d99329bfc90a 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/repeating/modifier/RepeatingModifierYears.java +++ b/src/main/java/de/deadlocker8/budgetmaster/repeating/modifier/RepeatingModifierYears.java @@ -9,9 +9,11 @@ public class RepeatingModifierYears extends RepeatingModifier { public RepeatingModifierYears(int numberOfYears) { - super(numberOfYears); + super(numberOfYears, "repeating.modifier.years"); } + public RepeatingModifierYears() {} + @Override public DateTime getNextDate(DateTime lastDate) { diff --git a/src/main/java/de/deadlocker8/budgetmaster/services/HelpersService.java b/src/main/java/de/deadlocker8/budgetmaster/services/HelpersService.java index a478c36e11e2e6163f9920a68127ecbc9905b5a7..df94a8ec23cb3c7de2335631d87fb2b923167d2f 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/services/HelpersService.java +++ b/src/main/java/de/deadlocker8/budgetmaster/services/HelpersService.java @@ -4,6 +4,7 @@ import de.deadlocker8.budgetmaster.entities.Account; import de.deadlocker8.budgetmaster.entities.Payment; import de.deadlocker8.budgetmaster.entities.Settings; import de.deadlocker8.budgetmaster.entities.Tag; +import de.deadlocker8.budgetmaster.repeating.modifier.RepeatingModifierType; import de.deadlocker8.budgetmaster.repositories.AccountRepository; import de.deadlocker8.budgetmaster.repositories.PaymentRepository; import de.deadlocker8.budgetmaster.repositories.SettingsRepository; @@ -22,6 +23,7 @@ import java.net.URLEncoder; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -212,4 +214,9 @@ public class HelpersService return sum; } + + public List<RepeatingModifierType> getRepeatingModifierTypes() + { + return Arrays.asList(RepeatingModifierType.values()); + } } \ No newline at end of file diff --git a/src/main/resources/languages/_de.properties b/src/main/resources/languages/_de.properties index 03be375e823ecdc17668d4451bd94dfdf663f78f..b82b391c08c56e972b6a628792ed5e7ecc4ca4b7 100644 --- a/src/main/resources/languages/_de.properties +++ b/src/main/resources/languages/_de.properties @@ -213,6 +213,7 @@ warning.name.character.limit.reached.150=Der Name darf maximal 150 Zeichen lang warning.description.character.limit.reached.200=Die Notiz darf maximal 200 Zeichen lang sein. warning.tag.character.limit.reached.45=Der Name eines Tags darf maximal 45 Zeichen lang sein. warning.payment.amount=Gib eine g�ltige Zahl f�r den Betrag ein. +warning.payment.number=Gib eine g�ltige Zahl gr��er 0 ein. warning.empty.payment.date=Bitte w�hle ein Datum aus. warning.payment.repeating=Wenn Wiederholung aktiviert ist d�rfen nicht beide Eingabefelder 0 sein.\n(Zur Deaktivierung der Wiederholung einfach die Checkbox abw�hlen) warning.empty.secret.client=Das Feld f�r das Client Passwort darf nicht leer sein. @@ -323,7 +324,11 @@ payment.new.label.description=Notiz payment.new.label.tags=Tags payment.new.label.account=Konto payment.new.label.repeating=Wiederholung +payment.new.label.repeating.all=Alle +repeating.modifier.days=Tage +repeating.modifier.months=Monate +repeating.modifier.years=Jahre paymenttab.button.filter=Filter paymenttab.label.filter.active=Filter aktiv diff --git a/src/main/resources/languages/_en.properties b/src/main/resources/languages/_en.properties index 5505dfe6695e53faeaaf0a10a3de7b89cc2686f0..635cba0ecba0b2da5980a0d5434d0ca3b711f98c 100644 --- a/src/main/resources/languages/_en.properties +++ b/src/main/resources/languages/_en.properties @@ -213,6 +213,7 @@ warning.name.character.limit.reached.150=The name must not exceed 150 characters warning.description.character.limit.reached.200=The description must not exceed 200 characters in length. warning.tag.character.limit.reached.45=A tag name must not exceed 45 characters in length. warning.payment.amount=Please enter a valid number in the amount field. +warning.payment.number=Please enter a valid number greater than 0. warning.empty.payment.date=Please select a date. warning.payment.repeating=If repeating is activated, both input fields may not be 0.\n(To deactivate the repeat, simply deselect the checkbox). warning.empty.secret.client=The field for the client password can not be empty. @@ -323,6 +324,11 @@ payment.new.label.description=Description payment.new.label.tags=Tags payment.new.label.account=Account payment.new.label.repeating=Repeating +payment.new.label.repeating.all=Every + +repeating.modifier.days=Days +repeating.modifier.months=Months +repeating.modifier.years=Years paymenttab.button.filter=Filter paymenttab.label.filter.active=Filter active diff --git a/src/main/resources/static/js/payments.js b/src/main/resources/static/js/payments.js index 78a7cea6c6bd4f8b87a14cafcd69ee482f5cc9ed..e908fc8c000f8ff38e968644b915f0d0cf71a83a 100644 --- a/src/main/resources/static/js/payments.js +++ b/src/main/resources/static/js/payments.js @@ -32,13 +32,21 @@ $( document ).ready(function() { }); } - if($("#payment-amount").length) + if($('#payment-amount').length) { $('#payment-amount').on('change keydown paste input', function() { validateAmount($(this).val()); }); } + var paymentRepeatingModifierID = "#payment-repeating-modifier"; + if($(paymentRepeatingModifierID).length) + { + $(paymentRepeatingModifierID).on('change keydown paste input', function() { + validateNumber($(this).val(), paymentRepeatingModifierID.substr(1), numberValidationMessage); + }); + } + if($(".chips-autocomplete").length) { $('.chips-autocomplete').material_chip({ @@ -51,6 +59,24 @@ $( document ).ready(function() { }); } + $('#enableRepeating').change(function(){ + if($(this).is(":checked")) + { + $('#payment-repeating-modifier').prop('disabled', false); + $('#payment-repeating-modifier-type').prop('disabled', false); + $('#payment-repeating-modifier-type').material_select(); + } + else + { + $('#payment-repeating-modifier').prop('disabled', true); + $('#payment-repeating-modifier-type').prop('disabled', true); + $('#payment-repeating-modifier-type').material_select(); + } + }); + + // fire change listener on page load + $('#enableRepeating').trigger("change"); + // prevent form submit on enter (otherwise tag functionality will be hard to use) $(document).on("keypress", 'form', function (e) { var code = e.keyCode || e.which; @@ -62,36 +88,65 @@ $( document ).ready(function() { }); AMOUNT_REGEX = new RegExp("^-?\\d+(,\\d+)?(\\.\\d+)?$"); +NUMBER_REGEX = new RegExp("^\\d+$"); ALLOWED_CHARACTERS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ",", "."]; function validateAmount(text) { - var element = document.getElementById("payment-amount"); + var id = "payment-amount"; if(text.match(AMOUNT_REGEX) == null) { - removeClass(element, "validate"); - removeClass(element, "valid"); - addClass(element, "tooltipped"); - addClass(element, "invalid"); - element.dataset.tooltip=amountValidationMessage; - element.dataset.position="bottom"; - element.dataset.delay="50"; - $('#payment-amount').tooltip(); - document.getElementById("hidden-payment-amount").value = ""; + addTooltip(id, amountValidationMessage); + document.getElementById("hidden-" + id).value = ""; } else { - removeClass(element, "validate"); - removeClass(element, "invalid"); - removeClass(element, "tooltipped"); - addClass(element, "valid"); - $('#payment-amount').tooltip('remove'); + removeTooltip(id); var amount = parseInt(parseFloat(text.replace(",", ".")) * 100); - document.getElementById("hidden-payment-amount").value = amount; + document.getElementById("hidden-" + id).value = amount; } } +function validateNumber(text, id, message) +{ + if(text.match(NUMBER_REGEX) == null) + { + addTooltip(id, message); + document.getElementById("hidden-" + id).value = ""; + } + else + { + removeTooltip(id); + document.getElementById("hidden-" + id).value = parseInt(text); + } +} + +function addTooltip(id, message) +{ + var element = document.getElementById(id); + + removeClass(element, "validate"); + removeClass(element, "valid"); + addClass(element, "tooltipped"); + addClass(element, "invalid"); + element.dataset.tooltip=message; + element.dataset.position="bottom"; + element.dataset.delay="50"; + $('#' + id).tooltip(); +} + +function removeTooltip(id) +{ + var element = document.getElementById(id); + + removeClass(element, "validate"); + removeClass(element, "invalid"); + removeClass(element, "tooltipped"); + addClass(element, "valid"); + $('#' + id).tooltip('remove'); +} + function validateForm() { var tags = $('.chips-autocomplete').material_chip('data'); diff --git a/src/main/resources/templates/payments/newPayment.ftl b/src/main/resources/templates/payments/newPayment.ftl index e510eb5f92839220b0a2047919c5b81607bcc8e2..d6910552ee203143684832fd9480bc70f5f5bbe2 100644 --- a/src/main/resources/templates/payments/newPayment.ftl +++ b/src/main/resources/templates/payments/newPayment.ftl @@ -23,7 +23,7 @@ <#-- isPayment switch --> <div class="row"> - <div class="s12 m12 l8 offset-l2 center-align"> + <div class="col s12 m12 l8 offset-l2 center-align"> <div class="switch"> <label> ${locale.getString("title.income")} @@ -70,7 +70,6 @@ </select> <label for="payment-category">${locale.getString("payment.new.label.category")}</label> </div> - <div id="hidden-payment-tags"></div> </div> <#-- date --> @@ -104,6 +103,7 @@ </#if> </div> </div> + <div id="hidden-payment-tags"></div> </div> <#-- account --> @@ -124,8 +124,46 @@ </select> <label for="payment-account">${locale.getString("payment.new.label.account")}</label> </div> - <div id="hidden-payment-tags"></div> </div> + + <#-- repeating options --> + <div class="row"> + <div class="col s12 m12 l8 offset-l2"> + <div class="switch"> + <label> + <input type="checkbox" id="enableRepeating" name="enableRepeating" <#if payment.getRepeatingOption()??>checked</#if>> + <span class="lever"></span> + ${locale.getString("payment.new.label.repeating")} + </label> + </div> + </div> + </div> + + <#-- repeating modifier --> + <div class="row"> + <div class="input-field col s6 m6 l4 offset-l2"> + <input id="payment-repeating-modifier" name="repeatingModifierNumber" type="text" <@validation.validation "repeatingModifierNumber"/> value="<#if payment.getRepeatingOption()??>${payment.getRepeatingOption().getModifier().getQuantity()}</#if>"> + <label for="payment-repeating-modifier">${locale.getString("payment.new.label.repeating.all")}</label> + </div> + <input type="hidden" id="hidden-payment-repeating-modifier" value="<#if payment.getRepeatingOption()??>${payment.getRepeatingOption().getModifier().getQuantity()}</#if>"> + + <div class="input-field col s6 m6 l4"> + <select id="payment-repeating-modifier-type" name="repeatingModifierType"> + <#list helpers.getRepeatingModifierTypes() as modifierType> + <#assign modifierName=locale.getString(modifierType.getLocalizationKey())> + <#if payment.getRepeatingOption()??> + <#if payment.getRepeatingOption().getModifier().getLocalizationKey() == modifierName> + <option selected value="${modifierName}">${modifierName}</option> + </#if> + </#if> + <option value="${modifierName}">${modifierName}</option> + </#list> + </select> + <input type="hidden" id="hidden-payment-repeating-modifier-type" value="<#if payment.getRepeatingOption()??>${locale.getString(payment.getRepeatingOption().getModifier().getLocalizationKey())}</#if>"> + </div> + </div> + + <#-- repeating end option --> <br> <#-- buttons --> @@ -165,6 +203,7 @@ <@datePicker.datePickerLocalization/> <script> amountValidationMessage = "${locale.getString("warning.payment.amount")}"; + numberValidationMessage = "${locale.getString("warning.payment.number")}"; tagsPlaceholder = "${locale.getString("tagfield.placeholder")}"; </script>