feat: more resources support importing

This commit is contained in:
Ahmed Bouhuolia
2024-04-05 02:00:12 +02:00
parent 3851d34ba4
commit dd9098bdc1
46 changed files with 1510 additions and 149 deletions

View File

@@ -244,6 +244,7 @@
"account.field.active": "Active", "account.field.active": "Active",
"account.field.currency": "Currency", "account.field.currency": "Currency",
"account.field.balance": "Balance", "account.field.balance": "Balance",
"account.field.parent_account": "Parent Account",
"account.field.created_at": "Created at", "account.field.created_at": "Created at",
"item.field.type": "Item Type", "item.field.type": "Item Type",
"item.field.type.inventory": "Inventory", "item.field.type.inventory": "Inventory",
@@ -277,8 +278,14 @@
"invoice.field.invoice_message": "Invoice message", "invoice.field.invoice_message": "Invoice message",
"invoice.field.terms_conditions": "Terms & conditions", "invoice.field.terms_conditions": "Terms & conditions",
"invoice.field.amount": "Amount", "invoice.field.amount": "Amount",
"invoice.field.exchange_rate": "Exchange Rate",
"invoice.field.payment_amount": "Payment amount", "invoice.field.payment_amount": "Payment amount",
"invoice.field.due_amount": "Due amount", "invoice.field.due_amount": "Due amount",
"invoice.field.delivered": "Delivered",
"invoice.field.item_name": "Item Name",
"invoice.field.rate": "Rate",
"invoice.field.quantity": "Quantity",
"invoice.field.description": "Description",
"invoice.field.status": "Status", "invoice.field.status": "Status",
"invoice.field.status.paid": "Paid", "invoice.field.status.paid": "Paid",
"invoice.field.status.partially-paid": "Partially paid", "invoice.field.status.partially-paid": "Partially paid",
@@ -287,6 +294,7 @@
"invoice.field.status.delivered": "Delivered", "invoice.field.status.delivered": "Delivered",
"invoice.field.status.draft": "Draft", "invoice.field.status.draft": "Draft",
"invoice.field.created_at": "Created at", "invoice.field.created_at": "Created at",
"invoice.field.currency": "Currency",
"estimate.field.amount": "Amount", "estimate.field.amount": "Amount",
"estimate.field.estimate_number": "Estimate number", "estimate.field.estimate_number": "Estimate number",
"estimate.field.customer": "Customer", "estimate.field.customer": "Customer",
@@ -311,12 +319,17 @@
"payment_receive.field.created_at": "Created at", "payment_receive.field.created_at": "Created at",
"bill_payment.field.vendor": "Vendor", "bill_payment.field.vendor": "Vendor",
"bill_payment.field.amount": "Amount", "bill_payment.field.amount": "Amount",
"bill_payment.field.due_amount": "Due amount", "bill_payment.field.due_amount": "Due Amount",
"bill_payment.field.payment_account": "Payment account", "bill_payment.field.payment_account": "Payment Account",
"bill_payment.field.payment_number": "Payment number", "bill_payment.field.payment_number": "Payment No.",
"bill_payment.field.payment_date": "Payment date", "bill_payment.field.payment_date": "Payment Date",
"bill_payment.field.reference_no": "Reference No.", "bill_payment.field.reference_no": "Reference No.",
"bill_payment.field.description": "Description", "bill_payment.field.description": "Description",
"bill_payment.field.exchange_rate": "Exchange Rate",
"bill_payment.field.statement": "Statement",
"bill_payment.field.entries.bill": "Bill No.",
"bill_payment.field.entries.payment_amount": "Payment Amount",
"bill_payment.field.reference": "Reference No.",
"bill_payment.field.created_at": "Created at", "bill_payment.field.created_at": "Created at",
"bill.field.vendor": "Vendor", "bill.field.vendor": "Vendor",
"bill.field.bill_number": "Bill number", "bill.field.bill_number": "Bill number",
@@ -344,12 +357,18 @@
"inventory_adjustment.field.description": "Description", "inventory_adjustment.field.description": "Description",
"inventory_adjustment.field.published_at": "Published at", "inventory_adjustment.field.published_at": "Published at",
"inventory_adjustment.field.created_at": "Created at", "inventory_adjustment.field.created_at": "Created at",
"expense.field.payment_date": "Payment date", "expense.field.payment_date": "Payment Date",
"expense.field.payment_account": "Payment account", "expense.field.payment_account": "Payment Account",
"expense.field.amount": "Amount", "expense.field.amount": "Amount",
"expense.field.currency_code": "Currency",
"expense.field.exchange_rate": "Exchange Rate",
"expense.field.reference_no": "Reference No.", "expense.field.reference_no": "Reference No.",
"expense.field.description": "Description", "expense.field.description": "Description",
"expense.field.line_description": "Line Description",
"expense.field.published": "Published", "expense.field.published": "Published",
"expense.field.categories": "Categories",
"expense.field.expense_account": "Expense Account",
"expense.field.publish": "Publish",
"expense.field.status": "Status", "expense.field.status": "Status",
"expense.field.status.draft": "Draft", "expense.field.status.draft": "Draft",
"expense.field.status.published": "Published", "expense.field.status.published": "Published",
@@ -414,6 +433,8 @@
"vendor.field.status.unpaid": "Unpaid", "vendor.field.status.unpaid": "Unpaid",
"Invoice write-off": "Invoice write-off", "Invoice write-off": "Invoice write-off",
"transaction_type.credit_note": "Credit note", "transaction_type.credit_note": "Credit note",
"transaction_type.refund_credit_note": "Refund credit note", "transaction_type.refund_credit_note": "Refund credit note",
"transaction_type.vendor_credit": "Vendor credit", "transaction_type.vendor_credit": "Vendor credit",

View File

