mirror of
https://github.com/frappe/books.git
synced 2024-12-22 10:58:59 +00:00
incr: add MouseFollower
- unpaid invoices tooltips - fix getstarted
This commit is contained in:
parent
1e35635170
commit
e9226f38c8
@ -56,6 +56,10 @@ export class Converter {
|
||||
}
|
||||
|
||||
static toDocValue(value: RawValue, field: Field, fyo: Fyo): DocValue {
|
||||
if (!field?.fieldtype) {
|
||||
console.log(value, field);
|
||||
console.trace();
|
||||
}
|
||||
switch (field.fieldtype) {
|
||||
case FieldTypeEnum.Currency:
|
||||
return toDocCurrency(value, field, fyo);
|
||||
@ -102,6 +106,9 @@ export class Converter {
|
||||
for (const fieldname in rawValueMap) {
|
||||
const field = fieldValueMap[fieldname];
|
||||
const rawValue = rawValueMap[fieldname];
|
||||
if (!field) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Array.isArray(rawValue)) {
|
||||
const parentSchemaName = (field as TargetField).target;
|
||||
|
@ -24,8 +24,13 @@
|
||||
"fieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"fieldname": "itemCreated",
|
||||
"label": "Item Created",
|
||||
"fieldname": "salesItemCreated",
|
||||
"label": "Purchase Item Created",
|
||||
"fieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"fieldname": "purchaseItemCreated",
|
||||
"label": "Sales Item Created",
|
||||
"fieldtype": "Check"
|
||||
},
|
||||
{
|
||||
|
@ -122,7 +122,7 @@
|
||||
>
|
||||
<div class="flex flex-col justify-center items-center">
|
||||
<p>
|
||||
{{ xi > -1 ? xLabels[xi] : '' }}
|
||||
{{ xi > -1 ? formatX(xLabels[xi]) : '' }}
|
||||
</p>
|
||||
<p class="font-semibold">
|
||||
{{ yi > -1 ? format(points[yi][xi]) : '' }}
|
||||
|
35
src/components/MouseFollower.vue
Normal file
35
src/components/MouseFollower.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<Tooltip ref="tooltip"><slot></slot></Tooltip>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue';
|
||||
import Tooltip from './Tooltip.vue';
|
||||
|
||||
export default defineComponent({
|
||||
props: { show: { type: Boolean, default: false } },
|
||||
components: { Tooltip },
|
||||
watch: {
|
||||
show(val) {
|
||||
if (val) {
|
||||
this.$refs.tooltip.create();
|
||||
this.setListeners();
|
||||
} else {
|
||||
this.$refs.tooltip.destroy();
|
||||
this.removeListener();
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
mousemoveListener(e) {
|
||||
this.$refs.tooltip.update(e);
|
||||
},
|
||||
setListeners() {
|
||||
window.addEventListener('mousemove', this.mousemoveListener);
|
||||
},
|
||||
removeListener() {
|
||||
window.removeEventListener('mousemove', this.mousemoveListener);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
@ -46,7 +46,8 @@ import { ModelNameEnum } from 'models/types';
|
||||
import LineChart from 'src/components/Charts/LineChart.vue';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { formatXLabels, getYMax } from 'src/utils/chart';
|
||||
import { getDatesAndPeriodicity } from 'src/utils/misc';
|
||||
import { getDatesAndPeriodList } from 'src/utils/misc';
|
||||
import { getMapFromList } from 'utils/';
|
||||
import PeriodSelector from './PeriodSelector';
|
||||
|
||||
export default {
|
||||
@ -89,8 +90,25 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async setData() {
|
||||
const { fromDate, toDate } = await getDatesAndPeriodicity(this.period);
|
||||
this.data = await fyo.db.getCashflow(fromDate, toDate);
|
||||
const { periodList, fromDate, toDate } = await getDatesAndPeriodList(
|
||||
this.period
|
||||
);
|
||||
|
||||
const data = await fyo.db.getCashflow(fromDate, toDate);
|
||||
const dataMap = getMapFromList(data, 'month-year');
|
||||
this.data = periodList.map((p) => {
|
||||
const key = p.toFormat('yyyy-MM');
|
||||
const item = dataMap[key];
|
||||
if (item) {
|
||||
return item;
|
||||
}
|
||||
|
||||
return {
|
||||
inflow: 0,
|
||||
outflow: 0,
|
||||
'month-year': key,
|
||||
};
|
||||
});
|
||||
},
|
||||
async setHasData() {
|
||||
const accounts = await fyo.db.getAllRaw('Account', {
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="flex justify-between gap-10">
|
||||
<div
|
||||
class="flex-col justify-between flex-1"
|
||||
v-for="invoice in invoices"
|
||||
v-for="(invoice, i) in invoices"
|
||||
:key="invoice.title"
|
||||
>
|
||||
<!-- Title and Period Selector -->
|
||||
@ -10,7 +10,7 @@
|
||||
<template #title>{{ invoice.title }}</template>
|
||||
<template #action>
|
||||
<PeriodSelector
|
||||
v-if="invoice.count"
|
||||
v-if="invoice.hasData"
|
||||
:value="$data[invoice.periodKey]"
|
||||
@change="(value) => ($data[invoice.periodKey] = value)"
|
||||
/>
|
||||
@ -53,7 +53,11 @@
|
||||
</div>
|
||||
|
||||
<!-- Widget Bar -->
|
||||
<div class="mt-2 relative rounded overflow-hidden">
|
||||
<div
|
||||
class="mt-2 relative rounded overflow-hidden"
|
||||
@mouseenter="idx = i"
|
||||
@mouseleave="idx = -1"
|
||||
>
|
||||
<div
|
||||
class="w-full h-4"
|
||||
:class="
|
||||
@ -74,14 +78,33 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MouseFollower
|
||||
v-if="invoices[0].hasData || invoices[1].hasData"
|
||||
:show="idx >= 0"
|
||||
class="text-sm shadow-md px-2 py-1 bg-white text-gray-900 border-l-2"
|
||||
:style="{ borderColor: colors[idx] }"
|
||||
>
|
||||
<div class="flex justify-between gap-4">
|
||||
<p>{{ t`Paid` }}</p>
|
||||
<p class="font-semibold">{{ invoices[idx]?.paidCount ?? 0 }}</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="invoices[idx]?.unpaidCount > 0"
|
||||
class="flex justify-between gap-4"
|
||||
>
|
||||
<p>{{ t`Unpaid` }}</p>
|
||||
<p class="font-semibold">{{ invoices[idx]?.unpaidCount ?? 0 }}</p>
|
||||
</div>
|
||||
</MouseFollower>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { t } from 'fyo';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import Button from 'src/components/Button.vue';
|
||||
import MouseFollower from 'src/components/MouseFollower.vue';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { getDatesAndPeriodicity } from 'src/utils/misc';
|
||||
import { getDatesAndPeriodList } from 'src/utils/misc';
|
||||
import { routeTo } from 'src/utils/ui';
|
||||
import PeriodSelector from './PeriodSelector.vue';
|
||||
import SectionHeader from './SectionHeader.vue';
|
||||
@ -92,16 +115,22 @@ export default {
|
||||
PeriodSelector,
|
||||
SectionHeader,
|
||||
Button,
|
||||
MouseFollower,
|
||||
},
|
||||
data: () => ({
|
||||
idx: -1,
|
||||
colors: ['#33A1FF', '#B7BFC6'],
|
||||
invoices: [
|
||||
{
|
||||
title: t`Sales Invoices`,
|
||||
schemaName: ModelNameEnum.SalesInvoice,
|
||||
total: 0,
|
||||
unpaid: 0,
|
||||
hasData: false,
|
||||
paid: 0,
|
||||
count: 0,
|
||||
unpaidCount: 0,
|
||||
paidCount: 0,
|
||||
color: 'blue',
|
||||
periodKey: 'salesInvoicePeriod',
|
||||
barWidth: 40,
|
||||
@ -111,8 +140,11 @@ export default {
|
||||
schemaName: ModelNameEnum.PurchaseInvoice,
|
||||
total: 0,
|
||||
unpaid: 0,
|
||||
hasData: false,
|
||||
paid: 0,
|
||||
count: 0,
|
||||
unpaidCount: 0,
|
||||
paidCount: 0,
|
||||
color: 'gray',
|
||||
periodKey: 'purchaseInvoicePeriod',
|
||||
barWidth: 60,
|
||||
@ -131,7 +163,7 @@ export default {
|
||||
methods: {
|
||||
async calculateInvoiceTotals() {
|
||||
for (const invoice of this.invoices) {
|
||||
const { fromDate, toDate } = await getDatesAndPeriodicity(
|
||||
const { fromDate, toDate } = await getDatesAndPeriodList(
|
||||
this.$data[invoice.periodKey]
|
||||
);
|
||||
|
||||
@ -141,14 +173,19 @@ export default {
|
||||
toDate
|
||||
);
|
||||
|
||||
const count = await fyo.db.count(invoice.schemaName, {
|
||||
filters: { cancelled: false, submitted: true },
|
||||
});
|
||||
const { countTotal, countOutstanding } = await this.getCounts(
|
||||
invoice.schemaName,
|
||||
DateTime.fromISO(fromDate),
|
||||
DateTime.fromISO(toDate)
|
||||
);
|
||||
|
||||
invoice.total = total ?? 0;
|
||||
invoice.unpaid = outstanding ?? 0;
|
||||
invoice.paid = total - outstanding;
|
||||
invoice.count = count;
|
||||
invoice.hasData = countTotal > 0;
|
||||
invoice.count = countTotal;
|
||||
invoice.paidCount = countTotal - countOutstanding;
|
||||
invoice.unpaidCount = countOutstanding;
|
||||
invoice.barWidth = (invoice.paid / (invoice.total || 1)) * 100;
|
||||
}
|
||||
},
|
||||
@ -156,6 +193,26 @@ export default {
|
||||
let doc = await fyo.doc.getNewDoc(invoice.schemaName);
|
||||
routeTo(`/edit/${invoice.schemaName}/${doc.name}`);
|
||||
},
|
||||
|
||||
async getCounts(schemaName, fromDate, toDate) {
|
||||
const outstandingAmounts = await fyo.db.getAllRaw(schemaName, {
|
||||
fields: ['outstandingAmount'],
|
||||
filters: {
|
||||
cancelled: false,
|
||||
submitted: true,
|
||||
date: ['<=', toDate.toISO(), '>=', fromDate.toISO()],
|
||||
},
|
||||
});
|
||||
|
||||
const isOutstanding = outstandingAmounts.map((o) =>
|
||||
parseFloat(o.outstandingAmount)
|
||||
);
|
||||
|
||||
return {
|
||||
countTotal: isOutstanding.length,
|
||||
countOutstanding: isOutstanding.filter((o) => o > 0).length,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -166,43 +166,40 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fyo.singles.GetStarted.itemCreated) {
|
||||
const count = await fyo.db.count('Item');
|
||||
if (count > 0) {
|
||||
toUpdate.itemCreated = 1;
|
||||
}
|
||||
if (!fyo.singles.GetStarted.salesItemCreated) {
|
||||
const count = await fyo.db.count('Item', { filters: { for: 'Sales' } });
|
||||
toUpdate.salesItemCreated = count > 0;
|
||||
}
|
||||
|
||||
if (!fyo.singles.GetStarted.purchaseItemCreated) {
|
||||
const count = await fyo.db.count('Item', {
|
||||
filters: { for: 'Purchases' },
|
||||
});
|
||||
toUpdate.purchaseItemCreated = count > 0;
|
||||
}
|
||||
|
||||
if (!fyo.singles.GetStarted.invoiceCreated) {
|
||||
const count = await fyo.db.count('SalesInvoice');
|
||||
if (count > 0) {
|
||||
toUpdate.invoiceCreated = 1;
|
||||
}
|
||||
toUpdate.invoiceCreated = count > 0;
|
||||
}
|
||||
|
||||
if (!fyo.singles.GetStarted.customerCreated) {
|
||||
const count = fyo.db.count('Party', {
|
||||
const count = await fyo.db.count('Party', {
|
||||
filters: { role: 'Customer' },
|
||||
});
|
||||
if (count > 0) {
|
||||
toUpdate.customerCreated = 1;
|
||||
}
|
||||
toUpdate.customerCreated = count > 0;
|
||||
}
|
||||
|
||||
if (!fyo.singles.GetStarted.billCreated) {
|
||||
const count = await fyo.db.count('SalesInvoice');
|
||||
if (count > 0) {
|
||||
toUpdate.billCreated = 1;
|
||||
}
|
||||
toUpdate.billCreated = count > 0;
|
||||
}
|
||||
|
||||
if (!fyo.singles.GetStarted.supplierCreated) {
|
||||
const count = fyo.db.count('Party', {
|
||||
const count = await fyo.db.count('Party', {
|
||||
filters: { role: 'Supplier' },
|
||||
});
|
||||
if (count > 0) {
|
||||
toUpdate.supplierCreated = 1;
|
||||
}
|
||||
toUpdate.supplierCreated = count > 0;
|
||||
}
|
||||
await this.updateChecks(toUpdate);
|
||||
},
|
||||
@ -211,10 +208,10 @@ export default {
|
||||
await fyo.doc.getSingle('GetStarted');
|
||||
},
|
||||
isCompleted(item) {
|
||||
return fyo.singles.GetStarted.get(item.fieldname) || 0;
|
||||
return fyo.singles.GetStarted.get(item.fieldname) || false;
|
||||
},
|
||||
getIconComponent(item) {
|
||||
let completed = fyo.singles.GetStarted[item.fieldname] || 0;
|
||||
let completed = fyo.singles.GetStarted[item.fieldname] || false;
|
||||
let name = completed ? 'green-check' : item.icon;
|
||||
let size = completed ? '24' : '18';
|
||||
return {
|
||||
|
@ -84,8 +84,8 @@ export function getGetStartedConfig() {
|
||||
label: t`Add Items`,
|
||||
icon: 'item',
|
||||
description: t`Add products or services that you sell to your customers`,
|
||||
action: () => routeTo('/list/Item'),
|
||||
fieldname: 'itemCreated',
|
||||
action: () => routeTo(`/list/Item/for/Sales/${t`Sales Items`}`),
|
||||
fieldname: 'salesItemCreated',
|
||||
documentation: 'https://frappebooks.com/docs/setting-up#3-add-items',
|
||||
},
|
||||
{
|
||||
@ -93,7 +93,7 @@ export function getGetStartedConfig() {
|
||||
label: t`Add Customers`,
|
||||
icon: 'customer',
|
||||
description: t`Add a few customers to create your first invoice`,
|
||||
action: () => routeTo('/list/Customer'),
|
||||
action: () => routeTo(`/list/Party/role/Customer/${t`Customers`}`),
|
||||
fieldname: 'customerCreated',
|
||||
documentation:
|
||||
'https://frappebooks.com/docs/setting-up#4-add-customers',
|
||||
@ -118,15 +118,16 @@ export function getGetStartedConfig() {
|
||||
label: t`Add Items`,
|
||||
icon: 'item',
|
||||
description: t`Add products or services that you buy from your suppliers`,
|
||||
action: () => routeTo('/list/Item'),
|
||||
fieldname: 'itemCreated',
|
||||
action: () =>
|
||||
routeTo(`/list/Item/for/Purchases/${t`Purchase Items`}`),
|
||||
fieldname: 'purchaseItemCreated',
|
||||
},
|
||||
{
|
||||
key: 'Add Suppliers',
|
||||
label: t`Add Suppliers`,
|
||||
icon: 'supplier',
|
||||
description: t`Add a few suppliers to create your first bill`,
|
||||
action: () => routeTo('/list/Supplier'),
|
||||
action: () => routeTo(`/list/Party/role/Supplier/${t`Suppliers`}`),
|
||||
fieldname: 'supplierCreated',
|
||||
},
|
||||
{
|
||||
|
@ -7,9 +7,9 @@ import SetupWizardSchema from 'schemas/app/SetupWizard.json';
|
||||
import { Schema } from 'schemas/types';
|
||||
import { fyo } from 'src/initFyo';
|
||||
|
||||
export function getDatesAndPeriodicity(
|
||||
export function getDatesAndPeriodList(
|
||||
period: 'This Year' | 'This Quarter' | 'This Month'
|
||||
): { fromDate: string; toDate: string } {
|
||||
): { periodList: DateTime[]; fromDate: string; toDate: string } {
|
||||
const toDate: DateTime = DateTime.now();
|
||||
let fromDate: DateTime;
|
||||
|
||||
@ -23,7 +23,22 @@ export function getDatesAndPeriodicity(
|
||||
fromDate = toDate.minus({ days: 1 });
|
||||
}
|
||||
|
||||
/**
|
||||
* periodList: Monthly decrements before toDate until fromDate
|
||||
*/
|
||||
const periodList: DateTime[] = [toDate];
|
||||
while (true) {
|
||||
const nextDate = periodList.at(0)!.minus({ months: 1 });
|
||||
if (nextDate.toMillis() < fromDate.toMillis()) {
|
||||
break;
|
||||
}
|
||||
|
||||
periodList.unshift(nextDate);
|
||||
}
|
||||
periodList.shift();
|
||||
|
||||
return {
|
||||
periodList,
|
||||
fromDate: fromDate.toISO(),
|
||||
toDate: toDate.toISO(),
|
||||
};
|
||||
|
@ -29,3 +29,21 @@ export function useKeys(callback?: (keys: Set<string>) => void) {
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
export function useMouseLocation() {
|
||||
const loc = ref({ clientX: 0, clientY: 0 });
|
||||
|
||||
const mousemoveListener = (e: MouseEvent) => {
|
||||
loc.value.clientX = e.clientX;
|
||||
loc.value.clientY = e.clientY;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('mousemove', mousemoveListener);
|
||||
});
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('mousemove', mousemoveListener);
|
||||
});
|
||||
|
||||
return loc;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user