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 39a376016..dd4666a0c 100644 --- a/schemas/core/SystemSettings.json +++ b/schemas/core/SystemSettings.json @@ -125,6 +125,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 @@