@@ -317,7 +317,7 @@ export class ExpensesController extends BaseController {
const { tenantId } = req; const { tenantId } = req;
const filter = { const filter = {
sortOrder: 'desc', sortOrder: 'desc',
columnSortBy: 'created_at', columnSortBy: 'createdAt',
page: 1, page: 1,
pageSize: 12, pageSize: 12,
...this.matchedQueryData(req), ...this.matchedQueryData(req),

View File

@@ -328,7 +328,7 @@ export default class ManualJournalsController extends BaseController {
const { tenantId } = req; const { tenantId } = req;
const filter = { const filter = {
sortOrder: 'desc', sortOrder: 'desc',
columnSortBy: 'created_at', columnSortBy: 'createdAt',
page: 1, page: 1,
pageSize: 12, pageSize: 12,
...this.matchedQueryData(req), ...this.matchedQueryData(req),

View File

@@ -459,7 +459,7 @@ export default class SaleInvoicesController extends BaseController {
const { tenantId } = req; const { tenantId } = req;
const filter = { const filter = {
sortOrder: 'desc', sortOrder: 'desc',
columnSortBy: 'created_at', columnSortBy: 'createdAt',
page: 1, page: 1,
pageSize: 12, pageSize: 12,
...this.matchedQueryData(req), ...this.matchedQueryData(req),

View File

@@ -119,6 +119,49 @@ export default {
exportable: true, exportable: true,
}, },
}, },
fields2: {
name: {
name: 'account.field.name',
fieldType: 'text',
unique: true,
required: true,
},
description: {
name: 'account.field.description',
fieldType: 'text',
},
code: {
name: 'account.field.code',
fieldType: 'text',
minLength: 3,
maxLength: 6,
unique: true,
importHint: 'Unique number to identify the account.',
},
accountType: {
name: 'account.field.type',
fieldType: 'enumeration',
options: ACCOUNT_TYPES.map((accountType) => ({
label: accountType.label,
key: accountType.key,
})),
required: true,
},
active: {
name: 'account.field.active',
fieldType: 'boolean',
},
currencyCode: {
name: 'account.field.currency',
fieldType: 'text',
},
parentAccountId: {
name: 'account.field.parent_account',
fieldType: 'relation',
relationModel: 'Account',
relationImportMatch: ['name', 'code'],
},
},
}; };
/** /**

View File

@@ -144,7 +144,7 @@ export default {
required: true, required: true,
}, },
description: { description: {
name: 'Description', name: 'Line Description',
fieldType: 'text', fieldType: 'text',
}, },
}, },

View File

@@ -4,6 +4,10 @@ export default {
sortOrder: 'DESC', sortOrder: 'DESC',
sortField: 'bill_date', sortField: 'bill_date',
}, },
importable: true,
importAggregator: 'group',
importAggregateOn: 'entries',
importAggregateBy: 'paymentNumber',
fields: { fields: {
vendor: { vendor: {
name: 'bill_payment.field.vendor', name: 'bill_payment.field.vendor',
@@ -63,4 +67,64 @@ export default {
fieldType: 'date', fieldType: 'date',
}, },
}, },
fields2: {
vendorId: {
name: 'bill_payment.field.vendor',
fieldType: 'relation',
relationModel: 'Contact',
relationImportMatch: ['displayName'],
required: true,
},
payment_date: {
name: 'bill_payment.field.payment_date',
fieldType: 'date',
required: true,
},
paymentNumber: {
name: 'bill_payment.field.payment_number',
fieldType: 'text',
unique: true,
},
paymentAccountId: {
name: 'bill_payment.field.payment_account',
fieldType: 'relation',
relationModel: 'Account',
relationImportMatch: ['name', 'code'],
required: true,
},
exchangeRate: {
name: 'bill_payment.field.exchange_rate',
fieldType: 'number',
},
statement: {
name: 'bill_payment.field.statement',
fieldType: 'text',
},
reference: {
name: 'bill_payment.field.reference',
fieldType: 'text',
},
entries: {
name: 'bill_payment.field.entries',
column: 'entries',
fieldType: 'collection',
collectionOf: 'object',
collectionMinLength: 1,
required: true,
fields: {
billId: {
name: 'bill_payment.field.entries.bill',
fieldType: 'relation',
relationModel: 'Bill',
relationImportMatch: 'billNumber',
required: true,
},
paymentAmount: {
name: 'bill_payment.field.entries.payment_amount',
fieldType: 'number',
required: true,
},
},
},
},
}; };

View File

@@ -212,6 +212,161 @@ export default {
fieldType: 'date', fieldType: 'date',
}, },
}, },
fields2: {
customerType: {
name: 'Customer Type',
fieldType: 'enumeration',
options: [
{ key: 'business', label: 'Business' },
{ key: 'individual', label: 'Individual' },
],
required: true,
},
firstName: {
name: 'customer.field.first_name',
column: 'first_name',
fieldType: 'text',
},
lastName: {
name: 'customer.field.last_name',
column: 'last_name',
fieldType: 'text',
},
displayName: {
name: 'customer.field.display_name',
column: 'display_name',
fieldType: 'text',
required: true,
},
email: {
name: 'customer.field.email',
column: 'email',
fieldType: 'text',
},
workPhone: {
name: 'customer.field.work_phone',
column: 'work_phone',
fieldType: 'text',
},
personalPhone: {
name: 'customer.field.personal_phone',
column: 'personal_phone',
fieldType: 'text',
},
companyName: {
name: 'customer.field.company_name',
column: 'company_name',
fieldType: 'text',
},
website: {
name: 'customer.field.website',
column: 'website',
fieldType: 'url',
},
openingBalance: {
name: 'customer.field.opening_balance',
column: 'opening_balance',
fieldType: 'number',
},
openingBalanceAt: {
name: 'customer.field.opening_balance_at',
column: 'opening_balance_at',
filterable: false,
fieldType: 'date',
},
openingBalanceExchangeRate: {
name: 'Opening Balance Ex. Rate',
column: 'opening_balance_exchange_rate',
fieldType: 'number',
},
currencyCode: {
name: 'customer.field.currency',
column: 'currency_code',
fieldType: 'text',
},
note: {
name: 'Note',
column: 'note',
fieldType: 'text',
},
active: {
name: 'Active',
column: 'active',
fieldType: 'boolean',
},
// Billing Address
billingAddress1: {
name: 'Billing Address 1',
column: 'billing_address1',
fieldType: 'text',
},
billingAddress2: {
name: 'Billing Address 2',
column: 'billing_address2',
fieldType: 'text',
},
billingAddressCity: {
name: 'Billing Address City',
column: 'billing_address_city',
fieldType: 'text',
},
billingAddressCountry: {
name: 'Billing Address Country',
column: 'billing_address_country',
fieldType: 'text',
},
billingAddressPostcode: {
name: 'Billing Address Postcode',
column: 'billing_address_postcode',
fieldType: 'text',
},
billingAddressState: {
name: 'Billing Address State',
column: 'billing_address_state',
fieldType: 'text',
},
billingAddressPhone: {
name: 'Billing Address Phone',
column: 'billing_address_phone',
fieldType: 'text',
},
// Shipping Address
shippingAddress1: {
name: 'Shipping Address 1',
column: 'shipping_address1',
fieldType: 'text',
},
shippingAddress2: {
name: 'Shipping Address 2',
column: 'shipping_address2',
fieldType: 'text',
},
shippingAddressCity: {
name: 'Shipping Address City',
column: 'shipping_address_city',
fieldType: 'text',
},
shippingAddressCountry: {
name: 'Shipping Address Country',
column: 'shipping_address_country',
fieldType: 'text',
},
shippingAddressPostcode: {
name: 'Shipping Address Postcode',
column: 'shipping_address_postcode',
fieldType: 'text',
},
shippingAddressPhone: {
name: 'Shipping Address Phone',
column: 'shipping_address_phone',
fieldType: 'text',
},
shippingAddressState: {
name: 'Shipping Address State',
column: 'shipping_address_state',
fieldType: 'text',
},
},
}; };
function statusFieldFilterQuery(query, role) { function statusFieldFilterQuery(query, role) {

View File

@@ -7,13 +7,14 @@ export default {
sortOrder: 'DESC', sortOrder: 'DESC',
sortField: 'name', sortField: 'name',
}, },
importable: true,
fields: { fields: {
'payment_date': { payment_date: {
name: 'expense.field.payment_date', name: 'expense.field.payment_date',
column: 'payment_date', column: 'payment_date',
fieldType: 'date', fieldType: 'date',
}, },
'payment_account': { payment_account: {
name: 'expense.field.payment_account', name: 'expense.field.payment_account',
column: 'payment_account_id', column: 'payment_account_id',
fieldType: 'relation', fieldType: 'relation',
@@ -24,27 +25,27 @@ export default {
relationEntityLabel: 'name', relationEntityLabel: 'name',
relationEntityKey: 'slug', relationEntityKey: 'slug',
}, },
'amount': { amount: {
name: 'expense.field.amount', name: 'expense.field.amount',
column: 'total_amount', column: 'total_amount',
fieldType: 'number', fieldType: 'number',
}, },
'reference_no': { reference_no: {
name: 'expense.field.reference_no', name: 'expense.field.reference_no',
column: 'reference_no', column: 'reference_no',
fieldType: 'text', fieldType: 'text',
}, },
'description': { description: {
name: 'expense.field.description', name: 'expense.field.description',
column: 'description', column: 'description',
fieldType: 'text', fieldType: 'text',
}, },
'published': { published: {
name: 'expense.field.published', name: 'expense.field.published',
column: 'published_at', column: 'published_at',
fieldType: 'date', fieldType: 'date',
}, },
'status': { status: {
name: 'expense.field.status', name: 'expense.field.status',
fieldType: 'enumeration', fieldType: 'enumeration',
options: [ options: [
@@ -54,12 +55,69 @@ export default {
filterCustomQuery: StatusFieldFilterQuery, filterCustomQuery: StatusFieldFilterQuery,
sortCustomQuery: StatusFieldSortQuery, sortCustomQuery: StatusFieldSortQuery,
}, },
'created_at': { createdAt: {
name: 'expense.field.created_at', name: 'expense.field.created_at',
column: 'created_at', column: 'created_at',
fieldType: 'date', fieldType: 'date',
}, },
}, },
fields2: {
paymentAccountId: {
name: 'expense.field.payment_account',
fieldType: 'relation',
relationModel: 'Account',
relationImportMatch: ['name', 'code'],
required: true,
},
referenceNo: {
name: 'expense.field.reference_no',
fieldType: 'text',
},
paymentDate: {
name: 'expense.field.payment_date',
fieldType: 'date',
required: true,
},
currencyCode: {
name: 'expense.field.currency_code',
fieldType: 'text',
},
exchangeRate: {
name: 'expense.field.exchange_rate',
fieldType: 'number',
},
description: {
name: 'expense.field.description',
fieldType: 'text',
},
categories: {
name: 'expense.field.categories',
fieldType: 'collection',
collectionOf: 'object',
fields: {
expenseAccountId: {
name: 'expense.field.expense_account',
fieldType: 'relation',
relationModel: 'Account',
relationImportMatch: ['name', 'code'],
required: true,
},
amount: {
name: 'expense.field.amount',
fieldType: 'number',
required: true,
},
description: {
name: 'expense.field.line_description',
fieldType: 'text',
},
},
},
publish: {
name: 'expense.field.publish',
fieldType: 'boolean',
},
},
}; };
function StatusFieldFilterQuery(query, role) { function StatusFieldFilterQuery(query, role) {

View File

@@ -5,6 +5,96 @@ export default {
sortField: 'name', sortField: 'name',
sortOrder: 'DESC', sortOrder: 'DESC',
}, },
fields2: {
type: {
name: 'item.field.type',
fieldType: 'enumeration',
options: [
{ key: 'inventory', label: 'item.field.type.inventory' },
{ key: 'service', label: 'item.field.type.service' },
{ key: 'non-inventory', label: 'item.field.type.non-inventory' },
],
required: true,
},
name: {
name: 'item.field.name',
fieldType: 'text',
required: true,
unique: true,
},
code: {
name: 'item.field.code',
fieldType: 'text',
unique: true,
},
sellable: {
name: 'item.field.sellable',
fieldType: 'boolean',
required: true,
},
purchasable: {
name: 'item.field.purchasable',
fieldType: 'boolean',
required: true,
},
sellPrice: {
name: 'item.field.sell_price',
fieldType: 'number',
required: true,
},
costPrice: {
name: 'item.field.cost_price',
fieldType: 'number',
required: true,
},
costAccount: {
name: 'item.field.cost_account',
fieldType: 'relation',
relationModel: 'Account',
relationImportMatch: ['name', 'code'],
required: true,
},
sellAccount: {
name: 'item.field.sell_account',
fieldType: 'relation',
relationModel: 'Account',
relationImportMatch: ['name', 'code'],
required: true,
},
inventoryAccount: {
name: 'item.field.inventory_account',
fieldType: 'relation',
relationModel: 'Account',
relationImportMatch: ['name', 'code'],
required: true,
},
sellDescription: {
name: 'Sell description',
column: 'sell_description',
fieldType: 'text',
},
purchaseDescription: {
name: 'Purchase description',
column: 'purchase_description',
fieldType: 'text',
importable: true,
},
note: {
name: 'item.field.note',
fieldType: 'text',
},
category: {
name: 'item.field.category',
fieldType: 'relation',
relationModel: 'ItemCategory',
relationImportMatch: 'name',
},
active: {
name: 'item.field.active',
fieldType: 'boolean',
importable: true,
},
},
fields: { fields: {
type: { type: {
name: 'item.field.type', name: 'item.field.type',
@@ -31,7 +121,6 @@ export default {
column: 'code', column: 'code',
fieldType: 'text', fieldType: 'text',
importable: true, importable: true,
}, },
sellable: { sellable: {
name: 'item.field.sellable', name: 'item.field.sellable',
@@ -66,13 +155,11 @@ export default {
column: 'cost_account_id', column: 'cost_account_id',
fieldType: 'relation', fieldType: 'relation',
relationType: 'enumeration',
relationKey: 'costAccount', relationKey: 'costAccount',
relationEntityLabel: 'name', relationEntityLabel: 'name',
relationEntityKey: 'slug', relationEntityKey: 'slug',
dataTransferObjectKey: 'costAccountId',
importableRelationLabel: ['name', 'code'], importableRelationLabel: ['name', 'code'],
importable: true, importable: true,
required: true, required: true,
@@ -82,8 +169,8 @@ export default {
column: 'sell_account_id', column: 'sell_account_id',
fieldType: 'relation', fieldType: 'relation',
relationType: 'enumeration',
relationKey: 'sellAccount', relationKey: 'sellAccount',
relationType: 'one-to-many',
relationEntityLabel: 'name', relationEntityLabel: 'name',
relationEntityKey: 'slug', relationEntityKey: 'slug',
@@ -98,7 +185,6 @@ export default {
column: 'inventory_account_id', column: 'inventory_account_id',
fieldType: 'relation', fieldType: 'relation',
relationType: 'enumeration',
relationKey: 'inventoryAccount', relationKey: 'inventoryAccount',
relationEntityLabel: 'name', relationEntityLabel: 'name',
@@ -137,7 +223,6 @@ export default {
column: 'category_id', column: 'category_id',
fieldType: 'relation', fieldType: 'relation',
relationType: 'enumeration',
relationKey: 'category', relationKey: 'category',
relationEntityLabel: 'name', relationEntityLabel: 'name',

View File

@@ -30,4 +30,16 @@ export default {
columnType: 'date', columnType: 'date',
}, },
}, },
fields2: {
name: {
name: 'item_category.field.name',
column: 'name',
fieldType: 'text',
},
description: {
name: 'item_category.field.description',
column: 'description',
fieldType: 'text',
},
},
}; };

View File

@@ -1,15 +1,36 @@
import { get } from 'lodash'; import { get } from 'lodash';
import { IModelMeta, IModelMetaField, IModelMetaDefaultSort } from '@/interfaces'; import {
IModelMeta,
IModelMetaField,
IModelMetaDefaultSort,
} from '@/interfaces';
const defaultModelMeta = {
fields: {},
fields2: {},
};
export default (Model) => export default (Model) =>
class ModelSettings extends Model { class ModelSettings extends Model {
/** /**
* *
* @returns {IModelMeta}
*/ */
static get meta(): IModelMeta { static get meta(): IModelMeta {
throw new Error(''); throw new Error('');
} }
/**
* Parsed meta merged with default emta.
* @returns {IModelMeta}
*/
static get parsedMeta(): IModelMeta {
return {
...defaultModelMeta,
...this.meta,
};
}
/** /**
* Retrieve specific model field meta of the given field key. * Retrieve specific model field meta of the given field key.
* @param {string} key * @param {string} key
@@ -22,12 +43,12 @@ export default (Model) =>
} }
/** /**
* Retrieve the specific model meta. * Retrieves the specific model meta.
* @param {string} key * @param {string} key
* @returns * @returns
*/ */
public static getMeta(key?: string) { public static getMeta(key?: string) {
return key ? get(this.meta, key): this.meta; return key ? get(this.parsedMeta, key) : this.parsedMeta;
} }
/** /**

View File

@@ -4,18 +4,22 @@ export default {
sortOrder: 'DESC', sortOrder: 'DESC',
sortField: 'estimate_date', sortField: 'estimate_date',
}, },
importable: true,
importAggregator: 'group',
importAggregateOn: 'entries',
importAggregateBy: 'estimateNumber',
fields: { fields: {
'amount': { amount: {
name: 'estimate.field.amount', name: 'estimate.field.amount',
column: 'amount', column: 'amount',
fieldType: 'number', fieldType: 'number',
}, },
'estimate_number': { estimate_number: {
name: 'estimate.field.estimate_number', name: 'estimate.field.estimate_number',
column: 'estimate_number', column: 'estimate_number',
fieldType: 'text', fieldType: 'text',
}, },
'customer': { customer: {
name: 'estimate.field.customer', name: 'estimate.field.customer',
column: 'customer_id', column: 'customer_id',
fieldType: 'relation', fieldType: 'relation',
@@ -26,32 +30,32 @@ export default {
relationEntityLabel: 'display_name', relationEntityLabel: 'display_name',
relationEntityKey: 'id', relationEntityKey: 'id',
}, },
'estimate_date': { estimate_date: {
name: 'estimate.field.estimate_date', name: 'estimate.field.estimate_date',
column: 'estimate_date', column: 'estimate_date',
fieldType: 'date', fieldType: 'date',
}, },
'expiration_date': { expiration_date: {
name: 'estimate.field.expiration_date', name: 'estimate.field.expiration_date',
column: 'expiration_date', column: 'expiration_date',
fieldType: 'date', fieldType: 'date',
}, },
'reference_no': { reference_no: {
name: 'estimate.field.reference_no', name: 'estimate.field.reference_no',
column: 'reference', column: 'reference',
fieldType: 'text', fieldType: 'text',
}, },
'note': { note: {
name: 'estimate.field.note', name: 'estimate.field.note',
column: 'note', column: 'note',
fieldType: 'text', fieldType: 'text',
}, },
'terms_conditions': { terms_conditions: {
name: 'estimate.field.terms_conditions', name: 'estimate.field.terms_conditions',
column: 'terms_conditions', column: 'terms_conditions',
fieldType: 'text', fieldType: 'text',
}, },
'status': { status: {
name: 'estimate.field.status', name: 'estimate.field.status',
fieldType: 'enumeration', fieldType: 'enumeration',
options: [ options: [
@@ -63,12 +67,89 @@ export default {
filterCustomQuery: StatusFieldFilterQuery, filterCustomQuery: StatusFieldFilterQuery,
sortCustomQuery: StatusFieldSortQuery, sortCustomQuery: StatusFieldSortQuery,
}, },
'created_at': { created_at: {
name: 'estimate.field.created_at', name: 'estimate.field.created_at',
column: 'created_at', column: 'created_at',
columnType: 'date', columnType: 'date',
}, },
}, },
fields2: {
customerId: {
name: 'Customer',
fieldType: 'relation',
relationModel: 'Contact',
relationImportMatch: ['displayName'],
required: true,
},
estimateDate: {
name: 'Estimate Date',
fieldType: 'date',
required: true,
},
expirationDate: {
name: 'Expiration Date',
fieldType: 'date',
required: true,
},
estimateNumber: {
name: 'Estimate No.',
fieldType: 'text',
},
reference: {
name: 'Reference No.',
fieldType: 'text',
},
exchangeRate: {
name: 'Exchange Rate',
fieldType: 'number',
},
currencyCode: {
name: 'Currency',
fieldType: 'text',
},
note: {
name: 'Note',
fieldType: 'text',
},
termsConditions: {
name: 'Terms & Conditions',
fieldType: 'text',
},
delivered: {
name: 'Delivered',
type: 'boolean',
},
entries: {
name: 'Entries',
fieldType: 'collection',
collectionOf: 'object',
collectionMinLength: 1,
required: true,
fields: {
itemId: {
name: 'invoice.field.item_name',
fieldType: 'relation',
relationModel: 'Item',
relationImportMatch: ['name', 'code'],
required: true,
},
rate: {
name: 'invoice.field.rate',
fieldType: 'number',
required: true,
},
quantity: {
name: 'invoice.field.quantity',
fieldType: 'number',
required: true,
},
description: {
name: 'Line Description',
fieldType: 'text',
},
},
},
},
}; };
function StatusFieldSortQuery(query, role) { function StatusFieldSortQuery(query, role) {

View File

@@ -2,8 +2,12 @@ export default {
defaultFilterField: 'customer', defaultFilterField: 'customer',
defaultSort: { defaultSort: {
sortOrder: 'DESC', sortOrder: 'DESC',
sortField: 'created_at', sortField: 'createdAt',
}, },
importable: true,
importAggregator: 'group',
importAggregateOn: 'entries',
importAggregateBy: 'invoiceNo',
fields: { fields: {
customer: { customer: {
name: 'invoice.field.customer', name: 'invoice.field.customer',
@@ -77,12 +81,89 @@ export default {
filterCustomQuery: StatusFieldFilterQuery, filterCustomQuery: StatusFieldFilterQuery,
sortCustomQuery: StatusFieldSortQuery, sortCustomQuery: StatusFieldSortQuery,
}, },
created_at: { createdAt: {
name: 'invoice.field.created_at', name: 'invoice.field.created_at',
column: 'created_at', column: 'created_at',
fieldType: 'date', fieldType: 'date',
}, },
}, },
fields2: {
invoiceDate: {
name: 'invoice.field.invoice_date',
fieldType: 'date',
required: true,
},
dueDate: {
name: 'invoice.field.due_date',
fieldType: 'date',
required: true,
},
referenceNo: {
name: 'invoice.field.reference_no',
fieldType: 'text',
},
invoiceNo: {
name: 'invoice.field.invoice_no',
fieldType: 'text',
},
customerId: {
name: 'invoice.field.customer',
fieldType: 'relation',
relationModel: 'Contact',
relationImportMatch: 'displayName',
required: true,
},
exchangeRate: {
name: 'invoice.field.exchange_rate',
fieldType: 'number',
},
currencyCode: {
name: 'invoice.field.currency',
fieldType: 'text',
},
invoiceMessage: {
name: 'invoice.field.invoice_message',
fieldType: 'text',
},
termsConditions: {
name: 'invoice.field.terms_conditions',
fieldType: 'text',
},
entries: {
name: 'invoice.field.entries',
fieldType: 'collection',
collectionOf: 'object',
collectionMinLength: 1,
required: true,
fields: {
itemId: {
name: 'invoice.field.item_name',
fieldType: 'relation',
relationModel: 'Item',
relationImportMatch: ['name', 'code'],
required: true,
},
rate: {
name: 'invoice.field.rate',
fieldType: 'number',
required: true,
},
quantity: {
name: 'invoice.field.quantity',
fieldType: 'number',
required: true,
},
description: {
name: 'invoice.field.description',
fieldType: 'text',
},
},
},
delivered: {
name: 'invoice.field.delivered',
fieldType: 'boolean',
},
},
}; };
/** /**

View File

@@ -51,4 +51,33 @@ export default {
importable: false, importable: false,
}, },
}, },
fields2: {
date: {
name: 'Date',
fieldType: 'date',
required: true,
},
payee: {
name: 'Payee',
column: 'payee',
fieldType: 'text',
},
description: {
name: 'Description',
column: 'description',
fieldType: 'text',
},
referenceNo: {
name: 'Reference No.',
column: 'reference_no',
fieldType: 'text',
},
amount: {
name: 'Amount',
column: 'Amount',
fieldType: 'numeric',
required: true,
},
}
}; };

View File

@@ -206,4 +206,149 @@ export default {
fieldType: 'date', fieldType: 'date',
}, },
}, },
fields2: {
firstName: {
name: 'vendor.field.first_name',
column: 'first_name',
fieldType: 'text',
},
lastName: {
name: 'vendor.field.last_name',
column: 'last_name',
fieldType: 'text',
},
displayName: {
name: 'vendor.field.display_name',
column: 'display_name',
fieldType: 'text',
required: true,
},
email: {
name: 'vendor.field.email',
column: 'email',
fieldType: 'text',
},
workPhone: {
name: 'vendor.field.work_phone',
column: 'work_phone',
fieldType: 'text',
},
personalPhone: {
name: 'vendor.field.personal_phone',
column: 'personal_phone',
fieldType: 'text',
},
companyName: {
name: 'vendor.field.company_name',
column: 'company_name',
fieldType: 'text',
},
website: {
name: 'vendor.field.website',
column: 'website',
fieldType: 'text',
},
openingBalance: {
name: 'vendor.field.opening_balance',
column: 'opening_balance',
fieldType: 'number',
},
openingBalanceAt: {
name: 'vendor.field.opening_balance_at',
column: 'opening_balance_at',
fieldType: 'date',
},
openingBalanceExchangeRate: {
name: 'Opening Balance Ex. Rate',
column: 'opening_balance_exchange_rate',
fieldType: 'number',
},
currencyCode: {
name: 'vendor.field.currency',
column: 'currency_code',
fieldType: 'text',
},
note: {
name: 'Note',
column: 'note',
fieldType: 'text',
},
active: {
name: 'Active',
column: 'active',
fieldType: 'boolean',
},
// Billing Address
billingAddress1: {
name: 'Billing Address 1',
column: 'billing_address1',
fieldType: 'text',
},
billingAddress2: {
name: 'Billing Address 2',
column: 'billing_address2',
fieldType: 'text',
},
billingAddressCity: {
name: 'Billing Address City',
column: 'billing_address_city',
fieldType: 'text',
},
billingAddressCountry: {
name: 'Billing Address Country',
column: 'billing_address_country',
fieldType: 'text',
},
billingAddressPostcode: {
name: 'Billing Address Postcode',
column: 'billing_address_postcode',
fieldType: 'text',
},
billingAddressState: {
name: 'Billing Address State',
column: 'billing_address_state',
fieldType: 'text',
},
billingAddressPhone: {
name: 'Billing Address Phone',
column: 'billing_address_phone',
fieldType: 'text',
},
// Shipping Address
shippingAddress1: {
name: 'Shipping Address 1',
column: 'shipping_address1',
fieldType: 'text',
},
shippingAddress2: {
name: 'Shipping Address 2',
column: 'shipping_address2',
fieldType: 'text',
},
shippingAddressCity: {
name: 'Shipping Address City',
column: 'shipping_address_city',
fieldType: 'text',
},
shippingAddressCountry: {
name: 'Shipping Address Country',
column: 'shipping_address_country',
fieldType: 'text',
},
shippingAddressPostcode: {
name: 'Shipping Address Postcode',
column: 'shipping_address_postcode',
fieldType: 'text',
},
shippingAddressState: {
name: 'Shipping Address State',
column: 'shipping_address_state',
fieldType: 'text',
},
shippingAddressPhone: {
name: 'Shipping Address Phone',
column: 'shipping_address_phone',
fieldType: 'text',
},
},
}; };

View File

@@ -12,7 +12,7 @@ export const VendorsSampleData = [
"Opening Balance At": "2022-02-02", "Opening Balance At": "2022-02-02",
"Opening Balance Ex. Rate": 2, "Opening Balance Ex. Rate": 2,
"Currency": "LYD", "Currency": "LYD",
"Active": "F", "Active": "T",
"Note": "Doloribus autem optio temporibus dolores mollitia sit.", "Note": "Doloribus autem optio temporibus dolores mollitia sit.",
"Billing Address 1": "862 Jessika Well", "Billing Address 1": "862 Jessika Well",
"Billing Address 2": "1091 Dorthy Mount", "Billing Address 2": "1091 Dorthy Mount",
@@ -42,7 +42,7 @@ export const VendorsSampleData = [
"Opening Balance At": "2022-02-02", "Opening Balance At": "2022-02-02",
"Opening Balance Ex. Rate": 2, "Opening Balance Ex. Rate": 2,
"Currency": "LYD", "Currency": "LYD",
"Active": "F", "Active": "T",
"Note": "Doloribus dolore dolor dicta vitae in fugit nisi quibusdam.", "Note": "Doloribus dolore dolor dicta vitae in fugit nisi quibusdam.",
"Billing Address 1": "532 Simonis Spring", "Billing Address 1": "532 Simonis Spring",
"Billing Address 2": "3122 Nicolas Inlet", "Billing Address 2": "3122 Nicolas Inlet",
@@ -72,7 +72,7 @@ export const VendorsSampleData = [
"Opening Balance At": "2022-02-02", "Opening Balance At": "2022-02-02",
"Opening Balance Ex. Rate": 2, "Opening Balance Ex. Rate": 2,
"Currency": "LYD", "Currency": "LYD",
"Active": "F", "Active": "T",
"Note": "Vero quibusdam rem fugit aperiam est modi.", "Note": "Vero quibusdam rem fugit aperiam est modi.",
"Billing Address 1": "214 Sauer Villages", "Billing Address 1": "214 Sauer Villages",
"Billing Address 2": "30687 Kacey Square", "Billing Address 2": "30687 Kacey Square",
@@ -102,7 +102,7 @@ export const VendorsSampleData = [
"Opening Balance At": "2022-02-02", "Opening Balance At": "2022-02-02",
"Opening Balance Ex. Rate": 2, "Opening Balance Ex. Rate": 2,
"Currency": "LYD", "Currency": "LYD",
"Active": "F", "Active": "T",
"Note": "Quis cumque molestias rerum.", "Note": "Quis cumque molestias rerum.",
"Billing Address 1": "22590 Cathy Harbor", "Billing Address 1": "22590 Cathy Harbor",
"Billing Address 2": "24493 Brycen Brooks", "Billing Address 2": "24493 Brycen Brooks",

View File

@@ -88,7 +88,8 @@ export class CreateExpense {
public newExpense = async ( public newExpense = async (
tenantId: number, tenantId: number,
expenseDTO: IExpenseCreateDTO, expenseDTO: IExpenseCreateDTO,
authorizedUser: ISystemUser authorizedUser: ISystemUser,
trx?: Knex.Transaction
): Promise<IExpense> => { ): Promise<IExpense> => {
const { Expense } = await this.tenancy.models(tenantId); const { Expense } = await this.tenancy.models(tenantId);
@@ -103,7 +104,9 @@ export class CreateExpense {
); );
// Writes the expense transaction with associated transactions under // Writes the expense transaction with associated transactions under
// unit-of-work envirement. // unit-of-work envirement.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { return this.uow.withTransaction(
tenantId,
async (trx: Knex.Transaction) => {
// Triggers `onExpenseCreating` event. // Triggers `onExpenseCreating` event.
await this.eventPublisher.emitAsync(events.expenses.onCreating, { await this.eventPublisher.emitAsync(events.expenses.onCreating, {
trx, trx,
@@ -125,6 +128,8 @@ export class CreateExpense {
} as IExpenseCreatedPayload); } as IExpenseCreatedPayload);
return expense; return expense;
}); },
trx
);
}; };
} }

View File

@@ -0,0 +1,46 @@
import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import { IExpenseCreateDTO } from '@/interfaces';
import { Importable } from '../Import/Importable';
import { CreateExpense } from './CRUD/CreateExpense';
import { ExpensesSampleData } from './constants';
@Service()
export class ExpensesImportable extends Importable {
@Inject()
private createExpenseService: CreateExpense;
/**
* Importing to account service.
* @param {number} tenantId
* @param {IAccountCreateDTO} createAccountDTO
* @returns
*/
public importable(
tenantId: number,
createAccountDTO: IExpenseCreateDTO,
trx?: Knex.Transaction
) {
return this.createExpenseService.newExpense(
tenantId,
createAccountDTO,
{},
trx
);
}
/**
* Concurrrency controlling of the importing process.
* @returns {number}
*/
public get concurrency() {
return 1;
}
/**
* Retrieves the sample data that used to download accounts sample sheet.
*/
public sampleData(): any[] {
return ExpensesSampleData;
}
}

View File

@@ -36,3 +36,43 @@ export const ERRORS = {
EXPENSE_ALREADY_PUBLISHED: 'expense_already_published', EXPENSE_ALREADY_PUBLISHED: 'expense_already_published',
EXPENSE_HAS_ASSOCIATED_LANDED_COST: 'EXPENSE_HAS_ASSOCIATED_LANDED_COST', EXPENSE_HAS_ASSOCIATED_LANDED_COST: 'EXPENSE_HAS_ASSOCIATED_LANDED_COST',
}; };
export const ExpensesSampleData = [
{
'Payment Date': '2024-03-01',
'Reference No.': 'REF-1',
'Payment Account': 'Petty Cash',
Description: 'Vel et dolorem architecto veniam.',
'Currency Code': '',
'Exchange Rate': '',
'Expense Account': 'Utilities Expense',
Amount: 9000,
'Line Description': 'Voluptates voluptas corporis vel.',
Publish: 'T',
},
{
'Payment Date': '2024-03-02',
'Reference No.': 'REF-2',
'Payment Account': 'Petty Cash',
Description: 'Id est molestias.',
'Currency Code': '',
'Exchange Rate': '',
'Expense Account': 'Utilities Expense',
Amount: 9000,
'Line Description': 'Eos voluptatem cumque et voluptate reiciendis.',
Publish: 'T',
},
{
'Payment Date': '2024-03-03',
'Reference No.': 'REF-3',
'Payment Account': 'Petty Cash',
Description: 'Quam cupiditate at nihil dicta dignissimos non fugit illo.',
'Currency Code': '',
'Exchange Rate': '',
'Expense Account': 'Utilities Expense',
Amount: 9000,
'Line Description':
'Hic alias rerum sed commodi dolores sint animi perferendis.',
Publish: 'T',
},
];

View File

@@ -8,6 +8,10 @@ import { ItemsImportable } from '../Items/ItemsImportable';
import { ItemCategoriesImportable } from '../ItemCategories/ItemCategoriesImportable'; import { ItemCategoriesImportable } from '../ItemCategories/ItemCategoriesImportable';
import { ManualJournalImportable } from '../ManualJournals/ManualJournalsImport'; import { ManualJournalImportable } from '../ManualJournals/ManualJournalsImport';
import { BillsImportable } from '../Purchases/Bills/BillsImportable'; import { BillsImportable } from '../Purchases/Bills/BillsImportable';
import { ExpensesImportable } from '../Expenses/ExpensesImportable';
import { SaleInvoicesImportable } from '../Sales/Invoices/SaleInvoicesImportable';
import { SaleEstimatesImportable } from '../Sales/Estimates/SaleEstimatesImportable';
import { BillPaymentsImportable } from '../Purchases/BillPayments/BillPaymentsImportable';
@Service() @Service()
export class ImportableResources { export class ImportableResources {
@@ -32,6 +36,10 @@ export class ImportableResources {
{ resource: 'ItemCategory', importable: ItemCategoriesImportable }, { resource: 'ItemCategory', importable: ItemCategoriesImportable },
{ resource: 'ManualJournal', importable: ManualJournalImportable }, { resource: 'ManualJournal', importable: ManualJournalImportable },
{ resource: 'Bill', importable: BillsImportable }, { resource: 'Bill', importable: BillsImportable },
{ resource: 'Expense', importable: ExpensesImportable },
{ resource: 'SaleInvoice', importable: SaleInvoicesImportable },
{ resource: 'SaleEstimate', importable: SaleEstimatesImportable },
{ resource: 'BillPayment', importable: BillPaymentsImportable },
]; ];
public get registry() { public get registry() {

View File

@@ -102,7 +102,10 @@ export default class ItemCategoriesService implements IItemCategoriesService {
} }
}); });
if (foundItemCategory) { if (foundItemCategory) {
throw new ServiceError(ERRORS.CATEGORY_NAME_EXISTS); throw new ServiceError(
ERRORS.CATEGORY_NAME_EXISTS,
'The item category name is already exist.'
);
} }
} }

