Compare commits
6 Commits
style-fina
...
@bigcapita
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04b16a7d2f | ||
|
|
fd27136274 | ||
|
|
951e360405 | ||
|
|
d2242b7744 | ||
|
|
4e9b0ae24f | ||
|
|
e5ffb5661a |
@@ -2,6 +2,5 @@
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||
"version": "independent",
|
||||
"npmClient": "pnpm",
|
||||
"useWorkspaces": true,
|
||||
"packages": ["packages/*"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"name": "bigcapital-monorepo",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"bootstrap": "lerna exec pnpm install",
|
||||
"dev": "lerna run dev",
|
||||
"build": "lerna run build",
|
||||
"dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\"",
|
||||
@@ -22,7 +23,7 @@
|
||||
"lerna": "^6.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "16.x || 17.x || 18.x"
|
||||
"node": "14.x"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
|
||||
13663
packages/server/package-lock.json
generated
13663
packages/server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@bigcapital/server",
|
||||
"version": "0.10.2",
|
||||
"version": "0.10.1",
|
||||
"description": "",
|
||||
"main": "src/server.ts",
|
||||
"scripts": {
|
||||
|
||||
@@ -149,11 +149,6 @@ export default class ItemsController extends BaseController {
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.TEXT }),
|
||||
check('sell_tax_rate_id').optional({ nullable: true }).isInt().toInt(),
|
||||
check('purchase_tax_rate_id')
|
||||
.optional({ nullable: true })
|
||||
.isInt()
|
||||
.toInt(),
|
||||
check('category_id')
|
||||
.optional({ nullable: true })
|
||||
.isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 })
|
||||
@@ -513,28 +508,6 @@ export default class ItemsController extends BaseController {
|
||||
],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'PURCHASE_TAX_RATE_NOT_FOUND') {
|
||||
return res.status(400).send({
|
||||
errors: [
|
||||
{
|
||||
type: 'PURCHASE_TAX_RATE_NOT_FOUND',
|
||||
message: 'Purchase tax rate has not found.',
|
||||
code: 410,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'SELL_TAX_RATE_NOT_FOUND') {
|
||||
return res.status(400).send({
|
||||
errors: [
|
||||
{
|
||||
type: 'SELL_TAX_RATE_NOT_FOUND',
|
||||
message: 'Sell tax rate is not found.',
|
||||
code: 420,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
|
||||
@@ -115,8 +115,6 @@ export default class BillsController extends BaseController {
|
||||
check('note').optional().trim().escape(),
|
||||
check('open').default(false).isBoolean().toBoolean(),
|
||||
|
||||
check('is_inclusive_tax').default(false).isBoolean().toBoolean(),
|
||||
|
||||
check('entries').isArray({ min: 1 }),
|
||||
|
||||
check('entries.*.index').exists().isNumeric().toInt(),
|
||||
@@ -139,15 +137,6 @@ export default class BillsController extends BaseController {
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
.toInt(),
|
||||
check('entries.*.tax_code')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.escape()
|
||||
.isString(),
|
||||
check('entries.*.tax_rate_id')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
.toInt(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -553,16 +542,6 @@ export default class BillsController extends BaseController {
|
||||
],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'ITEM_ENTRY_TAX_RATE_CODE_NOT_FOUND') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'ITEM_ENTRY_TAX_RATE_CODE_NOT_FOUND', code: 1800 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'ITEM_ENTRY_TAX_RATE_ID_NOT_FOUND') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'ITEM_ENTRY_TAX_RATE_ID_NOT_FOUND', code: 1900 }],
|
||||
});
|
||||
}
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
exports.up = (knex) => {
|
||||
return knex.schema.table('bills', (table) => {
|
||||
table.boolean('is_inclusive_tax').defaultTo(false);
|
||||
table.decimal('tax_amount_withheld');
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = (knex) => {
|
||||
return knex.schema.table('bills', () => {});
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
exports.up = (knex) => {
|
||||
return knex.schema.table('items', (table) => {
|
||||
table
|
||||
.integer('sell_tax_rate_id')
|
||||
.unsigned()
|
||||
.references('id')
|
||||
.inTable('tax_rates');
|
||||
table
|
||||
.integer('purchase_tax_rate_id')
|
||||
.unsigned()
|
||||
.references('id')
|
||||
.inTable('tax_rates');
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = (knex) => {
|
||||
return knex.schema.dropTableIfExists('tax_rates');
|
||||
};
|
||||
@@ -2,7 +2,6 @@ import { Knex } from 'knex';
|
||||
import { IDynamicListFilterDTO } from './DynamicFilter';
|
||||
import { IItemEntry, IItemEntryDTO } from './ItemEntry';
|
||||
import { IBillLandedCost } from './LandedCost';
|
||||
|
||||
export interface IBillDTO {
|
||||
vendorId: number;
|
||||
billNumber: string;
|
||||
@@ -16,10 +15,10 @@ export interface IBillDTO {
|
||||
exchangeRate?: number;
|
||||
open: boolean;
|
||||
entries: IItemEntryDTO[];
|
||||
|
||||
branchId?: number;
|
||||
warehouseId?: number;
|
||||
projectId?: number;
|
||||
isInclusiveTax?: boolean;
|
||||
}
|
||||
|
||||
export interface IBillEditDTO {
|
||||
@@ -81,15 +80,6 @@ export interface IBill {
|
||||
|
||||
localAmount?: number;
|
||||
locatedLandedCosts?: IBillLandedCost[];
|
||||
|
||||
amountLocal: number;
|
||||
subtotal: number;
|
||||
subtotalLocal: number;
|
||||
subtotalExcludingTax: number;
|
||||
taxAmountWithheld: number;
|
||||
taxAmountWithheldLocal: number;
|
||||
total: number;
|
||||
totalLocal: number;
|
||||
}
|
||||
|
||||
export interface IBillsFilter extends IDynamicListFilterDTO {
|
||||
|
||||
@@ -22,9 +22,6 @@ export interface IItem {
|
||||
sellDescription: string;
|
||||
purchaseDescription: string;
|
||||
|
||||
sellTaxRateId: number;
|
||||
purchaseTaxRateId: number;
|
||||
|
||||
quantityOnHand: number;
|
||||
|
||||
note: string;
|
||||
@@ -57,9 +54,6 @@ export interface IItemDTO {
|
||||
sellDescription: string;
|
||||
purchaseDescription: string;
|
||||
|
||||
sellTaxRateId: number;
|
||||
purchaseTaxRateId: number;
|
||||
|
||||
quantityOnHand: number;
|
||||
|
||||
note: string;
|
||||
|
||||
@@ -36,7 +36,6 @@ export interface ITaxRateCreatedPayload {
|
||||
}
|
||||
|
||||
export interface ITaxRateEditingPayload {
|
||||
oldTaxRate: ITaxRate;
|
||||
editTaxRateDTO: IEditTaxRateDTO;
|
||||
tenantId: number;
|
||||
trx: Knex.Transaction;
|
||||
|
||||
@@ -81,9 +81,6 @@ import { ProjectBillableBillSubscriber } from '@/services/Projects/Projects/Proj
|
||||
import { SyncActualTimeTaskSubscriber } from '@/services/Projects/Times/SyncActualTimeTaskSubscriber';
|
||||
import { SaleInvoiceTaxRateValidateSubscriber } from '@/services/TaxRates/subscribers/SaleInvoiceTaxRateValidateSubscriber';
|
||||
import { WriteInvoiceTaxTransactionsSubscriber } from '@/services/TaxRates/subscribers/WriteInvoiceTaxTransactionsSubscriber';
|
||||
import { BillTaxRateValidateSubscriber } from '@/services/TaxRates/subscribers/BillTaxRateValidateSubscriber';
|
||||
import { WriteBillTaxTransactionsSubscriber } from '@/services/TaxRates/subscribers/WriteBillTaxTransactionsSubscriber';
|
||||
import { SyncItemTaxRateOnEditTaxSubscriber } from '@/services/TaxRates/SyncItemTaxRateOnEditTaxSubscriber';
|
||||
|
||||
export default () => {
|
||||
return new EventPublisher();
|
||||
@@ -191,14 +188,8 @@ export const susbcribers = () => {
|
||||
ProjectBillableExpensesSubscriber,
|
||||
ProjectBillableBillSubscriber,
|
||||
|
||||
// Tax Rates - Sale Invoice
|
||||
// Tax Rates
|
||||
SaleInvoiceTaxRateValidateSubscriber,
|
||||
WriteInvoiceTaxTransactionsSubscriber,
|
||||
|
||||
// Tax Rates - Bills
|
||||
BillTaxRateValidateSubscriber,
|
||||
WriteBillTaxTransactionsSubscriber,
|
||||
|
||||
SyncItemTaxRateOnEditTaxSubscriber
|
||||
];
|
||||
};
|
||||
|
||||
@@ -13,109 +13,6 @@ export default class Bill extends mixin(TenantModel, [
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
public amount: number;
|
||||
public paymentAmount: number;
|
||||
public landedCostAmount: number;
|
||||
public allocatedCostAmount: number;
|
||||
public isInclusiveTax: boolean;
|
||||
public taxAmountWithheld: number;
|
||||
public exchangeRate: number;
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'balance',
|
||||
'dueAmount',
|
||||
'isOpen',
|
||||
'isPartiallyPaid',
|
||||
'isFullyPaid',
|
||||
'isPaid',
|
||||
'remainingDays',
|
||||
'overdueDays',
|
||||
'isOverdue',
|
||||
'unallocatedCostAmount',
|
||||
'localAmount',
|
||||
'localAllocatedCostAmount',
|
||||
'billableAmount',
|
||||
'amountLocal',
|
||||
'subtotal',
|
||||
'subtotalLocal',
|
||||
'subtotalExludingTax',
|
||||
'taxAmountWithheldLocal',
|
||||
'total',
|
||||
'totalLocal',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice amount in base currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get amountLocal() {
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtotal. (Tax inclusive) if the tax inclusive is enabled.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotal() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtotal in base currency. (Tax inclusive) if the tax inclusive is enabled.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotalLocal() {
|
||||
return this.amountLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sale invoice amount excluding tax.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotalExcludingTax() {
|
||||
return this.isInclusiveTax
|
||||
? this.subtotal - this.taxAmountWithheld
|
||||
: this.subtotal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tax amount withheld in base currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get taxAmountWithheldLocal() {
|
||||
return this.taxAmountWithheld * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice total. (Tax included)
|
||||
* @returns {number}
|
||||
*/
|
||||
get total() {
|
||||
return this.isInclusiveTax
|
||||
? this.subtotal
|
||||
: this.subtotal + this.taxAmountWithheld;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice total in local currency. (Tax included)
|
||||
* @returns {number}
|
||||
*/
|
||||
get totalLocal() {
|
||||
return this.total * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
@@ -261,13 +158,40 @@ export default class Bill extends mixin(TenantModel, [
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'balance',
|
||||
'dueAmount',
|
||||
'isOpen',
|
||||
'isPartiallyPaid',
|
||||
'isFullyPaid',
|
||||
'isPaid',
|
||||
'remainingDays',
|
||||
'overdueDays',
|
||||
'isOverdue',
|
||||
'unallocatedCostAmount',
|
||||
'localAmount',
|
||||
'localAllocatedCostAmount',
|
||||
'billableAmount',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice amount in organization base currency.
|
||||
* @deprecated
|
||||
* @returns {number}
|
||||
*/
|
||||
get localAmount() {
|
||||
return this.amountLocal;
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,7 +231,7 @@ export default class Bill extends mixin(TenantModel, [
|
||||
* @return {number}
|
||||
*/
|
||||
get dueAmount() {
|
||||
return Math.max(this.total - this.balance, 0);
|
||||
return Math.max(this.amount - this.balance, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -323,7 +247,7 @@ export default class Bill extends mixin(TenantModel, [
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isPartiallyPaid() {
|
||||
return this.dueAmount !== this.total && this.dueAmount > 0;
|
||||
return this.dueAmount !== this.amount && this.dueAmount > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -384,7 +308,7 @@ export default class Bill extends mixin(TenantModel, [
|
||||
* Retrieves the calculated amount which have not been invoiced.
|
||||
*/
|
||||
get billableAmount() {
|
||||
return Math.max(this.total - this.invoicedAmount, 0);
|
||||
return Math.max(this.amount - this.invoicedAmount, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -402,7 +326,6 @@ export default class Bill extends mixin(TenantModel, [
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const BillLandedCost = require('models/BillLandedCost');
|
||||
const Branch = require('models/Branch');
|
||||
const TaxRateTransaction = require('models/TaxRateTransaction');
|
||||
|
||||
return {
|
||||
vendor: {
|
||||
@@ -450,21 +373,6 @@ export default class Bill extends mixin(TenantModel, [
|
||||
to: 'branches.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Bill may has associated tax rate transactions.
|
||||
*/
|
||||
taxes: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: TaxRateTransaction.default,
|
||||
join: {
|
||||
from: 'bills.id',
|
||||
to: 'tax_rate_transactions.referenceId',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('reference_type', 'Bill');
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,6 @@ export default class Item extends mixin(TenantModel, [
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const WarehouseTransferEntry = require('models/WarehouseTransferEntry');
|
||||
const InventoryAdjustmentEntry = require('models/InventoryAdjustmentEntry');
|
||||
const TaxRate = require('models/TaxRate');
|
||||
|
||||
return {
|
||||
/**
|
||||
@@ -179,35 +178,11 @@ export default class Item extends mixin(TenantModel, [
|
||||
to: 'media.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Item may has sell tax rate.
|
||||
*/
|
||||
sellTaxRate: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: TaxRate.default,
|
||||
join: {
|
||||
from: 'items.sellTaxRateId',
|
||||
to: 'tax_rates.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Item may has purchase tax rate.
|
||||
*/
|
||||
purchaseTaxRate: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: TaxRate.default,
|
||||
join: {
|
||||
from: 'items.purchaseTaxRateId',
|
||||
to: 'tax_rates.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
static get secureDeleteRelations() {
|
||||
return [
|
||||
|
||||
@@ -99,13 +99,6 @@ export default class ItemEntry extends TenantModel {
|
||||
: getExlusiveTaxAmount(this.amount, this.taxRate);
|
||||
}
|
||||
|
||||
static calcAmount(itemEntry) {
|
||||
const { discount, quantity, rate } = itemEntry;
|
||||
const total = quantity * rate;
|
||||
|
||||
return discount ? total - total * discount * 0.01 : total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Item entry relations.
|
||||
*/
|
||||
|
||||
@@ -55,18 +55,6 @@ export class CreateItem {
|
||||
itemDTO.inventoryAccountId
|
||||
);
|
||||
}
|
||||
if (itemDTO.purchaseTaxRateId) {
|
||||
await this.validators.validatePurchaseTaxRateExistance(
|
||||
tenantId,
|
||||
itemDTO.purchaseTaxRateId
|
||||
);
|
||||
}
|
||||
if (itemDTO.sellTaxRateId) {
|
||||
await this.validators.validateSellTaxRateExistance(
|
||||
tenantId,
|
||||
itemDTO.sellTaxRateId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -76,20 +76,6 @@ export class EditItem {
|
||||
itemDTO.inventoryAccountId
|
||||
);
|
||||
}
|
||||
// Validate the purchase tax rate id existance.
|
||||
if (itemDTO.purchaseTaxRateId) {
|
||||
await this.validators.validatePurchaseTaxRateExistance(
|
||||
tenantId,
|
||||
itemDTO.purchaseTaxRateId
|
||||
);
|
||||
}
|
||||
// Validate the sell tax rate id existance.
|
||||
if (itemDTO.sellTaxRateId) {
|
||||
await this.validators.validateSellTaxRateExistance(
|
||||
tenantId,
|
||||
itemDTO.sellTaxRateId
|
||||
);
|
||||
}
|
||||
// Validate inventory account should be modified in inventory item
|
||||
// has inventory transactions.
|
||||
await this.validators.validateItemInvnetoryAccountModified(
|
||||
|
||||
@@ -27,8 +27,6 @@ export class GetItem {
|
||||
.withGraphFetched('category')
|
||||
.withGraphFetched('costAccount')
|
||||
.withGraphFetched('itemWarehouses.warehouse')
|
||||
.withGraphFetched('sellTaxRate')
|
||||
.withGraphFetched('purchaseTaxRate')
|
||||
.throwIfNotFound();
|
||||
|
||||
return this.transformer.transform(tenantId, item, new ItemTransformer());
|
||||
|
||||
@@ -241,40 +241,4 @@ export class ItemsValidators {
|
||||
throw new ServiceError(ERRORS.ITEM_CANNOT_CHANGE_INVENTORY_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the purchase tax rate id existance.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} taxRateId -
|
||||
*/
|
||||
public async validatePurchaseTaxRateExistance(
|
||||
tenantId: number,
|
||||
taxRateId: number
|
||||
) {
|
||||
const { TaxRate } = this.tenancy.models(tenantId);
|
||||
|
||||
const foundTaxRate = await TaxRate.query().findById(taxRateId);
|
||||
|
||||
if (!foundTaxRate) {
|
||||
throw new ServiceError(ERRORS.PURCHASE_TAX_RATE_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the sell tax rate id existance.
|
||||
* @param {number} tenantId
|
||||
* @param {number} taxRateId
|
||||
*/
|
||||
public async validateSellTaxRateExistance(
|
||||
tenantId: number,
|
||||
taxRateId: number
|
||||
) {
|
||||
const { TaxRate } = this.tenancy.models(tenantId);
|
||||
|
||||
const foundTaxRate = await TaxRate.query().findById(taxRateId);
|
||||
|
||||
if (!foundTaxRate) {
|
||||
throw new ServiceError(ERRORS.SELL_TAX_RATE_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,7 @@ export const ERRORS = {
|
||||
TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS: 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS',
|
||||
INVENTORY_ACCOUNT_CANNOT_MODIFIED: 'INVENTORY_ACCOUNT_CANNOT_MODIFIED',
|
||||
|
||||
ITEM_HAS_ASSOCIATED_TRANSACTIONS: 'ITEM_HAS_ASSOCIATED_TRANSACTIONS',
|
||||
|
||||
PURCHASE_TAX_RATE_NOT_FOUND: 'PURCHASE_TAX_RATE_NOT_FOUND',
|
||||
SELL_TAX_RATE_NOT_FOUND: 'SELL_TAX_RATE_NOT_FOUND',
|
||||
ITEM_HAS_ASSOCIATED_TRANSACTIONS: 'ITEM_HAS_ASSOCIATED_TRANSACTIONS'
|
||||
};
|
||||
|
||||
export const DEFAULT_VIEW_COLUMNS = [];
|
||||
|
||||
@@ -203,8 +203,8 @@ export class BillPaymentGLEntries {
|
||||
|
||||
/**
|
||||
* Retrieves the payment GL payable entry.
|
||||
* @param {IBillPayment} billPayment
|
||||
* @param {number} APAccountId
|
||||
* @param {IBillPayment} billPayment
|
||||
* @param {number} APAccountId
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getPaymentGLPayableEntry = (
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
||||
import { WarehouseTransactionDTOTransform } from '@/services/Warehouses/Integrations/WarehouseTransactionDTOTransform';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ItemEntriesTaxTransactions } from '@/services/TaxRates/ItemEntriesTaxTransactions';
|
||||
|
||||
@Service()
|
||||
export class BillDTOTransformer {
|
||||
@@ -24,9 +23,6 @@ export class BillDTOTransformer {
|
||||
@Inject()
|
||||
private warehouseDTOTransform: WarehouseTransactionDTOTransform;
|
||||
|
||||
@Inject()
|
||||
private taxDTOTransformer: ItemEntriesTaxTransactions;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@@ -77,24 +73,14 @@ export class BillDTOTransformer {
|
||||
const billNumber = billDTO.billNumber || oldBill?.billNumber;
|
||||
|
||||
const initialEntries = billDTO.entries.map((entry) => ({
|
||||
referenceType: 'Bill',
|
||||
isInclusiveTax: billDTO.isInclusiveTax,
|
||||
reference_type: 'Bill',
|
||||
...omit(entry, ['amount']),
|
||||
}));
|
||||
const asyncEntries = await composeAsync(
|
||||
// Associate tax rate from tax id to entries.
|
||||
this.taxDTOTransformer.assocTaxRateFromTaxIdToEntries(tenantId),
|
||||
// Associate tax rate id from tax code to entries.
|
||||
this.taxDTOTransformer.assocTaxRateIdFromCodeToEntries(tenantId),
|
||||
const entries = await composeAsync(
|
||||
// Sets the default cost account to the bill entries.
|
||||
this.setBillEntriesDefaultAccounts(tenantId)
|
||||
)(initialEntries);
|
||||
|
||||
const entries = R.compose(
|
||||
// Remove tax code from entries.
|
||||
R.map(R.omit(['taxCode']))
|
||||
)(asyncEntries);
|
||||
|
||||
const initialDTO = {
|
||||
...formatDateFields(omit(billDTO, ['open', 'entries']), [
|
||||
'billDate',
|
||||
@@ -114,8 +100,6 @@ export class BillDTOTransformer {
|
||||
userId: authorizedUser.id,
|
||||
};
|
||||
return R.compose(
|
||||
// Associates tax amount withheld to the model.
|
||||
this.taxDTOTransformer.assocTaxAmountWithheldFromEntries,
|
||||
this.branchDTOTransform.transformDTO(tenantId),
|
||||
this.warehouseDTOTransform.transformDTO(tenantId)
|
||||
)(initialDTO);
|
||||
|
||||
@@ -7,7 +7,6 @@ import { AccountNormal, IBill, IItemEntry, ILedgerEntry } from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import Ledger from '@/services/Accounting/Ledger';
|
||||
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
|
||||
@Service()
|
||||
export class BillGLEntries {
|
||||
@@ -17,9 +16,6 @@ export class BillGLEntries {
|
||||
@Inject()
|
||||
private ledgerStorage: LedgerStorageService;
|
||||
|
||||
@Inject()
|
||||
private itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
/**
|
||||
* Creates bill GL entries.
|
||||
* @param {number} tenantId -
|
||||
@@ -47,16 +43,8 @@ export class BillGLEntries {
|
||||
{},
|
||||
trx
|
||||
);
|
||||
// Find or create tax payable account.
|
||||
const taxPayableAccount = await accountRepository.findOrCreateTaxPayable(
|
||||
{},
|
||||
trx
|
||||
);
|
||||
const billLedger = this.getBillLedger(
|
||||
bill,
|
||||
APAccount.id,
|
||||
taxPayableAccount.id
|
||||
);
|
||||
const billLedger = this.getBillLedger(bill, APAccount.id);
|
||||
|
||||
// Commit the GL enties on the storage.
|
||||
await this.ledgerStorage.commit(tenantId, billLedger, trx);
|
||||
};
|
||||
@@ -95,7 +83,7 @@ export class BillGLEntries {
|
||||
|
||||
/**
|
||||
* Retrieves the bill common entry.
|
||||
* @param {IBill} bill
|
||||
* @param {IBill} bill
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getBillCommonEntry = (bill: IBill) => {
|
||||
@@ -131,7 +119,7 @@ export class BillGLEntries {
|
||||
(bill: IBill, entry: IItemEntry, index: number): ILedgerEntry => {
|
||||
const commonJournalMeta = this.getBillCommonEntry(bill);
|
||||
|
||||
const localAmount = bill.exchangeRate * entry.amountExludingTax;
|
||||
const localAmount = bill.exchangeRate * entry.amount;
|
||||
const landedCostAmount = sumBy(entry.allocatedCostEntries, 'cost');
|
||||
|
||||
return {
|
||||
@@ -185,7 +173,7 @@ export class BillGLEntries {
|
||||
|
||||
return {
|
||||
...commonJournalMeta,
|
||||
credit: bill.totalLocal,
|
||||
credit: bill.localAmount,
|
||||
accountId: payableAccountId,
|
||||
contactId: bill.vendorId,
|
||||
accountNormal: AccountNormal.CREDIT,
|
||||
@@ -194,62 +182,15 @@ export class BillGLEntries {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the bill tax GL entry.
|
||||
* @param {IBill} bill -
|
||||
* @param {number} taxPayableAccountId -
|
||||
* @param {IItemEntry} entry -
|
||||
* @param {number} index -
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getBillTaxEntry = R.curry(
|
||||
(
|
||||
bill: IBill,
|
||||
taxPayableAccountId: number,
|
||||
entry: IItemEntry,
|
||||
index: number
|
||||
): ILedgerEntry => {
|
||||
const commonJournalMeta = this.getBillCommonEntry(bill);
|
||||
|
||||
return {
|
||||
...commonJournalMeta,
|
||||
debit: entry.taxAmount,
|
||||
index,
|
||||
indexGroup: 30,
|
||||
accountId: taxPayableAccountId,
|
||||
accountNormal: AccountNormal.CREDIT,
|
||||
taxRateId: entry.taxRateId,
|
||||
taxRate: entry.taxRate,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Retrieves the bill tax GL entries.
|
||||
* @param {IBill} bill
|
||||
* @param {number} taxPayableAccountId
|
||||
* @returns {ILedgerEntry[]}
|
||||
*/
|
||||
private getBillTaxEntries = (bill: IBill, taxPayableAccountId: number) => {
|
||||
// Retrieves the non-zero tax entries.
|
||||
const nonZeroTaxEntries = this.itemsEntriesService.getNonZeroEntries(
|
||||
bill.entries
|
||||
);
|
||||
const transformTaxEntry = this.getBillTaxEntry(bill, taxPayableAccountId);
|
||||
|
||||
return nonZeroTaxEntries.map(transformTaxEntry);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the given bill GL entries.
|
||||
* @param {IBill} bill
|
||||
* @param {number} payableAccountId
|
||||
* @param {IBill} bill
|
||||
* @param {number} payableAccountId
|
||||
* @returns {ILedgerEntry[]}
|
||||
*/
|
||||
private getBillGLEntries = (
|
||||
bill: IBill,
|
||||
payableAccountId: number,
|
||||
taxPayableAccountId: number
|
||||
payableAccountId: number
|
||||
): ILedgerEntry[] => {
|
||||
const payableEntry = this.getBillPayableEntry(payableAccountId, bill);
|
||||
|
||||
@@ -260,28 +201,18 @@ export class BillGLEntries {
|
||||
const landedCostEntries = bill.locatedLandedCosts.map(
|
||||
landedCostTransformer
|
||||
);
|
||||
const taxEntries = this.getBillTaxEntries(bill, taxPayableAccountId);
|
||||
|
||||
// Allocate cost entries journal entries.
|
||||
return [payableEntry, ...itemsEntries, ...landedCostEntries, ...taxEntries];
|
||||
return [payableEntry, ...itemsEntries, ...landedCostEntries];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the given bill ledger.
|
||||
* @param {IBill} bill
|
||||
* @param {number} payableAccountId
|
||||
* @param {IBill} bill
|
||||
* @param {number} payableAccountId
|
||||
* @returns {Ledger}
|
||||
*/
|
||||
private getBillLedger = (
|
||||
bill: IBill,
|
||||
payableAccountId: number,
|
||||
taxPayableAccountId: number
|
||||
) => {
|
||||
const entries = this.getBillGLEntries(
|
||||
bill,
|
||||
payableAccountId,
|
||||
taxPayableAccountId
|
||||
);
|
||||
private getBillLedger = (bill: IBill, payableAccountId: number) => {
|
||||
const entries = this.getBillGLEntries(bill, payableAccountId);
|
||||
|
||||
return new Ledger(entries);
|
||||
};
|
||||
|
||||
@@ -28,8 +28,7 @@ export class GetBill {
|
||||
.findById(billId)
|
||||
.withGraphFetched('vendor')
|
||||
.withGraphFetched('entries.item')
|
||||
.withGraphFetched('branch')
|
||||
.withGraphFetched('taxes.taxRate');
|
||||
.withGraphFetched('branch');
|
||||
|
||||
// Validates the bill existance.
|
||||
this.validators.validateBillExistance(bill);
|
||||
|
||||
@@ -1,42 +1,27 @@
|
||||
import { IBill } from '@/interfaces';
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import { SaleInvoiceTaxEntryTransformer } from '@/services/Sales/Invoices/SaleInvoiceTaxEntryTransformer';
|
||||
import { formatNumber } from 'utils';
|
||||
|
||||
export class PurchaseInvoiceTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale bill object.
|
||||
* Include these attributes to sale invoice object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'formattedBillDate',
|
||||
'formattedDueDate',
|
||||
'formattedAmount',
|
||||
'formattedPaymentAmount',
|
||||
'formattedBalance',
|
||||
'formattedDueAmount',
|
||||
'formattedExchangeRate',
|
||||
'subtotalFormatted',
|
||||
'subtotalLocalFormatted',
|
||||
'subtotalExcludingTaxFormatted',
|
||||
'taxAmountWithheldLocalFormatted',
|
||||
'totalFormatted',
|
||||
'totalLocalFormatted',
|
||||
'taxes',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Excluded attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['amount', 'amountLocal', 'localAmount'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted bill date.
|
||||
* @param {IBill} bill
|
||||
* Retrieve formatted invoice date.
|
||||
* @param {IBill} invoice
|
||||
* @returns {String}
|
||||
*/
|
||||
protected formattedBillDate = (bill: IBill): string => {
|
||||
@@ -44,8 +29,8 @@ export class PurchaseInvoiceTransformer extends Transformer {
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted bill date.
|
||||
* @param {IBill} bill
|
||||
* Retrieve formatted invoice date.
|
||||
* @param {IBill} invoice
|
||||
* @returns {String}
|
||||
*/
|
||||
protected formattedDueDate = (bill: IBill): string => {
|
||||
@@ -54,7 +39,7 @@ export class PurchaseInvoiceTransformer extends Transformer {
|
||||
|
||||
/**
|
||||
* Retrieve formatted bill amount.
|
||||
* @param {IBill} bill
|
||||
* @param {IBill} invoice
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedAmount = (bill): string => {
|
||||
@@ -63,7 +48,7 @@ export class PurchaseInvoiceTransformer extends Transformer {
|
||||
|
||||
/**
|
||||
* Retrieve formatted bill amount.
|
||||
* @param {IBill} bill
|
||||
* @param {IBill} invoice
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedPaymentAmount = (bill): string => {
|
||||
@@ -74,7 +59,7 @@ export class PurchaseInvoiceTransformer extends Transformer {
|
||||
|
||||
/**
|
||||
* Retrieve formatted bill amount.
|
||||
* @param {IBill} bill
|
||||
* @param {IBill} invoice
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedDueAmount = (bill): string => {
|
||||
@@ -92,90 +77,10 @@ export class PurchaseInvoiceTransformer extends Transformer {
|
||||
|
||||
/**
|
||||
* Retrieve the formatted exchange rate.
|
||||
* @param {IBill} bill
|
||||
* @param {ISaleInvoice} invoice
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedExchangeRate = (bill): string => {
|
||||
return formatNumber(bill.exchangeRate, {
|
||||
currencyCode: this.context.organization.baseCurrency,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted subtotal.
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
protected subtotalFormatted = (bill): string => {
|
||||
return formatNumber(bill.subtotal, {
|
||||
currencyCode: bill.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the local subtotal formatted.
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
protected subtotalLocalFormatted = (bill): string => {
|
||||
return formatNumber(bill.subtotalLocal, {
|
||||
currencyCode: this.context.organization.baseCurrency,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted subtotal tax excluded.
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
protected subtotalExcludingTaxFormatted = (bill): string => {
|
||||
return formatNumber(bill.subtotalExludingTax, {
|
||||
currencyCode: bill.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the local formatted tax amount withheld
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
protected taxAmountWithheldLocalFormatted = (bill): string => {
|
||||
return formatNumber(bill.taxAmountWithheldLocal, {
|
||||
currencyCode: this.context.organization.baseCurrency,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the total formatted.
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
protected totalFormatted = (bill): string => {
|
||||
return formatNumber(bill.total, {
|
||||
currencyCode: bill.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the local total formatted.
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
protected totalLocalFormatted = (bill): string => {
|
||||
return formatNumber(bill.totalLocal, {
|
||||
currencyCode: this.context.organization.baseCurrency,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the taxes lines of bill.
|
||||
* @param {Bill} bill
|
||||
*/
|
||||
protected taxes = (bill) => {
|
||||
return this.item(bill.taxes, new SaleInvoiceTaxEntryTransformer(), {
|
||||
subtotal: bill.subtotal,
|
||||
isInclusiveTax: bill.isInclusiveTax,
|
||||
currencyCode: bill.currencyCode,
|
||||
});
|
||||
protected formattedExchangeRate = (invoice): string => {
|
||||
return formatNumber(invoice.exchangeRate, { money: false });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -218,8 +218,7 @@ export class SaleInvoiceGLEntries {
|
||||
...commonEntry,
|
||||
credit: entry.taxAmount,
|
||||
accountId: taxPayableAccountId,
|
||||
index: index + 1,
|
||||
indexGroup: 30,
|
||||
index: index + 3,
|
||||
accountNormal: AccountNormal.CREDIT,
|
||||
taxRateId: entry.taxRateId,
|
||||
taxRate: entry.taxRate,
|
||||
|
||||
@@ -62,8 +62,8 @@ export class SaleInvoiceTaxEntryTransformer extends Transformer {
|
||||
const taxRate = this.taxRate(taxEntry);
|
||||
|
||||
return this.options.isInclusiveTax
|
||||
? getInclusiveTaxAmount(this.options.subtotal, taxRate)
|
||||
: getExlusiveTaxAmount(this.options.subtotal, taxRate);
|
||||
? getInclusiveTaxAmount(this.options.amount, taxRate)
|
||||
: getExlusiveTaxAmount(this.options.amount, taxRate);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -171,7 +171,7 @@ export class SaleInvoiceTransformer extends Transformer {
|
||||
*/
|
||||
protected taxes = (invoice) => {
|
||||
return this.item(invoice.taxes, new SaleInvoiceTaxEntryTransformer(), {
|
||||
subtotal: invoice.subtotal,
|
||||
amount: invoice.amount,
|
||||
isInclusiveTax: invoice.isInclusiveTax,
|
||||
currencyCode: invoice.currencyCode,
|
||||
});
|
||||
|
||||
@@ -115,7 +115,6 @@ export class EditTaxRateService {
|
||||
// Triggers `onTaxRateEdited` event.
|
||||
await this.eventPublisher.emitAsync(events.taxRates.onEdited, {
|
||||
editTaxRateDTO,
|
||||
oldTaxRate,
|
||||
taxRate,
|
||||
tenantId,
|
||||
trx,
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import HasTenancyService from '../Tenancy/TenancyService';
|
||||
|
||||
@Service()
|
||||
export class SyncItemTaxRateOnEditTaxRate {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Syncs the new tax rate created to item default sell tax rate.
|
||||
* @param {number} tenantId
|
||||
* @param {number} itemId
|
||||
* @param {number} sellTaxRateId
|
||||
*/
|
||||
public updateItemSellTaxRate = async (
|
||||
tenantId: number,
|
||||
oldSellTaxRateId: number,
|
||||
sellTaxRateId: number,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
const { Item } = this.tenancy.models(tenantId);
|
||||
|
||||
// Can't continue if the old and new sell tax rate id are equal.
|
||||
if (oldSellTaxRateId === sellTaxRateId) return;
|
||||
|
||||
await Item.query().where('sellTaxRateId', oldSellTaxRateId).update({
|
||||
sellTaxRateId,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Syncs the new tax rate created to item default purchase tax rate.
|
||||
* @param {number} tenantId
|
||||
* @param {number} itemId
|
||||
* @param {number} purchaseTaxRateId
|
||||
*/
|
||||
public updateItemPurchaseTaxRate = async (
|
||||
tenantId: number,
|
||||
oldPurchaseTaxRateId: number,
|
||||
purchaseTaxRateId: number,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
const { Item } = this.tenancy.models(tenantId);
|
||||
|
||||
// Can't continue if the old and new sell tax rate id are equal.
|
||||
if (oldPurchaseTaxRateId === purchaseTaxRateId) return;
|
||||
|
||||
await Item.query(trx)
|
||||
.where('purchaseTaxRateId', oldPurchaseTaxRateId)
|
||||
.update({
|
||||
purchaseTaxRateId,
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { SyncItemTaxRateOnEditTaxRate } from './SyncItemTaxRateOnEditTaxRate';
|
||||
import events from '@/subscribers/events';
|
||||
import { ITaxRateEditedPayload } from '@/interfaces';
|
||||
import { runAfterTransaction } from '../UnitOfWork/TransactionsHooks';
|
||||
|
||||
@Service()
|
||||
export class SyncItemTaxRateOnEditTaxSubscriber {
|
||||
@Inject()
|
||||
private syncItemRateOnEdit: SyncItemTaxRateOnEditTaxRate;
|
||||
|
||||
/**
|
||||
* Attaches events with handles.
|
||||
*/
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.taxRates.onEdited,
|
||||
this.handleSyncNewTaxRateToItemTaxRate
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs the new tax rate created to default item tax rates.
|
||||
* @param {ITaxRateEditedPayload} payload -
|
||||
*/
|
||||
private handleSyncNewTaxRateToItemTaxRate = async ({
|
||||
taxRate,
|
||||
tenantId,
|
||||
oldTaxRate,
|
||||
trx,
|
||||
}: ITaxRateEditedPayload) => {
|
||||
runAfterTransaction(trx, async () => {
|
||||
await this.syncItemRateOnEdit.updateItemPurchaseTaxRate(
|
||||
tenantId,
|
||||
oldTaxRate.id,
|
||||
taxRate.id
|
||||
);
|
||||
await this.syncItemRateOnEdit.updateItemSellTaxRate(
|
||||
tenantId,
|
||||
oldTaxRate.id,
|
||||
taxRate.id
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { IBillCreatingPayload, IBillEditingPayload } from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import { CommandTaxRatesValidators } from '../CommandTaxRatesValidators';
|
||||
|
||||
@Service()
|
||||
export class BillTaxRateValidateSubscriber {
|
||||
@Inject()
|
||||
private taxRateDTOValidator: CommandTaxRatesValidators;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
*/
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.bill.onCreating,
|
||||
this.validateBillEntriesTaxCodeExistanceOnCreating
|
||||
);
|
||||
bus.subscribe(
|
||||
events.bill.onCreating,
|
||||
this.validateBillEntriesTaxIdExistanceOnCreating
|
||||
);
|
||||
bus.subscribe(
|
||||
events.bill.onEditing,
|
||||
this.validateBillEntriesTaxCodeExistanceOnEditing
|
||||
);
|
||||
bus.subscribe(
|
||||
events.bill.onEditing,
|
||||
this.validateBillEntriesTaxIdExistanceOnEditing
|
||||
);
|
||||
return bus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate bill entries tax rate code existance when creating.
|
||||
* @param {IBillCreatingPayload}
|
||||
*/
|
||||
private validateBillEntriesTaxCodeExistanceOnCreating = async ({
|
||||
billDTO,
|
||||
tenantId,
|
||||
}: IBillCreatingPayload) => {
|
||||
await this.taxRateDTOValidator.validateItemEntriesTaxCode(
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate the tax rate id existance when creating.
|
||||
* @param {IBillCreatingPayload}
|
||||
*/
|
||||
private validateBillEntriesTaxIdExistanceOnCreating = async ({
|
||||
billDTO,
|
||||
tenantId,
|
||||
}: IBillCreatingPayload) => {
|
||||
await this.taxRateDTOValidator.validateItemEntriesTaxCodeId(
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate bill entries tax rate code existance when editing.
|
||||
* @param {IBillEditingPayload}
|
||||
*/
|
||||
private validateBillEntriesTaxCodeExistanceOnEditing = async ({
|
||||
tenantId,
|
||||
billDTO,
|
||||
}: IBillEditingPayload) => {
|
||||
await this.taxRateDTOValidator.validateItemEntriesTaxCode(
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates the bill entries tax rate id existance when editing.
|
||||
* @param {ISaleInvoiceEditingPayload} payload -
|
||||
*/
|
||||
private validateBillEntriesTaxIdExistanceOnEditing = async ({
|
||||
tenantId,
|
||||
billDTO,
|
||||
}: IBillEditingPayload) => {
|
||||
await this.taxRateDTOValidator.validateItemEntriesTaxCodeId(
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import {
|
||||
IBIllEventDeletedPayload,
|
||||
IBillCreatedPayload,
|
||||
IBillEditedPayload,
|
||||
ISaleInvoiceCreatedPayload,
|
||||
ISaleInvoiceDeletedPayload,
|
||||
ISaleInvoiceEditedPayload,
|
||||
} from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import { WriteTaxTransactionsItemEntries } from '../WriteTaxTransactionsItemEntries';
|
||||
|
||||
@Service()
|
||||
export class WriteBillTaxTransactionsSubscriber {
|
||||
@Inject()
|
||||
private writeTaxTransactions: WriteTaxTransactionsItemEntries;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
*/
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.bill.onCreated,
|
||||
this.writeInvoiceTaxTransactionsOnCreated
|
||||
);
|
||||
bus.subscribe(
|
||||
events.bill.onEdited,
|
||||
this.rewriteInvoiceTaxTransactionsOnEdited
|
||||
);
|
||||
bus.subscribe(
|
||||
events.bill.onDeleted,
|
||||
this.removeInvoiceTaxTransactionsOnDeleted
|
||||
);
|
||||
return bus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the bill tax transactions on invoice created.
|
||||
* @param {ISaleInvoiceCreatingPaylaod}
|
||||
*/
|
||||
private writeInvoiceTaxTransactionsOnCreated = async ({
|
||||
tenantId,
|
||||
bill,
|
||||
trx,
|
||||
}: IBillCreatedPayload) => {
|
||||
await this.writeTaxTransactions.writeTaxTransactionsFromItemEntries(
|
||||
tenantId,
|
||||
bill.entries,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Rewrites the bill tax transactions on invoice edited.
|
||||
* @param {IBillEditedPayload} payload -
|
||||
*/
|
||||
private rewriteInvoiceTaxTransactionsOnEdited = async ({
|
||||
tenantId,
|
||||
bill,
|
||||
trx,
|
||||
}: IBillEditedPayload) => {
|
||||
await this.writeTaxTransactions.rewriteTaxRateTransactionsFromItemEntries(
|
||||
tenantId,
|
||||
bill.entries,
|
||||
'Bill',
|
||||
bill.id,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the invoice tax transactions on invoice deleted.
|
||||
* @param {IBIllEventDeletedPayload}
|
||||
*/
|
||||
private removeInvoiceTaxTransactionsOnDeleted = async ({
|
||||
tenantId,
|
||||
oldBill,
|
||||
trx,
|
||||
}: IBIllEventDeletedPayload) => {
|
||||
await this.writeTaxTransactions.removeTaxTransactionsFromItemEntries(
|
||||
tenantId,
|
||||
oldBill.id,
|
||||
'Bill',
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
87
packages/server/src/subscribers/Bills/WriteJournalEntries.ts
Normal file
87
packages/server/src/subscribers/Bills/WriteJournalEntries.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import events from '@/subscribers/events';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import BillsService from '@/services/Purchases/Bills';
|
||||
import {
|
||||
IBillCreatedPayload,
|
||||
IBillEditedPayload,
|
||||
IBIllEventDeletedPayload,
|
||||
} from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export default class BillWriteGLEntriesSubscriber {
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
@Inject()
|
||||
billsService: BillsService;
|
||||
|
||||
/**
|
||||
* Attaches events with handles.
|
||||
*/
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.bill.onCreated,
|
||||
this.handlerWriteJournalEntriesOnCreate
|
||||
);
|
||||
bus.subscribe(
|
||||
events.bill.onEdited,
|
||||
this.handleOverwriteJournalEntriesOnEdit
|
||||
);
|
||||
bus.subscribe(events.bill.onDeleted, this.handlerDeleteJournalEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles writing journal entries once bill created.
|
||||
* @param {IBillCreatedPayload} payload -
|
||||
*/
|
||||
private handlerWriteJournalEntriesOnCreate = async ({
|
||||
tenantId,
|
||||
billId,
|
||||
bill,
|
||||
trx,
|
||||
}: IBillCreatedPayload) => {
|
||||
// Can't continue if the bill is not opened yet.
|
||||
if (!bill.openedAt) return null;
|
||||
|
||||
await this.billsService.recordJournalTransactions(
|
||||
tenantId,
|
||||
billId,
|
||||
false,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the overwriting journal entries once bill edited.
|
||||
* @param {IBillEditedPayload} payload -
|
||||
*/
|
||||
private handleOverwriteJournalEntriesOnEdit = async ({
|
||||
tenantId,
|
||||
billId,
|
||||
bill,
|
||||
trx,
|
||||
}: IBillEditedPayload) => {
|
||||
// Can't continue if the bill is not opened yet.
|
||||
if (!bill.openedAt) return null;
|
||||
|
||||
await this.billsService.recordJournalTransactions(
|
||||
tenantId,
|
||||
billId,
|
||||
true,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles revert journal entries on bill deleted.
|
||||
* @param {IBIllEventDeletedPayload} payload -
|
||||
*/
|
||||
private handlerDeleteJournalEntries = async ({
|
||||
tenantId,
|
||||
billId,
|
||||
trx,
|
||||
}: IBIllEventDeletedPayload) => {
|
||||
await this.billsService.revertJournalEntries(tenantId, billId, trx);
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,18 @@
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const dotenv = require('dotenv-webpack');
|
||||
|
||||
module.exports = {
|
||||
webpack: {
|
||||
plugins: [
|
||||
new dotenv(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
MONOREPO_VERSION: JSON.stringify(require('../../lerna.json').version),
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, 'src'),
|
||||
},
|
||||
|
||||
@@ -117,12 +117,6 @@
|
||||
"resolutions": {
|
||||
"react-error-overlay": "6.0.9"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
import AccountDrawer from '@/containers/Drawers/AccountDrawer';
|
||||
import ManualJournalDrawer from '@/containers/Drawers/ManualJournalDrawer';
|
||||
import ExpenseDrawer from '@/containers/Drawers/ExpenseDrawer';
|
||||
|
||||
@@ -26,10 +26,6 @@ const SelectButton = styled(Button)`
|
||||
position: relative;
|
||||
padding-right: 30px;
|
||||
|
||||
&.bp4-small{
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
&:not(.is-selected):not([class*='bp4-intent-']):not(.bp4-minimal) {
|
||||
color: #5c7080;
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import * as R from 'ramda';
|
||||
import intl from 'react-intl-universal';
|
||||
import { FSelect } from '@/components';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
|
||||
// Create new account renderer.
|
||||
const createNewItemRenderer = (query, active, handleClick) => {
|
||||
return (
|
||||
<MenuItem
|
||||
icon="add"
|
||||
text={intl.get('list.create', { value: `"${query}"` })}
|
||||
active={active}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// Create new item from the given query string.
|
||||
const createNewItemFromQuery = (name) => ({ name });
|
||||
|
||||
/**
|
||||
* Tax rates select field binded with Formik form.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
function TaxRatesSelectRoot({
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
|
||||
// #ownProps
|
||||
allowCreate,
|
||||
|
||||
...restProps
|
||||
}) {
|
||||
// Maybe inject new item props to select component.
|
||||
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||
const maybeCreateNewItemFromQuery = allowCreate
|
||||
? createNewItemFromQuery
|
||||
: null;
|
||||
|
||||
// Handles the create item click.
|
||||
const handleCreateItemClick = () => {
|
||||
openDialog(DialogsName.TaxRateForm);
|
||||
};
|
||||
|
||||
return (
|
||||
<FSelect
|
||||
valueAccessor={'id'}
|
||||
labelAccessor={'code'}
|
||||
textAccessor={'name_formatted'}
|
||||
popoverProps={{ minimal: true, usePortal: true, inline: false }}
|
||||
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||
onCreateItemSelect={handleCreateItemClick}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const TaxRatesSelect = R.compose(withDialogActions)(TaxRatesSelectRoot);
|
||||
@@ -1,7 +0,0 @@
|
||||
|
||||
import { TaxType } from '@/interfaces/TaxRates';
|
||||
|
||||
export const InclusiveTaxOptions = [
|
||||
{ key: TaxType.Inclusive, label: 'Inclusive of Tax' },
|
||||
{ key: TaxType.Exclusive, label: 'Exclusive of Tax' },
|
||||
];
|
||||
@@ -1,4 +1,5 @@
|
||||
// @ts-nocheck
|
||||
import React, { useMemo } from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Formik } from 'formik';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
@@ -34,10 +34,10 @@ export function Sidebar() {
|
||||
* @returns {React.JSX}
|
||||
*/
|
||||
function SidebarFooterVersion() {
|
||||
const { REACT_APP_VERSION } = process.env;
|
||||
const { MONOREPO_VERSION } = process.env;
|
||||
|
||||
if (!REACT_APP_VERSION) {
|
||||
if (!MONOREPO_VERSION) {
|
||||
return null;
|
||||
}
|
||||
return <div class="sidebar__version">v{REACT_APP_VERSION}</div>;
|
||||
return <div class="sidebar__version">v{MONOREPO_VERSION}</div>;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Button, Popover, Menu, Position } from '@blueprintjs/core';
|
||||
|
||||
import { Icon } from '@/components';
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function BillDetailHeader() {
|
||||
<CommercialDocTopHeader>
|
||||
<DetailsMenu>
|
||||
<AmountDetailItem label={intl.get('amount')}>
|
||||
<h3 class="big-number">{bill.total_formatted}</h3>
|
||||
<h3 class="big-number">{bill.formatted_amount}</h3>
|
||||
</AmountDetailItem>
|
||||
<StatusDetailItem>
|
||||
<BillDetailsStatus bill={bill} />
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import {
|
||||
TotalLineBorderStyle,
|
||||
TotalLineTextStyle,
|
||||
FormatNumber,
|
||||
T,
|
||||
TotalLines,
|
||||
TotalLine,
|
||||
@@ -20,20 +23,12 @@ export function BillDetailTableFooter() {
|
||||
<BillTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
|
||||
<TotalLine
|
||||
title={<T id={'bill.details.subtotal'} />}
|
||||
value={bill.subtotal_formatted}
|
||||
value={<FormatNumber value={bill.amount} />}
|
||||
borderStyle={TotalLineBorderStyle.SingleDark}
|
||||
/>
|
||||
{bill.taxes.map((taxRate) => (
|
||||
<TotalLine
|
||||
key={taxRate.id}
|
||||
title={`${taxRate.name} [${taxRate.tax_rate}%]`}
|
||||
value={taxRate.tax_rate_amount_formatted}
|
||||
textStyle={TotalLineTextStyle.Regular}
|
||||
/>
|
||||
))}
|
||||
<TotalLine
|
||||
title={<T id={'bill.details.total'} />}
|
||||
value={bill.total_formatted}
|
||||
value={bill.formatted_amount}
|
||||
borderStyle={TotalLineBorderStyle.DoubleDark}
|
||||
textStyle={TotalLineTextStyle.Bold}
|
||||
/>
|
||||
@@ -44,7 +39,6 @@ export function BillDetailTableFooter() {
|
||||
<TotalLine
|
||||
title={<T id={'bill.details.due_amount'} />}
|
||||
value={bill.formatted_due_amount}
|
||||
textStyle={TotalLineTextStyle.Bold}
|
||||
/>
|
||||
</BillTotalLines>
|
||||
</BillDetailsFooterRoot>
|
||||
|
||||
@@ -67,14 +67,6 @@ export default function ItemDetailHeader() {
|
||||
label={intl.get('cost_account_id')}
|
||||
children={defaultTo(item.cost_account?.name, '-')}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('item.details.sell_tax_rate')}
|
||||
children={item?.sell_tax_rate?.name}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('item.details.purchase_tax_rate')}
|
||||
children={item?.purchase_tax_rate?.name}
|
||||
/>
|
||||
<If condition={item.type === 'inventory'}>
|
||||
<DetailItem
|
||||
label={intl.get('inventory_account')}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Box } from '@/components';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const EntriesActionsBar = styled(Box)`
|
||||
padding-bottom: 12px;
|
||||
display: flex;
|
||||
|
||||
.bp4-form-group {
|
||||
margin-bottom: 0;
|
||||
|
||||
label.bp4-label {
|
||||
opacity: 0.6;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -8,30 +8,20 @@ import { DataTableEditable } from '@/components';
|
||||
import { useEditableItemsEntriesColumns } from './components';
|
||||
import {
|
||||
useFetchItemRow,
|
||||
composeRowsOnNewRow,
|
||||
useComposeRowsOnEditTableCell,
|
||||
useComposeRowsOnRemoveTableRow,
|
||||
useComposeRowsOnNewRow,
|
||||
} from './utils';
|
||||
import {
|
||||
ItemEntriesTableProvider,
|
||||
useItemEntriesTableContext,
|
||||
} from './ItemEntriesTableProvider';
|
||||
import { useUncontrolled } from '@/hooks/useUncontrolled';
|
||||
import { ItemEntry } from '@/interfaces/ItemEntries';
|
||||
|
||||
interface ItemsEntriesTableProps {
|
||||
initialValue?: ItemEntry;
|
||||
value?: ItemEntry[];
|
||||
onChange?: (entries: ItemEntry[]) => void;
|
||||
taxRates?: any[];
|
||||
minLinesNumber?: number;
|
||||
enableTaxRates?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Items entries table.
|
||||
*/
|
||||
function ItemsEntriesTable(props: ItemsEntriesTableProps) {
|
||||
function ItemsEntriesTable(props) {
|
||||
const { value, initialValue, onChange } = props;
|
||||
|
||||
const [localValue, handleChange] = useUncontrolled({
|
||||
@@ -61,7 +51,6 @@ function ItemEntriesTableRoot() {
|
||||
currencyCode,
|
||||
landedCost,
|
||||
taxRates,
|
||||
itemType,
|
||||
} = useItemEntriesTableContext();
|
||||
|
||||
// Editiable items entries columns.
|
||||
@@ -69,12 +58,11 @@ function ItemEntriesTableRoot() {
|
||||
|
||||
const composeRowsOnEditCell = useComposeRowsOnEditTableCell();
|
||||
const composeRowsOnDeleteRow = useComposeRowsOnRemoveTableRow();
|
||||
const composeRowsOnNewRow = useComposeRowsOnNewRow();
|
||||
|
||||
// Handle the fetch item row details.
|
||||
const { setItemRow, cellsLoading, isItemFetching } = useFetchItemRow({
|
||||
landedCost,
|
||||
itemType,
|
||||
itemType: null,
|
||||
notifyNewRow: (newRow, rowIndex) => {
|
||||
// Update the rate, description and quantity data of the row.
|
||||
const newRows = composeRowsOnNewRow(rowIndex, newRow, localValue);
|
||||
@@ -131,11 +119,8 @@ ItemsEntriesTable.defaultProps = {
|
||||
discount: '',
|
||||
},
|
||||
initialEntries: [],
|
||||
taxRates: [],
|
||||
items: [],
|
||||
linesNumber: 1,
|
||||
minLinesNumber: 1,
|
||||
enableTaxRates: true,
|
||||
};
|
||||
|
||||
export default ItemsEntriesTable;
|
||||
|
||||
@@ -92,7 +92,7 @@ const LandedCostHeaderCell = () => {
|
||||
*/
|
||||
export function useEditableItemsEntriesColumns() {
|
||||
const { featureCan } = useFeatureCan();
|
||||
const { landedCost, enableTaxRates } = useItemEntriesTableContext();
|
||||
const { landedCost } = useItemEntriesTableContext();
|
||||
|
||||
const isProjectsFeatureEnabled = featureCan(Features.Projects);
|
||||
|
||||
@@ -132,17 +132,13 @@ export function useEditableItemsEntriesColumns() {
|
||||
width: 70,
|
||||
align: Align.Right,
|
||||
},
|
||||
...(enableTaxRates
|
||||
? [
|
||||
{
|
||||
Header: 'Tax rate',
|
||||
accessor: 'tax_rate_id',
|
||||
Cell: TaxRatesSuggestInputCell,
|
||||
disableSortBy: true,
|
||||
width: 110,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
Header: 'Tax rate',
|
||||
accessor: 'tax_rate_id',
|
||||
Cell: TaxRatesSuggestInputCell,
|
||||
disableSortBy: true,
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
Header: intl.get('discount'),
|
||||
accessor: 'discount',
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// @ts-nocheck
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import * as R from 'ramda';
|
||||
import { sumBy, isEmpty, last, keyBy, groupBy } from 'lodash';
|
||||
import { sumBy, isEmpty, last, keyBy } from 'lodash';
|
||||
|
||||
import { useItem } from '@/hooks/query';
|
||||
import {
|
||||
toSafeNumber,
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
updateAutoAddNewLine,
|
||||
orderingLinesIndexes,
|
||||
updateTableRow,
|
||||
formattedAmount,
|
||||
} from '@/utils';
|
||||
import { useItemEntriesTableContext } from './ItemEntriesTableProvider';
|
||||
|
||||
@@ -119,24 +119,17 @@ export function useFetchItemRow({ landedCost, itemType, notifyNewRow }) {
|
||||
// Detarmines whether the landed cost checkbox should be disabled.
|
||||
const landedCostDisabled = isLandedCostDisabled(item);
|
||||
|
||||
const taxRateId =
|
||||
itemType === ITEM_TYPE.PURCHASABLE
|
||||
? item.purchase_tax_rate_id
|
||||
: item.sell_tax_rate_id;
|
||||
|
||||
// The new row.
|
||||
const newRow = {
|
||||
rate: price,
|
||||
description,
|
||||
quantity: 1,
|
||||
tax_rate_id: taxRateId,
|
||||
...(landedCost
|
||||
? {
|
||||
landed_cost: false,
|
||||
landed_cost_disabled: landedCostDisabled,
|
||||
}
|
||||
: {}),
|
||||
taxRateId,
|
||||
};
|
||||
setItemRow(null);
|
||||
saveInvoke(notifyNewRow, newRow, rowIndex);
|
||||
@@ -165,21 +158,13 @@ export const composeRowsOnEditCell = R.curry(
|
||||
/**
|
||||
* Compose table rows when insert a new row to table rows.
|
||||
*/
|
||||
export const useComposeRowsOnNewRow = () => {
|
||||
const { taxRates, isInclusiveTax } = useItemEntriesTableContext();
|
||||
|
||||
return React.useMemo(() => {
|
||||
return R.curry((rowIndex, newRow, rows) => {
|
||||
return compose(
|
||||
assignEntriesTaxAmount(isInclusiveTax),
|
||||
assignEntriesTaxRate(taxRates),
|
||||
orderingLinesIndexes,
|
||||
updateItemsEntriesTotal,
|
||||
updateTableRow(rowIndex, newRow),
|
||||
)(rows);
|
||||
});
|
||||
}, [isInclusiveTax, taxRates]);
|
||||
};
|
||||
export const composeRowsOnNewRow = R.curry((rowIndex, newRow, rows) => {
|
||||
return compose(
|
||||
orderingLinesIndexes,
|
||||
updateItemsEntriesTotal,
|
||||
updateTableRow(rowIndex, newRow),
|
||||
)(rows);
|
||||
});
|
||||
|
||||
/**
|
||||
* Associate tax rate to entries.
|
||||
@@ -281,29 +266,3 @@ export const useComposeRowsOnRemoveTableRow = () => {
|
||||
[minLinesNumber, defaultEntry, localValue],
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the aggregate tax rates from the given item entries.
|
||||
*/
|
||||
export const aggregateItemEntriesTaxRates = R.curry((taxRates, entries) => {
|
||||
const taxRatesById = keyBy(taxRates, 'id');
|
||||
|
||||
// Calculate the total tax amount of invoice entries.
|
||||
const filteredEntries = entries.filter((e) => e.tax_rate_id);
|
||||
const groupedTaxRates = groupBy(filteredEntries, 'tax_rate_id');
|
||||
|
||||
return Object.keys(groupedTaxRates).map((taxRateId) => {
|
||||
const taxRate = taxRatesById[taxRateId];
|
||||
const taxRates = groupedTaxRates[taxRateId];
|
||||
const totalTaxAmount = sumBy(taxRates, 'tax_amount');
|
||||
const taxAmountFormatted = formattedAmount(totalTaxAmount, 'USD');
|
||||
|
||||
return {
|
||||
taxRateId,
|
||||
taxRate: taxRate.rate,
|
||||
label: `${taxRate.name} [${taxRate.rate}%]`,
|
||||
taxAmount: totalTaxAmount,
|
||||
taxAmountFormatted,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,7 +62,6 @@ const BalanceSheetDataTable = styled(ReportDataTable)`
|
||||
border-bottom: 0;
|
||||
padding-top: 0.32rem;
|
||||
padding-bottom: 0.32rem;
|
||||
color: #252A31;
|
||||
}
|
||||
&.is-expanded {
|
||||
.td:not(.name) .cell-inner {
|
||||
@@ -73,7 +72,6 @@ const BalanceSheetDataTable = styled(ReportDataTable)`
|
||||
.td {
|
||||
font-weight: 500;
|
||||
border-top: 1px solid #bbb;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,8 +60,9 @@ const CashflowStatementDataTable = styled(DataTable)`
|
||||
border-bottom: 0;
|
||||
padding-top: 0.32rem;
|
||||
padding-bottom: 0.32rem;
|
||||
color: #252a31;
|
||||
}
|
||||
|
||||
// &.row-type--AGGREGATE,
|
||||
&.row_type--ACCOUNTS {
|
||||
border-top: 1px solid #bbb;
|
||||
}
|
||||
@@ -71,9 +72,6 @@ const CashflowStatementDataTable = styled(DataTable)`
|
||||
&.row_type--TOTAL {
|
||||
font-weight: 500;
|
||||
|
||||
.td {
|
||||
color: #000;
|
||||
}
|
||||
&:not(:first-child) .td {
|
||||
border-top: 1px solid #bbb;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ const ProfitLossDataTable = styled(ReportDataTable)`
|
||||
border-bottom: 0;
|
||||
padding-top: 0.32rem;
|
||||
padding-bottom: 0.32rem;
|
||||
color: #252A31;
|
||||
}
|
||||
&.is-expanded {
|
||||
.td:not(.name) .cell-inner {
|
||||
@@ -72,7 +71,6 @@ const ProfitLossDataTable = styled(ReportDataTable)`
|
||||
.td {
|
||||
font-weight: 500;
|
||||
border-top: 1px solid #bbb;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
&:last-of-type .td {
|
||||
|
||||
@@ -29,16 +29,14 @@ import {
|
||||
costPriceFieldShouldUpdate,
|
||||
costAccountFieldShouldUpdate,
|
||||
purchaseDescFieldShouldUpdate,
|
||||
taxRateFieldShouldUpdate,
|
||||
} from './utils';
|
||||
import { compose, inputIntent } from '@/utils';
|
||||
import { TaxRatesSelect } from '@/components/TaxRates/TaxRatesSelect';
|
||||
|
||||
/**
|
||||
* Item form body.
|
||||
*/
|
||||
function ItemFormBody({ organization: { base_currency } }) {
|
||||
const { accounts, taxRates } = useItemFormContext();
|
||||
const { accounts } = useItemFormContext();
|
||||
const { values } = useFormikContext();
|
||||
|
||||
return (
|
||||
@@ -113,20 +111,7 @@ function ItemFormBody({ organization: { base_currency } }) {
|
||||
filterByParentTypes={[ACCOUNT_PARENT_TYPE.INCOME]}
|
||||
fill={true}
|
||||
allowCreate={true}
|
||||
fastField={true}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------- Sell Tax Rate ------------- */}
|
||||
<FFormGroup
|
||||
name={'sell_tax_rate_id'}
|
||||
label={'Tax Rate'}
|
||||
inline={true}
|
||||
>
|
||||
<TaxRatesSelect
|
||||
name={'sell_tax_rate_id'}
|
||||
items={taxRates}
|
||||
allowCreate
|
||||
fastField={true}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
@@ -228,24 +213,6 @@ function ItemFormBody({ organization: { base_currency } }) {
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------- Purchase Tax Rate ------------- */}
|
||||
<FFormGroup
|
||||
name={'purchase_tax_rate_id'}
|
||||
label={'Tax Rate'}
|
||||
inline={true}
|
||||
fastField={true}
|
||||
shouldUpdateDeps={{ taxRates }}
|
||||
shouldUpdate={taxRateFieldShouldUpdate}
|
||||
>
|
||||
<TaxRatesSelect
|
||||
name={'purchase_tax_rate_id'}
|
||||
items={taxRates}
|
||||
allowCreate={true}
|
||||
fastField={true}
|
||||
shouldUpdateDeps={{ taxRates }}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<FastField
|
||||
name={'purchase_description'}
|
||||
purchasable={values.purchasable}
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
useAccounts,
|
||||
} from '@/hooks/query';
|
||||
import { useWatchItemError } from './utils';
|
||||
import { useTaxRates } from '@/hooks/query/taxRates';
|
||||
|
||||
const ItemFormContext = createContext();
|
||||
|
||||
@@ -31,8 +30,6 @@ function ItemFormProvider({ itemId, ...props }) {
|
||||
data: { itemsCategories },
|
||||
} = useItemsCategories();
|
||||
|
||||
const { data: taxRates, isLoading: isTaxRatesLoading } = useTaxRates();
|
||||
|
||||
// Fetches the given item details.
|
||||
const itemQuery = useItem(itemId || duplicateId, {
|
||||
enabled: !!itemId || !!duplicateId,
|
||||
@@ -72,7 +69,6 @@ function ItemFormProvider({ itemId, ...props }) {
|
||||
accounts,
|
||||
item,
|
||||
itemsCategories,
|
||||
taxRates,
|
||||
submitPayload,
|
||||
isNewMode,
|
||||
|
||||
@@ -80,7 +76,6 @@ function ItemFormProvider({ itemId, ...props }) {
|
||||
isAccountsLoading,
|
||||
isItemsCategoriesLoading,
|
||||
isItemLoading,
|
||||
isTaxRatesLoading,
|
||||
|
||||
createItemMutate,
|
||||
editItemMutate,
|
||||
|
||||
@@ -23,14 +23,12 @@ const defaultInitialValues = {
|
||||
sell_price: '',
|
||||
cost_account_id: '',
|
||||
sell_account_id: '',
|
||||
sell_tax_rate_id: '',
|
||||
inventory_account_id: '',
|
||||
category_id: '',
|
||||
sellable: 1,
|
||||
purchasable: true,
|
||||
sell_description: '',
|
||||
purchase_description: '',
|
||||
purchase_tax_rate_id: '',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -189,13 +187,6 @@ export const purchaseDescFieldShouldUpdate = (newProps, oldProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const taxRateFieldShouldUpdate = (newProps, oldProps) => {
|
||||
return (
|
||||
newProps.shouldUpdateDeps.taxRates !== oldProps.shouldUpdateDeps.taxRates ||
|
||||
defaultFastFieldShouldUpdate(newProps, oldProps)
|
||||
);
|
||||
};
|
||||
|
||||
export function transformItemsTableState(tableState) {
|
||||
return {
|
||||
...transformTableStateToQuery(tableState),
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
handleErrors,
|
||||
} from './utils';
|
||||
import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization';
|
||||
import { BillFormEntriesActions } from './BillFormEntriesActions';
|
||||
|
||||
/**
|
||||
* Bill form.
|
||||
@@ -127,10 +126,7 @@ function BillForm({
|
||||
<Form>
|
||||
<BillFormTopBar />
|
||||
<BillFormHeader />
|
||||
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
|
||||
<BillFormEntriesActions />
|
||||
<BillItemsEntriesEditor />
|
||||
</div>
|
||||
<BillItemsEntriesEditor />
|
||||
<BillFormFooter />
|
||||
<BillFloatingActions />
|
||||
</Form>
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import styled from 'styled-components';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { FFormGroup, FSelect } from '@/components';
|
||||
import { InclusiveTaxOptions } from '@/constants/InclusiveTaxOptions';
|
||||
|
||||
import { composeEntriesOnEditInclusiveTax } from './utils';
|
||||
import { EntriesActionsBar } from '@/containers/Entries/EntriesActionBar';
|
||||
|
||||
export function BillFormEntriesActions() {
|
||||
return (
|
||||
<EntriesActionsBar>
|
||||
<BillExclusiveInclusiveSelect />
|
||||
</EntriesActionsBar>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bill exclusive/inclusive select.
|
||||
* @returns {React.ReactNode}
|
||||
*/
|
||||
export function BillExclusiveInclusiveSelect(props) {
|
||||
const { values, setFieldValue } = useFormikContext();
|
||||
|
||||
const handleItemSelect = (item) => {
|
||||
const newEntries = composeEntriesOnEditInclusiveTax(
|
||||
item.key,
|
||||
values.entries,
|
||||
);
|
||||
setFieldValue('inclusive_exclusive_tax', item.key);
|
||||
setFieldValue('entries', newEntries);
|
||||
};
|
||||
|
||||
return (
|
||||
<InclusiveFormGroup
|
||||
name={'inclusive_exclusive_tax'}
|
||||
label={'Amounts are'}
|
||||
inline={true}
|
||||
>
|
||||
<FSelect
|
||||
name={'inclusive_exclusive_tax'}
|
||||
items={InclusiveTaxOptions}
|
||||
textAccessor={'label'}
|
||||
labelAccessor={() => ''}
|
||||
valueAccessor={'key'}
|
||||
popoverProps={{ minimal: true, usePortal: true, inline: false }}
|
||||
buttonProps={{ small: true }}
|
||||
onItemSelect={handleItemSelect}
|
||||
filterable={false}
|
||||
{...props}
|
||||
/>
|
||||
</InclusiveFormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
const InclusiveFormGroup = styled(FFormGroup)`
|
||||
margin-left: auto;
|
||||
`;
|
||||
@@ -1,14 +1,15 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import {
|
||||
T,
|
||||
TotalLines,
|
||||
TotalLine,
|
||||
TotalLineBorderStyle,
|
||||
TotalLineTextStyle,
|
||||
} from '@/components';
|
||||
import { useBillAggregatedTaxRates, useBillTotals } from './utils';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { TaxType } from '@/interfaces/TaxRates';
|
||||
import { useBillTotals } from './utils';
|
||||
|
||||
export function BillFormFooterRight() {
|
||||
const {
|
||||
@@ -18,46 +19,26 @@ export function BillFormFooterRight() {
|
||||
formattedPaymentTotal,
|
||||
} = useBillTotals();
|
||||
|
||||
const {
|
||||
values: { inclusive_exclusive_tax, currency_code },
|
||||
} = useFormikContext();
|
||||
|
||||
const taxEntries = useBillAggregatedTaxRates();
|
||||
|
||||
return (
|
||||
<BillTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
|
||||
<TotalLine
|
||||
title={
|
||||
<>
|
||||
{inclusive_exclusive_tax === TaxType.Inclusive
|
||||
? 'Subtotal (Tax Inclusive)'
|
||||
: 'Subtotal'}
|
||||
</>
|
||||
}
|
||||
title={<T id={'bill_form.label.subtotal'} />}
|
||||
value={formattedSubtotal}
|
||||
borderStyle={TotalLineBorderStyle.None}
|
||||
/>
|
||||
{taxEntries.map((tax, index) => (
|
||||
<TotalLine
|
||||
key={index}
|
||||
title={tax.label}
|
||||
value={tax.taxAmountFormatted}
|
||||
borderStyle={TotalLineBorderStyle.None}
|
||||
/>
|
||||
))}
|
||||
<TotalLine
|
||||
title={`Total (${currency_code})`}
|
||||
title={<T id={'bill_form.label.total'} />}
|
||||
value={formattedTotal}
|
||||
borderStyle={TotalLineBorderStyle.SingleDark}
|
||||
textStyle={TotalLineTextStyle.Bold}
|
||||
/>
|
||||
<TotalLine
|
||||
title={'Paid Amount'}
|
||||
title={<T id={'bill_form.label.total'} />}
|
||||
value={formattedPaymentTotal}
|
||||
borderStyle={TotalLineBorderStyle.None}
|
||||
/>
|
||||
<TotalLine
|
||||
title={'Due Amount'}
|
||||
title={<T id={'bill_form.label.total'} />}
|
||||
value={formattedDueTotal}
|
||||
textStyle={TotalLineTextStyle.Bold}
|
||||
/>
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
useCreateBill,
|
||||
useEditBill,
|
||||
} from '@/hooks/query';
|
||||
import { useTaxRates } from '@/hooks/query/taxRates';
|
||||
|
||||
const BillFormContext = createContext();
|
||||
|
||||
@@ -84,9 +83,6 @@ function BillFormProvider({ billId, ...props }) {
|
||||
isSuccess: isBranchesSuccess,
|
||||
} = useBranches({}, { enabled: isBranchFeatureCan });
|
||||
|
||||
// Fetch tax rates.
|
||||
const { data: taxRates, isLoading: isTaxRatesLoading } = useTaxRates();
|
||||
|
||||
// Fetches the projects list.
|
||||
const {
|
||||
data: { projects },
|
||||
@@ -107,10 +103,7 @@ function BillFormProvider({ billId, ...props }) {
|
||||
|
||||
// Determines whether the warehouse and branches are loading.
|
||||
const isFeatureLoading =
|
||||
isWarehouesLoading ||
|
||||
isBranchesLoading ||
|
||||
isProjectsLoading ||
|
||||
isTaxRatesLoading;
|
||||
isWarehouesLoading || isBranchesLoading || isProjectsLoading;
|
||||
|
||||
const provider = {
|
||||
accounts,
|
||||
@@ -120,7 +113,6 @@ function BillFormProvider({ billId, ...props }) {
|
||||
warehouses,
|
||||
branches,
|
||||
projects,
|
||||
taxRates,
|
||||
submitPayload,
|
||||
isNewMode,
|
||||
|
||||
@@ -132,7 +124,6 @@ function BillFormProvider({ billId, ...props }) {
|
||||
isFeatureLoading,
|
||||
isBranchesSuccess,
|
||||
isWarehousesSuccess,
|
||||
isTaxRatesLoading,
|
||||
|
||||
createBillMutate,
|
||||
editBillMutate,
|
||||
|
||||
@@ -1,41 +1,45 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import ItemsEntriesTable from '@/containers/Entries/ItemsEntriesTable';
|
||||
import { FastField } from 'formik';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import { useBillFormContext } from './BillFormProvider';
|
||||
import { entriesFieldShouldUpdate } from './utils';
|
||||
import { ITEM_TYPE } from '@/containers/Entries/utils';
|
||||
|
||||
/**
|
||||
* Bill form body.
|
||||
* Bill form body.
|
||||
*/
|
||||
export default function BillFormBody({ defaultBill }) {
|
||||
const { items, taxRates } = useBillFormContext();
|
||||
const { items } = useBillFormContext();
|
||||
|
||||
return (
|
||||
<FastField
|
||||
name={'entries'}
|
||||
items={items}
|
||||
shouldUpdate={entriesFieldShouldUpdate}
|
||||
>
|
||||
{({
|
||||
form: { values, setFieldValue },
|
||||
field: { value },
|
||||
meta: { error, touched },
|
||||
}) => (
|
||||
<ItemsEntriesTable
|
||||
value={value}
|
||||
onChange={(entries) => {
|
||||
setFieldValue('entries', entries);
|
||||
}}
|
||||
items={items}
|
||||
errors={error}
|
||||
linesNumber={4}
|
||||
currencyCode={values.currency_code}
|
||||
itemType={ITEM_TYPE.PURCHASABLE}
|
||||
taxRates={taxRates}
|
||||
landedCost={true}
|
||||
/>
|
||||
)}
|
||||
</FastField>
|
||||
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
|
||||
<FastField
|
||||
name={'entries'}
|
||||
items={items}
|
||||
shouldUpdate={entriesFieldShouldUpdate}
|
||||
>
|
||||
{({
|
||||
form: { values, setFieldValue },
|
||||
field: { value },
|
||||
meta: { error, touched },
|
||||
}) => (
|
||||
<ItemsEntriesTable
|
||||
entries={value}
|
||||
onUpdateData={(entries) => {
|
||||
setFieldValue('entries', entries);
|
||||
}}
|
||||
items={items}
|
||||
errors={error}
|
||||
linesNumber={4}
|
||||
currencyCode={values.currency_code}
|
||||
itemType={ITEM_TYPE.PURCHASABLE}
|
||||
landedCost={true}
|
||||
/>
|
||||
)}
|
||||
</FastField>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react';
|
||||
import moment from 'moment';
|
||||
import intl from 'react-intl-universal';
|
||||
import * as R from 'ramda';
|
||||
import { first, chain } from 'lodash';
|
||||
import { first } from 'lodash';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { AppToaster } from '@/components';
|
||||
@@ -17,8 +17,6 @@ import {
|
||||
import {
|
||||
updateItemsEntriesTotal,
|
||||
ensureEntriesHaveEmptyLine,
|
||||
assignEntriesTaxAmount,
|
||||
aggregateItemEntriesTaxRates,
|
||||
} from '@/containers/Entries/utils';
|
||||
import { useCurrentOrganization } from '@/hooks/state';
|
||||
import {
|
||||
@@ -26,7 +24,6 @@ import {
|
||||
getEntriesTotal,
|
||||
} from '@/containers/Entries/utils';
|
||||
import { useBillFormContext } from './BillFormProvider';
|
||||
import { TaxType } from '@/interfaces/TaxRates';
|
||||
|
||||
export const MIN_LINES_NUMBER = 1;
|
||||
|
||||
@@ -40,9 +37,6 @@ export const defaultBillEntry = {
|
||||
description: '',
|
||||
amount: '',
|
||||
landed_cost: false,
|
||||
tax_rate_id: '',
|
||||
tax_rate: '',
|
||||
tax_amount: '',
|
||||
};
|
||||
|
||||
// Default bill.
|
||||
@@ -52,7 +46,6 @@ export const defaultBill = {
|
||||
bill_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||
due_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||
reference_no: '',
|
||||
inclusive_exclusive_tax: TaxType.Inclusive,
|
||||
note: '',
|
||||
open: '',
|
||||
branch_id: '',
|
||||
@@ -89,9 +82,6 @@ export const transformToEditForm = (bill) => {
|
||||
|
||||
return {
|
||||
...transformToForm(bill, defaultBill),
|
||||
inclusive_exclusive_tax: bill.is_inclusive_tax
|
||||
? TaxType.Inclusive
|
||||
: TaxType.Exclusive,
|
||||
entries,
|
||||
};
|
||||
};
|
||||
@@ -238,12 +228,11 @@ export const useSetPrimaryWarehouseToForm = () => {
|
||||
*/
|
||||
export const useBillTotals = () => {
|
||||
const {
|
||||
values: { currency_code: currencyCode },
|
||||
values: { entries, currency_code: currencyCode },
|
||||
} = useFormikContext();
|
||||
|
||||
// Retrieves the bill subtotal.
|
||||
const subtotal = useBillSubtotal();
|
||||
const total = useBillTotal();
|
||||
// Retrieves the bili entries total.
|
||||
const total = React.useMemo(() => getEntriesTotal(entries), [entries]);
|
||||
|
||||
// Retrieves the formatted total money.
|
||||
const formattedTotal = React.useMemo(
|
||||
@@ -252,8 +241,8 @@ export const useBillTotals = () => {
|
||||
);
|
||||
// Retrieves the formatted subtotal.
|
||||
const formattedSubtotal = React.useMemo(
|
||||
() => formattedAmount(subtotal, currencyCode, { money: false }),
|
||||
[subtotal, currencyCode],
|
||||
() => formattedAmount(total, currencyCode, { money: false }),
|
||||
[total, currencyCode],
|
||||
);
|
||||
// Retrieves the payment total.
|
||||
const paymentTotal = React.useMemo(() => 0, []);
|
||||
@@ -299,86 +288,3 @@ export const useBillIsForeignCustomer = () => {
|
||||
);
|
||||
return isForeignCustomer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Re-calculates the entries tax amount when editing.
|
||||
* @returns {string}
|
||||
*/
|
||||
export const composeEntriesOnEditInclusiveTax = (
|
||||
inclusiveExclusiveTax: string,
|
||||
entries,
|
||||
) => {
|
||||
return R.compose(
|
||||
assignEntriesTaxAmount(inclusiveExclusiveTax === 'inclusive'),
|
||||
)(entries);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retreives the bill aggregated tax rates.
|
||||
* @returns {Array}
|
||||
*/
|
||||
export const useBillAggregatedTaxRates = () => {
|
||||
const { values } = useFormikContext();
|
||||
const { taxRates } = useBillFormContext();
|
||||
|
||||
const aggregateTaxRates = React.useMemo(
|
||||
() => aggregateItemEntriesTaxRates(taxRates),
|
||||
[taxRates],
|
||||
);
|
||||
// Calculate the total tax amount of bill entries.
|
||||
return React.useMemo(() => {
|
||||
return aggregateTaxRates(values.entries);
|
||||
}, [aggregateTaxRates, values.entries]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the bill subtotal.
|
||||
* @returns {number}
|
||||
*/
|
||||
export const useBillSubtotal = () => {
|
||||
const {
|
||||
values: { entries },
|
||||
} = useFormikContext();
|
||||
|
||||
// Calculate the total due amount of bill entries.
|
||||
return React.useMemo(() => getEntriesTotal(entries), [entries]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retreives the bill total tax amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
export const useBillTotalTaxAmount = () => {
|
||||
const { values } = useFormikContext();
|
||||
|
||||
return React.useMemo(() => {
|
||||
return chain(values.entries)
|
||||
.filter((entry) => entry.tax_amount)
|
||||
.sumBy('tax_amount')
|
||||
.value();
|
||||
}, [values.entries]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Detarmines whether the tax is exclusive.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const useIsBillTaxExclusive = () => {
|
||||
const { values } = useFormikContext();
|
||||
|
||||
return values.inclusive_exclusive_tax === TaxType.Exclusive;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retreives the bill total.
|
||||
* @returns {number}
|
||||
*/
|
||||
export const useBillTotal = () => {
|
||||
const subtotal = useBillSubtotal();
|
||||
const totalTaxAmount = useBillTotalTaxAmount();
|
||||
const isExclusiveTax = useIsBillTaxExclusive();
|
||||
|
||||
return R.compose(R.when(R.always(isExclusiveTax), R.add(totalTaxAmount)))(
|
||||
subtotal,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,18 +9,20 @@ import {
|
||||
Tag,
|
||||
ProgressBar,
|
||||
} from '@blueprintjs/core';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import {
|
||||
FormatDateCell,
|
||||
FormattedMessage as T,
|
||||
Icon,
|
||||
If,
|
||||
Choose,
|
||||
Money,
|
||||
Can,
|
||||
} from '@/components';
|
||||
import {
|
||||
formattedAmount,
|
||||
safeCallback,
|
||||
isBlank,
|
||||
calculateStatus,
|
||||
} from '@/utils';
|
||||
import {
|
||||
@@ -28,7 +30,6 @@ import {
|
||||
PaymentMadeAction,
|
||||
AbilitySubject,
|
||||
} from '@/constants/abilityOption';
|
||||
import { CLASSES } from '@/constants';
|
||||
|
||||
/**
|
||||
* Actions menu.
|
||||
@@ -100,6 +101,17 @@ export function ActionsMenu({
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Amount accessor.
|
||||
*/
|
||||
export function AmountAccessor(bill) {
|
||||
return !isBlank(bill.amount) ? (
|
||||
<Money amount={bill.amount} currency={bill.currency_code} />
|
||||
) : (
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Status accessor.
|
||||
*/
|
||||
@@ -186,11 +198,11 @@ export function useBillsTableColumns() {
|
||||
{
|
||||
id: 'amount',
|
||||
Header: intl.get('amount'),
|
||||
accessor: 'total_formatted',
|
||||
accessor: AmountAccessor,
|
||||
width: 120,
|
||||
className: 'amount',
|
||||
align: 'right',
|
||||
clickable: true,
|
||||
className: clsx(CLASSES.FONT_BOLD),
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
|
||||
@@ -22,15 +22,14 @@ export default function VendorCreditNoteItemsEntriesEditor() {
|
||||
meta: { error, touched },
|
||||
}) => (
|
||||
<ItemsEntriesTable
|
||||
value={value}
|
||||
onChange={(entries) => {
|
||||
entries={value}
|
||||
onUpdateData={(entries) => {
|
||||
setFieldValue('entries', entries);
|
||||
}}
|
||||
items={items}
|
||||
errors={error}
|
||||
linesNumber={4}
|
||||
currencyCode={values.currency_code}
|
||||
enableTaxRates={false}
|
||||
/>
|
||||
)}
|
||||
</FastField>
|
||||
|
||||
@@ -26,15 +26,14 @@ export default function CreditNoteItemsEntriesEditorField() {
|
||||
meta: { error, touched },
|
||||
}) => (
|
||||
<ItemsEntriesTable
|
||||
value={value}
|
||||
onChange={(entries) => {
|
||||
entries={value}
|
||||
onUpdateData={(entries) => {
|
||||
setFieldValue('entries', entries);
|
||||
}}
|
||||
items={items}
|
||||
errors={error}
|
||||
linesNumber={4}
|
||||
currencyCode={values.currency_code}
|
||||
enableTaxRates={false}
|
||||
/>
|
||||
)}
|
||||
</FastField>
|
||||
|
||||
@@ -26,15 +26,14 @@ export default function EstimateFormItemsEntriesField() {
|
||||
meta: { error, touched },
|
||||
}) => (
|
||||
<ItemsEntriesTable
|
||||
value={value}
|
||||
onChange={(entries) => {
|
||||
entries={value}
|
||||
onUpdateData={(entries) => {
|
||||
setFieldValue('entries', entries);
|
||||
}}
|
||||
items={items}
|
||||
errors={error}
|
||||
linesNumber={4}
|
||||
currencyCode={values.currency_code}
|
||||
enableTaxRates={false}
|
||||
/>
|
||||
)}
|
||||
</FastField>
|
||||
|
||||
@@ -31,15 +31,6 @@ export const defaultEstimateEntry = {
|
||||
amount: '',
|
||||
};
|
||||
|
||||
const defaultEstimateEntryReq = {
|
||||
index: 0,
|
||||
item_id: '',
|
||||
rate: '',
|
||||
discount: '',
|
||||
quantity: '',
|
||||
description: '',
|
||||
};
|
||||
|
||||
export const defaultEstimate = {
|
||||
customer_id: '',
|
||||
estimate_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||
@@ -157,9 +148,7 @@ export const transfromsFormValuesToRequest = (values) => {
|
||||
...(values.estimate_number_manually && {
|
||||
estimate_number: values.estimate_number,
|
||||
}),
|
||||
entries: entries.map((entry) => ({
|
||||
...transformToForm(entry, defaultEstimateEntryReq),
|
||||
})),
|
||||
entries: entries.map((entry) => ({ ...omit(entry, ['amount']) })),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@ import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { InclusiveButtonOptions } from './constants';
|
||||
import { FFormGroup, FSelect } from '@/components';
|
||||
import { EntriesActionsBar } from '@/containers/Entries/EntriesActionBar';
|
||||
import { Box, FFormGroup, FSelect } from '@/components';
|
||||
import { composeEntriesOnEditInclusiveTax } from './utils';
|
||||
|
||||
/**
|
||||
@@ -13,9 +12,9 @@ import { composeEntriesOnEditInclusiveTax } from './utils';
|
||||
*/
|
||||
export function InvoiceFormActions() {
|
||||
return (
|
||||
<EntriesActionsBar>
|
||||
<InvoiceFormActionsRoot>
|
||||
<InvoiceExclusiveInclusiveSelect />
|
||||
</EntriesActionsBar>
|
||||
</InvoiceFormActionsRoot>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,7 +40,7 @@ export function InvoiceExclusiveInclusiveSelect(props) {
|
||||
label={'Amounts are'}
|
||||
inline={true}
|
||||
>
|
||||
<FSelect
|
||||
<InclusiveSelect
|
||||
name={'inclusive_exclusive_tax'}
|
||||
items={InclusiveButtonOptions}
|
||||
textAccessor={'label'}
|
||||
@@ -58,5 +57,23 @@ export function InvoiceExclusiveInclusiveSelect(props) {
|
||||
}
|
||||
|
||||
const InclusiveFormGroup = styled(FFormGroup)`
|
||||
margin-bottom: 0;
|
||||
margin-left: auto;
|
||||
|
||||
&.bp4-form-group.bp4-inline label.bp4-label {
|
||||
line-height: 1.25;
|
||||
opacity: 0.6;
|
||||
margin-right: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
const InclusiveSelect = styled(FSelect)`
|
||||
.bp4-button {
|
||||
padding-right: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
const InvoiceFormActionsRoot = styled(Box)`
|
||||
padding-bottom: 12px;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
TotalLineTextStyle,
|
||||
} from '@/components';
|
||||
import { useInvoiceAggregatedTaxRates, useInvoiceTotals } from './utils';
|
||||
import { TaxType } from '@/interfaces/TaxRates';
|
||||
|
||||
export function InvoiceFormFooterRight() {
|
||||
// Calculate the total due amount of invoice entries.
|
||||
@@ -33,7 +32,7 @@ export function InvoiceFormFooterRight() {
|
||||
<TotalLine
|
||||
title={
|
||||
<>
|
||||
{inclusive_exclusive_tax === TaxType.Inclusive
|
||||
{inclusive_exclusive_tax === 'inclusive'
|
||||
? 'Subtotal (Tax Inclusive)'
|
||||
: 'Subtotal'}
|
||||
</>
|
||||
|
||||
@@ -5,7 +5,6 @@ import ItemsEntriesTable from '@/containers/Entries/ItemsEntriesTable';
|
||||
import { useInvoiceFormContext } from './InvoiceFormProvider';
|
||||
import { entriesFieldShouldUpdate } from './utils';
|
||||
import { TaxType } from '@/interfaces/TaxRates';
|
||||
import { ITEM_TYPE } from '@/containers/Entries/utils';
|
||||
|
||||
/**
|
||||
* Invoice items entries editor field.
|
||||
@@ -32,7 +31,6 @@ export default function InvoiceItemsEntriesEditorField() {
|
||||
}}
|
||||
items={items}
|
||||
taxRates={taxRates}
|
||||
itemType={ITEM_TYPE.SELLABLE}
|
||||
errors={error}
|
||||
linesNumber={4}
|
||||
currencyCode={values.currency_code}
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { useFormikContext } from 'formik';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { omit, first, sumBy } from 'lodash';
|
||||
import {
|
||||
compose,
|
||||
transformToForm,
|
||||
repeatValue,
|
||||
formattedAmount,
|
||||
defaultFastFieldShouldUpdate,
|
||||
} from '@/utils';
|
||||
import { omit, first, keyBy, sumBy, groupBy } from 'lodash';
|
||||
import { compose, transformToForm, repeatValue } from '@/utils';
|
||||
import { useFormikContext } from 'formik';
|
||||
|
||||
import { formattedAmount, defaultFastFieldShouldUpdate } from '@/utils';
|
||||
import { ERROR } from '@/constants/errors';
|
||||
import { AppToaster } from '@/components';
|
||||
import { useCurrentOrganization } from '@/hooks/state';
|
||||
import {
|
||||
aggregateItemEntriesTaxRates,
|
||||
assignEntriesTaxAmount,
|
||||
getEntriesTotal,
|
||||
} from '@/containers/Entries/utils';
|
||||
@@ -332,14 +327,28 @@ export const useInvoiceAggregatedTaxRates = () => {
|
||||
const { values } = useFormikContext();
|
||||
const { taxRates } = useInvoiceFormContext();
|
||||
|
||||
const aggregateTaxRates = React.useMemo(
|
||||
() => aggregateItemEntriesTaxRates(taxRates),
|
||||
[taxRates],
|
||||
);
|
||||
const taxRatesById = useMemo(() => keyBy(taxRates, 'id'), [taxRates]);
|
||||
|
||||
// Calculate the total tax amount of invoice entries.
|
||||
return React.useMemo(() => {
|
||||
return aggregateTaxRates(values.entries);
|
||||
}, [aggregateTaxRates, values.entries]);
|
||||
const filteredEntries = values.entries.filter((e) => e.tax_rate_id);
|
||||
const groupedTaxRates = groupBy(filteredEntries, 'tax_rate_id');
|
||||
|
||||
return Object.keys(groupedTaxRates).map((taxRateId) => {
|
||||
const taxRate = taxRatesById[taxRateId];
|
||||
const taxRates = groupedTaxRates[taxRateId];
|
||||
const totalTaxAmount = sumBy(taxRates, 'tax_amount');
|
||||
const taxAmountFormatted = formattedAmount(totalTaxAmount, 'USD');
|
||||
|
||||
return {
|
||||
taxRateId,
|
||||
taxRate: taxRate.rate,
|
||||
label: `${taxRate.name} [${taxRate.rate}%]`,
|
||||
taxAmount: totalTaxAmount,
|
||||
taxAmountFormatted,
|
||||
};
|
||||
});
|
||||
}, [values.entries]);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,15 +19,14 @@ export default function ReceiptItemsEntriesEditor({ defaultReceipt }) {
|
||||
meta: { error, touched },
|
||||
}) => (
|
||||
<ItemsEntriesTable
|
||||
value={value}
|
||||
onChange={(entries) => {
|
||||
entries={value}
|
||||
onUpdateData={(entries) => {
|
||||
setFieldValue('entries', entries);
|
||||
}}
|
||||
items={items}
|
||||
errors={error}
|
||||
linesNumber={4}
|
||||
currencyCode={values.currency_code}
|
||||
enableTaxRates={false}
|
||||
/>
|
||||
)}
|
||||
</FastField>
|
||||
|
||||
@@ -32,15 +32,6 @@ export const defaultReceiptEntry = {
|
||||
amount: '',
|
||||
};
|
||||
|
||||
const defaultReceiptEntryReq = {
|
||||
index: 0,
|
||||
item_id: '',
|
||||
rate: '',
|
||||
discount: '',
|
||||
quantity: '',
|
||||
description: '',
|
||||
};
|
||||
|
||||
export const defaultReceipt = {
|
||||
customer_id: '',
|
||||
deposit_account_id: '',
|
||||
@@ -149,9 +140,7 @@ export const transformFormValuesToRequest = (values) => {
|
||||
...(values.receipt_number_manually && {
|
||||
receipt_number: values.receipt_number,
|
||||
}),
|
||||
entries: entries.map((entry) => ({
|
||||
...transformToForm(entry, defaultReceiptEntryReq),
|
||||
})),
|
||||
entries: entries.map((entry) => ({ ...omit(entry, ['amount']) })),
|
||||
closed: false,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { DialogContent } from '@/components';
|
||||
import { useTaxRate } from '@/hooks/query/taxRates';
|
||||
import { useTaxRate, useTaxRates } from '@/hooks/query/taxRates';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
const TaxRateFormDialogContext = React.createContext();
|
||||
|
||||
@@ -59,8 +59,6 @@ export function useEditTaxRate(props) {
|
||||
onSuccess: (res, id) => {
|
||||
commonInvalidateQueries(queryClient);
|
||||
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES, id]);
|
||||
queryClient.invalidateQueries(QUERY_TYPES.ITEM);
|
||||
queryClient.invalidateQueries(QUERY_TYPES.ITEMS);
|
||||
},
|
||||
...props,
|
||||
},
|
||||
|
||||
@@ -334,8 +334,6 @@
|
||||
"currency_name_": "Currency name",
|
||||
"cost_account_id": "Cost account",
|
||||
"sell_account_id": "Sell account",
|
||||
"item.details.sell_tax_rate": "Sell Tax Rate",
|
||||
"item.details.purchase_tax_rate": "Purchase Tax Rate",
|
||||
"item_type_": "Item type",
|
||||
"item_name_": "Item name",
|
||||
"organization_industry_": "Organization industry",
|
||||
|
||||
@@ -44,8 +44,7 @@ $sidebar-submenu-item-bg-color: rgba(255, 255, 255, 0.2);
|
||||
|
||||
$form-check-input-checked-color: #fff;
|
||||
$form-check-input-checked-bg-color: $blue1;
|
||||
$form-check-input-checked-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 16 16' enable-background='new 0 0 16 16' xml:space='preserve'><g id='small_tick_1_'><g><path fill='#{$form-check-input-checked-color}' fill-rule='evenodd' clip-rule='evenodd' d='M12,5c-0.28,0-0.53,0.11-0.71,0.29L7,9.59L4.71,7.29C4.53,7.11,4.28,7,4,7C3.45,7,3,7.45,3,8c0,0.28,0.11,0.53,0.29,0.71l3,3C6.47,11.89,6.72,12,7,12s0.53-0.11,0.71-0.29l5-5C12.89,6.53,13,6.28,13,6C13,5.45,12.55,5,12,5z'/></g></g></svg>") !default;
|
||||
$form-check-input-indeterminate-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 16 16' enable-background='new 0 0 16 16' xml:space='preserve'><g id='small_tick_1_'><g><path fill='#{$form-check-input-checked-color}' fill-rule='evenodd' clip-rule='evenodd' d='M11,7H5C4.45,7,4,7.45,4,8c0,0.55,0.45,1,1,1h6c0.55,0,1-0.45,1-1C12,7.45,11.55,7,11,7z'/></g></g></svg>") !default;
|
||||
$form-check-input-checked-bg-image: "" !default;
|
||||
|
||||
// z-indexs
|
||||
$zindex-dashboard-splash-screen: 39;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import 'src/style/_base.scss';
|
||||
@import 'src/style/Base.scss';
|
||||
|
||||
.bigcapital-datatable {
|
||||
display: block;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../_base.scss';
|
||||
@import '../../Base.scss';
|
||||
|
||||
// Dialog
|
||||
.#{$ns}-dialog{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../_base.scss';
|
||||
@import '../Base.scss';
|
||||
|
||||
.bp4-drawer {
|
||||
.bp4-drawer-header {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../_base.scss';
|
||||
@import '../../../Base.scss';
|
||||
|
||||
.view-detail-drawer {
|
||||
.bp4-tabs {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import 'src/style/_base.scss';
|
||||
@import 'src/style/Base.scss';
|
||||
|
||||
.sidebar {
|
||||
background: $sidebar-background;
|
||||
|
||||
@@ -21,9 +21,12 @@ label.bp4-label {
|
||||
.required {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.bp4-form-group.bp4-inline & {
|
||||
margin: 0 10px 0 0;
|
||||
line-height: 30px;
|
||||
line-height: 1.6;
|
||||
padding-top: calc(0.3rem + 1px);
|
||||
padding-bottom: calc(0.3rem + 1px);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../_base.scss';
|
||||
@import '../../Base.scss';
|
||||
|
||||
// Account Form Dialog.
|
||||
// ----------------------------
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../_base.scss';
|
||||
@import '../../Base.scss';
|
||||
|
||||
.dashboard__insider--accounts-chart {
|
||||
.bigcapital-datatable {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../_base.scss';
|
||||
@import '../../Base.scss';
|
||||
|
||||
.page-form--customer {
|
||||
$self: '.page-form';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../_base.scss';
|
||||
@import '../../Base.scss';
|
||||
$dashboard-views-bar-height: 44px;
|
||||
|
||||
.dashboard {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../_base.scss';
|
||||
@import '../../Base.scss';
|
||||
|
||||
.dashboard-content--preferences {
|
||||
flex-direction: row;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../_base.scss';
|
||||
@import '../../Base.scss';
|
||||
|
||||
// Preferences sidebar.
|
||||
// -----------------------------
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../_base.scss';
|
||||
@import '../../Base.scss';
|
||||
|
||||
// Preferences topbar.
|
||||
// -----------------------------
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../_base.scss";
|
||||
@import "../../Base.scss";
|
||||
|
||||
.setup-page {
|
||||
max-width: 1600px;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../_base.scss';
|
||||
@import '../../Base.scss';
|
||||
|
||||
.page-form--vendor {
|
||||
$self: '.page-form';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../_base.scss';
|
||||
@import '../../Base.scss';
|
||||
|
||||
body.page-vendor-new,
|
||||
body.page-vendor-edit{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"installCommand": "pnpm install",
|
||||
"buildCommand": "CI='' pnpm run build:webapp",
|
||||
"installCommand": "npm install && npm run bootstrap",
|
||||
"buildCommand": "CI='' npm run build:webapp",
|
||||
"outputDirectory": "packages/webapp/build",
|
||||
"rewrites": [{
|
||||
"source": "/api/:slug*",
|
||||
|
||||
Reference in New Issue
Block a user