mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-18 16:54:25 +08:00
support using duplicate checker to prevent duplicate submissions for new transaction record
This commit is contained in:
+25
-1
@@ -2,6 +2,8 @@ import Clipboard from 'clipboard';
|
||||
import CryptoJS from 'crypto-js';
|
||||
import uaParser from 'ua-parser-js';
|
||||
|
||||
import { base64encode } from './common.js';
|
||||
|
||||
export function asyncLoadAssets(type, assetUrl) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
let addElement = false;
|
||||
@@ -71,10 +73,32 @@ export function asyncLoadAssets(type, assetUrl) {
|
||||
}
|
||||
|
||||
export function generateRandomString() {
|
||||
const baseString = 'ebk_' + Math.round(new Date().getTime() / 1000) + '_' + Math.random();
|
||||
let baseString = 'ebk_' + new Date().getTime();
|
||||
|
||||
if (crypto && crypto.getRandomValues) {
|
||||
const randoms = new Uint8Array(256);
|
||||
crypto.getRandomValues(randoms);
|
||||
baseString += '_' + base64encode(randoms);
|
||||
} else {
|
||||
baseString += '_' + Math.random();
|
||||
}
|
||||
|
||||
return CryptoJS.SHA256(baseString).toString();
|
||||
}
|
||||
|
||||
export function generateRandomUUID() {
|
||||
const randomString = generateRandomString();
|
||||
|
||||
// convert hash string to UUID Version 8
|
||||
const uuid = randomString.substring(0, 8) + '-'
|
||||
+ randomString.substring(8, 12) + '-'
|
||||
+ '8' + randomString.substring(13, 16) + '-'
|
||||
+ (0x8 | (parseInt(randomString.charAt(16), 16) & 0x3)).toString(16) + randomString.substring(17, 20) + '-'
|
||||
+ randomString.substring(20, 32);
|
||||
|
||||
return uuid;
|
||||
}
|
||||
|
||||
export function parseUserAgent(ua) {
|
||||
const uaParseRet = uaParser(ua);
|
||||
|
||||
|
||||
+9
-6
@@ -238,7 +238,7 @@ export default {
|
||||
getAccount: ({ id }) => {
|
||||
return axios.get('v1/accounts/get.json?id=' + id);
|
||||
},
|
||||
addAccount: ({ category, type, name, icon, color, currency, balance, comment, subAccounts }) => {
|
||||
addAccount: ({ category, type, name, icon, color, currency, balance, comment, subAccounts, clientSessionId }) => {
|
||||
return axios.post('v1/accounts/add.json', {
|
||||
category,
|
||||
type,
|
||||
@@ -248,7 +248,8 @@ export default {
|
||||
currency,
|
||||
balance,
|
||||
comment,
|
||||
subAccounts
|
||||
subAccounts,
|
||||
clientSessionId
|
||||
});
|
||||
},
|
||||
modifyAccount: ({ id, category, name, icon, color, comment, hidden, subAccounts }) => {
|
||||
@@ -383,7 +384,7 @@ export default {
|
||||
getTransaction: ({ id }) => {
|
||||
return axios.get(`v1/transactions/get.json?id=${id}&trim_account=true&trim_category=true&trim_tag=true`);
|
||||
},
|
||||
addTransaction: ({ type, categoryId, time, sourceAccountId, destinationAccountId, sourceAmount, destinationAmount, hideAmount, tagIds, comment, geoLocation, utcOffset }) => {
|
||||
addTransaction: ({ type, categoryId, time, sourceAccountId, destinationAccountId, sourceAmount, destinationAmount, hideAmount, tagIds, comment, geoLocation, utcOffset, clientSessionId }) => {
|
||||
return axios.post('v1/transactions/add.json', {
|
||||
type,
|
||||
categoryId,
|
||||
@@ -396,7 +397,8 @@ export default {
|
||||
tagIds,
|
||||
comment,
|
||||
geoLocation,
|
||||
utcOffset
|
||||
utcOffset,
|
||||
clientSessionId
|
||||
});
|
||||
},
|
||||
modifyTransaction: ({ id, type, categoryId, time, sourceAccountId, destinationAccountId, sourceAmount, destinationAmount, hideAmount, tagIds, comment, geoLocation, utcOffset }) => {
|
||||
@@ -427,14 +429,15 @@ export default {
|
||||
getTransactionCategory: ({ id }) => {
|
||||
return axios.get('v1/transaction/categories/get.json?id=' + id);
|
||||
},
|
||||
addTransactionCategory: ({ name, type, parentId, icon, color, comment }) => {
|
||||
addTransactionCategory: ({ name, type, parentId, icon, color, comment, clientSessionId }) => {
|
||||
return axios.post('v1/transaction/categories/add.json', {
|
||||
name,
|
||||
type,
|
||||
parentId,
|
||||
icon,
|
||||
color,
|
||||
comment
|
||||
comment,
|
||||
clientSessionId
|
||||
});
|
||||
},
|
||||
addTransactionCategoryBatch: ({ categories }) => {
|
||||
|
||||
@@ -736,7 +736,7 @@ export const useAccountsStore = defineStore('accounts', {
|
||||
});
|
||||
});
|
||||
},
|
||||
saveAccount({ account, subAccounts, isEdit }) {
|
||||
saveAccount({ account, subAccounts, isEdit, clientSessionId }) {
|
||||
const self = this;
|
||||
|
||||
const submitSubAccounts = [];
|
||||
@@ -776,6 +776,10 @@ export const useAccountsStore = defineStore('accounts', {
|
||||
subAccounts: account.type === accountConstants.allAccountTypes.SingleAccount ? null : submitSubAccounts,
|
||||
};
|
||||
|
||||
if (clientSessionId) {
|
||||
submitAccount.clientSessionId = clientSessionId;
|
||||
}
|
||||
|
||||
if (isEdit) {
|
||||
submitAccount.id = account.id;
|
||||
submitAccount.hidden = !account.visible;
|
||||
|
||||
@@ -745,7 +745,7 @@ export const useTransactionsStore = defineStore('transactions', {
|
||||
});
|
||||
});
|
||||
},
|
||||
saveTransaction({ transaction, defaultCurrency, isEdit }) {
|
||||
saveTransaction({ transaction, defaultCurrency, isEdit, clientSessionId }) {
|
||||
const self = this;
|
||||
const settingsStore = useSettingsStore();
|
||||
const exchangeRatesStore = useExchangeRatesStore();
|
||||
@@ -764,6 +764,10 @@ export const useTransactionsStore = defineStore('transactions', {
|
||||
utcOffset: transaction.utcOffset
|
||||
};
|
||||
|
||||
if (clientSessionId) {
|
||||
submitTransaction.clientSessionId = clientSessionId;
|
||||
}
|
||||
|
||||
if (transaction.type === transactionConstants.allTransactionTypes.Expense) {
|
||||
submitTransaction.categoryId = transaction.expenseCategory;
|
||||
} else if (transaction.type === transactionConstants.allTransactionTypes.Income) {
|
||||
|
||||
@@ -257,7 +257,7 @@ export const useTransactionCategoriesStore = defineStore('transactionCategories'
|
||||
});
|
||||
});
|
||||
},
|
||||
saveCategory({ category, isEdit }) {
|
||||
saveCategory({ category, isEdit, clientSessionId }) {
|
||||
const self = this;
|
||||
|
||||
const submitCategory = {
|
||||
@@ -269,6 +269,10 @@ export const useTransactionCategoriesStore = defineStore('transactionCategories'
|
||||
comment: category.comment
|
||||
};
|
||||
|
||||
if (clientSessionId) {
|
||||
submitCategory.clientSessionId = clientSessionId;
|
||||
}
|
||||
|
||||
if (isEdit) {
|
||||
submitCategory.id = category.id;
|
||||
submitCategory.hidden = !category.visible;
|
||||
|
||||
@@ -181,6 +181,7 @@ import accountConstants from '@/consts/account.js';
|
||||
import iconConstants from '@/consts/icon.js';
|
||||
import colorConstants from '@/consts/color.js';
|
||||
import { isNumber } from '@/lib/common.js';
|
||||
import { generateRandomUUID } from '@/lib/misc.js';
|
||||
import {
|
||||
setAccountModelByAnotherAccount,
|
||||
setAccountSuitableIcon
|
||||
@@ -208,6 +209,7 @@ export default {
|
||||
showState: false,
|
||||
activeTab: 'account',
|
||||
editAccountId: null,
|
||||
clientSessionId: '',
|
||||
loading: false,
|
||||
account: newAccount,
|
||||
subAccounts: [],
|
||||
@@ -314,6 +316,7 @@ export default {
|
||||
}
|
||||
|
||||
self.editAccountId = null;
|
||||
self.clientSessionId = generateRandomUUID();
|
||||
self.loading = false;
|
||||
}
|
||||
|
||||
@@ -370,7 +373,8 @@ export default {
|
||||
self.accountsStore.saveAccount({
|
||||
account: self.account,
|
||||
subAccounts: self.subAccounts,
|
||||
isEdit: !!self.editAccountId
|
||||
isEdit: !!self.editAccountId,
|
||||
clientSessionId: self.clientSessionId
|
||||
}).then(() => {
|
||||
self.submitting = false;
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ import { useTransactionCategoriesStore } from '@/stores/transactionCategory.js';
|
||||
import categoryConstants from '@/consts/category.js';
|
||||
import iconConstants from '@/consts/icon.js';
|
||||
import colorConstants from '@/consts/color.js';
|
||||
import { generateRandomUUID } from '@/lib/misc.js';
|
||||
import {
|
||||
setCategoryModelByAnotherCategory,
|
||||
allVisiblePrimaryTransactionCategoriesByType
|
||||
@@ -121,6 +122,7 @@ export default {
|
||||
return {
|
||||
showState: false,
|
||||
editCategoryId: null,
|
||||
clientSessionId: '',
|
||||
loading: false,
|
||||
category: newTransactionCategory,
|
||||
submitting: false,
|
||||
@@ -220,6 +222,7 @@ export default {
|
||||
self.category.type = categoryType;
|
||||
self.category.parentId = options.parentId;
|
||||
|
||||
self.clientSessionId = generateRandomUUID();
|
||||
self.loading = false;
|
||||
}
|
||||
|
||||
@@ -242,7 +245,8 @@ export default {
|
||||
|
||||
self.transactionCategoriesStore.saveCategory({
|
||||
category: self.category,
|
||||
isEdit: !!self.editCategoryId
|
||||
isEdit: !!self.editCategoryId,
|
||||
clientSessionId: self.clientSessionId
|
||||
}).then(() => {
|
||||
self.submitting = false;
|
||||
|
||||
|
||||
@@ -338,6 +338,7 @@ import {
|
||||
getTimezoneOffsetMinutes,
|
||||
getCurrentUnixTime
|
||||
} from '@/lib/datetime.js';
|
||||
import { generateRandomUUID } from '@/lib/misc.js';
|
||||
import {
|
||||
getFirstAvailableCategoryId
|
||||
} from '@/lib/category.js';
|
||||
@@ -370,6 +371,7 @@ export default {
|
||||
activeTab: 'basicInfo',
|
||||
editTransactionId: null,
|
||||
originalTransactionEditable: false,
|
||||
clientSessionId: '',
|
||||
loading: true,
|
||||
transaction: newTransaction,
|
||||
geoLocationStatus: null,
|
||||
@@ -642,6 +644,10 @@ export default {
|
||||
self.transaction.type = parseInt(options.type);
|
||||
}
|
||||
|
||||
if (self.mode === 'add') {
|
||||
self.clientSessionId = generateRandomUUID();
|
||||
}
|
||||
|
||||
Promise.all(promises).then(function (responses) {
|
||||
if (self.editTransactionId && !responses[3]) {
|
||||
if (self.reject) {
|
||||
@@ -691,7 +697,8 @@ export default {
|
||||
self.transactionsStore.saveTransaction({
|
||||
transaction: self.transaction,
|
||||
defaultCurrency: self.defaultCurrency,
|
||||
isEdit: self.mode === 'edit'
|
||||
isEdit: self.mode === 'edit',
|
||||
clientSessionId: self.clientSessionId
|
||||
}).then(() => {
|
||||
self.submitting = false;
|
||||
|
||||
|
||||
@@ -428,6 +428,7 @@ import iconConstants from '@/consts/icon.js';
|
||||
import colorConstants from '@/consts/color.js';
|
||||
import transactionConstants from '@/consts/transaction.js';
|
||||
import { getNameByKeyValue } from '@/lib/common.js';
|
||||
import { generateRandomUUID } from '@/lib/misc.js';
|
||||
import {
|
||||
setAccountModelByAnotherAccount,
|
||||
setAccountSuitableIcon
|
||||
@@ -447,6 +448,7 @@ export default {
|
||||
|
||||
return {
|
||||
editAccountId: null,
|
||||
clientSessionId: '',
|
||||
loading: false,
|
||||
loadingError: null,
|
||||
account: newAccount,
|
||||
@@ -547,6 +549,7 @@ export default {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.clientSessionId = generateRandomUUID();
|
||||
self.loading = false;
|
||||
}
|
||||
},
|
||||
@@ -614,7 +617,8 @@ export default {
|
||||
self.accountsStore.saveAccount({
|
||||
account: self.account,
|
||||
subAccounts: self.subAccounts,
|
||||
isEdit: !!self.editAccountId
|
||||
isEdit: !!self.editAccountId,
|
||||
clientSessionId: self.clientSessionId
|
||||
}).then(() => {
|
||||
self.submitting = false;
|
||||
self.$hideLoading();
|
||||
|
||||
@@ -153,6 +153,7 @@ import categoryConstants from '@/consts/category.js';
|
||||
import iconConstants from '@/consts/icon.js';
|
||||
import colorConstants from '@/consts/color.js';
|
||||
import { getNameByKeyValue } from '@/lib/common.js';
|
||||
import { generateRandomUUID } from '@/lib/misc.js';
|
||||
import {
|
||||
setCategoryModelByAnotherCategory,
|
||||
allVisiblePrimaryTransactionCategoriesByType
|
||||
@@ -172,6 +173,7 @@ export default {
|
||||
|
||||
return {
|
||||
editCategoryId: null,
|
||||
clientSessionId: '',
|
||||
loading: false,
|
||||
loadingError: null,
|
||||
category: newTransactionCategory,
|
||||
@@ -257,6 +259,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
self.clientSessionId = generateRandomUUID();
|
||||
self.loading = false;
|
||||
}
|
||||
},
|
||||
@@ -280,7 +283,8 @@ export default {
|
||||
|
||||
self.transactionCategoriesStore.saveCategory({
|
||||
category: self.category,
|
||||
isEdit: !!self.editCategoryId
|
||||
isEdit: !!self.editCategoryId,
|
||||
clientSessionId: self.clientSessionId
|
||||
}).then(() => {
|
||||
self.submitting = false;
|
||||
self.$hideLoading();
|
||||
|
||||
@@ -367,6 +367,7 @@ import {
|
||||
getUtcOffsetByUtcOffsetMinutes,
|
||||
getActualUnixTimeForStore
|
||||
} from '@/lib/datetime.js';
|
||||
import { generateRandomUUID } from '@/lib/misc.js';
|
||||
import {
|
||||
getTransactionPrimaryCategoryName,
|
||||
getTransactionSecondaryCategoryName,
|
||||
@@ -389,6 +390,7 @@ export default {
|
||||
mode: 'add',
|
||||
editTransactionId: null,
|
||||
transaction: newTransaction,
|
||||
clientSessionId: '',
|
||||
loading: true,
|
||||
loadingError: null,
|
||||
geoLocationStatus: null,
|
||||
@@ -663,6 +665,10 @@ export default {
|
||||
self.transaction.type = parseInt(query.type);
|
||||
}
|
||||
|
||||
if (self.mode === 'add') {
|
||||
self.clientSessionId = generateRandomUUID();
|
||||
}
|
||||
|
||||
Promise.all(promises).then(function (responses) {
|
||||
if (query.id && !responses[3]) {
|
||||
self.$toast('Unable to retrieve transaction');
|
||||
@@ -723,7 +729,8 @@ export default {
|
||||
self.transactionsStore.saveTransaction({
|
||||
transaction: self.transaction,
|
||||
defaultCurrency: self.defaultCurrency,
|
||||
isEdit: self.mode === 'edit'
|
||||
isEdit: self.mode === 'edit',
|
||||
clientSessionId: self.clientSessionId
|
||||
}).then(() => {
|
||||
self.submitting = false;
|
||||
self.$hideLoading();
|
||||
|
||||
Reference in New Issue
Block a user