View File

@@ -82,7 +82,10 @@ export class CommandManualJournalValidators {
} }
}); });
if (journals.length > 0) { if (journals.length > 0) {
throw new ServiceError(ERRORS.JOURNAL_NUMBER_EXISTS); throw new ServiceError(
ERRORS.JOURNAL_NUMBER_EXISTS,
'The journal number is already exist.'
);
} }
} }

View File

@@ -0,0 +1,45 @@
import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import { IBillPaymentDTO } from '@/interfaces';
import { CreateBillPayment } from './CreateBillPayment';
import { Importable } from '@/services/Import/Importable';
import { BillsPaymentsSampleData } from './constants';
@Service()
export class BillPaymentsImportable extends Importable {
@Inject()
private createBillPaymentService: CreateBillPayment;
/**
* Importing to account service.
* @param {number} tenantId
* @param {IAccountCreateDTO} createAccountDTO
* @returns
*/
public importable(
tenantId: number,
billPaymentDTO: IBillPaymentDTO,
trx?: Knex.Transaction
) {
return this.createBillPaymentService.createBillPayment(
tenantId,
billPaymentDTO,
trx
);
}
/**
* Concurrrency controlling of the importing process.
* @returns {number}
*/
public get concurrency() {
return 1;
}
/**
* Retrieves the sample data that used to download accounts sample sheet.
*/
public sampleData(): any[] {
return BillsPaymentsSampleData;
}
}

