From 548f39b802f4943afd4bbe8c70d63fab103fbfbd Mon Sep 17 00:00:00 2001 From: Mildred Ki'Lya Date: Wed, 20 Nov 2024 01:54:49 +0100 Subject: [PATCH 01/11] feat: allow to turn off filtering --- schemas/core/SystemSettings.json | 8 ++++ src/components/Controls/Link.vue | 65 +++++++++++++++++++++----------- translations/fr.csv | 5 ++- 3 files changed, 55 insertions(+), 23 deletions(-) diff --git a/schemas/core/SystemSettings.json b/schemas/core/SystemSettings.json index 39a376016..f55ca4f4f 100644 --- a/schemas/core/SystemSettings.json +++ b/schemas/core/SystemSettings.json @@ -64,6 +64,14 @@ "readOnly": true, "section": "Default" }, + { + "fieldname": "allowFilterBypass", + "label": "Allow to bypass filters", + "fieldtype": "Check", + "default": false, + "description": "When linking documents, if no match is found and filtering is in effect, allow to disable filters.", + "section": "Default" + }, { "fieldname": "locale", "label": "Locale", diff --git a/src/components/Controls/Link.vue b/src/components/Controls/Link.vue index fe48501ce..99af4af5b 100644 --- a/src/components/Controls/Link.vue +++ b/src/components/Controls/Link.vue @@ -11,7 +11,7 @@ export default { name: 'Link', extends: AutoComplete, data() { - return { results: [] }; + return { results: [], filtersDisabled: false }; }, watch: { value: { @@ -45,7 +45,7 @@ export default { getTargetSchemaName() { return this.df.target; }, - async getOptions() { + async getOptions(filters) { const schemaName = this.getTargetSchemaName(); if (!schemaName) { return []; @@ -56,7 +56,6 @@ export default { } const schema = fyo.schemaMap[schemaName]; - const filters = await this.getFilters(); const fields = [ ...new Set(['name', schema.titleField, this.df.groupBy]), @@ -78,7 +77,8 @@ export default { .filter(Boolean)); }, async getSuggestions(keyword = '') { - let options = await this.getOptions(); + let filters = this.filtersDisabled ? null : await this.getFilters(); + let options = await this.getOptions(filters || {}); if (keyword) { options = options @@ -88,21 +88,34 @@ export default { .map(({ item }) => item); } - if (this.doc && this.df.create) { - options = options.concat(this.getCreateNewOption()); + if (options.length === 0 && !this.df.emptyMessage) { + if (filters && !!fyo.singles.SystemSettings?.allowFilterBypass) { + options = [ + { + component: markRaw({ + template: + '{{ t`No results found, disable filters` }}', + }), + action: () => this.disableFiltering(), + actionOnly: true, + }, + ]; + } else if (!this.doc || !this.df.create) { + options = [ + { + component: markRaw({ + template: + '{{ t`No results found` }}', + }), + action: () => {}, + actionOnly: true, + }, + ]; + } } - if (options.length === 0 && !this.df.emptyMessage) { - return [ - { - component: markRaw({ - template: - '{{ t`No results found` }}', - }), - action: () => {}, - actionOnly: true, - }, - ]; + if (this.doc && this.df.create) { + options = options.concat(this.getCreateNewOption()); } return options; @@ -129,6 +142,14 @@ export default { }), }; }, + disableFiltering(keyword) { + this.filtersDisabled = true; + this.results = []; + setTimeout(() => { + this.toggleDropdown(true); + this.updateSuggestions(keyword); + }, 1); + }, async openNewDoc() { const schemaName = this.df.target; const name = @@ -155,7 +176,7 @@ export default { return createFilters; } - const filters = await this.getFilters(); + const filters = (await this.getFilters()) ?? {}; return getCreateFiltersFromListViewFilters(filters); }, async getFilters() { @@ -163,17 +184,17 @@ export default { const getFilters = fyo.models[schemaName]?.filters?.[fieldname]; if (getFilters === undefined) { - return {}; + return null; } if (this.doc) { - return (await getFilters(this.doc)) ?? {}; + return await getFilters(this.doc); } try { - return (await getFilters()) ?? {}; + return await getFilters(); } catch { - return {}; + return null; } }, }, diff --git a/translations/fr.csv b/translations/fr.csv index a31c5993a..37d07a0b6 100644 --- a/translations/fr.csv +++ b/translations/fr.csv @@ -572,6 +572,7 @@ No,Non, "No filters selected","Aucun filtre sélectionné", "No linked entries found",, "No results found","Aucun résultat trouvé", +"No results found, disable filters","Aucun résultat trouvé, désactiver les filtres", "No rows added. Select a file or add rows.",, "No transactions yet","Aucune transaction pour le moment", "Non Active Serial Number ${0} cannot be used as Manufacture raw material",, @@ -1055,4 +1056,6 @@ Yes,Oui, "check values and click on","Vérifiez les valeurs et cliquez sur", "in Batch ${0}",, john@doe.com,, -"to apply changes","pour appliquer les changements", \ No newline at end of file +"to apply changes","pour appliquer les changements", +"Allow to bypass filters","Autoriser la désactivation des filtres" +"When linking documents, if no match is found and filtering is in effect, allow to disable filters.","Lors de la sélection d'un document lié, autoriser à désactiver les filtres si aucun résultat n'est trouvé" From c60a9453f4f0d55b23f6042cf5b78d9a34e2fdcb Mon Sep 17 00:00:00 2001 From: Mildred Ki'Lya Date: Wed, 20 Nov 2024 11:35:58 +0100 Subject: [PATCH 02/11] feat: Add provisional mode where submission can be undone --- fyo/model/doc.ts | 44 +++++++++++++ fyo/telemetry/types.ts | 1 + models/Transactional/Transactional.ts | 13 ++++ models/baseModels/Invoice/Invoice.ts | 30 ++++++++- models/baseModels/Payment/Payment.ts | 5 ++ models/inventory/StockManager.ts | 4 ++ models/inventory/StockMovement.ts | 5 ++ models/inventory/StockTransfer.ts | 7 ++ models/inventory/Transfer.ts | 13 ++++ schemas/core/SystemSettings.json | 7 ++ schemas/meta/submittable.json | 8 +++ src/components/Sidebar.vue | 93 ++++++++++++++++++++++++++- src/utils/ui.ts | 35 +++++++++- 13 files changed, 260 insertions(+), 5 deletions(-) diff --git a/fyo/model/doc.ts b/fyo/model/doc.ts index a5b23b6d2..60364ffcd 100644 --- a/fyo/model/doc.ts +++ b/fyo/model/doc.ts @@ -223,6 +223,30 @@ export class Doc extends Observable { 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; @@ -344,14 +368,17 @@ export class Doc extends Observable { 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; } @@ -940,12 +967,27 @@ export class Doc extends Observable { 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; @@ -1058,6 +1100,8 @@ export class Doc extends Observable { async afterSync() {} async beforeSubmit() {} async afterSubmit() {} + async beforeSubmitUndo() {} + async afterSubmitUndo() {} async beforeRename() {} async afterRename() {} async beforeCancel() {} diff --git a/fyo/telemetry/types.ts b/fyo/telemetry/types.ts index 79a65222c..42a72100e 100644 --- a/fyo/telemetry/types.ts +++ b/fyo/telemetry/types.ts @@ -8,6 +8,7 @@ export enum Verb { Created = 'created', Deleted = 'deleted', Submitted = 'submitted', + SubmitUndone = 'submitUndone', Cancelled = 'cancelled', Imported = 'imported', Exported = 'exported', diff --git a/models/Transactional/Transactional.ts b/models/Transactional/Transactional.ts index 76337a5d2..b02ee30d4 100644 --- a/models/Transactional/Transactional.ts +++ b/models/Transactional/Transactional.ts @@ -56,6 +56,15 @@ export abstract class Transactional extends Doc { await posting.post(); } + async afterSubmitUndo(): Promise { + await super.afterSubmitUndo(); + if (!this.isTransactional) { + return; + } + + await this._deletePostings(); + } + async afterCancel(): Promise { await super.afterCancel(); if (!this.isTransactional) { @@ -76,6 +85,10 @@ export abstract class Transactional extends Doc { return; } + await this._deletePostings(); + } + + async _deletePostings(): Promise { const ledgerEntryIds = (await this.fyo.db.getAll( ModelNameEnum.AccountingLedgerEntry, { diff --git a/models/baseModels/Invoice/Invoice.ts b/models/baseModels/Invoice/Invoice.ts index 4deca4086..7d436f01d 100644 --- a/models/baseModels/Invoice/Invoice.ts +++ b/models/baseModels/Invoice/Invoice.ts @@ -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(); @@ -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(); + } } } diff --git a/models/baseModels/Payment/Payment.ts b/models/baseModels/Payment/Payment.ts index 3f021ec3c..33e626df8 100644 --- a/models/baseModels/Payment/Payment.ts +++ b/models/baseModels/Payment/Payment.ts @@ -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(); diff --git a/models/inventory/StockManager.ts b/models/inventory/StockManager.ts index 392d32e1b..da0baffb8 100644 --- a/models/inventory/StockManager.ts +++ b/models/inventory/StockManager.ts @@ -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, { diff --git a/models/inventory/StockMovement.ts b/models/inventory/StockMovement.ts index cd24b54b1..bed559bfa 100644 --- a/models/inventory/StockMovement.ts +++ b/models/inventory/StockMovement.ts @@ -69,6 +69,11 @@ export class StockMovement extends Transfer { await updateSerialNumbers(this, false); } + async afterSubmitUndo(): Promise { + await super.afterSubmitUndo(); + await updateSerialNumbers(this, true); + } + async afterCancel(): Promise { await super.afterCancel(); await updateSerialNumbers(this, true); diff --git a/models/inventory/StockTransfer.ts b/models/inventory/StockTransfer.ts index 49c6509c8..020b58b29 100644 --- a/models/inventory/StockTransfer.ts +++ b/models/inventory/StockTransfer.ts @@ -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 { await super.afterCancel(); await updateSerialNumbers(this, true, this.isReturn); diff --git a/models/inventory/Transfer.ts b/models/inventory/Transfer.ts index 567dfd45b..b6ce74897 100644 --- a/models/inventory/Transfer.ts +++ b/models/inventory/Transfer.ts @@ -21,6 +21,19 @@ export abstract class Transfer extends Transactional { await this._getStockManager().createTransfers(transferDetails); } + async beforeSubmitUndo(): Promise { + await super.beforeSubmitUndo(); + const transferDetails = this._getTransferDetails(); + const stockManager = this._getStockManager(); + stockManager.isCancelled = true; + await stockManager.validateCancel(transferDetails); + } + + async afterSubmitUndo(): Promise { + await super.afterSubmitUndo(); + await this._getStockManager().undoTransfers(); + } + async beforeCancel(): Promise { await super.beforeCancel(); const transferDetails = this._getTransferDetails(); diff --git a/schemas/core/SystemSettings.json b/schemas/core/SystemSettings.json index f55ca4f4f..123ceb4d1 100644 --- a/schemas/core/SystemSettings.json +++ b/schemas/core/SystemSettings.json @@ -133,6 +133,13 @@ "default": false, "description": "Sets the theme of the app.", "section": "Theme" + }, + { + "fieldname": "provisionalModeSince", + "label": "Provisional Mode Since", + "fieldtype": "Datetime", + "description": "Date since the provisional mode is set, or NULL for definitive mode", + "hidden": true } ], "quickEditFields": [ diff --git a/schemas/meta/submittable.json b/schemas/meta/submittable.json index f02772ac5..85c6757bd 100644 --- a/schemas/meta/submittable.json +++ b/schemas/meta/submittable.json @@ -9,6 +9,14 @@ "meta": true, "section": "System" }, + { + "fieldname": "submittedAt", + "label": "Submition Date", + "fieldtype": "Datetime", + "required": false, + "meta": true, + "section": "System" + }, { "fieldname": "cancelled", "label": "Cancelled", diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue index 800c433b1..ed47d1ec1 100644 --- a/src/components/Sidebar.vue +++ b/src/components/Sidebar.vue @@ -109,6 +109,32 @@
+ +
@@ -214,6 +245,7 @@ diff --git a/src/pages/Report.vue b/src/pages/Report.vue index 02d94de17..cebade190 100644 --- a/src/pages/Report.vue +++ b/src/pages/Report.vue @@ -1,6 +1,9 @@