mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 06:40:31 +00:00
Compare commits
3 Commits
v0.19.9
...
debounce-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc6ebfea5c | ||
|
|
161d60393a | ||
|
|
79413fa85e |
@@ -37,6 +37,7 @@
|
|||||||
"agendash": "^3.1.0",
|
"agendash": "^3.1.0",
|
||||||
"app-root-path": "^3.0.0",
|
"app-root-path": "^3.0.0",
|
||||||
"async": "^3.2.0",
|
"async": "^3.2.0",
|
||||||
|
"async-mutex": "^0.5.0",
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
"babel-loader": "^9.1.2",
|
"babel-loader": "^9.1.2",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
|
|||||||
@@ -139,24 +139,26 @@ export default class InventoryService {
|
|||||||
) {
|
) {
|
||||||
const agenda = Container.get('agenda');
|
const agenda = Container.get('agenda');
|
||||||
|
|
||||||
|
const commonJobsQuery = {
|
||||||
|
name: 'compute-item-cost',
|
||||||
|
lastRunAt: { $exists: false },
|
||||||
|
'data.tenantId': tenantId,
|
||||||
|
'data.itemId': itemId,
|
||||||
|
};
|
||||||
// Cancel any `compute-item-cost` in the queue has upper starting date
|
// Cancel any `compute-item-cost` in the queue has upper starting date
|
||||||
// with the same given item.
|
// with the same given item.
|
||||||
await agenda.cancel({
|
await agenda.cancel({
|
||||||
name: 'compute-item-cost',
|
...commonJobsQuery,
|
||||||
nextRunAt: { $ne: null },
|
'data.startingDate': { $lte: startingDate },
|
||||||
'data.tenantId': tenantId,
|
|
||||||
'data.itemId': itemId,
|
|
||||||
'data.startingDate': { $gt: startingDate },
|
|
||||||
});
|
});
|
||||||
// Retrieve any `compute-item-cost` in the queue has lower starting date
|
// Retrieve any `compute-item-cost` in the queue has lower starting date
|
||||||
// with the same given item.
|
// with the same given item.
|
||||||
const dependsJobs = await agenda.jobs({
|
const dependsJobs = await agenda.jobs({
|
||||||
name: 'compute-item-cost',
|
...commonJobsQuery,
|
||||||
nextRunAt: { $ne: null },
|
'data.startingDate': { $gte: startingDate },
|
||||||
'data.tenantId': tenantId,
|
|
||||||
'data.itemId': itemId,
|
|
||||||
'data.startingDate': { $lte: startingDate },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// If the depends jobs cleared.
|
||||||
if (dependsJobs.length === 0) {
|
if (dependsJobs.length === 0) {
|
||||||
await agenda.schedule(
|
await agenda.schedule(
|
||||||
config.scheduleComputeItemCost,
|
config.scheduleComputeItemCost,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Mutex } from 'async-mutex';
|
||||||
import { Container, Service, Inject } from 'typedi';
|
import { Container, Service, Inject } from 'typedi';
|
||||||
import { chain } from 'lodash';
|
import { chain } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
@@ -34,17 +35,26 @@ export class SaleInvoicesCost {
|
|||||||
inventoryItemsIds: number[],
|
inventoryItemsIds: number[],
|
||||||
startingDate: Date
|
startingDate: Date
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const asyncOpers: Promise<[]>[] = [];
|
const mutex = new Mutex();
|
||||||
|
|
||||||
inventoryItemsIds.forEach((inventoryItemId: number) => {
|
const asyncOpers = inventoryItemsIds.map(
|
||||||
const oper: Promise<[]> = this.inventoryService.scheduleComputeItemCost(
|
async (inventoryItemId: number) => {
|
||||||
tenantId,
|
// @todo refactor the lock acquire to be distrbuted using Redis
|
||||||
inventoryItemId,
|
// and run the cost schedule job after running invoice transaction.
|
||||||
startingDate
|
const release = await mutex.acquire();
|
||||||
);
|
|
||||||
asyncOpers.push(oper);
|
try {
|
||||||
});
|
await this.inventoryService.scheduleComputeItemCost(
|
||||||
await Promise.all([...asyncOpers]);
|
tenantId,
|
||||||
|
inventoryItemId,
|
||||||
|
startingDate
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await Promise.all(asyncOpers);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,17 +96,22 @@ export class SaleInvoicesCost {
|
|||||||
tenantId: number,
|
tenantId: number,
|
||||||
inventoryTransactions: IInventoryTransaction[]
|
inventoryTransactions: IInventoryTransaction[]
|
||||||
) {
|
) {
|
||||||
const asyncOpers: Promise<[]>[] = [];
|
const mutex = new Mutex();
|
||||||
const reducedTransactions = this.getMaxDateInventoryTransactions(
|
const reducedTransactions = this.getMaxDateInventoryTransactions(
|
||||||
inventoryTransactions
|
inventoryTransactions
|
||||||
);
|
);
|
||||||
reducedTransactions.forEach((transaction) => {
|
const asyncOpers = reducedTransactions.map(async (transaction) => {
|
||||||
const oper: Promise<[]> = this.inventoryService.scheduleComputeItemCost(
|
const release = await mutex.acquire();
|
||||||
tenantId,
|
|
||||||
transaction.itemId,
|
try {
|
||||||
transaction.date
|
await this.inventoryService.scheduleComputeItemCost(
|
||||||
);
|
tenantId,
|
||||||
asyncOpers.push(oper);
|
transaction.itemId,
|
||||||
|
transaction.date
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
release();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
await Promise.all([...asyncOpers]);
|
await Promise.all([...asyncOpers]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,20 +86,14 @@ export default class InventorySubscriber {
|
|||||||
private handleScheduleItemsCostOnInventoryTransactionsCreated = async ({
|
private handleScheduleItemsCostOnInventoryTransactionsCreated = async ({
|
||||||
tenantId,
|
tenantId,
|
||||||
inventoryTransactions,
|
inventoryTransactions,
|
||||||
trx
|
trx,
|
||||||
}: IInventoryTransactionsCreatedPayload) => {
|
}: IInventoryTransactionsCreatedPayload) => {
|
||||||
const inventoryItemsIds = map(inventoryTransactions, 'itemId');
|
const inventoryItemsIds = map(inventoryTransactions, 'itemId');
|
||||||
|
|
||||||
runAfterTransaction(trx, async () => {
|
await this.saleInvoicesCost.computeItemsCostByInventoryTransactions(
|
||||||
try {
|
tenantId,
|
||||||
await this.saleInvoicesCost.computeItemsCostByInventoryTransactions(
|
inventoryTransactions
|
||||||
tenantId,
|
);
|
||||||
inventoryTransactions
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
ISaleInvoiceEditedPayload,
|
ISaleInvoiceEditedPayload,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import { SaleInvoiceGLEntries } from '@/services/Sales/Invoices/InvoiceGLEntries';
|
import { SaleInvoiceGLEntries } from '@/services/Sales/Invoices/InvoiceGLEntries';
|
||||||
|
import { runAfterTransaction } from '@/services/UnitOfWork/TransactionsHooks';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class SaleInvoiceWriteGLEntriesSubscriber {
|
export default class SaleInvoiceWriteGLEntriesSubscriber {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
.container {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 1.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconText {
|
||||||
|
display: inline-flex;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-right: 16px;
|
||||||
|
color: #00824d;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0; /* Remove the margin on the last item */
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import styles from './SubscriptionPlansOfferChecks.module.scss';
|
||||||
|
|
||||||
|
export function SubscriptionPlansOfferChecks() {
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<span className={styles.iconText}>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height="18px"
|
||||||
|
width="18px"
|
||||||
|
viewBox="0 -960 960 960"
|
||||||
|
fill="rgb(0, 130, 77)"
|
||||||
|
className={styles.icon}
|
||||||
|
>
|
||||||
|
<path d="M378-225 133-470l66-66 179 180 382-382 66 65-448 448Z"></path>
|
||||||
|
</svg>
|
||||||
|
14-day free trial
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className={styles.iconText}>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height="18px"
|
||||||
|
width="18px"
|
||||||
|
viewBox="0 -960 960 960"
|
||||||
|
fill="rgb(0, 130, 77)"
|
||||||
|
className={styles.icon}
|
||||||
|
>
|
||||||
|
<path d="M378-225 133-470l66-66 179 180 382-382 66 65-448 448Z"></path>
|
||||||
|
</svg>
|
||||||
|
24/7 online support
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ function SubscriptionPlansPeriodSwitcherRoot({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Group position={'center'} spacing={10} style={{ marginBottom: '1.2rem' }}>
|
<Group position={'center'} spacing={10} style={{ marginBottom: '1.6rem' }}>
|
||||||
<Text>Pay Monthly</Text>
|
<Text>Pay Monthly</Text>
|
||||||
<Switch
|
<Switch
|
||||||
large
|
large
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Callout } from '@blueprintjs/core';
|
import { Callout } from '@blueprintjs/core';
|
||||||
import { SubscriptionPlans } from './SubscriptionPlans';
|
import { SubscriptionPlans } from './SubscriptionPlans';
|
||||||
import { SubscriptionPlansPeriodSwitcher } from './SubscriptionPlansPeriodSwitcher';
|
import { SubscriptionPlansPeriodSwitcher } from './SubscriptionPlansPeriodSwitcher';
|
||||||
|
import { SubscriptionPlansOfferChecks } from './SubscriptionPlansOfferChecks';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Billing plans.
|
* Billing plans.
|
||||||
@@ -14,6 +15,7 @@ export function SubscriptionPlansSection() {
|
|||||||
include applicable taxes.
|
include applicable taxes.
|
||||||
</Callout>
|
</Callout>
|
||||||
|
|
||||||
|
<SubscriptionPlansOfferChecks />
|
||||||
<SubscriptionPlansPeriodSwitcher />
|
<SubscriptionPlansPeriodSwitcher />
|
||||||
<SubscriptionPlans />
|
<SubscriptionPlans />
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-bottom: 40px;
|
padding-bottom: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__left-section {
|
&__left-section {
|
||||||
|
|||||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -86,6 +86,9 @@ importers:
|
|||||||
async:
|
async:
|
||||||
specifier: ^3.2.0
|
specifier: ^3.2.0
|
||||||
version: 3.2.5
|
version: 3.2.5
|
||||||
|
async-mutex:
|
||||||
|
specifier: ^0.5.0
|
||||||
|
version: 0.5.0
|
||||||
axios:
|
axios:
|
||||||
specifier: ^1.6.0
|
specifier: ^1.6.0
|
||||||
version: 1.7.2
|
version: 1.7.2
|
||||||
@@ -8009,6 +8012,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==}
|
resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/async-mutex@0.5.0:
|
||||||
|
resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==}
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.6.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/async-settle@1.0.0:
|
/async-settle@1.0.0:
|
||||||
resolution: {integrity: sha512-VPXfB4Vk49z1LHHodrEQ6Xf7W4gg1w0dAPROHngx7qgDjqmIQ+fXmwgGXTW/ITLai0YLSvWepJOP9EVpMnEAcw==}
|
resolution: {integrity: sha512-VPXfB4Vk49z1LHHodrEQ6Xf7W4gg1w0dAPROHngx7qgDjqmIQ+fXmwgGXTW/ITLai0YLSvWepJOP9EVpMnEAcw==}
|
||||||
engines: {node: '>= 0.10'}
|
engines: {node: '>= 0.10'}
|
||||||
|
|||||||
Reference in New Issue
Block a user