View File

@@ -48,7 +48,8 @@ export class CreateBillPayment {
*/ */
public async createBillPayment( public async createBillPayment(
tenantId: number, tenantId: number,
billPaymentDTO: IBillPaymentDTO billPaymentDTO: IBillPaymentDTO,
trx?: Knex.Transaction
): Promise<IBillPayment> { ): Promise<IBillPayment> {
const { BillPayment, Contact } = this.tenancy.models(tenantId); const { BillPayment, Contact } = this.tenancy.models(tenantId);
@@ -97,7 +98,9 @@ export class CreateBillPayment {
); );
// Writes bill payment transacation with associated transactions // Writes bill payment transacation with associated transactions
// under unit-of-work envirement. // under unit-of-work envirement.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { return this.uow.withTransaction(
tenantId,
async (trx: Knex.Transaction) => {
// Triggers `onBillPaymentCreating` event. // Triggers `onBillPaymentCreating` event.
await this.eventPublisher.emitAsync(events.billPayment.onCreating, { await this.eventPublisher.emitAsync(events.billPayment.onCreating, {
tenantId, tenantId,
@@ -119,6 +122,8 @@ export class CreateBillPayment {
} as IBillPaymentEventCreatedPayload); } as IBillPaymentEventCreatedPayload);
return billPayment; return billPayment;
}); },
trx
);
} }
} }

