Compare commits
34 Commits
v0.19.6
...
listen-pay
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60f03f534b | ||
|
|
8e94c7a755 | ||
|
|
3a2ca36c07 | ||
|
|
88ece74c8a | ||
|
|
67a8610328 | ||
|
|
278d61ce61 | ||
|
|
e72d6ad6b8 | ||
|
|
bf66b31679 | ||
|
|
b4d426d2e8 | ||
|
|
7f7dd270e7 | ||
|
|
f6bad8fe30 | ||
|
|
820b363f79 | ||
|
|
07740a51ef | ||
|
|
d15fb6fe19 | ||
|
|
5749ccec81 | ||
|
|
4a99f6c0cf | ||
|
|
59f480f9d5 | ||
|
|
6cb9c919b5 | ||
|
|
1062b65b5b | ||
|
|
bf3a70cabd | ||
|
|
f46cd28f87 | ||
|
|
dffcfe50aa | ||
|
|
fac55efbc7 | ||
|
|
8b90ce5f6c | ||
|
|
705b8da053 | ||
|
|
4a05ccc692 | ||
|
|
3200d65d90 | ||
|
|
3f23038227 | ||
|
|
408c807fc2 | ||
|
|
d29079a8c5 | ||
|
|
cca596b4a9 | ||
|
|
fed620505d | ||
|
|
a008aea3f3 | ||
|
|
df8b68fda6 |
@@ -46,6 +46,10 @@ export class ExcludeBankTransactionsController extends BaseController {
|
||||
query('account_id').optional().isNumeric().toInt(),
|
||||
query('page').optional().isNumeric().toInt(),
|
||||
query('page_size').optional().isNumeric().toInt(),
|
||||
query('min_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||
query('max_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||
query('min_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||
query('max_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||
],
|
||||
this.validationResult,
|
||||
this.getExcludedBankTransactions.bind(this)
|
||||
|
||||
@@ -21,6 +21,10 @@ export class RecognizedTransactionsController extends BaseController {
|
||||
query('page').optional().isNumeric().toInt(),
|
||||
query('page_size').optional().isNumeric().toInt(),
|
||||
query('account_id').optional().isNumeric().toInt(),
|
||||
query('min_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||
query('max_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||
query('min_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||
query('max_amount').optional({ nullable: true }).isFloat().isFloat(),
|
||||
],
|
||||
this.validationResult,
|
||||
this.getRecognizedTransactions.bind(this)
|
||||
|
||||
@@ -84,6 +84,10 @@ export default class NewCashflowTransactionController extends BaseController {
|
||||
param('id').exists().isNumeric().toInt(),
|
||||
query('page').optional().isNumeric().toInt(),
|
||||
query('page_size').optional().isNumeric().toInt(),
|
||||
query('min_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||
query('max_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||
query('min_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||
query('max_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { body } from 'express-validator';
|
||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||
import BaseController from '@/api/controllers/BaseController';
|
||||
import { OneClickDemoApplication } from '@/services/OneClickDemo/OneClickDemoApplication';
|
||||
import config from '@/config';
|
||||
@Service()
|
||||
export class OneClickDemoController extends BaseController {
|
||||
@Inject()
|
||||
private oneClickDemoApp: OneClickDemoApplication;
|
||||
|
||||
/**
|
||||
* Router constructor method.
|
||||
*/
|
||||
router() {
|
||||
const router = Router();
|
||||
|
||||
// Protects the endpoints if the feature is not enabled.
|
||||
const protectMiddleware = (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
// Add your protection logic here
|
||||
if (config.oneClickDemoAccounts) {
|
||||
next();
|
||||
} else {
|
||||
res.status(403).send({ message: 'Forbidden' });
|
||||
}
|
||||
};
|
||||
router.post(
|
||||
'/one_click',
|
||||
protectMiddleware,
|
||||
asyncMiddleware(this.oneClickDemo.bind(this))
|
||||
);
|
||||
router.post(
|
||||
'/one_click_signin',
|
||||
[body('demo_id').exists()],
|
||||
this.validationResult,
|
||||
protectMiddleware,
|
||||
asyncMiddleware(this.oneClickSignIn.bind(this))
|
||||
);
|
||||
return router;
|
||||
}
|
||||
|
||||
/**
|
||||
* One-click demo application.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @param {NextFunction} next -
|
||||
*/
|
||||
private async oneClickDemo(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const data = await this.oneClickDemoApp.createOneClick();
|
||||
|
||||
return res.status(200).send({
|
||||
data,
|
||||
message: 'The one-click demo has been created successfully.',
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign-in to one-click demo account.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
private async oneClickSignIn(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { demoId } = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
const data = await this.oneClickDemoApp.autoSignIn(demoId);
|
||||
|
||||
return res.status(200).send(data);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,7 @@ export class SubscriptionController extends BaseController {
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
await this.subscriptionApp.cancelSubscription(tenantId, '455610');
|
||||
await this.subscriptionApp.cancelSubscription(tenantId);
|
||||
|
||||
return res.status(200).send({
|
||||
status: 200,
|
||||
|
||||
@@ -35,7 +35,7 @@ export class Webhooks extends BaseController {
|
||||
*/
|
||||
public async lemonWebhooks(req: Request, res: Response, next: NextFunction) {
|
||||
const data = req.body;
|
||||
const signature = req.headers['x-signature'] ?? '';
|
||||
const signature = req.headers['x-signature'] as string ?? '';
|
||||
const rawBody = req.rawBody;
|
||||
|
||||
try {
|
||||
|
||||
@@ -63,6 +63,7 @@ import { BankingController } from './controllers/Banking/BankingController';
|
||||
import { Webhooks } from './controllers/Webhooks/Webhooks';
|
||||
import { ExportController } from './controllers/Export/ExportController';
|
||||
import { AttachmentsController } from './controllers/Attachments/AttachmentsController';
|
||||
import { OneClickDemoController } from './controllers/OneClickDemo/OneClickDemoController';
|
||||
|
||||
export default () => {
|
||||
const app = Router();
|
||||
@@ -80,6 +81,7 @@ export default () => {
|
||||
app.use('/jobs', Container.get(Jobs).router());
|
||||
app.use('/account', Container.get(Account).router());
|
||||
app.use('/webhooks', Container.get(Webhooks).router());
|
||||
app.use('/demo', Container.get(OneClickDemoController).router())
|
||||
|
||||
// - Dashboard routes.
|
||||
// ---------------------------
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Container } from 'typedi';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import SettingsStore from '@/services/Settings/SettingsStore';
|
||||
|
||||
|
||||
export default async (req: Request, res: Response, next: NextFunction) => {
|
||||
const { tenantId } = req.user;
|
||||
|
||||
const Logger = Container.get('logger');
|
||||
const settings = await initializeTenantSettings(tenantId);
|
||||
req.settings = settings;
|
||||
|
||||
res.on('finish', async () => {
|
||||
await settings.save();
|
||||
});
|
||||
next();
|
||||
}
|
||||
|
||||
|
||||
export const initializeTenantSettings = async (tenantId: number) => {
|
||||
const tenantContainer = Container.of(`tenant-${tenantId}`);
|
||||
|
||||
if (tenantContainer && !tenantContainer.has('settings')) {
|
||||
@@ -18,10 +28,5 @@ export default async (req: Request, res: Response, next: NextFunction) => {
|
||||
|
||||
await settings.load();
|
||||
|
||||
req.settings = settings;
|
||||
|
||||
res.on('finish', async () => {
|
||||
await settings.save();
|
||||
});
|
||||
next();
|
||||
return settings;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { Request } from 'express';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import TenantsManagerService from '@/services/Tenancy/TenantsManager';
|
||||
import rtlDetect from 'rtl-detect';
|
||||
import { Tenant } from '@/system/models';
|
||||
|
||||
export default (req: Request, tenant: ITenant) => {
|
||||
const { id: tenantId, organizationId } = tenant;
|
||||
@@ -16,7 +17,7 @@ export default (req: Request, tenant: ITenant) => {
|
||||
|
||||
const tenantContainer = tenantServices.tenantContainer(tenantId);
|
||||
|
||||
tenantContainer.set('i18n', injectI18nUtils(req));
|
||||
tenantContainer.set('i18n', injectI18nUtils());
|
||||
|
||||
const knexInstance = tenantServices.knex(tenantId);
|
||||
const models = tenantServices.models(tenantId);
|
||||
@@ -33,14 +34,35 @@ export default (req: Request, tenant: ITenant) => {
|
||||
};
|
||||
|
||||
export const injectI18nUtils = (req) => {
|
||||
const locale = req.getLocale();
|
||||
const globalI18n = Container.get('i18n');
|
||||
const locale = globalI18n.getLocale();
|
||||
const direction = rtlDetect.getLangDir(locale);
|
||||
|
||||
return {
|
||||
locale,
|
||||
__: req.__,
|
||||
__: globalI18n.__,
|
||||
direction,
|
||||
isRtl: direction === 'rtl',
|
||||
isLtr: direction === 'ltr',
|
||||
};
|
||||
};
|
||||
|
||||
export const initalizeTenantServices = async (tenantId: number) => {
|
||||
const tenant = await Tenant.query()
|
||||
.findById(tenantId)
|
||||
.withGraphFetched('metadata');
|
||||
|
||||
const tenantServices = Container.get(TenancyService);
|
||||
const tenantsManager = Container.get(TenantsManagerService);
|
||||
|
||||
// Initialize the knex instance.
|
||||
tenantsManager.setupKnexInstance(tenant);
|
||||
|
||||
const tenantContainer = tenantServices.tenantContainer(tenantId);
|
||||
tenantContainer.set('i18n', injectI18nUtils());
|
||||
|
||||
tenantServices.knex(tenantId);
|
||||
tenantServices.models(tenantId);
|
||||
tenantServices.repositories(tenantId);
|
||||
tenantServices.cache(tenantId);
|
||||
};
|
||||
|
||||
@@ -245,4 +245,9 @@ module.exports = {
|
||||
loops: {
|
||||
apiKey: process.env.LOOPS_API_KEY,
|
||||
},
|
||||
|
||||
oneClickDemoAccounts: parseBoolean(
|
||||
process.env.ONE_CLICK_DEMO_ACCOUNTS,
|
||||
false
|
||||
),
|
||||
};
|
||||
|
||||
@@ -7,7 +7,6 @@ export interface IRegisterDTO {
|
||||
lastName: string;
|
||||
email: string;
|
||||
password: string;
|
||||
organizationName: string;
|
||||
}
|
||||
|
||||
export interface ILoginDTO {
|
||||
|
||||
@@ -167,11 +167,18 @@ export interface CategorizeTransactionAsExpenseDTO {
|
||||
export interface IGetUncategorizedTransactionsQuery {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
minDate?: Date;
|
||||
maxDate?: Date;
|
||||
minAmount?: number;
|
||||
maxAmount?: number;
|
||||
}
|
||||
|
||||
|
||||
export interface IGetRecognizedTransactionsQuery {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
accountId?: number;
|
||||
}
|
||||
minDate?: Date;
|
||||
maxDate?: Date;
|
||||
minAmount?: number;
|
||||
maxAmount?: number;
|
||||
}
|
||||
|
||||
@@ -33,3 +33,7 @@ export interface IOrganizationBuildEventPayload {
|
||||
buildDTO: IOrganizationBuildDTO;
|
||||
systemUser: ISystemUser;
|
||||
}
|
||||
|
||||
export interface IOrganizationBuiltEventPayload {
|
||||
tenantId: number;
|
||||
}
|
||||
8
packages/server/src/interfaces/Subscription.ts
Normal file
8
packages/server/src/interfaces/Subscription.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface SubscriptionPayload {
|
||||
lemonSqueezyId?: string;
|
||||
}
|
||||
|
||||
export enum SubscriptionPaymentStatus {
|
||||
Succeed = 'succeed',
|
||||
Failed = 'failed',
|
||||
}
|
||||
@@ -75,6 +75,7 @@ export * from './Times';
|
||||
export * from './ProjectProfitabilitySummary';
|
||||
export * from './TaxRate';
|
||||
export * from './Plaid';
|
||||
export * from './Subscription';
|
||||
|
||||
export interface I18nService {
|
||||
__: (input: string) => string;
|
||||
|
||||
@@ -164,6 +164,10 @@ export class Transformer {
|
||||
return date ? moment(date).format(this.dateFormat) : '';
|
||||
}
|
||||
|
||||
protected formatDateFromNow(date){
|
||||
return date ? moment(date).fromNow(true) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param number
|
||||
|
||||
@@ -116,6 +116,8 @@ import { DecrementUncategorizedTransactionOnCategorize } from '@/services/Cashfl
|
||||
import { DisconnectPlaidItemOnAccountDeleted } from '@/services/Banking/BankAccounts/events/DisconnectPlaidItemOnAccountDeleted';
|
||||
import { LoopsEventsSubscriber } from '@/services/Loops/LoopsEventsSubscriber';
|
||||
import { DeleteUncategorizedTransactionsOnAccountDeleting } from '@/services/Banking/BankAccounts/events/DeleteUncategorizedTransactionsOnAccountDeleting';
|
||||
import { SeedInitialDemoAccountDataOnOrgBuild } from '@/services/OneClickDemo/events/SeedInitialDemoAccountData';
|
||||
import { TriggerInvalidateCacheOnSubscriptionChange } from '@/services/Subscription/events/TriggerInvalidateCacheOnSubscriptionChange';
|
||||
|
||||
export default () => {
|
||||
return new EventPublisher();
|
||||
@@ -246,8 +248,10 @@ export const susbcribers = () => {
|
||||
DeleteCashflowTransactionOnUncategorize,
|
||||
PreventDeleteTransactionOnDelete,
|
||||
|
||||
// Subscription
|
||||
SubscribeFreeOnSignupCommunity,
|
||||
SendVerfiyMailOnSignUp,
|
||||
TriggerInvalidateCacheOnSubscriptionChange,
|
||||
|
||||
// Attachments
|
||||
AttachmentsOnSaleInvoiceCreated,
|
||||
@@ -281,6 +285,9 @@ export const susbcribers = () => {
|
||||
DeleteUncategorizedTransactionsOnAccountDeleting,
|
||||
|
||||
// Loops
|
||||
LoopsEventsSubscriber
|
||||
LoopsEventsSubscriber,
|
||||
|
||||
// Demo Account
|
||||
SeedInitialDemoAccountDataOnOrgBuild,
|
||||
];
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { I18n } from 'i18n';
|
||||
|
||||
export default () => new I18n({
|
||||
locales: ['en', 'ar'],
|
||||
defaultLocale: 'en',
|
||||
register: global,
|
||||
directory: global.__locales_dir,
|
||||
updateFiles: false,
|
||||
|
||||
@@ -257,25 +257,25 @@ export default {
|
||||
name: 'item.field.sell_price',
|
||||
fieldType: 'number',
|
||||
},
|
||||
cost_price: {
|
||||
costPrice: {
|
||||
name: 'item.field.cost_price',
|
||||
fieldType: 'number',
|
||||
},
|
||||
costAccount: {
|
||||
costAccountId: {
|
||||
name: 'item.field.cost_account',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Account',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
importHint: 'Matches the account name or code.',
|
||||
},
|
||||
sellAccount: {
|
||||
sellAccountId: {
|
||||
name: 'item.field.sell_account',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Account',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
importHint: 'Matches the account name or code.',
|
||||
},
|
||||
inventoryAccount: {
|
||||
inventoryAccountId: {
|
||||
name: 'item.field.inventory_account',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Account',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* eslint-disable global-require */
|
||||
import moment from 'moment';
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSettings from './ModelSetting';
|
||||
import Account from './Account';
|
||||
import UncategorizedCashflowTransactionMeta from './UncategorizedCashflowTransaction.meta';
|
||||
|
||||
export default class UncategorizedCashflowTransaction extends mixin(
|
||||
@@ -166,6 +166,28 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
||||
pending(query) {
|
||||
query.where('pending', true);
|
||||
},
|
||||
|
||||
minAmount(query, minAmount) {
|
||||
query.where('amount', '>=', minAmount);
|
||||
},
|
||||
|
||||
maxAmount(query, maxAmount) {
|
||||
query.where('amount', '<=', maxAmount);
|
||||
},
|
||||
|
||||
toDate(query, toDate) {
|
||||
const dateFormat = 'YYYY-MM-DD';
|
||||
const _toDate = moment(toDate).endOf('day').format(dateFormat);
|
||||
|
||||
query.where('date', '<=', _toDate);
|
||||
},
|
||||
|
||||
fromDate(query, fromDate) {
|
||||
const dateFormat = 'YYYY-MM-DD';
|
||||
const _fromDate = moment(fromDate).startOf('day').format(dateFormat);
|
||||
|
||||
query.where('date', '>=', _fromDate);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import moment from 'moment';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ExcludedBankTransactionsQuery } from './_types';
|
||||
import { UncategorizedTransactionTransformer } from '@/services/Cashflow/UncategorizedTransactionTransformer';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
@@ -39,6 +40,18 @@ export class GetExcludedBankTransactionsService {
|
||||
if (_query.accountId) {
|
||||
q.where('account_id', _query.accountId);
|
||||
}
|
||||
if (_query.minDate) {
|
||||
q.modify('fromDate', _query.minDate);
|
||||
}
|
||||
if (_query.maxDate) {
|
||||
q.modify('toDate', _query.maxDate);
|
||||
}
|
||||
if (_query.minAmount) {
|
||||
q.modify('minAmount', _query.minAmount);
|
||||
}
|
||||
if (_query.maxAmount) {
|
||||
q.modify('maxAmount', _query.maxAmount);
|
||||
}
|
||||
})
|
||||
.pagination(_query.page - 1, _query.pageSize);
|
||||
|
||||
|
||||
@@ -4,6 +4,10 @@ export interface ExcludedBankTransactionsQuery {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
accountId?: number;
|
||||
minDate?: Date;
|
||||
maxDate?: Date;
|
||||
minAmount?: number;
|
||||
maxAmount?: number;
|
||||
}
|
||||
|
||||
export interface IBankTransactionUnexcludingEventPayload {
|
||||
|
||||
@@ -106,6 +106,9 @@ export class TriggerRecognizedTransactions {
|
||||
const batch = importFile.paramsParsed.batch;
|
||||
const payload = { tenantId, transactionsCriteria: { batch } };
|
||||
|
||||
// Cannot continue if the imported resource is not bank account transactions.
|
||||
if (importFile.resource !== 'UncategorizedCashflowTransaction') return;
|
||||
|
||||
await this.agenda.now('recognize-uncategorized-transactions-job', payload);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,12 @@ export class CashflowAccountTransformer extends Transformer {
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return ['formattedAmount'];
|
||||
return [
|
||||
'formattedAmount',
|
||||
'lastFeedsUpdatedAt',
|
||||
'lastFeedsUpdatedAtFormatted',
|
||||
'lastFeedsUpdatedFromNow',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -29,7 +34,7 @@ export class CashflowAccountTransformer extends Transformer {
|
||||
|
||||
/**
|
||||
* Retrieve formatted account amount.
|
||||
* @param {IAccount} invoice
|
||||
* @param {IAccount} invoice
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedAmount = (account: IAccount): string => {
|
||||
@@ -37,4 +42,22 @@ export class CashflowAccountTransformer extends Transformer {
|
||||
currencyCode: account.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the last feeds update at formatted date.
|
||||
* @param {IAccount} account
|
||||
* @returns {string}
|
||||
*/
|
||||
protected lastFeedsUpdatedAtFormatted(account: IAccount): string {
|
||||
return this.formatDate(account.lastFeedsUpdatedAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the last feeds updated from now.
|
||||
* @param {IAccount} account
|
||||
* @returns {string}
|
||||
*/
|
||||
protected lastFeedsUpdatedFromNow(account: IAccount): string {
|
||||
return this.formatDateFromNow(account.lastFeedsUpdatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export class GetRecognizedTransactionsService {
|
||||
) {
|
||||
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
||||
|
||||
const _filter = {
|
||||
const _query = {
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
...filter,
|
||||
@@ -41,11 +41,26 @@ export class GetRecognizedTransactionsService {
|
||||
// Exclude the pending transactions.
|
||||
q.modify('notPending');
|
||||
|
||||
if (_filter.accountId) {
|
||||
q.where('accountId', _filter.accountId);
|
||||
if (_query.accountId) {
|
||||
q.where('accountId', _query.accountId);
|
||||
}
|
||||
if (_query.minDate) {
|
||||
q.modify('fromDate', _query.minDate);
|
||||
}
|
||||
if (_query.maxDate) {
|
||||
q.modify('toDate', _query.maxDate);
|
||||
}
|
||||
if (_query.minAmount) {
|
||||
q.modify('minAmount', _query.minAmount);
|
||||
}
|
||||
if (_query.maxAmount) {
|
||||
q.modify('maxAmount', _query.maxAmount);
|
||||
}
|
||||
if (_query.accountId) {
|
||||
q.where('accountId', _query.accountId);
|
||||
}
|
||||
})
|
||||
.pagination(_filter.page - 1, _filter.pageSize);
|
||||
.pagination(_query.page - 1, _query.pageSize);
|
||||
|
||||
const data = await this.transformer.transform(
|
||||
tenantId,
|
||||
|
||||
@@ -62,6 +62,19 @@ export class GetUncategorizedTransactions {
|
||||
|
||||
q.whereNull('matchedBankTransactions.id');
|
||||
q.orderBy('date', 'DESC');
|
||||
|
||||
if (_query.minDate) {
|
||||
q.modify('fromDate', _query.minDate);
|
||||
}
|
||||
if (_query.maxDate) {
|
||||
q.modify('toDate', _query.maxDate);
|
||||
}
|
||||
if (_query.minAmount) {
|
||||
q.modify('minAmount', _query.minAmount);
|
||||
}
|
||||
if (_query.maxAmount) {
|
||||
q.modify('maxAmount', _query.maxAmount);
|
||||
}
|
||||
})
|
||||
.pagination(_query.page - 1, _query.pageSize);
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ export class UncategorizedTransactionsImportable extends Importable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformes the import params before storing them.
|
||||
* Transforms the import params before storing them.
|
||||
* @param {Record<string, any>} parmas
|
||||
*/
|
||||
public transformParams(parmas: Record<string, any>) {
|
||||
|
||||
@@ -23,7 +23,7 @@ export class ImportFileMapping {
|
||||
*/
|
||||
public async mapping(
|
||||
tenantId: number,
|
||||
importId: number,
|
||||
importId: string,
|
||||
maps: ImportMappingAttr[]
|
||||
): Promise<ImportFileMapPOJO> {
|
||||
const importFile = await Import.query()
|
||||
|
||||
@@ -19,7 +19,7 @@ export class ImportFilePreview {
|
||||
*/
|
||||
public async preview(
|
||||
tenantId: number,
|
||||
importId: number
|
||||
importId: string
|
||||
): Promise<ImportFilePreviewPOJO> {
|
||||
const knex = this.tenancy.knex(tenantId);
|
||||
const trx = await knex.transaction({ isolationLevel: 'read uncommitted' });
|
||||
|
||||
@@ -37,7 +37,7 @@ export class ImportFileProcess {
|
||||
*/
|
||||
public async import(
|
||||
tenantId: number,
|
||||
importId: number,
|
||||
importId: string,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<ImportFilePreviewPOJO> {
|
||||
const importFile = await Import.query()
|
||||
|
||||
@@ -25,7 +25,7 @@ export class ImportFileProcessCommit {
|
||||
*/
|
||||
public async commit(
|
||||
tenantId: number,
|
||||
importId: number
|
||||
importId: string
|
||||
): Promise<ImportFilePreviewPOJO> {
|
||||
const knex = this.tenancy.knex(tenantId);
|
||||
const trx = await knex.transaction({ isolationLevel: 'read uncommitted' });
|
||||
|
||||
@@ -55,7 +55,7 @@ export class ImportResourceApplication {
|
||||
*/
|
||||
public async mapping(
|
||||
tenantId: number,
|
||||
importId: number,
|
||||
importId: string,
|
||||
maps: ImportMappingAttr[]
|
||||
) {
|
||||
return this.importMappingService.mapping(tenantId, importId, maps);
|
||||
@@ -67,7 +67,7 @@ export class ImportResourceApplication {
|
||||
* @param {number} importId - Import id.
|
||||
* @returns {Promise<ImportFilePreviewPOJO>}
|
||||
*/
|
||||
public async preview(tenantId: number, importId: number) {
|
||||
public async preview(tenantId: number, importId: string) {
|
||||
return this.ImportFilePreviewService.preview(tenantId, importId);
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ export class ImportResourceApplication {
|
||||
* @param {number} importId
|
||||
* @returns {Promise<ImportFilePreviewPOJO>}
|
||||
*/
|
||||
public async process(tenantId: number, importId: number) {
|
||||
public async process(tenantId: number, importId: string) {
|
||||
return this.importProcessCommit.commit(tenantId, importId);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,12 +43,22 @@ export class CreateItem {
|
||||
itemDTO.sellAccountId
|
||||
);
|
||||
}
|
||||
// Validate the income account id existance if the item is sellable.
|
||||
this.validators.validateIncomeAccountExistance(
|
||||
itemDTO.sellable,
|
||||
itemDTO.sellAccountId
|
||||
);
|
||||
if (itemDTO.costAccountId) {
|
||||
await this.validators.validateItemCostAccountExistance(
|
||||
tenantId,
|
||||
itemDTO.costAccountId
|
||||
);
|
||||
}
|
||||
// Validate the cost account id existance if the item is purchasable.
|
||||
this.validators.validateCostAccountExistance(
|
||||
itemDTO.purchasable,
|
||||
itemDTO.costAccountId
|
||||
);
|
||||
if (itemDTO.inventoryAccountId) {
|
||||
await this.validators.validateItemInventoryAccountExistance(
|
||||
tenantId,
|
||||
|
||||
@@ -55,6 +55,11 @@ export class EditItem {
|
||||
itemDTO.categoryId
|
||||
);
|
||||
}
|
||||
// Validate the income account id existance if the item is sellable.
|
||||
this.validators.validateIncomeAccountExistance(
|
||||
itemDTO.sellable,
|
||||
itemDTO.sellAccountId
|
||||
);
|
||||
// Validate the sell account existance on the storage.
|
||||
if (itemDTO.sellAccountId) {
|
||||
await this.validators.validateItemSellAccountExistance(
|
||||
@@ -62,6 +67,11 @@ export class EditItem {
|
||||
itemDTO.sellAccountId
|
||||
);
|
||||
}
|
||||
// Validate the cost account id existance if the item is purchasable.
|
||||
this.validators.validateCostAccountExistance(
|
||||
itemDTO.purchasable,
|
||||
itemDTO.costAccountId
|
||||
);
|
||||
// Validate the cost account existance on the storage.
|
||||
if (itemDTO.costAccountId) {
|
||||
await this.validators.validateItemCostAccountExistance(
|
||||
|
||||
@@ -85,6 +85,42 @@ export class ItemsValidators {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates income account existance.
|
||||
* @param {number|null} sellable - Detarmines if the item sellable.
|
||||
* @param {number|null} incomeAccountId - Income account id.
|
||||
* @throws {ServiceError(ERRORS.INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM)}
|
||||
*/
|
||||
public validateIncomeAccountExistance(
|
||||
sellable?: boolean,
|
||||
incomeAccountId?: number
|
||||
) {
|
||||
if (sellable && !incomeAccountId) {
|
||||
throw new ServiceError(
|
||||
ERRORS.INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM,
|
||||
'Income account is require with sellable item.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the cost account existance.
|
||||
* @param {boolean|null} purchasable - Detarmines if the item purchasble.
|
||||
* @param {number|null} costAccountId - Cost account id.
|
||||
* @throws {ServiceError(ERRORS.COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM)}
|
||||
*/
|
||||
public validateCostAccountExistance(
|
||||
purchasable: boolean,
|
||||
costAccountId?: number
|
||||
) {
|
||||
if (purchasable && !costAccountId) {
|
||||
throw new ServiceError(
|
||||
ERRORS.COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM,
|
||||
'The cost account is required with purchasable item.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate item inventory account existance and type.
|
||||
* @param {number} tenantId
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ServiceError } from '@/exceptions';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ItemEntry } from '@/models';
|
||||
import { entriesAmountDiff } from 'utils';
|
||||
import { Knex } from 'knex';
|
||||
|
||||
const ERRORS = {
|
||||
ITEMS_NOT_FOUND: 'ITEMS_NOT_FOUND',
|
||||
@@ -58,13 +59,14 @@ export default class ItemsEntriesService {
|
||||
*/
|
||||
public async filterInventoryEntries(
|
||||
tenantId: number,
|
||||
entries: IItemEntry[]
|
||||
entries: IItemEntry[],
|
||||
trx?: Knex.Transaction
|
||||
): Promise<IItemEntry[]> {
|
||||
const { Item } = this.tenancy.models(tenantId);
|
||||
const entriesItemsIds = entries.map((e) => e.itemId);
|
||||
|
||||
// Retrieve entries inventory items.
|
||||
const inventoryItems = await Item.query()
|
||||
const inventoryItems = await Item.query(trx)
|
||||
.whereIn('id', entriesItemsIds)
|
||||
.where('type', 'inventory');
|
||||
|
||||
|
||||
@@ -26,6 +26,11 @@ export const ERRORS = {
|
||||
|
||||
PURCHASE_TAX_RATE_NOT_FOUND: 'PURCHASE_TAX_RATE_NOT_FOUND',
|
||||
SELL_TAX_RATE_NOT_FOUND: 'SELL_TAX_RATE_NOT_FOUND',
|
||||
|
||||
INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM:
|
||||
'INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM',
|
||||
COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM:
|
||||
'COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM',
|
||||
};
|
||||
|
||||
export const DEFAULT_VIEW_COLUMNS = [];
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import uniqid from 'uniqid';
|
||||
import AuthenticationApplication from '../Authentication/AuthApplication';
|
||||
import OrganizationService from '../Organization/OrganizationService';
|
||||
import { OneClickDemo } from '@/system/models/OneclickDemo';
|
||||
import { SystemUser } from '@/system/models';
|
||||
import { IAuthSignInPOJO } from '@/interfaces';
|
||||
import { ICreateOneClickDemoPOJO } from './interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import { defaultDemoOrganizationDTO } from './_constants';
|
||||
|
||||
@Service()
|
||||
export class CreateOneClickDemo {
|
||||
@Inject()
|
||||
private authApp: AuthenticationApplication;
|
||||
|
||||
@Inject()
|
||||
private organizationService: OrganizationService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
|
||||
/**
|
||||
* Creates one-click demo account.
|
||||
* @returns {Promise<ICreateOneClickDemoPOJO>}
|
||||
*/
|
||||
public async createOneClickDemo(): Promise<ICreateOneClickDemoPOJO> {
|
||||
const firstName = faker.person.firstName();
|
||||
const lastName = faker.person.lastName();
|
||||
const email = faker.internet.email();
|
||||
const password = '123123123';
|
||||
const demoId = uniqid();
|
||||
|
||||
await this.authApp.signUp({ firstName, lastName, email, password });
|
||||
|
||||
const signedIn = await this.authApp.signIn(email, password);
|
||||
const tenantId = signedIn.tenant.id;
|
||||
const userId = signedIn.user.id;
|
||||
|
||||
// Creates a new one-click demo.
|
||||
await OneClickDemo.query().insert({ key: demoId, tenantId, userId });
|
||||
|
||||
const buildJob = await this.organizationService.buildRunJob(
|
||||
tenantId,
|
||||
defaultDemoOrganizationDTO,
|
||||
signedIn.user
|
||||
);
|
||||
return { email, demoId, signedIn, buildJob };
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign-in automicatlly using the demo id one creating an account finish.
|
||||
* @param {string} oneClickDemoId -
|
||||
* @returns {Promise<IAuthSignInPOJO>}
|
||||
*/
|
||||
async autoSignIn(oneClickDemoId: string): Promise<IAuthSignInPOJO> {
|
||||
const foundOneclickDemo = await OneClickDemo.query()
|
||||
.findOne('key', oneClickDemoId)
|
||||
.throwIfNotFound();
|
||||
|
||||
const userId = foundOneclickDemo.userId;
|
||||
const user = await SystemUser.query().findById(userId);
|
||||
|
||||
const email = user.email;
|
||||
const password = '123123123';
|
||||
|
||||
const signedIn = await this.authApp.signIn(email, password);
|
||||
|
||||
return signedIn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export class SeedDemoAbstract {
|
||||
/**
|
||||
* Retrieves the seeder file mapping.
|
||||
* @returns {Array<>}
|
||||
*/
|
||||
get mapping() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retireves the seeder file import params.
|
||||
* @returns {Record<string, any>}
|
||||
*/
|
||||
get importParams() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { SeedDemoAbstract } from './SeedDemoAbstract';
|
||||
|
||||
export class SeedDemoBankTransactions extends SeedDemoAbstract {
|
||||
get mapping() {
|
||||
return [
|
||||
{ from: 'Date', to: 'date' },
|
||||
{ from: 'Payee', to: 'payee' },
|
||||
{ from: 'Description', to: 'description' },
|
||||
{ from: 'Reference No.', to: 'referenceNo' },
|
||||
{ from: 'Amount', to: 'amount' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the seeder file name.
|
||||
* @returns {string}
|
||||
*/
|
||||
get importFileName() {
|
||||
return `bank-transactions.csv`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the resource name of the seeder.
|
||||
* @returns {string}
|
||||
*/
|
||||
get resource() {
|
||||
return 'UncategorizedCashflowTransaction';
|
||||
}
|
||||
|
||||
get importParams() {
|
||||
return {
|
||||
accountId: 1001,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { SeedDemoAbstract } from './SeedDemoAbstract';
|
||||
|
||||
export class SeedDemoAccountCustomers extends SeedDemoAbstract {
|
||||
/**
|
||||
* Retrieves the seeder file mapping.
|
||||
*/
|
||||
get mapping() {
|
||||
return [
|
||||
{ from: 'Customer Type', to: 'customerType' },
|
||||
{ from: 'First Name', to: 'firstName' },
|
||||
{ from: 'Last Name', to: 'lastName' },
|
||||
{ from: 'Display Name', to: 'displayName' },
|
||||
{ from: 'Email', to: 'email' },
|
||||
{ from: 'Work Phone Number', to: 'workPhone' },
|
||||
{ from: 'Personal Phone Number', to: 'personalPhone' },
|
||||
{ from: 'Company Name', to: 'companyName' },
|
||||
{ from: 'Website', to: 'website' },
|
||||
{ from: 'Active', to: 'active' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the seeder file name.
|
||||
* @returns {string}
|
||||
*/
|
||||
get importFileName() {
|
||||
return `customers.csv`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the resource name of the seeder.
|
||||
* @returns {string}
|
||||
*/
|
||||
get resource() {
|
||||
return 'Customer';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { SeedDemoAbstract } from './SeedDemoAbstract';
|
||||
|
||||
export class SeedDemoAccountExpenses extends SeedDemoAbstract {
|
||||
/**
|
||||
* Retrieves the seeder file mapping.
|
||||
*/
|
||||
get mapping() {
|
||||
return [
|
||||
{ from: 'Payment Account', to: 'paymentAccountId' },
|
||||
{ from: 'Reference No.', to: 'referenceNo' },
|
||||
{ from: 'Payment Date', to: 'paymentDate' },
|
||||
{ from: 'Description', to: 'description' },
|
||||
{ from: 'Publish', to: 'publish' },
|
||||
{
|
||||
from: 'Expense Account',
|
||||
to: 'expenseAccountId',
|
||||
group: 'categories',
|
||||
},
|
||||
{ from: 'Amount', to: 'amount', group: 'categories' },
|
||||
{ from: 'Line Description', to: 'description', group: 'categories' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the seeder file name.
|
||||
* @returns {string}
|
||||
*/
|
||||
get importFileName() {
|
||||
return `Expenses.csv`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the resource name of the seeder.
|
||||
* @returns {string}
|
||||
*/
|
||||
get resource() {
|
||||
return 'Expense';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { SeedDemoAbstract } from './SeedDemoAbstract';
|
||||
|
||||
export class SeedDemoAccountItems extends SeedDemoAbstract {
|
||||
/**
|
||||
* Retrieves the seeder file mapping.
|
||||
*/
|
||||
get mapping() {
|
||||
return [
|
||||
{ from: 'Item Type', to: 'type' },
|
||||
{ from: 'Item Name', to: 'name' },
|
||||
{ from: 'Item Code', to: 'code' },
|
||||
{ from: 'Sellable', to: 'sellable' },
|
||||
{ from: 'Purchasable', to: 'purchasable' },
|
||||
{ from: 'Sell Price', to: 'sellPrice' },
|
||||
{ from: 'Cost Price', to: 'costPrice' },
|
||||
{ from: 'Cost Account', to: 'costAccountId' },
|
||||
{ from: 'Sell Account', to: 'sellAccountId' },
|
||||
{ from: 'Inventory Account', to: 'inventoryAccountId' },
|
||||
{ from: 'Sell Description', to: 'sellDescription' },
|
||||
{
|
||||
from: 'Purchase Description',
|
||||
to: 'purchaseDescription',
|
||||
},
|
||||
{ from: 'Note', to: 'note' },
|
||||
{ from: 'Category', to: 'category' },
|
||||
{ from: 'Active', to: 'active' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the seeder file name.
|
||||
* @returns {string}
|
||||
*/
|
||||
get importFileName() {
|
||||
return `items.csv`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the resource name of the seeder.
|
||||
* @returns {string}
|
||||
*/
|
||||
get resource() {
|
||||
return 'Item';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { SeedDemoAbstract } from './SeedDemoAbstract';
|
||||
|
||||
export class SeedDemoAccountManualJournals extends SeedDemoAbstract {
|
||||
/**
|
||||
* Retrieves the seeder file mapping.
|
||||
*/
|
||||
get mapping() {
|
||||
return [
|
||||
{ from: 'Date', to: 'date' },
|
||||
{ from: 'Journal No', to: 'journalNumber' },
|
||||
{ from: 'Reference No.', to: 'reference' },
|
||||
{ from: 'Description', to: 'description' },
|
||||
{ from: 'Publish', to: 'publish' },
|
||||
{ from: 'Credit', to: 'credit', group: 'entries' },
|
||||
{ from: 'Debit', to: 'debit', group: 'entries' },
|
||||
{ from: 'Account', to: 'accountId', group: 'entries' },
|
||||
{ from: 'Note', to: 'note', group: 'entries' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the seeder file name.
|
||||
* @returns {string}
|
||||
*/
|
||||
get importFileName() {
|
||||
return `manual-journals.csv`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the resource name of the seeder.
|
||||
* @returns {string}
|
||||
*/
|
||||
get resource() {
|
||||
return 'ManualJournal';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { SeedDemoAbstract } from './SeedDemoAbstract';
|
||||
|
||||
export class SeedDemoSaleInvoices extends SeedDemoAbstract {
|
||||
get mapping() {
|
||||
return [
|
||||
{ from: 'Invoice Date', to: 'invoiceDate' },
|
||||
{ from: 'Due Date', to: 'dueDate' },
|
||||
{ from: 'Reference No.', to: 'referenceNo' },
|
||||
{ from: 'Invoice No.', to: 'invoiceNo' },
|
||||
{ from: 'Customer', to: 'customerId' },
|
||||
{ from: 'Exchange Rate', to: 'exchangeRate' },
|
||||
{ from: 'Invoice Message', to: 'invoiceMessage' },
|
||||
{ from: 'Terms & Conditions', to: 'termsConditions' },
|
||||
{ from: 'Delivered', to: 'delivered' },
|
||||
{ from: 'Item', to: 'itemId', group: 'entries' },
|
||||
{ from: 'Rate', to: 'rate', group: 'entries' },
|
||||
{ from: 'Quantity', to: 'quantity', group: 'entries' },
|
||||
{ from: 'Description', to: 'description', group: 'entries' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the seeder file name.
|
||||
* @returns {string}
|
||||
*/
|
||||
get importFileName() {
|
||||
return `sale-invoices.csv`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the resource name of the seeder.
|
||||
* @returns {string}
|
||||
*/
|
||||
get resource() {
|
||||
return 'SaleInvoice';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { SeedDemoAbstract } from './SeedDemoAbstract';
|
||||
|
||||
export class SeedDemoAccountVendors extends SeedDemoAbstract {
|
||||
/**
|
||||
* Retrieves the seeder file mapping.
|
||||
*/
|
||||
get mapping() {
|
||||
return [
|
||||
{ from: 'First Name', to: 'firstName' },
|
||||
{ from: 'Last Name', to: 'lastName' },
|
||||
{ from: 'Display Name', to: 'displayName' },
|
||||
{ from: 'Email', to: 'email' },
|
||||
{ from: 'Work Phone Number', to: 'workPhone' },
|
||||
{ from: 'Personal Phone Number', to: 'personalPhone' },
|
||||
{ from: 'Company Name', to: 'companyName' },
|
||||
{ from: 'Website', to: 'website' },
|
||||
{ from: 'Active', to: 'active' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the seeder file name.
|
||||
* @returns {string}
|
||||
*/
|
||||
get importFileName() {
|
||||
return `vendors.csv`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the resource name of the seeder.
|
||||
* @returns {string}
|
||||
*/
|
||||
get resource() {
|
||||
return 'Vendor';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { CreateOneClickDemo } from './CreateOneClickDemo';
|
||||
|
||||
@Service()
|
||||
export class OneClickDemoApplication {
|
||||
@Inject()
|
||||
private createOneClickDemoService: CreateOneClickDemo;
|
||||
|
||||
/**
|
||||
* Creates one-click demo account.
|
||||
* @returns {Promise<ICreateOneClickDemoPOJO>}
|
||||
*/
|
||||
public createOneClick() {
|
||||
return this.createOneClickDemoService.createOneClickDemo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-sign-in to created demo account.
|
||||
* @param {string} demoId -
|
||||
* @returns {Promise<IAuthSignInPOJO>}
|
||||
*/
|
||||
public autoSignIn(demoId: string) {
|
||||
return this.createOneClickDemoService.autoSignIn(demoId);
|
||||
}
|
||||
}
|
||||
12
packages/server/src/services/OneClickDemo/_constants.ts
Normal file
12
packages/server/src/services/OneClickDemo/_constants.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
|
||||
export const defaultDemoOrganizationDTO = {
|
||||
name: 'BIGCAPITAL, INC',
|
||||
baseCurrency: 'USD',
|
||||
location: 'US',
|
||||
language: 'en',
|
||||
industry: 'Technology',
|
||||
fiscalYear: 'march',
|
||||
timezone: 'US/Central',
|
||||
dateFormat: 'MM/DD/yyyy',
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import { Inject } from 'typedi';
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import uniqid from 'uniqid';
|
||||
import { isEmpty } from 'lodash';
|
||||
import events from '@/subscribers/events';
|
||||
import { PromisePool } from '@supercharge/promise-pool';
|
||||
import { IOrganizationBuiltEventPayload } from '@/interfaces';
|
||||
import { SeedDemoAccountItems } from '../DemoSeeders/SeedDemoItems';
|
||||
import { ImportResourceApplication } from '@/services/Import/ImportResourceApplication';
|
||||
import { getImportsStoragePath } from '@/services/Import/_utils';
|
||||
import { OneClickDemo } from '@/system/models/OneclickDemo';
|
||||
import { SeedDemoAccountCustomers } from '../DemoSeeders/SeedDemoCustomers';
|
||||
import { SeedDemoAccountVendors } from '../DemoSeeders/SeedDemoVendors';
|
||||
import { SeedDemoAccountManualJournals } from '../DemoSeeders/SeedDemoManualJournals';
|
||||
import { SeedDemoAccountExpenses } from '../DemoSeeders/SeedDemoExpenses';
|
||||
import { SeedDemoBankTransactions } from '../DemoSeeders/SeedDemoBankTransactions';
|
||||
import { SeedDemoSaleInvoices } from '../DemoSeeders/SeedDemoSaleInvoices';
|
||||
|
||||
export class SeedInitialDemoAccountDataOnOrgBuild {
|
||||
@Inject()
|
||||
private importApp: ImportResourceApplication;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
*/
|
||||
public attach = (bus) => {
|
||||
bus.subscribe(
|
||||
events.organization.built,
|
||||
this.seedInitialDemoAccountDataOnOrgBuild.bind(this)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Demo account seeder.
|
||||
*/
|
||||
get seedDemoAccountSeeders() {
|
||||
return [
|
||||
SeedDemoAccountItems,
|
||||
SeedDemoBankTransactions,
|
||||
SeedDemoAccountCustomers,
|
||||
SeedDemoAccountVendors,
|
||||
SeedDemoAccountManualJournals,
|
||||
SeedDemoSaleInvoices,
|
||||
SeedDemoAccountExpenses,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the seeder sheet file to the import storage first.
|
||||
* @param {string} fileName -
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async initiateSeederFile(fileName: string) {
|
||||
const destFileName = uniqid();
|
||||
const source = path.join(global.__views_dir, `/demo-sheets`, fileName);
|
||||
const destination = path.join(getImportsStoragePath(), destFileName);
|
||||
|
||||
// Use the fs.promises.copyFile method to copy the file
|
||||
await fs.copyFile(source, destination);
|
||||
|
||||
return destFileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeds initial demo account data on organization build
|
||||
* @param {IOrganizationBuildEventPayload}
|
||||
*/
|
||||
async seedInitialDemoAccountDataOnOrgBuild({
|
||||
tenantId,
|
||||
}: IOrganizationBuiltEventPayload) {
|
||||
const foundDemo = await OneClickDemo.query().findOne('tenantId', tenantId);
|
||||
|
||||
// Can't continue if the found demo is not exists.
|
||||
// Means that account is not demo account.
|
||||
if (!foundDemo) {
|
||||
return null;
|
||||
}
|
||||
const results = await PromisePool.for(this.seedDemoAccountSeeders)
|
||||
.withConcurrency(1)
|
||||
.process(async (SeedDemoAccountSeeder) => {
|
||||
const seederInstance = new SeedDemoAccountSeeder();
|
||||
|
||||
// Initialize the seeder sheet file before importing.
|
||||
const importFileName = await this.initiateSeederFile(seederInstance.importFileName);
|
||||
|
||||
// Import the given seeder file.
|
||||
const importedFile = await this.importApp.import(
|
||||
tenantId,
|
||||
seederInstance.resource,
|
||||
importFileName,
|
||||
seederInstance.importParams
|
||||
);
|
||||
// Mapping the columns with resource fields.
|
||||
await this.importApp.mapping(
|
||||
tenantId,
|
||||
importedFile.import.importId,
|
||||
seederInstance.mapping
|
||||
);
|
||||
await this.importApp.preview(tenantId, importedFile.import.importId);
|
||||
|
||||
// Commit the imported file.
|
||||
await this.importApp.process(
|
||||
tenantId,
|
||||
importedFile.import.importId
|
||||
);
|
||||
});
|
||||
|
||||
if (!isEmpty(results.errors)) {
|
||||
throw results.errors;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
packages/server/src/services/OneClickDemo/interfaces.ts
Normal file
8
packages/server/src/services/OneClickDemo/interfaces.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { IAuthSignInPOJO } from '@/interfaces';
|
||||
|
||||
export interface ICreateOneClickDemoPOJO {
|
||||
email: string;
|
||||
demoId: string;
|
||||
signedIn: IAuthSignInPOJO;
|
||||
buildJob: any;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { ServiceError } from '@/exceptions';
|
||||
import {
|
||||
IOrganizationBuildDTO,
|
||||
IOrganizationBuildEventPayload,
|
||||
IOrganizationBuiltEventPayload,
|
||||
IOrganizationUpdateDTO,
|
||||
ISystemUser,
|
||||
ITenant,
|
||||
@@ -17,6 +18,8 @@ import { Tenant } from '@/system/models';
|
||||
import OrganizationBaseCurrencyLocking from './OrganizationBaseCurrencyLocking';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ERRORS } from './constants';
|
||||
import { initializeTenantSettings } from '@/api/middleware/SettingsMiddleware';
|
||||
import { initalizeTenantServices } from '@/api/middleware/TenantDependencyInjection';
|
||||
|
||||
@Service()
|
||||
export default class OrganizationService {
|
||||
@@ -62,6 +65,10 @@ export default class OrganizationService {
|
||||
// Migrated tenant.
|
||||
const migratedTenant = await tenant.$query().withGraphFetched('metadata');
|
||||
|
||||
// Injects the given tenant IoC services.
|
||||
await initalizeTenantServices(tenantId);
|
||||
await initializeTenantSettings(tenantId);
|
||||
|
||||
// Creates a tenancy object from given tenant model.
|
||||
const tenancyContext =
|
||||
this.tenantsManager.getSeedMigrationContext(migratedTenant);
|
||||
@@ -82,6 +89,11 @@ export default class OrganizationService {
|
||||
|
||||
//
|
||||
await this.flagTenantDBBatch(tenantId);
|
||||
|
||||
// Triggers the organization built event.
|
||||
await this.eventPublisher.emitAsync(events.organization.built, {
|
||||
tenantId: tenant.id,
|
||||
} as IOrganizationBuiltEventPayload)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,7 +44,7 @@ export class SaleInvoiceGLEntries {
|
||||
|
||||
// Find or create the A/R account.
|
||||
const ARAccount = await accountRepository.findOrCreateAccountReceivable(
|
||||
saleInvoice.currencyCode
|
||||
saleInvoice.currencyCode, {}, trx
|
||||
);
|
||||
// Find or create tax payable account.
|
||||
const taxPayableAccount = await accountRepository.findOrCreateTaxPayable(
|
||||
|
||||
@@ -32,7 +32,8 @@ export class InvoiceInventoryTransactions {
|
||||
const inventoryEntries =
|
||||
await this.itemsEntriesService.filterInventoryEntries(
|
||||
tenantId,
|
||||
saleInvoice.entries
|
||||
saleInvoice.entries,
|
||||
trx
|
||||
);
|
||||
const transaction = {
|
||||
transactionId: saleInvoice.id,
|
||||
|
||||
@@ -4,8 +4,8 @@ import { configureLemonSqueezy } from './utils';
|
||||
import { PlanSubscription } from '@/system/models';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import { ERRORS, IOrganizationSubscriptionCancel } from './types';
|
||||
import events from '@/subscribers/events';
|
||||
import { ERRORS, IOrganizationSubscriptionCanceled } from './types';
|
||||
|
||||
@Service()
|
||||
export class LemonCancelSubscription {
|
||||
@@ -18,12 +18,15 @@ export class LemonCancelSubscription {
|
||||
* @param {number} subscriptionId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async cancelSubscription(tenantId: number) {
|
||||
public async cancelSubscription(
|
||||
tenantId: number,
|
||||
subscriptionSlug: string = 'main'
|
||||
) {
|
||||
configureLemonSqueezy();
|
||||
|
||||
const subscription = await PlanSubscription.query().findOne({
|
||||
tenantId,
|
||||
slug: 'main',
|
||||
slug: subscriptionSlug,
|
||||
});
|
||||
if (!subscription) {
|
||||
throw new ServiceError(ERRORS.SUBSCRIPTION_ID_NOT_ASSOCIATED_TO_TENANT);
|
||||
@@ -35,13 +38,10 @@ export class LemonCancelSubscription {
|
||||
if (cancelledSub.error) {
|
||||
throw new Error(cancelledSub.error.message);
|
||||
}
|
||||
await PlanSubscription.query().findById(subscriptionId).patch({
|
||||
canceledAt: new Date(),
|
||||
});
|
||||
// Triggers `onSubscriptionCanceled` event.
|
||||
// Triggers `onSubscriptionCancelled` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.subscription.onSubscriptionCanceled,
|
||||
{ tenantId, subscriptionId } as IOrganizationSubscriptionCanceled
|
||||
events.subscription.onSubscriptionCancel,
|
||||
{ tenantId, subscriptionId } as IOrganizationSubscriptionCancel
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,25 +18,30 @@ export class LemonChangeSubscriptionPlan {
|
||||
* @param {number} newVariantId - New variant id.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async changeSubscriptionPlan(tenantId: number, newVariantId: number) {
|
||||
public async changeSubscriptionPlan(
|
||||
tenantId: number,
|
||||
newVariantId: number,
|
||||
subscriptionSlug: string = 'main'
|
||||
) {
|
||||
configureLemonSqueezy();
|
||||
|
||||
const subscription = await PlanSubscription.query().findOne({
|
||||
tenantId,
|
||||
slug: 'main',
|
||||
slug: subscriptionSlug,
|
||||
});
|
||||
const lemonSubscriptionId = subscription.lemonSubscriptionId;
|
||||
|
||||
// Send request to Lemon Squeezy to change the subscription.
|
||||
const updatedSub = await updateSubscription(lemonSubscriptionId, {
|
||||
variantId: newVariantId,
|
||||
invoiceImmediately: true,
|
||||
});
|
||||
if (updatedSub.error) {
|
||||
throw new ServiceError('SOMETHING_WENT_WRONG');
|
||||
}
|
||||
// Triggers `onSubscriptionPlanChanged` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.subscription.onSubscriptionPlanChanged,
|
||||
events.subscription.onSubscriptionPlanChange,
|
||||
{
|
||||
tenantId,
|
||||
lemonSubscriptionId,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { updateSubscription } from '@lemonsqueezy/lemonsqueezy.js';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
import { configureLemonSqueezy } from './utils';
|
||||
import { PlanSubscription } from '@/system/models';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { ERRORS, IOrganizationSubscriptionResumed } from './types';
|
||||
import { updateSubscription } from '@lemonsqueezy/lemonsqueezy.js';
|
||||
import { ERRORS, IOrganizationSubscriptionResume } from './types';
|
||||
|
||||
@Service()
|
||||
export class LemonResumeSubscription {
|
||||
@@ -14,15 +14,16 @@ export class LemonResumeSubscription {
|
||||
|
||||
/**
|
||||
* Resumes the main subscription of the given tenant.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {string} subscriptionSlug - Subscription slug by default main subscription.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async resumeSubscription(tenantId: number) {
|
||||
public async resumeSubscription(tenantId: number, subscriptionSlug: string = 'main') {
|
||||
configureLemonSqueezy();
|
||||
|
||||
const subscription = await PlanSubscription.query().findOne({
|
||||
tenantId,
|
||||
slug: 'main',
|
||||
slug: subscriptionSlug,
|
||||
});
|
||||
if (!subscription) {
|
||||
throw new ServiceError(ERRORS.SUBSCRIPTION_ID_NOT_ASSOCIATED_TO_TENANT);
|
||||
@@ -33,16 +34,12 @@ export class LemonResumeSubscription {
|
||||
cancelled: false,
|
||||
});
|
||||
if (returnedSub.error) {
|
||||
throw new ServiceError('');
|
||||
throw new ServiceError(ٌٌُERRORS.SOMETHING_WENT_WRONG_WITH_LS);
|
||||
}
|
||||
// Update the subscription of the organization.
|
||||
await PlanSubscription.query().findById(subscriptionId).patch({
|
||||
canceledAt: null,
|
||||
});
|
||||
// Triggers `onSubscriptionCanceled` event.
|
||||
// Triggers `onSubscriptionResume` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.subscription.onSubscriptionResumed,
|
||||
{ tenantId, subscriptionId } as IOrganizationSubscriptionResumed
|
||||
events.subscription.onSubscriptionResume,
|
||||
{ tenantId, subscriptionId } as IOrganizationSubscriptionResume
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,11 +59,25 @@ export class LemonSqueezyWebhooks {
|
||||
|
||||
const userId = eventBody.meta.custom_data?.user_id;
|
||||
const tenantId = eventBody.meta.custom_data?.tenant_id;
|
||||
const subscriptionSlug = 'main';
|
||||
|
||||
if (!webhookHasMeta(eventBody)) {
|
||||
throw new Error("Event body is missing the 'meta' property.");
|
||||
} else if (webhookHasData(eventBody)) {
|
||||
if (webhookEvent.startsWith('subscription_payment_')) {
|
||||
// Marks the main subscription payment as succeed.
|
||||
if (webhookEvent === 'subscription_payment_success') {
|
||||
await this.subscriptionService.markSubscriptionPaymentSucceed(
|
||||
tenantId,
|
||||
subscriptionSlug
|
||||
);
|
||||
// Marks the main subscription payment as failed.
|
||||
} else if (webhookEvent === 'subscription_payment_failed') {
|
||||
await this.subscriptionService.markSubscriptionPaymentFailed(
|
||||
tenantId,
|
||||
subscriptionSlug
|
||||
);
|
||||
}
|
||||
// Save subscription invoices; eventBody is a SubscriptionInvoice
|
||||
// Not implemented.
|
||||
} else if (webhookEvent.startsWith('subscription_')) {
|
||||
@@ -74,16 +88,39 @@ export class LemonSqueezyWebhooks {
|
||||
// We assume that the Plan table is up to date.
|
||||
const plan = await Plan.query().findOne('lemonVariantId', variantId);
|
||||
|
||||
// Update the subscription in the database.
|
||||
const priceId = attributes.first_subscription_item.price_id;
|
||||
const subscriptionId = eventBody.data.id;
|
||||
|
||||
// Throw error early if the given lemon variant id is not associated to any plan.
|
||||
if (!plan) {
|
||||
throw new Error(`Plan with variantId ${variantId} not found.`);
|
||||
} else {
|
||||
// Update the subscription in the database.
|
||||
const priceId = attributes.first_subscription_item.price_id;
|
||||
|
||||
// Create a new subscription of the tenant.
|
||||
if (webhookEvent === 'subscription_created') {
|
||||
await this.subscriptionService.newSubscribtion(tenantId, plan.slug);
|
||||
}
|
||||
}
|
||||
// Create a new subscription of the tenant.
|
||||
if (webhookEvent === 'subscription_created') {
|
||||
await this.subscriptionService.newSubscribtion(
|
||||
tenantId,
|
||||
plan.slug,
|
||||
subscriptionSlug,
|
||||
{ lemonSqueezyId: subscriptionId }
|
||||
);
|
||||
// Cancel the given subscription of the organization.
|
||||
} else if (webhookEvent === 'subscription_cancelled') {
|
||||
await this.subscriptionService.cancelSubscription(
|
||||
tenantId,
|
||||
subscriptionSlug
|
||||
);
|
||||
} else if (webhookEvent === 'subscription_plan_changed') {
|
||||
await this.subscriptionService.subscriptionPlanChanged(
|
||||
tenantId,
|
||||
plan.slug,
|
||||
subscriptionSlug
|
||||
);
|
||||
} else if (webhookEvent === 'subscription_resumed') {
|
||||
await this.subscriptionService.resumeSubscription(
|
||||
tenantId,
|
||||
subscriptionSlug
|
||||
);
|
||||
}
|
||||
} else if (webhookEvent.startsWith('order_')) {
|
||||
// Save orders; eventBody is a "Order"
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
import { Service } from 'typedi';
|
||||
import { NotAllowedChangeSubscriptionPlan } from '@/exceptions';
|
||||
import { Plan, Tenant } from '@/system/models';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { NotAllowedChangeSubscriptionPlan, ServiceError } from '@/exceptions';
|
||||
import { Plan, PlanSubscription, Tenant } from '@/system/models';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
import { SubscriptionPayload, SubscriptionPaymentStatus } from '@/interfaces';
|
||||
import { ERRORS } from './types';
|
||||
|
||||
@Service()
|
||||
export class Subscription {
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Give the tenant a new subscription.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {string} planSlug - Plan slug.
|
||||
* @param {string} invoiceInterval
|
||||
* @param {number} invoicePeriod
|
||||
* @param {string} subscriptionSlug
|
||||
* @param {string} planSlug - Plan slug of the new subscription.
|
||||
* @param {string} subscriptionSlug - Subscription slug by default takes main subscription
|
||||
* @param {SubscriptionPayload} payload - Subscription payload.
|
||||
*/
|
||||
public async newSubscribtion(
|
||||
tenantId: number,
|
||||
planSlug: string,
|
||||
subscriptionSlug: string = 'main'
|
||||
) {
|
||||
subscriptionSlug: string = 'main',
|
||||
payload?: SubscriptionPayload
|
||||
): Promise<void> {
|
||||
const tenant = await Tenant.query().findById(tenantId).throwIfNotFound();
|
||||
const plan = await Plan.query().findOne('slug', planSlug).throwIfNotFound();
|
||||
|
||||
@@ -45,8 +52,169 @@ export class Subscription {
|
||||
plan.id,
|
||||
invoiceInterval,
|
||||
invoicePeriod,
|
||||
subscriptionSlug
|
||||
subscriptionSlug,
|
||||
payload
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the given tenant subscription.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {string} subscriptionSlug - Subscription slug.
|
||||
*/
|
||||
async cancelSubscription(
|
||||
tenantId: number,
|
||||
subscriptionSlug: string = 'main'
|
||||
): Promise<void> {
|
||||
const tenant = await Tenant.query().findById(tenantId).throwIfNotFound();
|
||||
|
||||
const subscription = await PlanSubscription.query().findOne({
|
||||
tenantId,
|
||||
slug: subscriptionSlug,
|
||||
});
|
||||
// Throw error early if the subscription is not exist.
|
||||
if (!subscription) {
|
||||
throw new ServiceError(ERRORS.SUBSCRIPTION_NOT_EXIST);
|
||||
}
|
||||
// Throw error early if the subscription is already canceled.
|
||||
if (subscription.canceled()) {
|
||||
throw new ServiceError(ERRORS.SUBSCRIPTION_ALREADY_CANCELED);
|
||||
}
|
||||
await subscription.$query().patch({ canceledAt: new Date() });
|
||||
|
||||
// Triggers `onSubscriptionCancelled` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.subscription.onSubscriptionCancelled,
|
||||
{
|
||||
tenantId,
|
||||
subscriptionSlug,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the given tenant subscription.
|
||||
* @param {number} tenantId
|
||||
* @param {string} subscriptionSlug - Subscription slug by deafult main subscription.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async resumeSubscription(
|
||||
tenantId: number,
|
||||
subscriptionSlug: string = 'main'
|
||||
) {
|
||||
const tenant = await Tenant.query().findById(tenantId).throwIfNotFound();
|
||||
|
||||
const subscription = await PlanSubscription.query().findOne({
|
||||
tenantId,
|
||||
slug: subscriptionSlug,
|
||||
});
|
||||
// Throw error early if the subscription is not exist.
|
||||
if (!subscription) {
|
||||
throw new ServiceError(ERRORS.SUBSCRIPTION_NOT_EXIST);
|
||||
}
|
||||
// Throw error early if the subscription is not cancelled.
|
||||
if (!subscription.canceled()) {
|
||||
throw new ServiceError(ERRORS.SUBSCRIPTION_ALREADY_ACTIVE);
|
||||
}
|
||||
await subscription.$query().patch({ canceledAt: null });
|
||||
|
||||
// Triggers `onSubscriptionResumed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.subscription.onSubscriptionResumed,
|
||||
{ tenantId, subscriptionSlug }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the given subscription payment of the tenant as succeed.
|
||||
* @param {number} tenantId
|
||||
* @param {string} newPlanSlug
|
||||
* @param {string} subscriptionSlug
|
||||
*/
|
||||
async subscriptionPlanChanged(
|
||||
tenantId: number,
|
||||
newPlanSlug: string,
|
||||
subscriptionSlug: string = 'main'
|
||||
): Promise<void> {
|
||||
const tenant = await Tenant.query().findById(tenantId).throwIfNotFound();
|
||||
const newPlan = await Plan.query()
|
||||
.findOne('slug', newPlanSlug)
|
||||
.throwIfNotFound();
|
||||
|
||||
const subscription = await PlanSubscription.query().findOne({
|
||||
tenantId,
|
||||
slug: subscriptionSlug,
|
||||
});
|
||||
if (subscription.planId === newPlan.id) {
|
||||
throw new ServiceError('');
|
||||
}
|
||||
await subscription.$query().patch({ planId: newPlan.id });
|
||||
|
||||
// Triggers `onSubscriptionPlanChanged` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.subscription.onSubscriptionPlanChanged,
|
||||
{
|
||||
tenantId,
|
||||
newPlanSlug,
|
||||
subscriptionSlug,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the subscription payment as succeed.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {string} subscriptionSlug - Given subscription slug by default main subscription.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async markSubscriptionPaymentSucceed(
|
||||
tenantId: number,
|
||||
subscriptionSlug: string = 'main'
|
||||
): Promise<void> {
|
||||
const subscription = await PlanSubscription.query()
|
||||
.findOne({ tenantId, slug: subscriptionSlug })
|
||||
.throwIfNotFound();
|
||||
|
||||
await subscription
|
||||
.$query()
|
||||
.patch({ paymentStatus: SubscriptionPaymentStatus.Succeed });
|
||||
|
||||
// Triggers `onSubscriptionSucceed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.subscription.onSubscriptionPaymentSucceed,
|
||||
{
|
||||
tenantId,
|
||||
subscriptionSlug,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the given subscription payment of the tenant as failed.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {string} subscriptionSlug - Given subscription slug.
|
||||
* @returns {Prmise<void>}
|
||||
*/
|
||||
async markSubscriptionPaymentFailed(
|
||||
tenantId: number,
|
||||
subscriptionSlug: string = 'main'
|
||||
): Promise<void> {
|
||||
const subscription = await PlanSubscription.query()
|
||||
.findOne({ tenantId, slug: subscriptionSlug })
|
||||
.throwIfNotFound();
|
||||
|
||||
await subscription
|
||||
.$query()
|
||||
.patch({ paymentStatus: SubscriptionPaymentStatus.Failed });
|
||||
|
||||
// Triggers `onSubscriptionPaymentFailed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.subscription.onSubscriptionPaymentFailed,
|
||||
{
|
||||
tenantId,
|
||||
subscriptionSlug,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,14 @@ export class SubscriptionApplication {
|
||||
* @param {string} id
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public cancelSubscription(tenantId: number, id: string) {
|
||||
return this.cancelSubscriptionService.cancelSubscription(tenantId, id);
|
||||
public cancelSubscription(
|
||||
tenantId: number,
|
||||
subscriptionSlug: string = 'main'
|
||||
) {
|
||||
return this.cancelSubscriptionService.cancelSubscription(
|
||||
tenantId,
|
||||
subscriptionSlug
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,8 +35,14 @@ export class SubscriptionApplication {
|
||||
* @param {number} tenantId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public resumeSubscription(tenantId: number) {
|
||||
return this.resumeSubscriptionService.resumeSubscription(tenantId);
|
||||
public resumeSubscription(
|
||||
tenantId: number,
|
||||
subscriptionSlug: string = 'main'
|
||||
) {
|
||||
return this.resumeSubscriptionService.resumeSubscription(
|
||||
tenantId,
|
||||
subscriptionSlug
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { IAuthSignedUpEventPayload } from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import config from '@/config';
|
||||
import { Subscription } from '../Subscription';
|
||||
import { Inject, Service } from 'typedi';
|
||||
|
||||
@Service()
|
||||
export class SubscribeFreeOnSignupCommunity {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import events from '@/subscribers/events';
|
||||
import Container from 'typedi';
|
||||
|
||||
export class TriggerInvalidateCacheOnSubscriptionChange {
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
*/
|
||||
public attach = (bus) => {
|
||||
bus.subscribe(
|
||||
events.subscription.onSubscriptionCancelled,
|
||||
this.triggerInvalidateCache.bind(this)
|
||||
);
|
||||
bus.subscribe(
|
||||
events.subscription.onSubscriptionResumed,
|
||||
this.triggerInvalidateCache.bind(this)
|
||||
);
|
||||
bus.subscribe(
|
||||
events.subscription.onSubscriptionPlanChanged,
|
||||
this.triggerInvalidateCache.bind(this)
|
||||
);
|
||||
};
|
||||
|
||||
private triggerInvalidateCache() {
|
||||
const io = Container.get('socket');
|
||||
|
||||
// Notify the frontend to reflect the new transactions changes.
|
||||
io.emit('SUBSCRIPTION_CHANGED', { subscriptionSlug: 'main' });
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
export const ERRORS = {
|
||||
SUBSCRIPTION_ID_NOT_ASSOCIATED_TO_TENANT:
|
||||
'SUBSCRIPTION_ID_NOT_ASSOCIATED_TO_TENANT',
|
||||
SUBSCRIPTION_NOT_EXIST: 'SUBSCRIPTION_NOT_EXIST',
|
||||
SUBSCRIPTION_ALREADY_CANCELED: 'SUBSCRIPTION_ALREADY_CANCELED',
|
||||
SUBSCRIPTION_ALREADY_ACTIVE: 'SUBSCRIPTION_ALREADY_ACTIVE',
|
||||
SOMETHING_WENT_WRONG_WITH_LS: 'SOMETHING_WENT_WRONG_WITH_LS',
|
||||
};
|
||||
|
||||
export interface IOrganizationSubscriptionChanged {
|
||||
@@ -9,11 +13,20 @@ export interface IOrganizationSubscriptionChanged {
|
||||
newVariantId: number;
|
||||
}
|
||||
|
||||
export interface IOrganizationSubscriptionCanceled {
|
||||
export interface IOrganizationSubscriptionCancel {
|
||||
tenantId: number;
|
||||
subscriptionId: string;
|
||||
}
|
||||
|
||||
export interface IOrganizationSubscriptionCancelled {
|
||||
tenantId: number;
|
||||
subscriptionId: string;
|
||||
}
|
||||
|
||||
export interface IOrganizationSubscriptionResume {
|
||||
tenantId: number;
|
||||
subscriptionId: number;
|
||||
}
|
||||
export interface IOrganizationSubscriptionResumed {
|
||||
tenantId: number;
|
||||
subscriptionId: number;
|
||||
|
||||
@@ -35,6 +35,8 @@ export default {
|
||||
*/
|
||||
organization: {
|
||||
build: 'onOrganizationBuild',
|
||||
built: 'onOrganizationBuilt',
|
||||
|
||||
seeded: 'onOrganizationSeeded',
|
||||
|
||||
baseCurrencyUpdated: 'onOrganizationBaseCurrencyUpdated',
|
||||
@@ -44,10 +46,19 @@ export default {
|
||||
* Organization subscription.
|
||||
*/
|
||||
subscription: {
|
||||
onSubscriptionCanceled: 'onSubscriptionCanceled',
|
||||
onSubscriptionCancel: 'onSubscriptionCancel',
|
||||
onSubscriptionCancelled: 'onSubscriptionCancelled',
|
||||
|
||||
onSubscriptionResume: 'onSubscriptionResume',
|
||||
onSubscriptionResumed: 'onSubscriptionResumed',
|
||||
|
||||
onSubscriptionPlanChange: 'onSubscriptionPlanChange',
|
||||
onSubscriptionPlanChanged: 'onSubscriptionPlanChanged',
|
||||
onSubscribed: 'onOrganizationSubscribed',
|
||||
|
||||
onSubscriptionSubscribed: 'onSubscriptionSubscribed',
|
||||
|
||||
onSubscriptionPaymentSucceed: 'onSubscriptionPaymentSucceed',
|
||||
onSubscriptionPaymentFailed: 'onSubscriptionPaymentFailed'
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('oneclick_demos', (table) => {
|
||||
table.increments('id');
|
||||
table.string('key');
|
||||
table.integer('tenant_id').unsigned();
|
||||
table.integer('user_id').unsigned();
|
||||
table.timestamps();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.dropTableIfExists('oneclick_demos');
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.table('subscription_plan_subscriptions', (table) => {
|
||||
table.string('payment_status');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.table('subscription_plan_subscriptions', (table) => {
|
||||
table.dropColumn('payment_status');
|
||||
});
|
||||
};
|
||||
17
packages/server/src/system/models/OneclickDemo.ts
Normal file
17
packages/server/src/system/models/OneclickDemo.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import SystemModel from '@/system/models/SystemModel';
|
||||
|
||||
export class OneClickDemo extends SystemModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'oneclick_demos';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt'];
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,10 @@ import SystemModel from '@/system/models/SystemModel';
|
||||
import { PlanSubscription } from '..';
|
||||
|
||||
export default class Plan extends mixin(SystemModel) {
|
||||
price: number;
|
||||
invoiceInternal: number;
|
||||
invoicePeriod: string;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
|
||||
@@ -198,14 +198,16 @@ export default class Tenant extends BaseModel {
|
||||
planId,
|
||||
invoiceInterval,
|
||||
invoicePeriod,
|
||||
subscriptionSlug
|
||||
subscriptionSlug,
|
||||
payload?,
|
||||
) {
|
||||
return Tenant.newSubscription(
|
||||
this.id,
|
||||
planId,
|
||||
invoiceInterval,
|
||||
invoicePeriod,
|
||||
subscriptionSlug
|
||||
subscriptionSlug,
|
||||
payload
|
||||
);
|
||||
}
|
||||
|
||||
@@ -217,7 +219,8 @@ export default class Tenant extends BaseModel {
|
||||
planId: number,
|
||||
invoiceInterval: 'month' | 'year',
|
||||
invoicePeriod: number,
|
||||
subscriptionSlug: string
|
||||
subscriptionSlug: string,
|
||||
payload?: { lemonSqueezyId: string }
|
||||
) {
|
||||
const period = new SubscriptionPeriod(invoiceInterval, invoicePeriod);
|
||||
|
||||
@@ -227,6 +230,7 @@ export default class Tenant extends BaseModel {
|
||||
planId,
|
||||
startsAt: period.getStartDate(),
|
||||
endsAt: period.getEndDate(),
|
||||
lemonSubscriptionId: payload?.lemonSqueezyId || null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
BIN
packages/server/views/.DS_Store
vendored
Normal file
BIN
packages/server/views/.DS_Store
vendored
Normal file
Binary file not shown.
23
packages/server/views/demo-sheets/Expenses.csv
Normal file
23
packages/server/views/demo-sheets/Expenses.csv
Normal file
@@ -0,0 +1,23 @@
|
||||
Payment Date,Reference No.,Payment Account,Description,Currency Code,Exchange Rate,Expense Account,Amount,Line Description,Publish
|
||||
2024-03-01,REF-1,Petty Cash,Vel et dolorem architecto veniam.,,,Office expenses,9000,Voluptates voluptas corporis vel.,T
|
||||
2024-03-02,REF-2,Petty Cash,Id est molestias.,,,Office expenses,9000,Eos voluptatem cumque et voluptate reiciendis.,T
|
||||
2024-03-03,REF-3,Petty Cash,Quam cupiditate at nihil dicta dignissimos non fugit illo.,,,Office expenses,9000,Hic alias rerum sed commodi dolores sint animi perferendis.,T
|
||||
2024-03-04,REF-4,Petty Cash,Et voluptatem consequatur corrupti beatae sit.,,,Office expenses,9000,Exercitationem impedit praesentium et eaque.,T
|
||||
2024-03-05,REF-5,Petty Cash,Illo aut ad id et non error et reiciendis optio.,,,Office expenses,9000,Accusantium modi consequuntur eaque consequatur deleniti consequuntur et qui.,T
|
||||
2024-03-06,REF-6,Petty Cash,Ea consequatur placeat aut et enim.,,,Office expenses,9000,Itaque odio fugiat recusandae.,T
|
||||
2024-03-07,REF-7,Petty Cash,A expedita consequatur sequi eveniet quos rerum.,,,Office expenses,9000,Quidem doloremque dignissimos totam dolor iure sed necessitatibus optio.,T
|
||||
2024-03-08,REF-8,Petty Cash,Est libero deleniti animi delectus eligendi necessitatibus expedita fugit.,,,Office expenses,9000,Velit rerum aperiam mollitia ut eius error est quo aut.,T
|
||||
2024-03-09,REF-9,Petty Cash,Ut dolor tempora quam consequuntur mollitia aut quos consectetur commodi.,,,Office expenses,9000,Culpa architecto ea vero nisi quis voluptas animi.,T
|
||||
2024-03-10,REF-10,Petty Cash,Nihil hic soluta.,,,Office expenses,9000,Omnis recusandae ducimus vel.,T
|
||||
2024-03-11,REF-11,Petty Cash,Aspernatur placeat odit asperiores et tempora quam.,,,Office expenses,9000,Sit tempora optio ullam velit beatae architecto et.,T
|
||||
2024-03-12,REF-12,Petty Cash,Harum soluta sed.,,,Office expenses,9000,Nobis est earum saepe.,T
|
||||
2024-03-13,REF-13,Petty Cash,Ea quod mollitia non illo dolores voluptatem distinctio.,,,Office expenses,9000,Sit eos dolores autem rerum voluptate quia ipsam.,T
|
||||
2024-03-14,REF-14,Petty Cash,Et quod distinctio atque.,,,Office expenses,9000,Facilis sed expedita reiciendis.,T
|
||||
2024-03-15,REF-15,Petty Cash,Omnis delectus tempore.,,,Office expenses,9000,Autem non reprehenderit placeat aut et quo.,T
|
||||
2024-03-16,REF-16,Petty Cash,Dolores optio qui dolore quia aut explicabo eaque.,,,Office expenses,9000,Odit dolores ut.,T
|
||||
2024-03-17,REF-17,Petty Cash,Odit quibusdam sunt in a quod error.,,,Office expenses,9000,Quo explicabo quae dolor enim nisi voluptas id et temporibus.,T
|
||||
2024-03-18,REF-18,Petty Cash,Hic quibusdam officiis voluptatem facilis repellat molestiae non.,,,Office expenses,9000,Quo sit ea et itaque error.,T
|
||||
2024-03-19,REF-19,Petty Cash,Dolor doloremque quia qui.,,,Office expenses,9000,Ut deleniti laboriosam et.,T
|
||||
2024-03-20,REF-20,Petty Cash,Ad enim repellat sed et vero aliquid.,,,Office expenses,9000,Error in voluptas non quae quibusdam id excepturi illo neque.,T
|
||||
2024-03-21,REF-21,Petty Cash,Doloribus ut excepturi.,,,Office expenses,9000,Sint magni et reiciendis harum praesentium vero sit blanditiis.,T
|
||||
2024-03-22,REF-22,Petty Cash,Id rerum sunt et.,,,Office expenses,9000,Autem magnam eum error ex sunt temporibus exercitationem ullam est.,T
|
||||
|
123
packages/server/views/demo-sheets/bank-transactions.csv
Normal file
123
packages/server/views/demo-sheets/bank-transactions.csv
Normal file
@@ -0,0 +1,123 @@
|
||||
Date,Amount,Description,Payee
|
||||
2024-07-23,-48.25,CREDIT CARD PURCHASE SMART AND FINAL #1 ANAHEIM CA DATE 12/14 15277301931256 9 CARD 5,
|
||||
2024-08-21,-21.77,PURCHASE AUTHORIZED ON 05/27 ROYAL FARMS 100 MONUMENT AVE A P NATIONAL HARBOR MD 0846248421604 CARD 44,
|
||||
2024-08-21,-58,72116609 POS PURCHASE EARNIN RE SC 6 721582259,
|
||||
2024-08-21,-70.61,Dunkin' Donuts,
|
||||
2024-08-20,-52.34,Uber Eats,
|
||||
2024-08-19,-15.47,Panera Bread,
|
||||
2024-08-19,-4.79,5 PURCHASE-SIG DENNY'S 366992 EUGENE OR 6 039488,
|
||||
2024-08-18,-186,American Airlines,
|
||||
2024-08-18,-3,3947558 VISA PURCHASE PLAYSTUD 07057808 TAYLOR MI 8840321 748,
|
||||
2024-08-18,-9.95,Bojangles,
|
||||
2024-08-15,-134,GrubHub,
|
||||
2024-08-15,-91.53,671 POS PURCHASE EARNIN RE SC 9671543 349619,
|
||||
2024-08-15,-4.78,Popeyes,
|
||||
2024-08-14,496,CASH DEPOSIT,
|
||||
2024-08-14,-97.67,APPLE 857979823 NEWKIRK #4,
|
||||
2024-08-07,-2.81,POS PUR 449399193 04/07 TIMESTAMP THE PIZZA PLACE 4205 NEW YORK NY 634330,
|
||||
2024-08-06,-4.27,QUEENS BAR 35 E GRAND RIVER AVE #9 DETROIT MI CARD 29602991,
|
||||
2024-08-06,-6.28,Walmart,
|
||||
2024-08-06,-20.52,60904 PURCHASE-SIG 09/21 TIMESTAMP UPLIFT RE 9123348 562542 SC 63432961 3968766,
|
||||
2024-08-06,-12.2,UBER 588639690 RIDE 05/12 CA 12095 DEBIT CARD PURCHASE 04/04 TIMESTAMP 8102241,
|
||||
2024-08-05,-5.58,WITHDRAWAL 02178293119 POS POUR CHOICES,
|
||||
2024-08-05,-25.98,Planet Fitness,
|
||||
2024-08-05,-20,CASH APP TRANSFER,
|
||||
2024-08-04,-38,WITHDRAWAL DEBIT CARD CONSUMER DEBIT PULSZ 252-291-2362 VA DATE 04/12 23978676735612 63546108 CARD 94353,
|
||||
2024-08-04,-19,CASH APP TRANSFER,
|
||||
2024-08-04,-3.25,MICROSOFT 979333884,
|
||||
2024-08-03,-16,TOPGOLF 1010 GREENWOOD BLVD,
|
||||
2024-08-03,-59.52,EFT DEBIT MICROSOFT XBOX CA 830 08/03 TIMESTAMP,
|
||||
2024-08-03,-145.09,Venmo,
|
||||
2024-08-03,-76.02,Albertsons,
|
||||
2024-08-03,-9.79,STOP & SHOP #25402 WEST CALDWELL MODATE 01/24 31772803806094 17096869 CARD 34321 WITHDRAWAL DEBIT CARD,
|
||||
2024-08-02,-103.44,Dunkin' Donuts,
|
||||
2024-08-02,-72,Uber Eats,
|
||||
2024-08-02,-143.93,Food Lion,
|
||||
2024-08-02,-128.12,McDonald's,
|
||||
2024-07-31,-37.47,CHECKCARD 538036 CLEO AI IN MOUNTAIN VIEWCA 172788892 RECURRING,
|
||||
2024-07-30,-162.69,McDonald's,
|
||||
2024-07-29,95,Brigit XX/XX #406735377 PMNT RCVD Brigit,
|
||||
2024-07-29,-45.47,Starbucks,
|
||||
2024-07-29,-595.11,INTERAC PURCHASE - 67200 TARGET #052,
|
||||
2024-07-28,-89,TRANSFER PAYPAL ADD TO BALANCE INTERNET PAYMENT,
|
||||
2024-07-27,-6.04,Krispy Kreme,
|
||||
2024-07-27,-66.63,McDonald's,
|
||||
2024-07-26,-133.56,CARD PURCHASE PAYBYPHONE 804665947 11/19,
|
||||
2024-07-26,-10.02,70252237 AMAZON - LEN N,
|
||||
2024-07-26,-5,BLAZE PIZZA #31 SIG PUR 9906,
|
||||
2024-07-25,-30.9,CITGO,
|
||||
2024-07-25,-69,Tropical Smoothie Cafe,
|
||||
2024-07-24,-744,Internal Revenue Service,
|
||||
2024-07-24,-15.61,Walmart,
|
||||
2024-08-04,-38,WITHDRAWAL DEBIT CARD CONSUMER DEBIT,
|
||||
2024-08-05,-5.58,WITHDRAWAL POS POUR,
|
||||
2024-08-06,-6.28,Walmart,
|
||||
2024-07-24,-15.61,Walmart,
|
||||
2024-08-03,-145.09,Venmo,
|
||||
2024-08-20,-52.34,Uber Eats,
|
||||
2024-08-02,-72,Uber Eats,
|
||||
2024-07-25,-69,Tropical Smoothie Cafe,
|
||||
2024-07-28,-89,TRANSFER ADD TO BALANCE INTERNET PAYMENT,
|
||||
2024-08-03,-76.02,Albertsons,
|
||||
2024-08-18,-186,American Airlines,
|
||||
2024-08-15,-91.53,POS PURCHASE EARNIN RE SC 9671543 349619,
|
||||
2024-07-29,-95,PMNT RCVD Brigit,
|
||||
2024-08-04,-19,CASH APP TRANSFER,
|
||||
2024-08-05,-20,CASH APP TRANSFER,
|
||||
2024-08-14,496,CASH DEPOSIT,
|
||||
2024-07-31,-37.47,CHECKCARD 538036 CLEO AI IN MOUNTAIN VIEWCA,
|
||||
2024-07-25,-30.9,Citgo,
|
||||
2024-08-18,-9.95,Bojangles,
|
||||
2024-07-24,-744,Internal Revenue Service,
|
||||
2024-07-27,-6.04,Krispy Kreme,
|
||||
2024-07-27,-66.63,McDonald's,
|
||||
2024-07-30,-162.69,McDonald's,
|
||||
2024-08-02,-128.12,McDonald's,
|
||||
2024-08-04,-3.25,MICROSOFT,
|
||||
2024-08-19,-15.47,Panera Bread,
|
||||
2024-08-05,-25.98,Planet Fitness,
|
||||
2024-08-15,-4.78,Popeyes,
|
||||
2024-08-07,-2.81,POS PUR,
|
||||
2024-08-21,-21.77,PURCHASE AUTHORIZED ON 05/27,
|
||||
2024-07-29,-45.47,Starbucks,
|
||||
2024-08-03,-9.79,WITHDRAWAL DEBIT CARD,
|
||||
2024-08-03,-16,TOPGOLF 1010 GREENWOOD BLVD,
|
||||
2024-08-04,-38,WITHDRAWAL DEBIT CARD CONSUMER DEBIT,
|
||||
2024-08-05,-5.58,WITHDRAWAL POS POUR,
|
||||
2024-08-06,-6.28,Walmart,
|
||||
2024-07-24,-15.61,Walmart,
|
||||
2024-08-03,-145.09,Venmo,
|
||||
2024-08-20,-52.34,Uber Eats,
|
||||
2024-08-02,-72,Uber Eats,
|
||||
2024-07-25,-69,Tropical Smoothie Cafe,
|
||||
2024-07-28,-89,TRANSFER ADD TO BALANCE INTERNET PAYMENT,
|
||||
2024-08-03,-76.02,Albertsons,
|
||||
2024-08-18,-186,American Airlines,
|
||||
2024-08-15,-91.53,POS PURCHASE EARNIN RE SC 9671543 349619,
|
||||
2024-07-29,-95,PMNT RCVD Brigit,
|
||||
2024-08-04,-19,CASH APP TRANSFER,
|
||||
2024-08-05,-20,CASH APP TRANSFER,
|
||||
2024-08-14,496,CASH DEPOSIT,
|
||||
2024-07-31,-37.47,CHECKCARD 538036 CLEO AI IN MOUNTAIN VIEWCA,
|
||||
2024-07-25,-30.9,Citgo,
|
||||
2024-08-18,-9.95,Bojangles,
|
||||
2024-07-24,-744,Internal Revenue Service,
|
||||
2024-07-27,-6.04,Krispy Kreme,
|
||||
2024-07-27,-66.63,McDonald's,
|
||||
2024-07-30,-162.69,McDonald's,
|
||||
2024-08-02,-128.12,McDonald's,
|
||||
2024-08-04,-3.25,MICROSOFT,
|
||||
2024-08-19,-15.47,Panera Bread,
|
||||
2024-08-05,-25.98,Planet Fitness,
|
||||
2024-08-15,-4.78,Popeyes,
|
||||
2024-08-07,-2.81,POS PUR,
|
||||
2024-08-21,-21.77,PURCHASE AUTHORIZED ON 05/27,
|
||||
2024-07-29,-45.47,Starbucks,
|
||||
2024-08-03,-9.79,WITHDRAWAL DEBIT CARD,
|
||||
2024-08-03,-16,TOPGOLF 1010 GREENWOOD BLVD,
|
||||
2024-08-04,-38,WITHDRAWAL DEBIT CARD CONSUMER DEBIT,
|
||||
2024-08-05,-5.58,WITHDRAWAL POS POUR,
|
||||
2024-08-04,-3.25,MICROSOFT,
|
||||
2024-08-07,-2.81,POS PUR,
|
||||
2024-08-21,-21.77,PURCHASE AUTHORIZED ON 05/27,
|
||||
2024-08-03,-9.79,WITHDRAWAL DEBIT CARD,
|
||||
|
6
packages/server/views/demo-sheets/customers.csv
Normal file
6
packages/server/views/demo-sheets/customers.csv
Normal file
@@ -0,0 +1,6 @@
|
||||
Customer Type,First Name,Last Name,Company Name,Display Name,Email,Personal Phone Number,Work Phone Number,Website,Opening Balance,Opening Balance At,Opening Balance Ex. Rate,Currency,Active,Note,Billing Address 1,Billing Address 2,Billing Address City,Billing Address Country,Billing Address Phone,Billing Address Postcode,Billing Address State,Shipping Address 1,Shipping Address 2,Shipping Address City,Shipping Address Country,Shipping Address Phone,Shipping Address Postcode,Shipping Address State
|
||||
Business,Nicolette,Schamberger,Homenick - Hane,Rowland Rowe,cicero86@yahoo.com,811-603-2235,906-993-5190,http://google.com,54302.23,2022-02-02,2,LYD,F,Doloribus autem optio temporibus dolores mollitia sit.,862 Jessika Well,1091 Dorthy Mount,Deckowfort,Ghana,825-011-5207,38228,Oregon,37626 Thiel Villages,132 Batz Avenue,Pagacburgh,Albania,171-546-3701,13709,Georgia
|
||||
Business,Hermann,Crooks,Veum - Schaefer,Harley Veum,immanuel56@hotmail.com,449-780-9999,970-473-5785,http://google.com,54302.23,2022-02-02,2,LYD,T,Doloribus dolore dolor dicta vitae in fugit nisi quibusdam.,532 Simonis Spring,3122 Nicolas Inlet,East Matteofort,Holy See (Vatican City State),366-084-8629,41607,Montana,2889 Tremblay Plaza,71355 Kutch Isle,D'Amorehaven,Monaco,614-189-3328,09634-0435,Nevada
|
||||
Business,Nellie,Gulgowski,"Boyle, Heller and Jones",Randall Kohler,anibal_frami@yahoo.com,498-578-0740,394-550-6827,http://google.com,54302.23,2022-02-02,2,LYD,T,Vero quibusdam rem fugit aperiam est modi.,214 Sauer Villages,30687 Kacey Square,Jayceborough,Benin,332-820-1127,16425-3887,Mississippi,562 Diamond Loaf,9595 Satterfield Trafficway,Alexandrinefort,Puerto Rico,776-500-8456,30258,South Dakota
|
||||
Business,Stone,Jerde,"Cassin, Casper and Maggio",Clint McLaughlin,nathanael22@yahoo.com,562-790-6059,686-838-0027,http://google.com,54302.23,2022-02-02,2,LYD,F,Quis cumque molestias rerum.,22590 Cathy Harbor,24493 Brycen Brooks,Elnorashire,Andorra,701-852-8005,5680,Nevada,5355 Erdman Bridge,421 Jeanette Camp,East Philip,Venezuela,426-119-0858,34929-0501,Tennessee
|
||||
Individual,Lempi,Kling,"Schamberger, O'Connell and Bechtelar",Alexie Barton,eulah.kreiger@hotmail.com,745-756-1063,965-150-1945,http://google.com,54302.23,2022-02-02,2,LYD,F,Maxime laboriosam hic voluptate maiores est officia.,0851 Jones Flat,845 Bailee Drives,Kamrenport,Niger,220-125-0608,30311,Delaware,929 Ferry Row,020 Adam Plaza,West Carmellaside,Ghana,053-333-6679,79221-4681,Illinois
|
||||
|
61
packages/server/views/demo-sheets/items.csv
Normal file
61
packages/server/views/demo-sheets/items.csv
Normal file
@@ -0,0 +1,61 @@
|
||||
Item Type,Item Name,Item Code,Sellable,Purchasable,Cost Price,Sell Price,Cost Account,Sell Account,Inventory Account,Sell Description,Purchase Description,Category,Note,Active
|
||||
Inventory,Amoxicillin and Clavulanatea,1000,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,At dolor est non tempore et quisquam.,TRUE
|
||||
Inventory,Azithromycin,1001,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Id perspiciatis at adipisci minus accusamus dolor iure dolore.,TRUE
|
||||
Inventory,Marks - Carroll,1002,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Odio odio minus similique.,TRUE
|
||||
Inventory,"VonRueden, Ruecker and Hettinger",1003,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Quibusdam dolores illo.,TRUE
|
||||
Inventory,Aimovig SureClick Autoinjector,1004,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Labore atque quo nam natus ducimus.,TRUE
|
||||
Inventory,Romaguera Group,1005,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Sit et aut rem necessitatibus.,TRUE
|
||||
Inventory,Thompson - Reichert,1006,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Non non fuga esse fugit dolor soluta vel a.,TRUE
|
||||
Inventory,AirDuo Digihaler with eModule,1007,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Error ut est soluta quos et qui.,TRUE
|
||||
Inventory,"Reichert, Sanford and Shanahan",1008,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Nesciunt laboriosam nobis porro numquam hic expedita vel quod praesentium.,TRUE
|
||||
Inventory,Crooks - Fay,1009,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Amet magni architecto voluptas itaque.,TRUE
|
||||
Inventory,Becker - Kozey,1010,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Provident consequatur quos qui explicabo dicta nam.,TRUE
|
||||
Inventory,Robel - Weber,1011,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Quia cum eos rerum odio omnis quasi.,TRUE
|
||||
Inventory,"Runte, Johns and Konopelski",1012,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Soluta et et accusantium corrupti autem repellendus assumenda fugit.,TRUE
|
||||
Inventory,McCullough - Beer,1013,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Quo quod sunt distinctio aliquid accusantium.,TRUE
|
||||
Inventory,"Hintz, Wisozk and Mills",1014,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Asperiores esse at quos veritatis voluptas officiis.,TRUE
|
||||
Inventory,"Zieme, Crist and Abbott",1015,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Ea ut qui architecto voluptates error.,TRUE
|
||||
Inventory,Kiehn - Cormier,1016,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Libero sit impedit sint qui voluptatum id qui sed.,TRUE
|
||||
Inventory,"Jast, Carter and Reynolds",1017,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Sint sunt quaerat nisi voluptate praesentium vero labore veniam quia.,TRUE
|
||||
Inventory,Stehr Inc,1018,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Voluptas aut ipsum et iste.,TRUE
|
||||
Inventory,"Friesen, Hamill and Hessel",1019,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Neque tenetur minima amet aut optio et recusandae vel ad.,TRUE
|
||||
Inventory,Lemke - Hayes,1020,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Deleniti enim autem corrupti fuga quibusdam non.,TRUE
|
||||
Inventory,Mirtazapine,1021,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Molestiae quia asperiores praesentium et voluptas iste sit et accusantium.,TRUE
|
||||
Inventory,Medroxyprogesterone,1022,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Est quia omnis at.,TRUE
|
||||
Inventory,Purdy and Sons,1023,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Veniam eos quibusdam neque consectetur eius aliquam.,TRUE
|
||||
Inventory,"Zemlak, Kassulke and Veum",1024,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Vitae optio odit.,TRUE
|
||||
Inventory,Kris LLC,1025,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Libero voluptatibus voluptatem quo.,TRUE
|
||||
Inventory,"Jaskolski, Parker and Blanda",1026,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Laborum optio est voluptas dolores sunt voluptates.,TRUE
|
||||
Inventory,Roberts Kilback,1027,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Ut illo molestiae dolore recusandae aut sequi molestiae fugit eum.,TRUE
|
||||
Inventory,Olson Daugherty,1028,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Non est aut sit nulla nisi labore commodi delectus.,TRUE
|
||||
Inventory,Hermann Inc,1029,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Commodi maxime minus dicta quia.,TRUE
|
||||
Inventory,"Rowe, Nolan and Schaefer",1030,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Quos et est eum odit quam omnis velit impedit tempora.,TRUE
|
||||
Inventory,Cole - Lesch,1031,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Aut accusantium non.,TRUE
|
||||
Inventory,"Emmerich, Prohaska and Schimmel",1032,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Doloribus aut qui sequi.,TRUE
|
||||
Inventory,"Stracke, Champlin and Bayer",1033,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Quisquam odio et et quod eum necessitatibus quaerat.,TRUE
|
||||
Inventory,Spinka Okuneva,1034,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Rerum unde neque sit ut aut voluptas eum.,TRUE
|
||||
Inventory,Swaniawski Muller,1035,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Quia dolore minus voluptatibus modi.,TRUE
|
||||
Inventory,Emard Satterfield,1036,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Nobis et nulla alias.,TRUE
|
||||
Inventory,"Treutel, Muller and Sipes",1037,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Sit perspiciatis voluptate qui eos.,TRUE
|
||||
Inventory,Cole and Sons,1038,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Qui sint ab iusto.,TRUE
|
||||
Inventory,"Ullrich, Murazik and Roob",1039,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Explicabo sed maiores.,TRUE
|
||||
Inventory,Dare and Sons,1040,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Aut eveniet omnis dolores labore qui delectus repellat.,TRUE
|
||||
Inventory,Trantow and Sons,1041,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Animi rerum quaerat qui voluptas.,TRUE
|
||||
Inventory,Dooley Price,1042,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Reiciendis beatae delectus qui corporis beatae autem modi laudantium.,TRUE
|
||||
Inventory,"Shanahan, Considine and Towne",1043,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Molestiae nihil delectus eos.,TRUE
|
||||
Inventory,"Moen, Mohr and Reilly",1044,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Dolor odio soluta eaque consequatur rerum iste vel.,TRUE
|
||||
Inventory,Price Bradtke,1045,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Beatae autem illo ea alias ipsum nobis.,TRUE
|
||||
Inventory,Fadel Toy,1046,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Et molestiae eum ut dolores.,TRUE
|
||||
Inventory,Homenick Inc,1047,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Officia repellendus laboriosam iste quidem explicabo nemo quasi aliquam distinctio.,TRUE
|
||||
Inventory,Witting Lemke,1048,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Nihil qui voluptatem unde.,TRUE
|
||||
Inventory,Gerlach Thompson,1049,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Quo quas debitis vel.,TRUE
|
||||
Inventory,Schaefer Thompson,1050,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Minima nobis voluptas autem aut adipisci tenetur.,TRUE
|
||||
Inventory,"Kovacek, Schuster and Kemmer",1051,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Aspernatur earum praesentium laudantium velit.,TRUE
|
||||
Inventory,Treutel Stanton,1052,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Laboriosam repellendus quisquam labore ipsum et.,TRUE
|
||||
Inventory,Bartell and Sons,1053,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Sed voluptatem consequatur reprehenderit ab aut molestiae.,TRUE
|
||||
Inventory,"Bergnaum, Murray and McGlynn",1054,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Ullam in ut molestiae non eveniet et totam dolor.,TRUE
|
||||
Inventory,"Wilkinson, Schowalter and Lindgren",1055,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Distinctio rerum iusto explicabo fugiat doloremque aut omnis at est.,TRUE
|
||||
Inventory,"Glover, Zemlak and Kerluke",1056,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Eum at laboriosam aut.,TRUE
|
||||
Inventory,Durgan Kirlin,1057,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Reprehenderit sunt expedita voluptatum nobis deleniti aliquam numquam quas.,TRUE
|
||||
Inventory,Towne Crooks,1058,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Aliquam animi libero modi quisquam voluptas fugiat.,TRUE
|
||||
Inventory,Wuckert McGlynn,1059,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Quidem ut et ut reprehenderit.,TRUE
|
||||
|
11
packages/server/views/demo-sheets/manual-journals.csv
Normal file
11
packages/server/views/demo-sheets/manual-journals.csv
Normal file
@@ -0,0 +1,11 @@
|
||||
Date,Journal No,Reference No.,Currency Code,Exchange Rate,Journal Type,Description,Credit,Debit,Note,Account,Contact,Publish
|
||||
2024-02-02,J-100022,REF-10000,,,,Animi quasi qui itaque aut possimus illum est magnam enim.,1000,0,Qui reprehenderit voluptate.,Bank Account,,T
|
||||
2024-02-02,J-100022,REF-10000,,,,In assumenda dicta autem non est corrupti non et.,0,1000,Omnis tempora qui fugiat neque dolor voluptatem aut repudiandae nihil.,Bank Account,,T
|
||||
2024-02-01,J-100023,REF-10000,,,,Animi quasi qui itaque aut possimus illum est magnam enim.,1400,0,Qui reprehenderit voluptate.,Bank Account,,T
|
||||
2024-02-01,J-100023,REF-10000,,,,In assumenda dicta autem non est corrupti non et.,0,1400,Omnis tempora qui fugiat neque dolor voluptatem aut repudiandae nihil.,Bank Account,,T
|
||||
2024-02-15,J-100024,REF-10000,,,,Animi quasi qui itaque aut possimus illum est magnam enim.,1900,0,Qui reprehenderit voluptate.,Bank Account,,T
|
||||
2024-02-15,J-100024,REF-10000,,,,In assumenda dicta autem non est corrupti non et.,0,1900,Omnis tempora qui fugiat neque dolor voluptatem aut repudiandae nihil.,Bank Account,,T
|
||||
2024-02-15,J-100025,REF-10000,,,,Animi quasi qui itaque aut possimus illum est magnam enim.,1900,0,Qui reprehenderit voluptate.,Bank Account,,T
|
||||
2024-02-15,J-100025,REF-10000,,,,In assumenda dicta autem non est corrupti non et.,0,1900,Omnis tempora qui fugiat neque dolor voluptatem aut repudiandae nihil.,Bank Account,,T
|
||||
2024-02-15,J-100026,REF-10000,,,,Animi quasi qui itaque aut possimus illum est magnam enim.,3494,0,Qui reprehenderit voluptate.,Bank Account,,T
|
||||
2024-02-15,J-100026,REF-10000,,,,In assumenda dicta autem non est corrupti non et.,0,3494,Omnis tempora qui fugiat neque dolor voluptatem aut repudiandae nihil.,Bank Account,,T
|
||||
|
50
packages/server/views/demo-sheets/sale-invoices.csv
Normal file
50
packages/server/views/demo-sheets/sale-invoices.csv
Normal file
@@ -0,0 +1,50 @@
|
||||
Invoice No.,Reference No.,Invoice Date,Due Date,Customer,Exchange Rate,Invoice Message,Terms & Conditions,Delivered,Item,Quantity,Rate,Description
|
||||
B-101,REF-0,2024-04-15,2024-04-17,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-102,REF-1,2024-04-16,2024-04-18,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-103,REF-2,2024-04-17,2024-04-19,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-104,REF-3,2024-04-18,2024-04-20,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-105,REF-4,2024-04-19,2024-04-21,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-106,REF-5,2024-04-20,2024-04-22,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-107,REF-6,2024-04-21,2024-04-23,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-108,REF-7,2024-04-22,2024-04-24,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-109,REF-8,2024-04-23,2024-04-25,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-110,REF-9,2024-04-24,2024-04-26,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-111,REF-10,2024-04-25,2024-04-27,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-112,REF-11,2024-04-26,2024-04-28,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-113,REF-12,2024-04-27,2024-04-29,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-114,REF-13,2024-04-28,2024-04-30,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-115,REF-14,2024-04-29,2024-05-01,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-116,REF-15,2024-04-30,2024-05-02,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-117,REF-16,2024-05-01,2024-05-03,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-118,REF-17,2024-05-02,2024-05-04,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-119,REF-18,2024-05-03,2024-05-05,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-120,REF-19,2024-05-04,2024-05-06,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-121,REF-20,2024-05-05,2024-05-07,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-122,REF-21,2024-05-06,2024-05-08,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-123,REF-22,2024-05-07,2024-05-09,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-124,REF-23,2024-05-08,2024-05-10,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-125,REF-24,2024-05-09,2024-05-11,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-126,REF-25,2024-05-10,2024-05-12,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-127,REF-26,2024-05-11,2024-05-13,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-128,REF-27,2024-05-12,2024-05-14,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-129,REF-28,2024-05-13,2024-05-15,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-130,REF-29,2024-05-14,2024-05-16,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-131,REF-30,2024-05-15,2024-05-17,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-132,REF-31,2024-05-16,2024-05-18,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-133,REF-32,2024-05-17,2024-05-19,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-134,REF-33,2024-05-18,2024-05-20,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-135,REF-34,2024-05-19,2024-05-21,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-136,REF-35,2024-05-20,2024-05-22,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-137,REF-36,2024-05-21,2024-05-23,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-138,REF-37,2024-05-22,2024-05-24,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-139,REF-38,2024-05-23,2024-05-25,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-140,REF-39,2024-05-24,2024-05-26,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-141,REF-40,2024-05-25,2024-05-27,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-142,REF-41,2024-05-26,2024-05-28,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-143,REF-42,2024-05-27,2024-05-29,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-144,REF-43,2024-05-28,2024-05-30,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-145,REF-44,2024-05-29,2024-05-31,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-146,REF-45,2024-05-30,2024-06-01,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-147,REF-46,2024-05-31,2024-06-02,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-148,REF-47,2024-06-01,2024-06-03,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
B-149,REF-48,2024-06-02,2024-06-04,Harley Veum,1,Aspernatur doloremque amet quia aut.,Quia illum aut dolores.,T,Amoxicillin and Clavulanatea,100,100,Description
|
||||
|
5
packages/server/views/demo-sheets/vendors.csv
Normal file
5
packages/server/views/demo-sheets/vendors.csv
Normal file
@@ -0,0 +1,5 @@
|
||||
First Name,Last Name,Company Name,Display Name,Email,Personal Phone Number,Work Phone Number,Website,Opening Balance,Opening Balance At,Opening Balance Ex. Rate,Currency,Active,Note,Billing Address 1,Billing Address 2,Billing Address City,Billing Address Country,Billing Address Phone,Billing Address Postcode,Billing Address State,Shipping Address 1,Shipping Address 2,Shipping Address City,Shipping Address Country,Shipping Address Phone,Shipping Address Postcode,Shipping Address State
|
||||
Nicolette,Schamberger,Homenick - Hane,Rowland Rowe,cicero86@yahoo.com,811-603-2235,906-993-5190,http://google.com,54302.23,2022-02-02,2,LYD,T,Doloribus autem optio temporibus dolores mollitia sit.,862 Jessika Well,1091 Dorthy Mount,Deckowfort,Ghana,825-011-5207,38228,Oregon,37626 Thiel Villages,132 Batz Avenue,Pagacburgh,Albania,171-546-3701,13709,Georgia
|
||||
Hermann,Crooks,Veum - Schaefer,Harley Veum,immanuel56@hotmail.com,449-780-9999,970-473-5785,http://google.com,54302.23,2022-02-02,2,LYD,T,Doloribus dolore dolor dicta vitae in fugit nisi quibusdam.,532 Simonis Spring,3122 Nicolas Inlet,East Matteofort,Holy See (Vatican City State),366-084-8629,41607,Montana,2889 Tremblay Plaza,71355 Kutch Isle,D'Amorehaven,Monaco,614-189-3328,09634-0435,Nevada
|
||||
Nellie,Gulgowski,"Boyle, Heller and Jones",Randall Kohler,anibal_frami@yahoo.com,498-578-0740,394-550-6827,http://google.com,54302.23,2022-02-02,2,LYD,T,Vero quibusdam rem fugit aperiam est modi.,214 Sauer Villages,30687 Kacey Square,Jayceborough,Benin,332-820-1127,16425-3887,Mississippi,562 Diamond Loaf,9595 Satterfield Trafficway,Alexandrinefort,Puerto Rico,776-500-8456,30258,South Dakota
|
||||
Stone,Jerde,"Cassin, Casper and Maggio",Clint McLaughlin,nathanael22@yahoo.com,562-790-6059,686-838-0027,http://google.com,54302.23,2022-02-02,2,LYD,T,Quis cumque molestias rerum.,22590 Cathy Harbor,24493 Brycen Brooks,Elnorashire,Andorra,701-852-8005,5680,Nevada,5355 Erdman Bridge,421 Jeanette Camp,East Philip,Venezuela,426-119-0858,34929-0501,Tennessee
|
||||
|
@@ -20,6 +20,7 @@ import { queryConfig } from '../hooks/query/base';
|
||||
import { EnsureUserEmailVerified } from './Guards/EnsureUserEmailVerified';
|
||||
import { EnsureAuthNotAuthenticated } from './Guards/EnsureAuthNotAuthenticated';
|
||||
import { EnsureUserEmailNotVerified } from './Guards/EnsureUserEmailNotVerified';
|
||||
import { EnsureOneClickDemoAccountEnabled } from '@/containers/OneClickDemo/EnsureOneClickDemoAccountEnabled';
|
||||
|
||||
const EmailConfirmation = LazyLoader({
|
||||
loader: () => import('@/containers/Authentication/EmailConfirmation'),
|
||||
@@ -27,7 +28,9 @@ const EmailConfirmation = LazyLoader({
|
||||
const RegisterVerify = LazyLoader({
|
||||
loader: () => import('@/containers/Authentication/RegisterVerify'),
|
||||
});
|
||||
|
||||
const OneClickDemoPage = LazyLoader({
|
||||
loader: () => import('@/containers/OneClickDemo/OneClickDemoPage'),
|
||||
});
|
||||
/**
|
||||
* App inner.
|
||||
*/
|
||||
@@ -37,6 +40,13 @@ function AppInsider({ history }) {
|
||||
<DashboardThemeProvider>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path={'/one_click_demo'}>
|
||||
<EnsureOneClickDemoAccountEnabled>
|
||||
<EnsureAuthNotAuthenticated>
|
||||
<OneClickDemoPage />
|
||||
</EnsureAuthNotAuthenticated>
|
||||
</EnsureOneClickDemoAccountEnabled>
|
||||
</Route>
|
||||
<Route path={'/auth/register/verify'}>
|
||||
<EnsureAuthenticated>
|
||||
<EnsureUserEmailNotVerified>
|
||||
|
||||
@@ -62,6 +62,7 @@ export function BankAccount({
|
||||
balance,
|
||||
loading = false,
|
||||
updatedBeforeText,
|
||||
uncategorizedTransactionsCount,
|
||||
...restProps
|
||||
}) {
|
||||
return (
|
||||
@@ -77,17 +78,19 @@ export function BankAccount({
|
||||
</BankAccountHeader>
|
||||
|
||||
<BankAccountMeta>
|
||||
{false && (
|
||||
{uncategorizedTransactionsCount > 0 && (
|
||||
<BankAccountMetaLine
|
||||
title={intl.get('cash_flow.transactions_for_review')}
|
||||
value={'0'}
|
||||
title={intl.get('banking.transactions_for_review')}
|
||||
value={uncategorizedTransactionsCount}
|
||||
className={clsx({ [Classes.SKELETON]: loading })}
|
||||
/>
|
||||
)}
|
||||
{updatedBeforeText && (
|
||||
<BankAccountMetaLine
|
||||
title={updatedBeforeText}
|
||||
className={clsx({ [Classes.SKELETON]: loading })}
|
||||
/>
|
||||
)}
|
||||
<BankAccountMetaLine
|
||||
title={updatedBeforeText}
|
||||
className={clsx({ [Classes.SKELETON]: loading })}
|
||||
/>
|
||||
</BankAccountMeta>
|
||||
|
||||
<BankAccountBalance amount={balance} loading={loading} />
|
||||
|
||||
@@ -23,6 +23,9 @@ export function DashboardSockets() {
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
});
|
||||
socket.current.on('SUBSCRIPTION_CHANGED', () => {
|
||||
client.invalidateQueries('GetSubscriptions');
|
||||
});
|
||||
return () => {
|
||||
socket.current.removeAllListeners();
|
||||
socket.current.close();
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.tag{
|
||||
|
||||
6
packages/webapp/src/config.ts
Normal file
6
packages/webapp/src/config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const Config = {
|
||||
oneClickDemo: {
|
||||
enable: process.env.REACT_APP_ONE_CLICK_DEMO_ENABLE === 'true',
|
||||
demoUrl: process.env.REACT_APP_DEMO_ACCOUNT_URL || '',
|
||||
},
|
||||
};
|
||||
@@ -3,30 +3,30 @@ import intl from 'react-intl-universal';
|
||||
|
||||
export const getAddMoneyInOptions = () => [
|
||||
{
|
||||
name: intl.get('cash_flow.owner_contribution'),
|
||||
name: intl.get('banking.owner_contribution'),
|
||||
value: 'owner_contribution',
|
||||
},
|
||||
{
|
||||
name: intl.get('cash_flow.other_income'),
|
||||
name: intl.get('banking.other_income'),
|
||||
value: 'other_income',
|
||||
},
|
||||
{
|
||||
name: intl.get('cash_flow.transfer_form_account'),
|
||||
name: intl.get('banking.transfer_form_account'),
|
||||
value: 'transfer_from_account',
|
||||
},
|
||||
];
|
||||
|
||||
export const getAddMoneyOutOptions = () => [
|
||||
{
|
||||
name: intl.get('cash_flow.owner_drawings'),
|
||||
name: intl.get('banking.owner_drawings'),
|
||||
value: 'OwnerDrawing',
|
||||
},
|
||||
{
|
||||
name: intl.get('cash_flow.expenses'),
|
||||
name: intl.get('banking.expenses'),
|
||||
value: 'other_expense',
|
||||
},
|
||||
{
|
||||
name: intl.get('cash_flow.transfer_to_account'),
|
||||
name: intl.get('banking.transfer_to_account'),
|
||||
value: 'transfer_to_account',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -441,12 +441,12 @@ export const SidebarMenu = [
|
||||
// # Cashflow
|
||||
// ---------------
|
||||
{
|
||||
text: <T id={'siebar.cashflow'} />,
|
||||
text: <T id={'sidebar.banking'} />,
|
||||
type: ISidebarMenuItemType.Overlay,
|
||||
overlayId: ISidebarMenuOverlayIds.Cashflow,
|
||||
children: [
|
||||
{
|
||||
text: <T id={'siebar.cashflow'} />,
|
||||
text: <T id={'sidebar.banking'} />,
|
||||
type: ISidebarMenuItemType.Group,
|
||||
children: [
|
||||
{
|
||||
|
||||
@@ -227,7 +227,7 @@ function AccountTransactionsActionsBar({
|
||||
<CashFlowMenuItems
|
||||
items={addMoneyInOptions}
|
||||
onItemSelect={handleMoneyInFormTransaction}
|
||||
text={<T id={'cash_flow.label.add_money_in'} />}
|
||||
text={<T id={'banking.label.add_money_in'} />}
|
||||
buttonProps={{
|
||||
icon: <Icon icon={'arrow-downward'} iconSize={20} />,
|
||||
}}
|
||||
@@ -235,7 +235,7 @@ function AccountTransactionsActionsBar({
|
||||
<CashFlowMenuItems
|
||||
items={addMoneyOutOptions}
|
||||
onItemSelect={handlMoneyOutFormTransaction}
|
||||
text={<T id={'cash_flow.label.add_money_out'} />}
|
||||
text={<T id={'banking.label.add_money_out'} />}
|
||||
buttonProps={{
|
||||
icon: <Icon icon={'arrow-upward'} iconSize={20} />,
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
.dateFieldGroup{
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
// @ts-nocheck
|
||||
import { Button, FormGroup, Intent, Position } from '@blueprintjs/core';
|
||||
import * as Yup from 'yup';
|
||||
import moment from 'moment';
|
||||
import { Form, Formik, FormikConfig, useFormikContext } from 'formik';
|
||||
import {
|
||||
FDateInput,
|
||||
FFormGroup,
|
||||
FSelect,
|
||||
Group,
|
||||
Icon,
|
||||
Stack,
|
||||
} from '@/components';
|
||||
|
||||
const defaultValues = {
|
||||
period: 'all_dates',
|
||||
fromDate: '',
|
||||
toDate: '',
|
||||
};
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
fromDate: Yup.date()
|
||||
.nullable()
|
||||
.required('From Date is required')
|
||||
.max(Yup.ref('toDate'), 'From Date cannot be after To Date'),
|
||||
toDate: Yup.date()
|
||||
.nullable()
|
||||
.required('To Date is required')
|
||||
.min(Yup.ref('fromDate'), 'To Date cannot be before From Date'),
|
||||
});
|
||||
|
||||
interface AccountTransactionsDateFilterFormValues {
|
||||
period: string;
|
||||
fromDate: string;
|
||||
toDate: string;
|
||||
}
|
||||
|
||||
interface UncategorizedTransactionsDateFilterProps {
|
||||
initialValues?: AccountTransactionsDateFilterFormValues;
|
||||
onSubmit?: FormikConfig<AccountTransactionsDateFilterFormValues>['onSubmit'];
|
||||
}
|
||||
|
||||
export function AccountTransactionsDateFilterForm({
|
||||
initialValues = {},
|
||||
onSubmit,
|
||||
}: UncategorizedTransactionsDateFilterProps) {
|
||||
const handleSubmit = (values, bag) => {
|
||||
return onSubmit && onSubmit(values, bag);
|
||||
};
|
||||
|
||||
const formInitialValues = {
|
||||
...defaultValues,
|
||||
...initialValues,
|
||||
};
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={formInitialValues}
|
||||
onSubmit={handleSubmit}
|
||||
validationSchema={validationSchema}
|
||||
>
|
||||
<Form>
|
||||
<Stack spacing={15}>
|
||||
<Group spacing={10}>
|
||||
<AccountTransactionDatePeriodField />
|
||||
|
||||
<FFormGroup
|
||||
name={'fromDate'}
|
||||
label={'From Date'}
|
||||
style={{ marginBottom: 0, flex: '1' }}
|
||||
>
|
||||
<FDateInput
|
||||
name={'fromDate'}
|
||||
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
||||
formatDate={(date) => date.toLocaleDateString()}
|
||||
parseDate={(str) => new Date(str)}
|
||||
inputProps={{
|
||||
fill: true,
|
||||
placeholder: 'MM/DD/YYY',
|
||||
leftElement: <Icon icon={'date-range'} />,
|
||||
}}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<FormGroup
|
||||
label={'To Date'}
|
||||
name={'toDate'}
|
||||
style={{ marginBottom: 0, flex: '1' }}
|
||||
>
|
||||
<FDateInput
|
||||
name={'toDate'}
|
||||
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
||||
formatDate={(date) => date.toLocaleDateString()}
|
||||
parseDate={(str) => new Date(str)}
|
||||
inputProps={{
|
||||
fill: true,
|
||||
placeholder: 'MM/DD/YYY',
|
||||
leftElement: <Icon icon={'date-range'} />,
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Group>
|
||||
|
||||
<AccountTransactionsDateFilterFooter />
|
||||
</Stack>
|
||||
</Form>
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
|
||||
function AccountTransactionsDateFilterFooter() {
|
||||
const { submitForm, setValues } = useFormikContext();
|
||||
|
||||
const handleFilterBtnClick = () => {
|
||||
submitForm();
|
||||
};
|
||||
const handleClearBtnClick = () => {
|
||||
setValues({
|
||||
...defaultValues,
|
||||
});
|
||||
submitForm();
|
||||
};
|
||||
|
||||
return (
|
||||
<Group spacing={10}>
|
||||
<Button
|
||||
small
|
||||
intent={Intent.PRIMARY}
|
||||
onClick={handleFilterBtnClick}
|
||||
style={{ minWidth: 75 }}
|
||||
>
|
||||
Filter
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
intent={Intent.DANGER}
|
||||
small
|
||||
onClick={handleClearBtnClick}
|
||||
minimal
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
function AccountTransactionDatePeriodField() {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
|
||||
const handleItemChange = (item) => {
|
||||
const { fromDate, toDate } = getDateRangePeriod(item.value);
|
||||
|
||||
setFieldValue('fromDate', fromDate);
|
||||
setFieldValue('toDate', toDate);
|
||||
setFieldValue('period', item.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<FFormGroup
|
||||
name={'period'}
|
||||
label={'Date'}
|
||||
style={{ marginBottom: 0, flex: '0 28%' }}
|
||||
>
|
||||
<FSelect
|
||||
name={'period'}
|
||||
items={periodOptions}
|
||||
onItemSelect={handleItemChange}
|
||||
popoverProps={{ captureDismiss: true }}
|
||||
/>
|
||||
</FFormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
const periodOptions = [
|
||||
{ text: 'All Dates', value: 'all_dates' },
|
||||
{ text: 'Custom', value: 'custom' },
|
||||
{ text: 'Today', value: 'today' },
|
||||
{ text: 'Yesterday', value: 'yesterday' },
|
||||
{ text: 'This week', value: 'this_week' },
|
||||
{ text: 'This year', value: 'this_year' },
|
||||
{ text: 'This month', value: 'this_month' },
|
||||
{ text: 'last week', value: 'last_week' },
|
||||
{ text: 'Last year', value: 'last_year' },
|
||||
{ text: 'Last month', value: 'last_month' },
|
||||
{ text: 'Last month', value: 'last_month' },
|
||||
];
|
||||
|
||||
const getDateRangePeriod = (period: string) => {
|
||||
switch (period) {
|
||||
case 'today':
|
||||
return {
|
||||
fromDate: moment().startOf('day').toDate(),
|
||||
toDate: moment().endOf('day').toDate(),
|
||||
};
|
||||
case 'yesterday':
|
||||
return {
|
||||
fromDate: moment().subtract(1, 'days').startOf('day').toDate(),
|
||||
toDate: moment().subtract(1, 'days').endOf('day').toDate(),
|
||||
};
|
||||
case 'this_week':
|
||||
return {
|
||||
fromDate: moment().startOf('week').toDate(),
|
||||
toDate: moment().endOf('week').toDate(),
|
||||
};
|
||||
case 'this_month':
|
||||
return {
|
||||
fromDate: moment().startOf('month').toDate(),
|
||||
toDate: moment().endOf('month').toDate(),
|
||||
};
|
||||
case 'this_year':
|
||||
return {
|
||||
fromDate: moment().startOf('year').toDate(),
|
||||
toDate: moment().endOf('year').toDate(),
|
||||
};
|
||||
case 'last_week':
|
||||
return {
|
||||
fromDate: moment().subtract(1, 'weeks').startOf('week').toDate(),
|
||||
toDate: moment().subtract(1, 'weeks').endOf('week').toDate(),
|
||||
};
|
||||
case 'last_month':
|
||||
return {
|
||||
fromDate: moment().subtract(1, 'months').startOf('month').toDate(),
|
||||
toDate: moment().subtract(1, 'months').endOf('month').toDate(),
|
||||
};
|
||||
case 'last_year':
|
||||
return {
|
||||
fromDate: moment().subtract(1, 'years').startOf('year').toDate(),
|
||||
toDate: moment().subtract(1, 'years').endOf('year').toDate(),
|
||||
};
|
||||
case 'all_dates':
|
||||
case 'custom':
|
||||
default:
|
||||
return { fromDate: null, toDate: null };
|
||||
}
|
||||
};
|
||||
@@ -1,10 +1,12 @@
|
||||
// @ts-nocheck
|
||||
import * as R from 'ramda';
|
||||
import { useMemo } from 'react';
|
||||
import * as R from 'ramda';
|
||||
import { useAppQueryString } from '@/hooks';
|
||||
import { Group } from '@/components';
|
||||
import { Group, Stack, } from '@/components';
|
||||
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
|
||||
import { TagsControl } from '@/components/TagsControl';
|
||||
import { AccountUncategorizedDateFilter } from './UncategorizedTransactions/AccountUncategorizedDateFilter';
|
||||
import { Divider } from '@blueprintjs/core';
|
||||
|
||||
export function AccountTransactionsUncategorizeFilter() {
|
||||
const { bankAccountMetaSummary } = useAccountTransactionsContext();
|
||||
@@ -54,12 +56,17 @@ export function AccountTransactionsUncategorizeFilter() {
|
||||
);
|
||||
|
||||
return (
|
||||
<Group position={'apart'}>
|
||||
<TagsControl
|
||||
options={options}
|
||||
value={locationQuery?.uncategorizedFilter || 'all'}
|
||||
onValueChange={handleTabsChange}
|
||||
/>
|
||||
<Group position={'apart'} style={{ marginBottom: 14 }}>
|
||||
<Group align={'stretch'} spacing={10}>
|
||||
<TagsControl
|
||||
options={options}
|
||||
value={locationQuery?.uncategorizedFilter || 'all'}
|
||||
onValueChange={handleTabsChange}
|
||||
/>
|
||||
<Divider />
|
||||
<AccountUncategorizedDateFilter />
|
||||
</Group>
|
||||
|
||||
<TagsControl
|
||||
options={[{ value: 'excluded', label: 'Excluded' }]}
|
||||
value={locationQuery?.uncategorizedFilter || 'all'}
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
import React from 'react';
|
||||
import { flatten, map } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import { IntersectionObserver } from '@/components';
|
||||
import { useAccountUncategorizedTransactionsInfinity } from '@/hooks/query';
|
||||
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
|
||||
import { withBanking } from '../withBanking';
|
||||
|
||||
const AccountUncategorizedTransactionsContext = React.createContext();
|
||||
|
||||
@@ -13,9 +15,15 @@ function flattenInfinityPagesData(data) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Account uncategorized transctions provider.
|
||||
* Account un-categorized transactions provider.
|
||||
*/
|
||||
function AccountUncategorizedTransactionsBoot({ children }) {
|
||||
function AccountUncategorizedTransactionsBootRoot({
|
||||
// #withBanking
|
||||
uncategorizedTransactionsFilter,
|
||||
|
||||
// #ownProps
|
||||
children,
|
||||
}) {
|
||||
const { accountId } = useAccountTransactionsContext();
|
||||
|
||||
// Fetches the uncategorized transactions.
|
||||
@@ -29,6 +37,8 @@ function AccountUncategorizedTransactionsBoot({ children }) {
|
||||
hasNextPage: hasUncategorizedTransactionsNextPage,
|
||||
} = useAccountUncategorizedTransactionsInfinity(accountId, {
|
||||
page_size: 50,
|
||||
min_date: uncategorizedTransactionsFilter?.fromDate || null,
|
||||
max_date: uncategorizedTransactionsFilter?.toDate || null,
|
||||
});
|
||||
// Memorized the cashflow account transactions.
|
||||
const uncategorizedTransactions = React.useMemo(
|
||||
@@ -69,6 +79,12 @@ function AccountUncategorizedTransactionsBoot({ children }) {
|
||||
);
|
||||
}
|
||||
|
||||
const AccountUncategorizedTransactionsBoot = R.compose(
|
||||
withBanking(({ uncategorizedTransactionsFilter }) => ({
|
||||
uncategorizedTransactionsFilter,
|
||||
})),
|
||||
)(AccountUncategorizedTransactionsBootRoot);
|
||||
|
||||
const useAccountUncategorizedTransactionsContext = () =>
|
||||
React.useContext(AccountUncategorizedTransactionsContext);
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { flatten, map } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import { IntersectionObserver } from '@/components';
|
||||
import { useAccountTransactionsContext } from '../AccountTransactionsProvider';
|
||||
import { useExcludedBankTransactionsInfinity } from '@/hooks/query/bank-rules';
|
||||
import { withBanking } from '../../withBanking';
|
||||
|
||||
interface ExcludedBankTransactionsContextValue {
|
||||
isExcludedTransactionsLoading: boolean;
|
||||
@@ -27,7 +29,11 @@ interface ExcludedBankTransactionsTableBootProps {
|
||||
/**
|
||||
* Account uncategorized transctions provider.
|
||||
*/
|
||||
function ExcludedBankTransactionsTableBoot({
|
||||
function ExcludedBankTransactionsTableBootRoot({
|
||||
// #withBanking
|
||||
uncategorizedTransactionsFilter,
|
||||
|
||||
// #ownProps
|
||||
children,
|
||||
}: ExcludedBankTransactionsTableBootProps) {
|
||||
const { accountId } = useAccountTransactionsContext();
|
||||
@@ -44,6 +50,8 @@ function ExcludedBankTransactionsTableBoot({
|
||||
} = useExcludedBankTransactionsInfinity({
|
||||
page_size: 50,
|
||||
account_id: accountId,
|
||||
min_date: uncategorizedTransactionsFilter?.fromDate || null,
|
||||
max_date: uncategorizedTransactionsFilter.toDate || null,
|
||||
});
|
||||
// Memorized the cashflow account transactions.
|
||||
const excludedBankTransactions = React.useMemo(
|
||||
@@ -84,6 +92,12 @@ function ExcludedBankTransactionsTableBoot({
|
||||
);
|
||||
}
|
||||
|
||||
const ExcludedBankTransactionsTableBoot = R.compose(
|
||||
withBanking(({ uncategorizedTransactionsFilter }) => ({
|
||||
uncategorizedTransactionsFilter,
|
||||
})),
|
||||
)(ExcludedBankTransactionsTableBootRoot);
|
||||
|
||||
const useExcludedTransactionsBoot = () =>
|
||||
React.useContext(ExcludedTransactionsContext);
|
||||
|
||||
|
||||
@@ -37,12 +37,13 @@ export function useExcludedTransactionsColumns() {
|
||||
() => [
|
||||
{
|
||||
Header: 'Date',
|
||||
accessor: 'formatted_date',
|
||||
accessor: 'formatted_date',
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
Header: 'Description',
|
||||
accessor: descriptionAccessor,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: 'Payee',
|
||||
|
||||
@@ -41,7 +41,7 @@ export function usePendingTransactionsTableColumns() {
|
||||
},
|
||||
{
|
||||
id: 'deposit',
|
||||
Header: intl.get('cash_flow.label.deposit'),
|
||||
Header: intl.get('banking.label.deposit'),
|
||||
accessor: 'formatted_deposit_amount',
|
||||
width: 40,
|
||||
className: 'deposit',
|
||||
@@ -51,7 +51,7 @@ export function usePendingTransactionsTableColumns() {
|
||||
},
|
||||
{
|
||||
id: 'withdrawal',
|
||||
Header: intl.get('cash_flow.label.withdrawal'),
|
||||
Header: intl.get('banking.label.withdrawal'),
|
||||
accessor: 'formatted_withdrawal_amount',
|
||||
className: 'withdrawal',
|
||||
width: 40,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { flatten, map } from 'lodash';
|
||||
import { IntersectionObserver } from '@/components';
|
||||
import * as R from 'ramda';
|
||||
import { IntersectionObserver, NumericInputCell } from '@/components';
|
||||
import { useAccountTransactionsContext } from '../AccountTransactionsProvider';
|
||||
import { useRecognizedBankTransactionsInfinity } from '@/hooks/query/bank-rules';
|
||||
import { withBanking } from '../../withBanking';
|
||||
|
||||
interface RecognizedTransactionsContextValue {
|
||||
isRecongizedTransactionsLoading: boolean;
|
||||
@@ -27,7 +29,10 @@ interface RecognizedTransactionsTableBootProps {
|
||||
/**
|
||||
* Account uncategorized transctions provider.
|
||||
*/
|
||||
function RecognizedTransactionsTableBoot({
|
||||
function RecognizedTransactionsTableBootRoot({
|
||||
// #withBanking
|
||||
uncategorizedTransactionsFilter,
|
||||
|
||||
children,
|
||||
}: RecognizedTransactionsTableBootProps) {
|
||||
const { accountId } = useAccountTransactionsContext();
|
||||
@@ -44,6 +49,8 @@ function RecognizedTransactionsTableBoot({
|
||||
} = useRecognizedBankTransactionsInfinity({
|
||||
page_size: 50,
|
||||
account_id: accountId,
|
||||
min_date: uncategorizedTransactionsFilter.fromDate || null,
|
||||
max_date: uncategorizedTransactionsFilter?.toDate || null,
|
||||
});
|
||||
// Memorized the cashflow account transactions.
|
||||
const recognizedTransactions = React.useMemo(
|
||||
@@ -84,6 +91,12 @@ function RecognizedTransactionsTableBoot({
|
||||
);
|
||||
}
|
||||
|
||||
const RecognizedTransactionsTableBoot = R.compose(
|
||||
withBanking(({ uncategorizedTransactionsFilter }) => ({
|
||||
uncategorizedTransactionsFilter,
|
||||
})),
|
||||
)(RecognizedTransactionsTableBootRoot);
|
||||
|
||||
const useRecognizedTransactionsBoot = () =>
|
||||
React.useContext(RecognizedTransactionsContext);
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
// @ts-nocheck
|
||||
import { useState } from 'react';
|
||||
import * as R from 'ramda';
|
||||
import moment from 'moment';
|
||||
import { Box, Icon } from '@/components';
|
||||
import { Classes, Popover, Position } from '@blueprintjs/core';
|
||||
import { withBankingActions } from '../../withBankingActions';
|
||||
import { withBanking } from '../../withBanking';
|
||||
import { AccountTransactionsDateFilterForm } from '../AccountTransactionsDateFilter';
|
||||
import { TagButton } from './TagButton';
|
||||
|
||||
function AccountUncategorizedDateFilterRoot({
|
||||
uncategorizedTransactionsFilter,
|
||||
}) {
|
||||
const fromDate = uncategorizedTransactionsFilter?.fromDate;
|
||||
const toDate = uncategorizedTransactionsFilter?.toDate;
|
||||
|
||||
const fromDateFormatted = moment(fromDate).isSame(
|
||||
moment().format('YYYY'),
|
||||
'year',
|
||||
)
|
||||
? moment(fromDate).format('MMM, DD')
|
||||
: moment(fromDate).format('MMM, DD, YYYY');
|
||||
const toDateFormatted = moment(toDate).isSame(moment().format('YYYY'), 'year')
|
||||
? moment(toDate).format('MMM, DD')
|
||||
: moment(toDate).format('MMM, DD, YYYY');
|
||||
|
||||
const buttonText =
|
||||
fromDate && toDate
|
||||
? `Date: ${fromDateFormatted} → ${toDateFormatted}`
|
||||
: 'Date Filter';
|
||||
|
||||
// Popover open state.
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
|
||||
// Handle the filter form submitting.
|
||||
const handleSubmit = () => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={
|
||||
<Box style={{ padding: 18 }}>
|
||||
<UncategorizedTransactionsDateFilter onSubmit={handleSubmit} />
|
||||
</Box>
|
||||
}
|
||||
position={Position.RIGHT}
|
||||
popoverClassName={Classes.POPOVER_CONTENT}
|
||||
isOpen={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
>
|
||||
<TagButton
|
||||
outline
|
||||
icon={<Icon icon={'date-range'} />}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
{buttonText}
|
||||
</TagButton>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
export const AccountUncategorizedDateFilter = R.compose(
|
||||
withBanking(({ uncategorizedTransactionsFilter }) => ({
|
||||
uncategorizedTransactionsFilter,
|
||||
})),
|
||||
)(AccountUncategorizedDateFilterRoot);
|
||||
|
||||
export const UncategorizedTransactionsDateFilter = R.compose(
|
||||
withBankingActions,
|
||||
withBanking(({ uncategorizedTransactionsFilter }) => ({
|
||||
uncategorizedTransactionsFilter,
|
||||
})),
|
||||
)(
|
||||
({
|
||||
// #withBankingActions
|
||||
setUncategorizedTransactionsFilter,
|
||||
|
||||
// #withBanking
|
||||
uncategorizedTransactionsFilter,
|
||||
|
||||
// #ownProps
|
||||
onSubmit,
|
||||
}) => {
|
||||
const initialValues = {
|
||||
...uncategorizedTransactionsFilter,
|
||||
};
|
||||
|
||||
const handleSubmit = (values) => {
|
||||
setUncategorizedTransactionsFilter({
|
||||
fromDate: values.fromDate,
|
||||
toDate: values.toDate,
|
||||
});
|
||||
onSubmit && onSubmit(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<AccountTransactionsDateFilterForm
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,11 @@
|
||||
.root{
|
||||
min-height: 26px;
|
||||
border-radius: 15px;
|
||||
font-size: 13px;
|
||||
padding: 0 10px;
|
||||
|
||||
&:global(.bp4-button:not([class*=bp4-intent-]):not(.bp4-minimal)) {
|
||||
background: #fff;
|
||||
border: 1px solid #e1e2e8;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// @ts-nocheck
|
||||
import { Button } from "@blueprintjs/core"
|
||||
import styles from './TagButton.module.scss';
|
||||
|
||||
|
||||
|
||||
export const TagButton = (props) => {
|
||||
return <Button {...props} className={styles.root} />
|
||||
}
|
||||
@@ -121,7 +121,7 @@ export function useAccountUncategorizedTransactionsColumns() {
|
||||
},
|
||||
{
|
||||
id: 'deposit',
|
||||
Header: intl.get('cash_flow.label.deposit'),
|
||||
Header: intl.get('banking.label.deposit'),
|
||||
accessor: 'formatted_deposit_amount',
|
||||
width: 40,
|
||||
className: 'deposit',
|
||||
@@ -131,7 +131,7 @@ export function useAccountUncategorizedTransactionsColumns() {
|
||||
},
|
||||
{
|
||||
id: 'withdrawal',
|
||||
Header: intl.get('cash_flow.label.withdrawal'),
|
||||
Header: intl.get('banking.label.withdrawal'),
|
||||
accessor: 'formatted_withdrawal_amount',
|
||||
className: 'withdrawal',
|
||||
width: 40,
|
||||
|
||||
@@ -116,7 +116,7 @@ export function useAccountTransactionsColumns() {
|
||||
},
|
||||
{
|
||||
id: 'deposit',
|
||||
Header: intl.get('cash_flow.label.deposit'),
|
||||
Header: intl.get('banking.label.deposit'),
|
||||
accessor: 'formatted_deposit',
|
||||
width: 110,
|
||||
className: 'deposit',
|
||||
@@ -126,7 +126,7 @@ export function useAccountTransactionsColumns() {
|
||||
},
|
||||
{
|
||||
id: 'withdrawal',
|
||||
Header: intl.get('cash_flow.label.withdrawal'),
|
||||
Header: intl.get('banking.label.withdrawal'),
|
||||
accessor: 'formatted_withdrawal',
|
||||
className: 'withdrawal',
|
||||
width: 150,
|
||||
@@ -136,7 +136,7 @@ export function useAccountTransactionsColumns() {
|
||||
},
|
||||
{
|
||||
id: 'running_balance',
|
||||
Header: intl.get('cash_flow.label.running_balance'),
|
||||
Header: intl.get('banking.label.running_balance'),
|
||||
accessor: 'formatted_running_balance',
|
||||
className: 'running_balance',
|
||||
width: 150,
|
||||
|
||||
@@ -78,13 +78,13 @@ function CashFlowAccountsActionsBar({
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'plus-24'} iconSize={20} />}
|
||||
text={<T id={'cash_flow.label.add_cash_account'} />}
|
||||
text={<T id={'banking.label.add_cash_account'} />}
|
||||
onClick={handleAddBankAccount}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'plus-24'} iconSize={20} />}
|
||||
text={<T id={'cash_flow.label.add_bank_account'} />}
|
||||
text={<T id={'banking.label.add_bank_account'} />}
|
||||
onClick={handleAddCashAccount}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
|
||||
@@ -123,7 +123,12 @@ function CashflowBankAccount({
|
||||
code={account.code}
|
||||
balance={!isNull(account.amount) ? account.formatted_amount : '-'}
|
||||
type={account.account_type}
|
||||
updatedBeforeText={getUpdatedBeforeText(account.createdAt)}
|
||||
updatedBeforeText={
|
||||
account.last_feeds_updated_from_now
|
||||
? `Updated ${account.last_feeds_updated_from_now} ago`
|
||||
: ''
|
||||
}
|
||||
uncategorizedTransactionsCount={account.uncategorized_transactions}
|
||||
/>
|
||||
</CashflowAccountAnchor>
|
||||
</ContextMenu2>
|
||||
|
||||
@@ -25,6 +25,8 @@ export const withBanking = (mapState) => {
|
||||
|
||||
categorizedTransactionsSelected:
|
||||
state.plaid.categorizedTransactionsSelected,
|
||||
|
||||
uncategorizedTransactionsFilter: state.plaid.uncategorizedFilter
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user