Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ux: allow custom views #1024

Closed
wants to merge 11 commits into from
44 changes: 44 additions & 0 deletions fyo/model/doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,30 @@ export class Doc extends Observable<DocValue | Doc[]> {
return true;
}

get canUndoSubmit() {
if (!this.canCancel) {
return false;
}

if (this.fyo.singles.SystemSettings?.provisionalModeSince == null) {
return false;
}

const submittedAt: Date = (this.submittedAt || this.created) as Date;
if (!submittedAt) {
return false;
}

if (
submittedAt <
(this.fyo.singles.SystemSettings.provisionalModeSince as Date)
) {
return false;
}

return true;
}

get canCancel() {
if (!this.schema.isSubmittable) {
return false;
Expand Down Expand Up @@ -344,14 +368,17 @@ export class Doc extends Observable<DocValue | Doc[]> {
value?: DocValue | Doc[] | DocValueMap[]
): boolean {
if (fieldname === 'numberSeries' && !this.notInserted) {
// console.log("cannot set %s, numberSeries inserted", fieldname)
return false;
}

if (value === undefined) {
// console.log("cannot set %s, undefined value", fieldname)
return false;
}

if (this.fieldMap[fieldname] === undefined) {
// console.log("cannot set %s, no fieldMap", fieldname, this.fieldMap)
return false;
}

Expand Down Expand Up @@ -940,12 +967,27 @@ export class Doc extends Observable<DocValue | Doc[]> {

await this.trigger('beforeSubmit');
await this.setAndSync('submitted', true);
await this.setAndSync('submittedAt', new Date());
await this.trigger('afterSubmit');

this.fyo.telemetry.log(Verb.Submitted, this.schemaName);
this.fyo.doc.observer.trigger(`submit:${this.schemaName}`, this.name);
}

async submitUndo() {
if (!this.schema.isSubmittable || !this.submitted || this.cancelled) {
return;
}

await this.trigger('beforeSubmitUndo');
await this.setAndSync('submitted', false);
await this.setAndSync('submittedAt', null);
await this.trigger('afterSubmitUndo');

this.fyo.telemetry.log(Verb.SubmitUndone, this.schemaName);
this.fyo.doc.observer.trigger(`submitUndo:${this.schemaName}`, this.name);
}

async cancel() {
if (!this.schema.isSubmittable || !this.submitted || this.cancelled) {
return;
Expand Down Expand Up @@ -1058,6 +1100,8 @@ export class Doc extends Observable<DocValue | Doc[]> {
async afterSync() {}
async beforeSubmit() {}
async afterSubmit() {}
async beforeSubmitUndo() {}
async afterSubmitUndo() {}
async beforeRename() {}
async afterRename() {}
async beforeCancel() {}
Expand Down
1 change: 1 addition & 0 deletions fyo/telemetry/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export enum Verb {
Created = 'created',
Deleted = 'deleted',
Submitted = 'submitted',
SubmitUndone = 'submitUndone',
Cancelled = 'cancelled',
Imported = 'imported',
Exported = 'exported',
Expand Down
26 changes: 18 additions & 8 deletions main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { emitMainProcessError } from 'backend/helpers';
import {
shell,
app,
BrowserWindow,
BrowserWindowConstructorOptions,
Expand All @@ -22,6 +23,17 @@
import registerIpcMainMessageListeners from './main/registerIpcMainMessageListeners';
import registerProcessListeners from './main/registerProcessListeners';

const EXTENSIONS = {
'.js': 'text/javascript',
'.css': 'text/css',
'.html': 'text/html',
'.svg': 'image/svg+xml',
'.json': 'application/json',
'.pdf': 'application/pdf',
}

Check warning on line 33 in main.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

Insert `;`

const MIME_TYPES = Object.fromEntries(Object.entries(EXTENSIONS).map(([ext, mime]) => [mime, ext]))

Check warning on line 35 in main.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

'MIME_TYPES' is assigned a value but never used

Check warning on line 35 in main.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

Replace `Object.entries(EXTENSIONS).map(([ext,·mime])·=>·[mime,·ext]))` with `⏎··Object.entries(EXTENSIONS).map(([ext,·mime])·=>·[mime,·ext])⏎);`

export class Main {
title = 'Frappe Books';
icon: string;
Expand Down Expand Up @@ -119,6 +131,11 @@
const options = this.getOptions();
this.mainWindow = new BrowserWindow(options);

this.mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);

Check warning on line 135 in main.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
return { action: 'deny' };
});

if (this.isDevelopment) {
this.setViteServerURL();
} else {
Expand Down Expand Up @@ -189,16 +206,9 @@

fs.readFile(filePath, (_, data) => {
const extension = path.extname(filePath).toLowerCase();
const mimeType =
{
'.js': 'text/javascript',
'.css': 'text/css',
'.html': 'text/html',
'.svg': 'image/svg+xml',
'.json': 'application/json',
}[extension] ?? '';
const mimeType = EXTENSIONS[extension] ?? '';

Check failure on line 209 in main.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

Unsafe assignment of an `any` value

callback({ mimeType, data });

Check failure on line 211 in main.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

Unsafe assignment of an `any` value
});
}

Expand Down
4 changes: 4 additions & 0 deletions main/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@
ipcRenderer.send(IPC_MESSAGES.OPEN_EXTERNAL, link);
},

openDataURL(link: string, filename: string) {
ipcRenderer.send(IPC_MESSAGES.OPEN_DATA_URL, {link, filename});

Check warning on line 121 in main/preload.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

Replace `link,·filename` with `·link,·filename·`
},

async deleteFile(filePath: string) {
return (await ipcRenderer.invoke(
IPC_ACTIONS.DELETE_FILE,
Expand Down
41 changes: 40 additions & 1 deletion main/registerIpcMainMessageListeners.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,36 @@
import { ipcMain, Menu, shell } from 'electron';
import { ipcMain, Menu, shell, app } from 'electron';
import fs from 'fs';
import path from 'path';
import { Main } from '../main';
import { IPC_MESSAGES } from '../utils/messages';
import { emitMainProcessError } from 'backend/helpers';

function parseDataURL(url) {
const regex = /^data:([a-z]+\/[a-z0-9-+.]+(;[a-z0-9-.!#$%*+.{}|~`]+=[a-z0-9-.!#$%*+.{}()_|~`]+)*)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s<>]*?)$/i;

Check warning on line 9 in main/registerIpcMainMessageListeners.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

Insert `⏎···`

const parts = url.trim().match(regex);

Check failure on line 11 in main/registerIpcMainMessageListeners.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

Unsafe assignment of an `any` value

Check failure on line 11 in main/registerIpcMainMessageListeners.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

Unsafe member access .match on an `any` value

Check failure on line 11 in main/registerIpcMainMessageListeners.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

Unsafe call of an `any` typed value

Check failure on line 11 in main/registerIpcMainMessageListeners.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

Unsafe member access .trim on an `any` value

Check failure on line 11 in main/registerIpcMainMessageListeners.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

Unsafe call of an `any` typed value
if (!parts) return null

Check warning on line 12 in main/registerIpcMainMessageListeners.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

Insert `;`

const parsed = {};

parsed.mediaType = (parts[1] || 'text/plain;charset=us-ascii').toLowerCase();

Check failure on line 16 in main/registerIpcMainMessageListeners.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

Unsafe assignment of an `any` value

Check failure on line 16 in main/registerIpcMainMessageListeners.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

Unsafe member access .toLowerCase on an `any` value

Check failure on line 16 in main/registerIpcMainMessageListeners.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

Unsafe call of an `any` typed value

const mediaTypeParts = parsed.mediaType.split(';').map(x => x.toLowerCase());

Check warning on line 18 in main/registerIpcMainMessageListeners.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

Replace `.split(';').map(x` with `⏎····.split(';')⏎····.map((x)`
parsed.contentType = mediaTypeParts[0];

mediaTypeParts.slice(1).forEach((attribute) => {
const p = attribute.split('=');
parsed[p[0]] = p[1];
});

parsed.base64 = !!parts[parts.length - 2];

Check warning on line 26 in main/registerIpcMainMessageListeners.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

Delete `··`
parsed.data = parts[parts.length - 1] || '';

Check warning on line 27 in main/registerIpcMainMessageListeners.ts

View workflow job for this annotation

GitHub Actions / setup_and_lint

Delete `····`
parsed.encoding = parsed.base64 ? 'base64' : 'utf8';
parsed.buffer = Buffer.from(parsed.base64 ? parsed.data : decodeURIComponent(parsed.data), parsed.encoding)

return parsed;
}

export default function registerIpcMainMessageListeners(main: Main) {
ipcMain.on(IPC_MESSAGES.OPEN_MENU, (event) => {
if (event.sender === null) {
Expand Down Expand Up @@ -49,6 +77,17 @@
shell.openExternal(link).catch((err) => emitMainProcessError(err));
});

ipcMain.on(IPC_MESSAGES.OPEN_DATA_URL, (_, {link, filename}: {link: string, filename: string}) => {
let data = parseDataURL(link)
if (data) {
const s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const temp = Array.apply(null, Array(16)).map(function() { return s.charAt(Math.floor(Math.random() * s.length)); }).join('');
const filepath = path.join(app.getPath("temp"), temp + ' ' + filename)
fs.writeFileSync(filepath, data.buffer)
shell.openPath(filepath)
}
});

ipcMain.on(IPC_MESSAGES.SHOW_ITEM_IN_FOLDER, (_, filePath: string) => {
return shell.showItemInFolder(filePath);
});
Expand Down
13 changes: 13 additions & 0 deletions models/Transactional/Transactional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ export abstract class Transactional extends Doc {
await posting.post();
}

async afterSubmitUndo(): Promise<void> {
await super.afterSubmitUndo();
if (!this.isTransactional) {
return;
}

await this._deletePostings();
}

async afterCancel(): Promise<void> {
await super.afterCancel();
if (!this.isTransactional) {
Expand All @@ -76,6 +85,10 @@ export abstract class Transactional extends Doc {
return;
}

await this._deletePostings();
}

async _deletePostings(): Promise<void> {
const ledgerEntryIds = (await this.fyo.db.getAll(
ModelNameEnum.AccountingLedgerEntry,
{
Expand Down
30 changes: 27 additions & 3 deletions models/baseModels/Invoice/Invoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,29 @@ export abstract class Invoice extends Transactional {
}
}

async afterSubmitUndo() {
await super.afterSubmitUndo();
await this._cancelPayments({ undo: true });
await this._updatePartyOutStanding();
await this._updateIsItemsReturned();
await this._removeLoyaltyPointEntry();
this.reduceUsedCountOfCoupons();
}

async _undoPayments() {
const paymentIds = await this.getPaymentIds();
for (const paymentId of paymentIds) {
const paymentDoc = (await this.fyo.doc.getDoc(
'Payment',
paymentId
)) as Payment;
await paymentDoc.cancel();
}
}

async afterCancel() {
await super.afterCancel();
await this._cancelPayments();
await this._cancelPayments({ undo: false });
await this._updatePartyOutStanding();
await this._updateIsItemsReturned();
await this._removeLoyaltyPointEntry();
Expand All @@ -258,14 +278,18 @@ export abstract class Invoice extends Transactional {
await removeLoyaltyPoint(this);
}

async _cancelPayments() {
async _cancelPayments({ undo }: { undo: boolean }) {
const paymentIds = await this.getPaymentIds();
for (const paymentId of paymentIds) {
const paymentDoc = (await this.fyo.doc.getDoc(
'Payment',
paymentId
)) as Payment;
await paymentDoc.cancel();
if (undo) {
await paymentDoc.submitUndo();
} else {
await paymentDoc.cancel();
}
}
}

Expand Down
5 changes: 5 additions & 0 deletions models/baseModels/Payment/Payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,11 @@ export class Payment extends Transactional {
}
}

async afterSubmitUndo() {
await super.afterSubmitUndo();
await this.revertOutstandingAmount();
}

async afterCancel() {
await super.afterCancel();
await this.revertOutstandingAmount();
Expand Down
22 changes: 22 additions & 0 deletions models/baseModels/SidebarEntry/SidebarEntry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Doc } from 'fyo/model/doc';

export class SidebarEntry extends Doc {
section?: string;
route?: string;
model?: string;
filters?: string;

fullRoute() {
switch(this.route) {
case '/list':
return `/list/${this.model}/${this.name}`;
default:
return this.route;
}
}

parsedFilters() {
return JSON.parse(this.filters)
}
}

2 changes: 2 additions & 0 deletions models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import { SalesQuote } from './baseModels/SalesQuote/SalesQuote';
import { SalesQuoteItem } from './baseModels/SalesQuoteItem/SalesQuoteItem';
import { SetupWizard } from './baseModels/SetupWizard/SetupWizard';
import { SidebarEntry } from `./baseModels/SidebarEntry/SidebarEntry`;

Check failure on line 33 in models/index.ts

View workflow job for this annotation

GitHub Actions / setup_and_test

String literal expected.

Check failure on line 33 in models/index.ts

View workflow job for this annotation

GitHub Actions / setup_and_test

Cannot find module './baseModels/SidebarEntry/SidebarEntry' or its corresponding type declarations.
import { Tax } from './baseModels/Tax/Tax';
import { TaxSummary } from './baseModels/TaxSummary/TaxSummary';
import { Batch } from './inventory/Batch';
Expand Down Expand Up @@ -83,6 +84,7 @@
SalesQuoteItem,
SerialNumber,
SetupWizard,
SidebarEntry,
PrintTemplate,
Tax,
TaxSummary,
Expand Down
4 changes: 4 additions & 0 deletions models/inventory/StockManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export class StockManager {
await this.#sync();
}

async undoTransfers() {
await this.cancelTransfers();
}

async cancelTransfers() {
const { referenceName, referenceType } = this.details;
await this.fyo.db.deleteAll(ModelNameEnum.StockLedgerEntry, {
Expand Down
5 changes: 5 additions & 0 deletions models/inventory/StockMovement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ export class StockMovement extends Transfer {
await updateSerialNumbers(this, false);
}

async afterSubmitUndo(): Promise<void> {
await super.afterSubmitUndo();
await updateSerialNumbers(this, true);
}

async afterCancel(): Promise<void> {
await super.afterCancel();
await updateSerialNumbers(this, true);
Expand Down
7 changes: 7 additions & 0 deletions models/inventory/StockTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@ export abstract class StockTransfer extends Transfer {
await this._updateItemsReturned();
}

async afterSubmitUndo() {
await super.afterSubmitUndo();
await updateSerialNumbers(this, false, this.isReturn);
await this._updateBackReference();
await this._updateItemsReturned();
}

async afterCancel(): Promise<void> {
await super.afterCancel();
await updateSerialNumbers(this, true, this.isReturn);
Expand Down
Loading
Loading