View File

@@ -15,3 +15,36 @@ export const ERRORS = {
}; };
export const DEFAULT_VIEWS = []; export const DEFAULT_VIEWS = [];
export const BillsPaymentsSampleData = [
{
'Payment Date': '2024-03-01',
Vendor: 'Gabriel Kovacek',
'Payment No.': 'P-10001',
'Reference No.': 'REF-1',
'Payment Account': 'Petty Cash',
Statement: 'Vel et dolorem architecto veniam.',
'Bill No': 'B-120',
'Payment Amount': 100,
},
{
'Payment Date': '2024-03-02',
Vendor: 'Gabriel Kovacek',
'Payment No.': 'P-10002',
'Reference No.': 'REF-2',
'Payment Account': 'Petty Cash',
Statement: 'Id est molestias.',
'Bill No': 'B-121',
'Payment Amount': 100,
},
{
'Payment Date': '2024-03-03',
Vendor: 'Gabriel Kovacek',
'Payment No.': 'P-10003',
'Reference No.': 'REF-3',
'Payment Account': 'Petty Cash',
Statement: 'Quam cupiditate at nihil dicta dignissimos non fugit illo.',
'Bill No': 'B-122',
'Payment Amount': 100,
},
];

View File

@@ -3,6 +3,7 @@ import { Knex } from 'knex';
import { Importable } from '@/services/Import/Importable'; import { Importable } from '@/services/Import/Importable';
import { CreateBill } from './CreateBill'; import { CreateBill } from './CreateBill';
import { IBillDTO } from '@/interfaces'; import { IBillDTO } from '@/interfaces';
import { BillsSampleData } from './constants';
@Service() @Service()
export class BillsImportable extends Importable { export class BillsImportable extends Importable {
@@ -20,7 +21,6 @@ export class BillsImportable extends Importable {
createAccountDTO: IBillDTO, createAccountDTO: IBillDTO,
trx?: Knex.Transaction trx?: Knex.Transaction
) { ) {
console.log(JSON.stringify(createAccountDTO));
return this.createBillService.createBill( return this.createBillService.createBill(
tenantId, tenantId,
createAccountDTO, createAccountDTO,
@@ -41,6 +41,6 @@ export class BillsImportable extends Importable {
* Retrieves the sample data that used to download accounts sample sheet. * Retrieves the sample data that used to download accounts sample sheet.
*/ */
public sampleData(): any[] { public sampleData(): any[] {
return []; return BillsSampleData;
} }
} }

View File

@@ -28,7 +28,7 @@ export class BillsValidators {
*/ */
public validateBillAmountBiggerPaidAmount( public validateBillAmountBiggerPaidAmount(
billAmount: number, billAmount: number,
paidAmount: number, paidAmount: number
) { ) {
if (billAmount < paidAmount) { if (billAmount < paidAmount) {
throw new ServiceError(ERRORS.BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT); throw new ServiceError(ERRORS.BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT);
@@ -53,7 +53,10 @@ export class BillsValidators {
}); });
if (foundBills.length > 0) { if (foundBills.length > 0) {
throw new ServiceError(ERRORS.BILL_NUMBER_EXISTS); throw new ServiceError(
ERRORS.BILL_NUMBER_EXISTS,
'The bill number is not unique.'
);
} }
} }

View File

@@ -75,3 +75,49 @@ export const DEFAULT_VIEWS = [
columns: DEFAULT_VIEW_COLUMNS, columns: DEFAULT_VIEW_COLUMNS,
}, },
]; ];
export const BillsSampleData = [
{
'Bill No.': 'B-101',
'Reference No.': 'REF0',
Date: '2024-01-01',
'Due Date': '2024-03-01',
Vendor: 'Gabriel Kovacek',
'Exchange Rate': 1,
Note: 'Vel in sit sint.',
Open: 'T',
Item: 'VonRueden, Ruecker and Hettinger',
Quantity: 100,
Rate: 100,
'Line Description': 'Id a vel quis vel aut.',
},
{
'Bill No.': 'B-102',
'Reference No.': 'REF0',
Date: '2024-01-01',
'Due Date': '2024-03-01',
Vendor: 'Gabriel Kovacek',
'Exchange Rate': 1,
Note: 'Quia ut dolorem qui sint velit.',
Open: 'T',
Item: 'Thompson - Reichert',
Quantity: 200,
Rate: 50,
'Line Description':
'Nesciunt in adipisci quia ab reiciendis nam sed saepe consequatur.',
},
{
'Bill No.': 'B-103',
'Reference No.': 'REF0',
Date: '2024-01-01',
'Due Date': '2024-03-01',
Vendor: 'Gabriel Kovacek',
'Exchange Rate': 1,
Note: 'Dolore aut voluptatem minus pariatur alias pariatur.',
Open: 'T',
Item: 'VonRueden, Ruecker and Hettinger',
Quantity: 100,
Rate: 100,
'Line Description': 'Quam eligendi provident.',
},
];

View File

@@ -105,11 +105,14 @@ export default class ResourceService {
const $enumerationType = (field) => const $enumerationType = (field) =>
field.fieldType === 'enumeration' ? field : undefined; field.fieldType === 'enumeration' ? field : undefined;
const $hasFields = (field) => 'undefined' !== typeof field.fields ? field : undefined;
const naviagations = [ const naviagations = [
['fields', qim.$each, 'name'], ['fields', qim.$each, 'name'],
['fields2', qim.$each, 'name'],
['fields', qim.$each, $enumerationType, 'options', qim.$each, 'label'], ['fields', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
['fields2', qim.$each, 'name'],
['fields2', qim.$each, $enumerationType, 'options', qim.$each, 'label'], ['fields2', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
['fields2', qim.$each, $hasFields, 'fields', qim.$each, 'name'],
]; ];
return this.i18nService.i18nApply(naviagations, meta, tenantId); return this.i18nService.i18nApply(naviagations, meta, tenantId);
} }

View File

@@ -43,7 +43,8 @@ export class CreateSaleEstimate {
*/ */
public async createEstimate( public async createEstimate(
tenantId: number, tenantId: number,
estimateDTO: ISaleEstimateDTO estimateDTO: ISaleEstimateDTO,
trx?: Knex.Transaction
): Promise<ISaleEstimate> { ): Promise<ISaleEstimate> {
const { SaleEstimate, Contact } = this.tenancy.models(tenantId); const { SaleEstimate, Contact } = this.tenancy.models(tenantId);
@@ -75,7 +76,9 @@ export class CreateSaleEstimate {
estimateDTO.entries estimateDTO.entries
); );
// Creates a sale estimate transaction with associated transactions as UOW. // Creates a sale estimate transaction with associated transactions as UOW.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { return this.uow.withTransaction(
tenantId,
async (trx: Knex.Transaction) => {
// Triggers `onSaleEstimateCreating` event. // Triggers `onSaleEstimateCreating` event.
await this.eventPublisher.emitAsync(events.saleEstimate.onCreating, { await this.eventPublisher.emitAsync(events.saleEstimate.onCreating, {
estimateDTO, estimateDTO,
@@ -97,6 +100,8 @@ export class CreateSaleEstimate {
} as ISaleEstimateCreatedPayload); } as ISaleEstimateCreatedPayload);
return saleEstimate; return saleEstimate;
}); },
trx
);
} }
} }

