-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #118 from msramalho/refactor/bills-extractor
- Support for "All Day" events - Migrate Bills extractor to `EventExtractor`, use modal rather than inline dropdowns, include fees in total amount - New extractor for bills with ATM references, BillsPaymentRefs
- Loading branch information
Showing
17 changed files
with
1,698 additions
and
234 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,5 +2,5 @@ | |
"compilerOptions": { | ||
"target": "es6" | ||
}, | ||
"include": ["src/js/**/*"] | ||
"include": ["src/js/**/*", "src/test/**/*"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,85 +1,181 @@ | ||
class Bill extends Extractor { | ||
class Bills extends EventExtractor { | ||
/** @type {boolean} */ | ||
ignoreHasPaymentRefs = null; | ||
|
||
constructor() { | ||
super(); | ||
this.tableSelector = '#tab0 > table > tbody > tr'; | ||
this.ready(); | ||
} | ||
|
||
structure() { | ||
return { | ||
extractor: "bills", | ||
name: "Pending Bills", | ||
description: "Extracts all pending bills from sigarra", | ||
icon: "bill.png", | ||
parameters: [{ | ||
name: "description", | ||
description: "eg: Propinas - Mestrado Integrado em Engenharia Informática e Computação - Prestação 1" | ||
}, { | ||
name: "date", | ||
description: "The payment deadline eg: 2019-03-31" | ||
}, { | ||
name: "amount", | ||
description: "The amount to pay e.g. 99,90€" | ||
}], | ||
storage: { | ||
text: [{ | ||
name: "title", | ||
default: "${description}" | ||
}], | ||
textarea: [{ | ||
name: "description", | ||
default: "Amount: ${amount}" | ||
}] | ||
} | ||
} | ||
return super.structure( | ||
{ | ||
extractor: "bills", | ||
name: "Pending Bills", | ||
description: "Extracts all pending bills from sigarra", | ||
icon: "bill.png", | ||
parameters: [ | ||
{ | ||
name: "description", | ||
description: | ||
"e.g. Propinas - Mestrado Integrado em Engenharia Informática e Computação - Prestação 1", | ||
}, | ||
{ | ||
name: "deadline", | ||
description: "The payment deadline, e.g. 2019-03-31", | ||
}, | ||
{ | ||
name: "amount", | ||
description: "The amount to pay, e.g. 99,90 €", | ||
}, | ||
], | ||
storage: { | ||
boolean: [ | ||
{ | ||
name: "ignoreHasPaymentRefs", | ||
default: true, | ||
}, | ||
], | ||
}, | ||
}, | ||
"${description}", | ||
"Amount: ${amount}", | ||
"", | ||
false, | ||
CalendarEventStatus.FREE | ||
); | ||
} | ||
|
||
attachIfPossible() { | ||
$('<th>Sigtools</th>').appendTo(this._getBillsHeader()[0]) | ||
this._getBills().forEach((element, index) => { | ||
let event = this._parsePendingBill(element); | ||
let drop = getDropdown(event, this, undefined, { | ||
target: "dropdown_" + index, | ||
divClass: "dropdown removeFrame", | ||
divStyle: "display:contents;", | ||
dropdownStyle: "position: absolute;" | ||
}); | ||
$('<td></td>').appendTo(element).append(drop[0]); | ||
}, this); | ||
// parse all pending bills | ||
const events = this.getEvents(); | ||
if (events.length === 0) return; | ||
|
||
// create button for opening the modal | ||
const $calendarBtn = createElementFromString( | ||
`<a class="calendarBtn" | ||
style="display: inline-block;" | ||
title="Save bills deadlines to your Calendar"> | ||
<img src="${chrome.extension.getURL("icons/calendar.svg")}"/> | ||
</a>` | ||
); | ||
$calendarBtn.addEventListener("click", (e) => createEventsModal(events)); | ||
|
||
setDropdownListeners(this, undefined); | ||
// insert the button before the table | ||
this.$pendingBillsTable().insertAdjacentElement("beforebegin", $calendarBtn); | ||
} | ||
|
||
_getBills() { | ||
let _billsDOM = $(this.tableSelector); // array-like object | ||
return Array.prototype.slice.call(_billsDOM, 1); // array object, removing header row | ||
/** | ||
* The DOM element for the table that lists the pending bills | ||
* @returns {HTMLElement | null} | ||
*/ | ||
$pendingBillsTable() { | ||
const $tables = Sig.doc.querySelectorAll("#tab0 > table"); | ||
return $tables.length > 0 ? $tables[0] : null; | ||
} | ||
|
||
_getBillsHeader() { | ||
return $(this.tableSelector); | ||
/** | ||
* All table rows for pending bills. Each row corresponds to a bill | ||
* @returns | ||
*/ | ||
$pendingBillsTableRows() { | ||
const $table = this.$pendingBillsTable(); | ||
return $table ? $table.querySelectorAll("tbody > tr") : null; | ||
} | ||
|
||
_parsePendingBill(billEl) { | ||
let getDateFromBill = function (index) { | ||
let dateFromBill = Bill._getDateOrUndefined($(billEl).children(`:nth(${index})`).text()); | ||
if (dateFromBill === undefined) dateFromBill = new Date(); | ||
return dateFromBill; | ||
/** | ||
* Creates calendar events for all pending bills, as long as they have | ||
* a deadline | ||
* | ||
* @returns {CalendarEvent[]} | ||
*/ | ||
getEvents() { | ||
const eventsLst = []; | ||
|
||
for (const bill of this.parsePendingBills()) { | ||
// If the bill has a deadline, create an event for it, otherwise skip | ||
// | ||
// Also consider the 'ignore if has payment ref' user option | ||
// The reasoning is if the ATM button does not exist for a pending | ||
// bill has an ATM, the same bill is listed in another table with | ||
// the ATM reference. See BillsPaymentRefs extractor. If the user | ||
// wants, it can be skipped. | ||
|
||
if (bill.deadline && (!this.ignoreHasPaymentRefs || bill.hasATMBtn)) { | ||
const ev = CalendarEvent.initAllDayEvent( | ||
this.getTitle(bill), | ||
this.getDescription(bill), | ||
this.isHTML, | ||
bill.deadline | ||
) | ||
.setLocation(this.getLocation(bill)) | ||
.setStatus(CalendarEventStatus.FREE); | ||
eventsLst.push(ev); | ||
} | ||
} | ||
return { | ||
description: $(billEl).children(':nth(2)').text(), | ||
amount: $(billEl).children(':nth(7)').text(), | ||
from: getDateFromBill(3), | ||
to: getDateFromBill(4), | ||
date: getDateFromBill(4), | ||
location: "", | ||
download: false | ||
}; | ||
|
||
return eventsLst; | ||
} | ||
|
||
static _getDateOrUndefined(dateString) { | ||
return dateString ? new Date(dateString) : undefined | ||
/** | ||
* Parses all pending bills found in the table | ||
* | ||
* @returns {{ | ||
* description: string, | ||
* amount: string, | ||
* deadline: string | null, | ||
* }[]} | ||
*/ | ||
parsePendingBills() { | ||
/** | ||
* @param {number} index The column index, starting at 1 | ||
* @returns {string | null} | ||
*/ | ||
const getColumnAsText = ($tr, index) => { | ||
const $td = $tr.querySelector(`td:nth-child(${index})`); | ||
return $td ? $td.innerText.trim() : null; | ||
}; | ||
|
||
/** | ||
* @param {number} index The column index, starting at 1 | ||
* @returns {Number | null} | ||
*/ | ||
const getColumnAsCurrency = ($tr, index) => { | ||
const value = getColumnAsText($tr, index).replace("€", "").trim(); | ||
// note that parseFloat only supports decimal literals, | ||
// https://262.ecma-international.org/5.1/#sec-A.2 | ||
// sigarra numbers are formatted in portuguese locale, therefore the | ||
// , must be replaced by . | ||
return value && Number.parseFloat(value.replace(".", "").replace(",", ".")); | ||
}; | ||
|
||
// get all <tr> for the pendings bills | ||
const $bills = this.$pendingBillsTableRows(); | ||
if (!$bills) return []; | ||
|
||
// iterate over the table rows, each row => a bill | ||
// skip first row, it is a table header, inside tbody :) | ||
const pendingBills = []; | ||
|
||
for (let i = 1; i < $bills.length; i++) { | ||
const $bill = $bills[i]; | ||
|
||
// parse the initial bill amount | ||
const initialAmount = getColumnAsCurrency($bill, 8); | ||
// parse the fees value if it exists | ||
const fees = getColumnAsCurrency($bill, 10) || 0; | ||
// append new bill information | ||
pendingBills.push({ | ||
description: getColumnAsText($bill, 3), | ||
amount: Intl.NumberFormat("pt-PT", { style: "currency", currency: "EUR" }).format(initialAmount + fees), | ||
deadline: getColumnAsText($bill, 5) || null, | ||
hasATMBtn: $bill.querySelector(`td:nth-child(9)`).childElementCount !== 0, | ||
}); | ||
} | ||
|
||
return pendingBills; | ||
} | ||
} | ||
|
||
// add an instance to the EXTRACTORS variable, and also trigger attachIfPossible due to constructor | ||
EXTRACTORS.push(new Bill()); | ||
EXTRACTORS.push(new Bills()); |
Oops, something went wrong.