Обновить invoice/invoice.js
This commit is contained in:
@@ -0,0 +1,240 @@
|
|||||||
|
function ready(fn) {
|
||||||
|
if (document.readyState !== 'loading'){
|
||||||
|
fn();
|
||||||
|
} else {
|
||||||
|
document.addEventListener('DOMContentLoaded', fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDemo(row) {
|
||||||
|
if (!row.Issued && !row.Due) {
|
||||||
|
for (const key of ['Number', 'Issued', 'Due']) {
|
||||||
|
if (!row[key]) { row[key] = key; }
|
||||||
|
}
|
||||||
|
for (const key of ['Subtotal', 'Deduction', 'Taxes', 'Total']) {
|
||||||
|
if (!(key in row)) { row[key] = key; }
|
||||||
|
}
|
||||||
|
if (!('Note' in row)) { row.Note = '(Anything in a Note column goes here)'; }
|
||||||
|
}
|
||||||
|
if (!row.Invoicer) {
|
||||||
|
row.Invoicer = {
|
||||||
|
Name: 'Invoicer.Name',
|
||||||
|
Street1: 'Invoicer.Street1',
|
||||||
|
Street2: 'Invoicer.Street2',
|
||||||
|
City: 'Invoicer.City',
|
||||||
|
State: '.State',
|
||||||
|
Zip: '.Zip',
|
||||||
|
Email: 'Invoicer.Email',
|
||||||
|
Phone: 'Invoicer.Phone',
|
||||||
|
Website: 'Invoicer.Website'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!row.Client) {
|
||||||
|
row.Client = {
|
||||||
|
Name: 'Client.Name',
|
||||||
|
Street1: 'Client.Street1',
|
||||||
|
Street2: 'Client.Street2',
|
||||||
|
City: 'Client.City',
|
||||||
|
State: '.State',
|
||||||
|
Zip: '.Zip'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!row.Items) {
|
||||||
|
row.Items = [
|
||||||
|
{
|
||||||
|
Description: 'Items[0].Description',
|
||||||
|
Quantity: '.Quantity',
|
||||||
|
Total: '.Total',
|
||||||
|
Price: '.Price',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: 'Items[1].Description',
|
||||||
|
Quantity: '.Quantity',
|
||||||
|
Total: '.Total',
|
||||||
|
Price: '.Price',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
count: 0,
|
||||||
|
invoice: '',
|
||||||
|
status: 'waiting',
|
||||||
|
tableConnected: false,
|
||||||
|
rowConnected: false,
|
||||||
|
haveRows: false,
|
||||||
|
};
|
||||||
|
let app = undefined;
|
||||||
|
|
||||||
|
Vue.filter('currency', formatNumberAsUSD)
|
||||||
|
function formatNumberAsUSD(value) {
|
||||||
|
if (typeof value !== "number") {
|
||||||
|
return value || '—'; // falsy value would be shown as a dash.
|
||||||
|
}
|
||||||
|
value = Math.round(value * 100) / 100; // Round to nearest cent.
|
||||||
|
value = (value === -0 ? 0 : value); // Avoid negative zero.
|
||||||
|
|
||||||
|
const result = value.toLocaleString('en', {
|
||||||
|
style: 'currency', currency: 'USD'
|
||||||
|
})
|
||||||
|
if (result.includes('NaN')) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vue.filter('fallback', function(value, str) {
|
||||||
|
if (!value) {
|
||||||
|
throw new Error("Please provide column " + str);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.filter('asDate', function(value) {
|
||||||
|
if (typeof(value) === 'number') {
|
||||||
|
value = new Date(value * 1000);
|
||||||
|
}
|
||||||
|
const date = moment.utc(value)
|
||||||
|
return date.isValid() ? date.format('MMMM DD, YYYY') : value;
|
||||||
|
});
|
||||||
|
|
||||||
|
function tweakUrl(url) {
|
||||||
|
if (!url) { return url; }
|
||||||
|
if (url.toLowerCase().startsWith('http')) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
return 'https://' + url;
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleError(err) {
|
||||||
|
console.error(err);
|
||||||
|
const target = app || data;
|
||||||
|
target.invoice = '';
|
||||||
|
target.status = String(err).replace(/^Error: /, '');
|
||||||
|
console.log(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareList(lst, order) {
|
||||||
|
if (order) {
|
||||||
|
let orderedLst = [];
|
||||||
|
const remaining = new Set(lst);
|
||||||
|
for (const key of order) {
|
||||||
|
if (remaining.has(key)) {
|
||||||
|
remaining.delete(key);
|
||||||
|
orderedLst.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lst = [...orderedLst].concat([...remaining].sort());
|
||||||
|
} else {
|
||||||
|
lst = [...lst].sort();
|
||||||
|
}
|
||||||
|
return lst;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateInvoice(row) {
|
||||||
|
try {
|
||||||
|
data.status = '';
|
||||||
|
if (row === null) {
|
||||||
|
throw new Error("(No data - not on row - please add or select a row)");
|
||||||
|
}
|
||||||
|
console.log("GOT...", JSON.stringify(row));
|
||||||
|
if (row.References) {
|
||||||
|
try {
|
||||||
|
Object.assign(row, row.References);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error('Could not understand References column. ' + err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add some guidance about columns.
|
||||||
|
const want = new Set(Object.keys(addDemo({})));
|
||||||
|
const accepted = new Set(['References']);
|
||||||
|
const importance = ['Number', 'Client', 'Items', 'Total', 'Invoicer', 'Due', 'Issued', 'Subtotal', 'Deduction', 'Taxes', 'Note'];
|
||||||
|
if (!(row.Due || row.Issued)) {
|
||||||
|
const seen = new Set(Object.keys(row).filter(k => k !== 'id' && k !== '_error_'));
|
||||||
|
const help = row.Help = {};
|
||||||
|
help.seen = prepareList(seen);
|
||||||
|
const missing = [...want].filter(k => !seen.has(k));
|
||||||
|
const ignoring = [...seen].filter(k => !want.has(k) && !accepted.has(k));
|
||||||
|
const recognized = [...seen].filter(k => want.has(k) || accepted.has(k));
|
||||||
|
if (missing.length > 0) {
|
||||||
|
help.expected = prepareList(missing, importance);
|
||||||
|
}
|
||||||
|
if (ignoring.length > 0) {
|
||||||
|
help.ignored = prepareList(ignoring);
|
||||||
|
}
|
||||||
|
if (recognized.length > 0) {
|
||||||
|
help.recognized = prepareList(recognized);
|
||||||
|
}
|
||||||
|
if (!seen.has('References') && !(row.Issued || row.Due)) {
|
||||||
|
row.SuggestReferencesColumn = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addDemo(row);
|
||||||
|
if (!row.Subtotal && !row.Total && row.Items && Array.isArray(row.Items)) {
|
||||||
|
try {
|
||||||
|
row.Subtotal = row.Items.reduce((a, b) => a + b.Price * b.Quantity, 0);
|
||||||
|
row.Total = row.Subtotal + (row.Taxes || 0) - (row.Deduction || 0);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (row.Invoicer && row.Invoicer.Website && !row.Invoicer.Url) {
|
||||||
|
row.Invoicer.Url = tweakUrl(row.Invoicer.Website);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fiddle around with updating Vue (I'm not an expert).
|
||||||
|
for (const key of want) {
|
||||||
|
Vue.delete(data.invoice, key);
|
||||||
|
}
|
||||||
|
for (const key of ['Help', 'SuggestReferencesColumn', 'References']) {
|
||||||
|
Vue.delete(data.invoice, key);
|
||||||
|
}
|
||||||
|
data.invoice = Object.assign({}, data.invoice, row);
|
||||||
|
|
||||||
|
// Make invoice information available for debugging.
|
||||||
|
window.invoice = row;
|
||||||
|
} catch (err) {
|
||||||
|
handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ready(function() {
|
||||||
|
// Update the invoice anytime the document data changes.
|
||||||
|
grist.ready();
|
||||||
|
grist.onRecord(updateInvoice);
|
||||||
|
|
||||||
|
// Monitor status so we can give user advice.
|
||||||
|
grist.on('message', msg => {
|
||||||
|
// If we are told about a table but not which row to access, check the
|
||||||
|
// number of rows. Currently if the table is empty, and "select by" is
|
||||||
|
// not set, onRecord() will never be called.
|
||||||
|
if (msg.tableId && !app.rowConnected) {
|
||||||
|
grist.docApi.fetchSelectedTable().then(table => {
|
||||||
|
if (table.id && table.id.length >= 1) {
|
||||||
|
app.haveRows = true;
|
||||||
|
}
|
||||||
|
}).catch(e => console.log(e));
|
||||||
|
}
|
||||||
|
if (msg.tableId) { app.tableConnected = true; }
|
||||||
|
if (msg.tableId && !msg.dataChange) { app.RowConnected = true; }
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.config.errorHandler = function (err, vm, info) {
|
||||||
|
handleError(err);
|
||||||
|
};
|
||||||
|
|
||||||
|
app = new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
|
||||||
|
if (document.location.search.includes('demo')) {
|
||||||
|
updateInvoice(exampleData);
|
||||||
|
}
|
||||||
|
if (document.location.search.includes('labels')) {
|
||||||
|
updateInvoice({});
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user