View File

@@ -41,7 +41,10 @@ export class SaleEstimateValidators {
} }
}); });
if (foundSaleEstimate) { if (foundSaleEstimate) {
throw new ServiceError(ERRORS.SALE_ESTIMATE_NUMBER_EXISTANCE); throw new ServiceError(
ERRORS.SALE_ESTIMATE_NUMBER_EXISTANCE,
'The given sale estimate is not unique.'
);
} }
} }

View File

@@ -0,0 +1,45 @@
import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import { ISaleEstimateDTO } from '@/interfaces';
import { CreateSaleEstimate } from './CreateSaleEstimate';
import { Importable } from '@/services/Import/Importable';
import { SaleEstimatesSampleData } from './constants';
@Service()
export class SaleEstimatesImportable extends Importable {
@Inject()
private createEstimateService: CreateSaleEstimate;
/**
* Importing to account service.
* @param {number} tenantId
* @param {IAccountCreateDTO} createAccountDTO
* @returns
*/
public importable(
tenantId: number,
createEstimateDTO: ISaleEstimateDTO,
trx?: Knex.Transaction
) {
return this.createEstimateService.createEstimate(
tenantId,
createEstimateDTO,
trx
);
}
/**
* Concurrrency controlling of the importing process.
* @returns {number}
*/
public get concurrency() {
return 1;
}
/**
* Retrieves the sample data that used to download accounts sample sheet.
*/
public sampleData(): any[] {
return SaleEstimatesSampleData;
}
}

