Skip to content

Commit

Permalink
feat: Add provisional mode where submission can be undone
Browse files Browse the repository at this point in the history
  • Loading branch information
mildred committed Nov 20, 2024
1 parent 548f39b commit c60a945
Show file tree
Hide file tree
Showing 13 changed files with 260 additions and 5 deletions.
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
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
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
13 changes: 13 additions & 0 deletions models/inventory/Transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ export abstract class Transfer extends Transactional {
await this._getStockManager().createTransfers(transferDetails);
}

async beforeSubmitUndo(): Promise<void> {
await super.beforeSubmitUndo();
const transferDetails = this._getTransferDetails();
const stockManager = this._getStockManager();
stockManager.isCancelled = true;
await stockManager.validateCancel(transferDetails);
}

async afterSubmitUndo(): Promise<void> {
await super.afterSubmitUndo();
await this._getStockManager().undoTransfers();
}

async beforeCancel(): Promise<void> {
await super.beforeCancel();
const transferDetails = this._getTransferDetails();
Expand Down
7 changes: 7 additions & 0 deletions schemas/core/SystemSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
8 changes: 8 additions & 0 deletions schemas/meta/submittable.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
93 changes: 92 additions & 1 deletion src/components/Sidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,32 @@

<!-- Report Issue and DB Switcher -->
<div class="window-no-drag flex flex-col gap-2 py-2 px-4">
<button
class="
flex
text-sm text-gray-600
dark:text-gray-500
hover:text-gray-800
dark:hover:text-gray-400
gap-1
items-center
"
@click="toggleProvisionalMode"
:title="
isProvisionalMode()
? t`Provisional mode since` + ' ' + provisionalModeDate()
: ''
"
>
<feather-icon
:name="isProvisionalMode() ? 'pause-circle' : 'play-circle'"
class="h-4 w-4 flex-shrink-0"
/>
<p>
{{ isProvisionalMode() ? t`Provisional mode` : t`Definitive mode` }}
</p>
</button>

<button
class="
flex
Expand Down Expand Up @@ -184,7 +210,12 @@
@click="showDevMode = false"
title="Open dev tools with Ctrl+Shift+I"
>
dev mode
<feather-icon
name="hash"
class="h-4 w-4 flex-shrink-0"
style="display: inline"
/>
hide dev mode
</p>
</div>

Expand Down Expand Up @@ -214,6 +245,7 @@
</div>
</template>
<script lang="ts">
import { t } from 'fyo';
import { reportIssue } from 'src/errorHandling';
import { fyo } from 'src/initFyo';
import { languageDirectionKey, shortcutsKey } from 'src/utils/injectionKeys';
Expand All @@ -226,6 +258,7 @@ import router from '../router';
import Icon from './Icon.vue';
import Modal from './Modal.vue';
import ShortcutsHelper from './ShortcutsHelper.vue';
import { showDialog } from 'src/utils/interactive';
const COMPONENT_NAME = 'Sidebar';
Expand Down Expand Up @@ -291,6 +324,64 @@ export default defineComponent({
routeTo,
reportIssue,
toggleSidebar,
async toggleProvisionalMode() {
let title, detail, provisionalModeSince, showUnlimited;
if (fyo.singles.SystemSettings?.provisionalModeSince != null) {
title = t`Leave provisional mode?`;
detail = t`All submissions while provisional mode was effective will be definitive.`;
provisionalModeSince = null;
} else {
title = t`Enter provisional mode?`;
detail = t`Documents submission while in provisional mode can be undone.`;
provisionalModeSince = new Date();
showUnlimited = this.showDevMode;
}
let response = (await showDialog({
title,
detail,
type: 'warning',
buttons: [
{
label: t`Yes`,
action() {
return true;
},
isPrimary: true,
},
{
label: t`No`,
action() {
return false;
},
isEscape: true,
},
showUnlimited
? {
label: t`Unlimited`,
action() {
provisionalModeSince = new Date(0);
return true;
},
isPrimary: false,
}
: null,
].filter((x) => x),
})) as boolean;
if (response) {
await fyo.singles.SystemSettings?.setAndSync(
'provisionalModeSince',
provisionalModeSince
);
}
},
isProvisionalMode() {
return fyo.singles.SystemSettings?.provisionalModeSince != null;
},
provisionalModeDate() {
return fyo.singles.SystemSettings?.provisionalModeSince;
},
openDocumentation() {
ipc.openLink('https://docs.frappe.io/' + docsPathRef.value);
},
Expand Down
Loading

0 comments on commit c60a945

Please sign in to comment.