View File

@@ -122,3 +122,54 @@ export const DEFAULT_VIEWS = [
columns: DEFAULT_VIEW_COLUMNS, columns: DEFAULT_VIEW_COLUMNS,
}, },
]; ];
export const SaleEstimatesSampleData = [
{
Customer: 'Ambrose Olson',
'Estimate Date': '2024-01-01',
'Expiration Date': '2025-01-01',
'Estimate No.': 'EST-0001',
'Reference No.': 'REF-0001',
Currency: '',
'Exchange Rate': '',
Note: 'Vel autem quis aut ab.',
'Terms & Conditions': 'Provident illo architecto sit iste in.',
Delivered: 'T',
'Item Name': 'Hettinger, Schumm and Bartoletti',
Quantity: 1000,
Rate: 20,
'Line Description': 'Rem esse doloremque praesentium harum maiores.',
},
{
Customer: 'Ambrose Olson',
'Estimate Date': '2024-01-02',
'Expiration Date': '2025-01-02',
'Estimate No.': 'EST-0002',
'Reference No.': 'REF-0002',
Currency: '',
'Exchange Rate': '',
Note: 'Tempora voluptas odio deleniti rerum vitae consequatur nihil quis sunt.',
'Terms & Conditions': 'Ut eum incidunt quibusdam rerum vero.',
Delivered: 'T',
'Item Name': 'Hettinger, Schumm and Bartoletti',
Quantity: 1000,
Rate: 20,
'Line Description': 'Qui voluptate aliquam maxime aliquam.',
},
{
Customer: 'Ambrose Olson',
'Estimate Date': '2024-01-03',
'Expiration Date': '2025-01-03',
'Estimate No.': 'EST-0003',
'Reference No.': 'REF-0003',
Currency: '',
'Exchange Rate': '',
Note: 'Quia voluptatem delectus doloremque.',
'Terms & Conditions': 'Facilis porro vitae ratione.',
Delivered: 'T',
'Item Name': 'Hettinger, Schumm and Bartoletti',
Quantity: 1000,
Rate: 20,
'Line Description': 'Qui suscipit ducimus qui qui.',
},
];

View File

@@ -51,7 +51,8 @@ export class CreateSaleInvoice {
public createSaleInvoice = async ( public createSaleInvoice = async (
tenantId: number, tenantId: number,
saleInvoiceDTO: ISaleInvoiceCreateDTO, saleInvoiceDTO: ISaleInvoiceCreateDTO,
authorizedUser: ITenantUser authorizedUser: ITenantUser,
trx?: Knex.Transaction
): Promise<ISaleInvoice> => { ): Promise<ISaleInvoice> => {
const { SaleInvoice, SaleEstimate, Contact } = const { SaleInvoice, SaleEstimate, Contact } =
this.tenancy.models(tenantId); this.tenancy.models(tenantId);
@@ -96,7 +97,9 @@ export class CreateSaleInvoice {
); );
} }
// Creates a new sale invoice and associated transactions under unit of work env. // Creates a new sale invoice and associated transactions under unit of work env.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { return this.uow.withTransaction(
tenantId,
async (trx: Knex.Transaction) => {
// Triggers `onSaleInvoiceCreating` event. // Triggers `onSaleInvoiceCreating` event.
await this.eventPublisher.emitAsync(events.saleInvoice.onCreating, { await this.eventPublisher.emitAsync(events.saleInvoice.onCreating, {
saleInvoiceDTO, saleInvoiceDTO,
@@ -122,7 +125,9 @@ export class CreateSaleInvoice {
eventPayload eventPayload
); );
return saleInvoice; return saleInvoice;
}); },
trx
);
}; };
/** /**

View File

@@ -0,0 +1,45 @@
import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import { ISaleInvoiceCreateDTO } from '@/interfaces';
import { CreateSaleInvoice } from './CreateSaleInvoice';
import { Importable } from '@/services/Import/Importable';
@Service()
export class SaleInvoicesImportable extends Importable {
@Inject()
private createInvoiceService: CreateSaleInvoice;
/**
* Importing to account service.
* @param {number} tenantId
* @param {IAccountCreateDTO} createAccountDTO
* @returns
*/
public importable(
tenantId: number,
createAccountDTO: ISaleInvoiceCreateDTO,
trx?: Knex.Transaction
) {
return this.createInvoiceService.createSaleInvoice(
tenantId,
createAccountDTO,
{},
trx
);
}
/**
* Concurrrency controlling of the importing process.
* @returns {number}
*/
public get concurrency() {
return 1;
}
/**
* Retrieves the sample data that used to download accounts sample sheet.
*/
public sampleData(): any[] {
return [];
}
}

View File

@@ -0,0 +1,25 @@
// @ts-nocheck
import { useHistory } from 'react-router-dom';
import { DashboardInsider } from '@/components';
import { ImportView } from '../Import/ImportView';
export default function ExpensesImport() {
const history = useHistory();
const handleCancelBtnClick = () => {
history.push('/expenses');
};
const handleImportSuccess = () => {
history.push('/expenses');
};
return (
<DashboardInsider name={'import-expenses'}>
<ImportView
resource={'expenses'}
onCancelClick={handleCancelBtnClick}
onImportSuccess={handleImportSuccess}
/>
</DashboardInsider>
);
}

View File

@@ -79,6 +79,11 @@ function ExpensesActionsBar({
refresh(); refresh();
}; };
// Handle the import button click.
const handleImportBtnClick = () => {
history.push('/expenses/import');
}
// Handle table row size change. // Handle table row size change.
const handleTableRowSizeChange = (size) => { const handleTableRowSizeChange = (size) => {
addSetting('expenses', 'tableSize', size); addSetting('expenses', 'tableSize', size);
@@ -135,6 +140,7 @@ function ExpensesActionsBar({
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon="file-import-16" iconSize={16} />} icon={<Icon icon="file-import-16" iconSize={16} />}
text={<T id={'import'} />} text={<T id={'import'} />}
onClick={handleImportBtnClick}
/> />
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}

View File

@@ -1,5 +1,6 @@
// @ts-nocheck
import { useMemo } from 'react'; import { useMemo } from 'react';
import { chain, isEmpty, lowerCase, head, last, set } from 'lodash'; import { chain, isEmpty, lowerCase, head, last, set, get } from 'lodash';
import { useImportFileContext } from './ImportFileProvider'; import { useImportFileContext } from './ImportFileProvider';
import { useImportFileMapBootContext } from './ImportFileMappingBoot'; import { useImportFileMapBootContext } from './ImportFileMappingBoot';
import { deepdash, transformToForm } from '@/utils'; import { deepdash, transformToForm } from '@/utils';
@@ -21,7 +22,7 @@ export const transformValueToReq = (
): { mapping: ImportFileMappingRes[] } => { ): { mapping: ImportFileMappingRes[] } => {
const mapping = chain(value) const mapping = chain(value)
.thru(deepdash.index) .thru(deepdash.index)
.pickBy((_value, key) => !isEmpty(_.get(value, key))) .pickBy((_value, key) => !isEmpty(get(value, key)))
.map((from, key) => ({ .map((from, key) => ({
from, from,
to: key.includes('.') ? last(key.split('.')) : key, to: key.includes('.') ? last(key.split('.')) : key,

View File

@@ -0,0 +1,25 @@
// @ts-nocheck
import { useHistory } from 'react-router-dom';
import { DashboardInsider } from '@/components';
import { ImportView } from '@/containers/Import';
export default function PaymentMadesImport() {
const history = useHistory();
const handleCancelBtnClick = () => {
history.push('/payment-mades');
};
const handleImportSuccess = () => {
history.push('/payment-mades');
};
return (
<DashboardInsider name={'import-payment-mades'}>
<ImportView
resource={'bill_payment'}
onCancelClick={handleCancelBtnClick}
onImportSuccess={handleImportSuccess}
/>
</DashboardInsider>
);
}

View File

@@ -78,6 +78,11 @@ function PaymentMadeActionsBar({
addSetting('billPayments', 'tableSize', size); addSetting('billPayments', 'tableSize', size);
}; };
// Handle the import button click.
const handleImportBtnClick = () => {
history.push('/payment-mades/import');
}
return ( return (
<DashboardActionsBar> <DashboardActionsBar>
<NavbarGroup> <NavbarGroup>
@@ -128,6 +133,7 @@ function PaymentMadeActionsBar({
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon={'file-import-16'} />} icon={<Icon icon={'file-import-16'} />}
text={<T id={'import'} />} text={<T id={'import'} />}
onClick={handleImportBtnClick}
/> />
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}

View File

@@ -0,0 +1,25 @@
// @ts-nocheck
import { useHistory } from 'react-router-dom';
import { DashboardInsider } from '@/components';
import { ImportView } from '@/containers/Import';
export default function EstimatesImport() {
const history = useHistory();
const handleCancelBtnClick = () => {
history.push('/estimates');
};
const handleImportSuccess = () => {
history.push('/estimates');
};
return (
<DashboardInsider name={'import-accounts'}>
<ImportView
resource={'sale_estimate'}
onCancelClick={handleCancelBtnClick}
onImportSuccess={handleImportSuccess}
/>
</DashboardInsider>
);
}

View File

@@ -77,6 +77,11 @@ function EstimateActionsBar({
addSetting('salesEstimates', 'tableSize', size); addSetting('salesEstimates', 'tableSize', size);
}; };
// Handle the import button click.
const handleImportBtnClick = () => {
history.push('/estimates/import');
}
return ( return (
<DashboardActionsBar> <DashboardActionsBar>
<NavbarGroup> <NavbarGroup>
@@ -130,6 +135,7 @@ function EstimateActionsBar({
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon={'file-import-16'} />} icon={<Icon icon={'file-import-16'} />}
text={<T id={'import'} />} text={<T id={'import'} />}
onClick={handleImportBtnClick}
/> />
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}

View File

@@ -0,0 +1,25 @@
// @ts-nocheck
import { useHistory } from 'react-router-dom';
import { DashboardInsider } from '@/components';
import { ImportView } from '@/containers/Import';
export default function InvoicesImport() {
const history = useHistory();
const handleCancelBtnClick = () => {
history.push('/invoices');
};
const handleImportSuccess = () => {
history.push('/invoices');
};
return (
<DashboardInsider name={'import-invoices'}>
<ImportView
resource={'sale_invoice'}
onCancelClick={handleCancelBtnClick}
onImportSuccess={handleImportSuccess}
/>
</DashboardInsider>
);
}

View File

@@ -74,6 +74,11 @@ function InvoiceActionsBar({
addSetting('salesInvoices', 'tableSize', size); addSetting('salesInvoices', 'tableSize', size);
}; };
// Handle the import button click.
const handleImportBtnClick = () => {
history.push('/invoices/import');
};
return ( return (
<DashboardActionsBar> <DashboardActionsBar>
<NavbarGroup> <NavbarGroup>
@@ -124,6 +129,7 @@ function InvoiceActionsBar({
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon={'file-import-16'} />} icon={<Icon icon={'file-import-16'} />}
text={<T id={'import'} />} text={<T id={'import'} />}
onClick={handleImportBtnClick}
/> />
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}

View File

@@ -507,6 +507,16 @@ export const getDashboardRoutes = () => [
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN], subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
}, },
// Expenses. // Expenses.
{
path: `/expenses/import`,
component: lazy(() => import('@/containers/Expenses/ExpensesImport')),
breadcrumb: 'Expenses Import',
hotkey: 'ctrl+shift+x',
pageTitle: 'Expenses Import',
sidebarExpand: false,
backLink: true,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{ {
path: `/expenses/new`, path: `/expenses/new`,
component: lazy( component: lazy(
@@ -657,6 +667,19 @@ export const getDashboardRoutes = () => [
}, },
// Estimates // Estimates
{
path: `/estimates/import`,
component: lazy(
() => import('@/containers/Sales/Estimates/EstimatesImport'),
),
name: 'estimate-edit',
breadcrumb: 'Estimates Import',
pageTitle: 'Estimates Import',
backLink: true,
sidebarExpand: false,
defaultSearchResource: RESOURCES_TYPES.ESTIMATE,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{ {
path: `/estimates/:id/edit`, path: `/estimates/:id/edit`,
component: lazy( component: lazy(
@@ -715,6 +738,17 @@ export const getDashboardRoutes = () => [
}, },
// Invoices. // Invoices.
{
path: `/invoices/import`,
component: lazy(() => import('@/containers/Sales/Invoices/InvoicesImport')),
name: 'invoice-edit',
breadcrumb: 'Invoices Import',
pageTitle: 'Invoices Import',
sidebarExpand: false,
backLink: true,
defaultSearchResource: RESOURCES_TYPES.INVOICE,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{ {
path: `/invoices/:id/edit`, path: `/invoices/:id/edit`,
component: lazy( component: lazy(
@@ -1025,6 +1059,19 @@ export const getDashboardRoutes = () => [
subscriptionInactive: [SUBSCRIPTION_TYPE.MAIN], subscriptionInactive: [SUBSCRIPTION_TYPE.MAIN],
}, },
// Payment modes. // Payment modes.
{
path: `/payment-mades/import`,
component: lazy(
() => import('@/containers/Purchases/PaymentMades/PaymentMadesImport'),
),
name: 'payment-made-edit',
breadcrumb: intl.get('edit'),
pageTitle: 'Bills Payments Import',
sidebarExpand: false,
backLink: true,
defaultSearchResource: RESOURCES_TYPES.PAYMENT_MADE,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{ {
path: `/payment-mades/:id/edit`, path: `/payment-mades/:id/edit`,
component: lazy( component: lazy(