diff --git a/packages/server/resources/locales/en.json b/packages/server/resources/locales/en.json index a82ad052e..8d0739801 100644 --- a/packages/server/resources/locales/en.json +++ b/packages/server/resources/locales/en.json @@ -244,6 +244,7 @@ "account.field.active": "Active", "account.field.currency": "Currency", "account.field.balance": "Balance", + "account.field.parent_account": "Parent Account", "account.field.created_at": "Created at", "item.field.type": "Item Type", "item.field.type.inventory": "Inventory", @@ -277,8 +278,14 @@ "invoice.field.invoice_message": "Invoice message", "invoice.field.terms_conditions": "Terms & conditions", "invoice.field.amount": "Amount", + "invoice.field.exchange_rate": "Exchange Rate", "invoice.field.payment_amount": "Payment 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.paid": "Paid", "invoice.field.status.partially-paid": "Partially paid", @@ -287,6 +294,7 @@ "invoice.field.status.delivered": "Delivered", "invoice.field.status.draft": "Draft", "invoice.field.created_at": "Created at", + "invoice.field.currency": "Currency", "estimate.field.amount": "Amount", "estimate.field.estimate_number": "Estimate number", "estimate.field.customer": "Customer", @@ -311,12 +319,17 @@ "payment_receive.field.created_at": "Created at", "bill_payment.field.vendor": "Vendor", "bill_payment.field.amount": "Amount", - "bill_payment.field.due_amount": "Due amount", - "bill_payment.field.payment_account": "Payment account", - "bill_payment.field.payment_number": "Payment number", - "bill_payment.field.payment_date": "Payment date", + "bill_payment.field.due_amount": "Due Amount", + "bill_payment.field.payment_account": "Payment Account", + "bill_payment.field.payment_number": "Payment No.", + "bill_payment.field.payment_date": "Payment Date", "bill_payment.field.reference_no": "Reference No.", "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.field.vendor": "Vendor", "bill.field.bill_number": "Bill number", @@ -344,12 +357,18 @@ "inventory_adjustment.field.description": "Description", "inventory_adjustment.field.published_at": "Published at", "inventory_adjustment.field.created_at": "Created at", - "expense.field.payment_date": "Payment date", - "expense.field.payment_account": "Payment account", + "expense.field.payment_date": "Payment Date", + "expense.field.payment_account": "Payment Account", "expense.field.amount": "Amount", + "expense.field.currency_code": "Currency", + "expense.field.exchange_rate": "Exchange Rate", "expense.field.reference_no": "Reference No.", "expense.field.description": "Description", + "expense.field.line_description": "Line Description", "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.draft": "Draft", "expense.field.status.published": "Published", @@ -414,6 +433,8 @@ "vendor.field.status.unpaid": "Unpaid", "Invoice write-off": "Invoice write-off", + + "transaction_type.credit_note": "Credit note", "transaction_type.refund_credit_note": "Refund credit note", "transaction_type.vendor_credit": "Vendor credit", diff --git a/packages/server/src/api/controllers/Expenses/Expenses.ts b/packages/server/src/api/controllers/Expenses/Expenses.ts index fe3746910..5d6fbe327 100644 --- a/packages/server/src/api/controllers/Expenses/Expenses.ts +++ b/packages/server/src/api/controllers/Expenses/Expenses.ts @@ -317,7 +317,7 @@ export class ExpensesController extends BaseController { const { tenantId } = req; const filter = { sortOrder: 'desc', - columnSortBy: 'created_at', + columnSortBy: 'createdAt', page: 1, pageSize: 12, ...this.matchedQueryData(req), diff --git a/packages/server/src/api/controllers/ManualJournals.ts b/packages/server/src/api/controllers/ManualJournals.ts index 8af8c13b5..a7ff994e8 100644 --- a/packages/server/src/api/controllers/ManualJournals.ts +++ b/packages/server/src/api/controllers/ManualJournals.ts @@ -328,7 +328,7 @@ export default class ManualJournalsController extends BaseController { const { tenantId } = req; const filter = { sortOrder: 'desc', - columnSortBy: 'created_at', + columnSortBy: 'createdAt', page: 1, pageSize: 12, ...this.matchedQueryData(req), diff --git a/packages/server/src/api/controllers/Sales/SalesInvoices.ts b/packages/server/src/api/controllers/Sales/SalesInvoices.ts index b45604e43..b8ee3a6e2 100644 --- a/packages/server/src/api/controllers/Sales/SalesInvoices.ts +++ b/packages/server/src/api/controllers/Sales/SalesInvoices.ts @@ -459,7 +459,7 @@ export default class SaleInvoicesController extends BaseController { const { tenantId } = req; const filter = { sortOrder: 'desc', - columnSortBy: 'created_at', + columnSortBy: 'createdAt', page: 1, pageSize: 12, ...this.matchedQueryData(req), diff --git a/packages/server/src/models/Account.Settings.ts b/packages/server/src/models/Account.Settings.ts index 8d82ac1de..9ea061df3 100644 --- a/packages/server/src/models/Account.Settings.ts +++ b/packages/server/src/models/Account.Settings.ts @@ -119,6 +119,49 @@ export default { 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'], + }, + }, }; /** diff --git a/packages/server/src/models/Bill.Settings.ts b/packages/server/src/models/Bill.Settings.ts index 962aafca5..3d07d68d7 100644 --- a/packages/server/src/models/Bill.Settings.ts +++ b/packages/server/src/models/Bill.Settings.ts @@ -144,7 +144,7 @@ export default { required: true, }, description: { - name: 'Description', + name: 'Line Description', fieldType: 'text', }, }, diff --git a/packages/server/src/models/BillPayment.Settings.ts b/packages/server/src/models/BillPayment.Settings.ts index 8ca4113b2..15abe6866 100644 --- a/packages/server/src/models/BillPayment.Settings.ts +++ b/packages/server/src/models/BillPayment.Settings.ts @@ -4,6 +4,10 @@ export default { sortOrder: 'DESC', sortField: 'bill_date', }, + importable: true, + importAggregator: 'group', + importAggregateOn: 'entries', + importAggregateBy: 'paymentNumber', fields: { vendor: { name: 'bill_payment.field.vendor', @@ -33,7 +37,7 @@ export default { relationType: 'enumeration', relationKey: 'paymentAccount', - + relationEntityLabel: 'name', relationEntityKey: 'slug', }, @@ -63,4 +67,64 @@ export default { 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, + }, + }, + }, + }, }; diff --git a/packages/server/src/models/Customer.Settings.ts b/packages/server/src/models/Customer.Settings.ts index b789c9adf..10ef4ac1e 100644 --- a/packages/server/src/models/Customer.Settings.ts +++ b/packages/server/src/models/Customer.Settings.ts @@ -212,6 +212,161 @@ export default { 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) { diff --git a/packages/server/src/models/Expense.Settings.ts b/packages/server/src/models/Expense.Settings.ts index 0d8ea8712..e56089560 100644 --- a/packages/server/src/models/Expense.Settings.ts +++ b/packages/server/src/models/Expense.Settings.ts @@ -7,13 +7,14 @@ export default { sortOrder: 'DESC', sortField: 'name', }, + importable: true, fields: { - 'payment_date': { + payment_date: { name: 'expense.field.payment_date', column: 'payment_date', fieldType: 'date', }, - 'payment_account': { + payment_account: { name: 'expense.field.payment_account', column: 'payment_account_id', fieldType: 'relation', @@ -24,27 +25,27 @@ export default { relationEntityLabel: 'name', relationEntityKey: 'slug', }, - 'amount': { + amount: { name: 'expense.field.amount', column: 'total_amount', fieldType: 'number', }, - 'reference_no': { + reference_no: { name: 'expense.field.reference_no', column: 'reference_no', fieldType: 'text', }, - 'description': { + description: { name: 'expense.field.description', column: 'description', fieldType: 'text', }, - 'published': { + published: { name: 'expense.field.published', column: 'published_at', fieldType: 'date', }, - 'status': { + status: { name: 'expense.field.status', fieldType: 'enumeration', options: [ @@ -54,12 +55,69 @@ export default { filterCustomQuery: StatusFieldFilterQuery, sortCustomQuery: StatusFieldSortQuery, }, - 'created_at': { + createdAt: { name: 'expense.field.created_at', column: 'created_at', 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) { diff --git a/packages/server/src/models/Item.Settings.ts b/packages/server/src/models/Item.Settings.ts index a1bdec592..740bd9b16 100644 --- a/packages/server/src/models/Item.Settings.ts +++ b/packages/server/src/models/Item.Settings.ts @@ -5,6 +5,96 @@ export default { sortField: 'name', 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: { type: { name: 'item.field.type', @@ -31,7 +121,6 @@ export default { column: 'code', fieldType: 'text', importable: true, - }, sellable: { name: 'item.field.sellable', @@ -66,13 +155,11 @@ export default { column: 'cost_account_id', fieldType: 'relation', - relationType: 'enumeration', relationKey: 'costAccount', relationEntityLabel: 'name', relationEntityKey: 'slug', - dataTransferObjectKey: 'costAccountId', importableRelationLabel: ['name', 'code'], importable: true, required: true, @@ -82,8 +169,8 @@ export default { column: 'sell_account_id', fieldType: 'relation', - relationType: 'enumeration', relationKey: 'sellAccount', + relationType: 'one-to-many', relationEntityLabel: 'name', relationEntityKey: 'slug', @@ -98,7 +185,6 @@ export default { column: 'inventory_account_id', fieldType: 'relation', - relationType: 'enumeration', relationKey: 'inventoryAccount', relationEntityLabel: 'name', @@ -137,7 +223,6 @@ export default { column: 'category_id', fieldType: 'relation', - relationType: 'enumeration', relationKey: 'category', relationEntityLabel: 'name', diff --git a/packages/server/src/models/ItemCategory.Settings.ts b/packages/server/src/models/ItemCategory.Settings.ts index 75337c203..c0d329f0f 100644 --- a/packages/server/src/models/ItemCategory.Settings.ts +++ b/packages/server/src/models/ItemCategory.Settings.ts @@ -30,4 +30,16 @@ export default { columnType: 'date', }, }, + fields2: { + name: { + name: 'item_category.field.name', + column: 'name', + fieldType: 'text', + }, + description: { + name: 'item_category.field.description', + column: 'description', + fieldType: 'text', + }, + }, }; diff --git a/packages/server/src/models/ModelSetting.ts b/packages/server/src/models/ModelSetting.ts index e3c76bde7..bd96d32ae 100644 --- a/packages/server/src/models/ModelSetting.ts +++ b/packages/server/src/models/ModelSetting.ts @@ -1,33 +1,54 @@ import { get } from 'lodash'; -import { IModelMeta, IModelMetaField, IModelMetaDefaultSort } from '@/interfaces'; +import { + IModelMeta, + IModelMetaField, + IModelMetaDefaultSort, +} from '@/interfaces'; + +const defaultModelMeta = { + fields: {}, + fields2: {}, +}; export default (Model) => class ModelSettings extends Model { /** * + * @returns {IModelMeta} */ static get meta(): IModelMeta { 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. * @param {string} key * @returns {IModelMetaField} */ - public static getField(key: string, attribute?:string): IModelMetaField { + public static getField(key: string, attribute?: string): IModelMetaField { const field = get(this.meta.fields, key); return attribute ? get(field, attribute) : field; } /** - * Retrieve the specific model meta. + * Retrieves the specific model meta. * @param {string} key * @returns */ public static getMeta(key?: string) { - return key ? get(this.meta, key): this.meta; + return key ? get(this.parsedMeta, key) : this.parsedMeta; } /** diff --git a/packages/server/src/models/SaleEstimate.Settings.ts b/packages/server/src/models/SaleEstimate.Settings.ts index 9d1ea90a4..5d967d2b8 100644 --- a/packages/server/src/models/SaleEstimate.Settings.ts +++ b/packages/server/src/models/SaleEstimate.Settings.ts @@ -4,18 +4,22 @@ export default { sortOrder: 'DESC', sortField: 'estimate_date', }, + importable: true, + importAggregator: 'group', + importAggregateOn: 'entries', + importAggregateBy: 'estimateNumber', fields: { - 'amount': { + amount: { name: 'estimate.field.amount', column: 'amount', fieldType: 'number', }, - 'estimate_number': { + estimate_number: { name: 'estimate.field.estimate_number', column: 'estimate_number', fieldType: 'text', }, - 'customer': { + customer: { name: 'estimate.field.customer', column: 'customer_id', fieldType: 'relation', @@ -26,32 +30,32 @@ export default { relationEntityLabel: 'display_name', relationEntityKey: 'id', }, - 'estimate_date': { + estimate_date: { name: 'estimate.field.estimate_date', column: 'estimate_date', fieldType: 'date', }, - 'expiration_date': { + expiration_date: { name: 'estimate.field.expiration_date', column: 'expiration_date', fieldType: 'date', }, - 'reference_no': { + reference_no: { name: 'estimate.field.reference_no', column: 'reference', fieldType: 'text', }, - 'note': { + note: { name: 'estimate.field.note', column: 'note', fieldType: 'text', }, - 'terms_conditions': { + terms_conditions: { name: 'estimate.field.terms_conditions', column: 'terms_conditions', fieldType: 'text', }, - 'status': { + status: { name: 'estimate.field.status', fieldType: 'enumeration', options: [ @@ -63,12 +67,89 @@ export default { filterCustomQuery: StatusFieldFilterQuery, sortCustomQuery: StatusFieldSortQuery, }, - 'created_at': { + created_at: { name: 'estimate.field.created_at', column: 'created_at', 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) { diff --git a/packages/server/src/models/SaleInvoice.Settings.ts b/packages/server/src/models/SaleInvoice.Settings.ts index 842c5618b..f8b7c8136 100644 --- a/packages/server/src/models/SaleInvoice.Settings.ts +++ b/packages/server/src/models/SaleInvoice.Settings.ts @@ -2,8 +2,12 @@ export default { defaultFilterField: 'customer', defaultSort: { sortOrder: 'DESC', - sortField: 'created_at', + sortField: 'createdAt', }, + importable: true, + importAggregator: 'group', + importAggregateOn: 'entries', + importAggregateBy: 'invoiceNo', fields: { customer: { name: 'invoice.field.customer', @@ -77,12 +81,89 @@ export default { filterCustomQuery: StatusFieldFilterQuery, sortCustomQuery: StatusFieldSortQuery, }, - created_at: { + createdAt: { name: 'invoice.field.created_at', column: 'created_at', 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', + }, + }, }; /** diff --git a/packages/server/src/models/UncategorizedCashflowTransaction.meta.ts b/packages/server/src/models/UncategorizedCashflowTransaction.meta.ts index 9d02576c1..10cced266 100644 --- a/packages/server/src/models/UncategorizedCashflowTransaction.meta.ts +++ b/packages/server/src/models/UncategorizedCashflowTransaction.meta.ts @@ -51,4 +51,33 @@ export default { 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, + }, + + } }; diff --git a/packages/server/src/models/Vendor.Settings.ts b/packages/server/src/models/Vendor.Settings.ts index 0317d43c5..9551be723 100644 --- a/packages/server/src/models/Vendor.Settings.ts +++ b/packages/server/src/models/Vendor.Settings.ts @@ -206,4 +206,149 @@ export default { 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', + }, + }, }; diff --git a/packages/server/src/services/Contacts/Vendors/_SampleData.ts b/packages/server/src/services/Contacts/Vendors/_SampleData.ts index dc88d71f9..5e6e4edda 100644 --- a/packages/server/src/services/Contacts/Vendors/_SampleData.ts +++ b/packages/server/src/services/Contacts/Vendors/_SampleData.ts @@ -12,7 +12,7 @@ export const VendorsSampleData = [ "Opening Balance At": "2022-02-02", "Opening Balance Ex. Rate": 2, "Currency": "LYD", - "Active": "F", + "Active": "T", "Note": "Doloribus autem optio temporibus dolores mollitia sit.", "Billing Address 1": "862 Jessika Well", "Billing Address 2": "1091 Dorthy Mount", @@ -42,7 +42,7 @@ export const VendorsSampleData = [ "Opening Balance At": "2022-02-02", "Opening Balance Ex. Rate": 2, "Currency": "LYD", - "Active": "F", + "Active": "T", "Note": "Doloribus dolore dolor dicta vitae in fugit nisi quibusdam.", "Billing Address 1": "532 Simonis Spring", "Billing Address 2": "3122 Nicolas Inlet", @@ -72,7 +72,7 @@ export const VendorsSampleData = [ "Opening Balance At": "2022-02-02", "Opening Balance Ex. Rate": 2, "Currency": "LYD", - "Active": "F", + "Active": "T", "Note": "Vero quibusdam rem fugit aperiam est modi.", "Billing Address 1": "214 Sauer Villages", "Billing Address 2": "30687 Kacey Square", @@ -102,7 +102,7 @@ export const VendorsSampleData = [ "Opening Balance At": "2022-02-02", "Opening Balance Ex. Rate": 2, "Currency": "LYD", - "Active": "F", + "Active": "T", "Note": "Quis cumque molestias rerum.", "Billing Address 1": "22590 Cathy Harbor", "Billing Address 2": "24493 Brycen Brooks", diff --git a/packages/server/src/services/Expenses/CRUD/CreateExpense.ts b/packages/server/src/services/Expenses/CRUD/CreateExpense.ts index c0f0824bf..8d002f9b0 100644 --- a/packages/server/src/services/Expenses/CRUD/CreateExpense.ts +++ b/packages/server/src/services/Expenses/CRUD/CreateExpense.ts @@ -88,7 +88,8 @@ export class CreateExpense { public newExpense = async ( tenantId: number, expenseDTO: IExpenseCreateDTO, - authorizedUser: ISystemUser + authorizedUser: ISystemUser, + trx?: Knex.Transaction ): Promise => { const { Expense } = await this.tenancy.models(tenantId); @@ -103,28 +104,32 @@ export class CreateExpense { ); // Writes the expense transaction with associated transactions under // unit-of-work envirement. - return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { - // Triggers `onExpenseCreating` event. - await this.eventPublisher.emitAsync(events.expenses.onCreating, { - trx, - tenantId, - expenseDTO, - } as IExpenseCreatingPayload); + return this.uow.withTransaction( + tenantId, + async (trx: Knex.Transaction) => { + // Triggers `onExpenseCreating` event. + await this.eventPublisher.emitAsync(events.expenses.onCreating, { + trx, + tenantId, + expenseDTO, + } as IExpenseCreatingPayload); - // Creates a new expense transaction graph. - const expense: IExpense = await Expense.query(trx).upsertGraph( - expenseObj - ); - // Triggers `onExpenseCreated` event. - await this.eventPublisher.emitAsync(events.expenses.onCreated, { - tenantId, - expenseId: expense.id, - authorizedUser, - expense, - trx, - } as IExpenseCreatedPayload); + // Creates a new expense transaction graph. + const expense: IExpense = await Expense.query(trx).upsertGraph( + expenseObj + ); + // Triggers `onExpenseCreated` event. + await this.eventPublisher.emitAsync(events.expenses.onCreated, { + tenantId, + expenseId: expense.id, + authorizedUser, + expense, + trx, + } as IExpenseCreatedPayload); - return expense; - }); + return expense; + }, + trx + ); }; } diff --git a/packages/server/src/services/Expenses/ExpensesImportable.ts b/packages/server/src/services/Expenses/ExpensesImportable.ts new file mode 100644 index 000000000..28aee2c84 --- /dev/null +++ b/packages/server/src/services/Expenses/ExpensesImportable.ts @@ -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; + } +} diff --git a/packages/server/src/services/Expenses/constants.ts b/packages/server/src/services/Expenses/constants.ts index 424acb9bb..69144e065 100644 --- a/packages/server/src/services/Expenses/constants.ts +++ b/packages/server/src/services/Expenses/constants.ts @@ -36,3 +36,43 @@ export const ERRORS = { EXPENSE_ALREADY_PUBLISHED: 'expense_already_published', 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', + }, +]; diff --git a/packages/server/src/services/Import/ImportableResources.ts b/packages/server/src/services/Import/ImportableResources.ts index 1cffc1a8a..8f51dc261 100644 --- a/packages/server/src/services/Import/ImportableResources.ts +++ b/packages/server/src/services/Import/ImportableResources.ts @@ -8,6 +8,10 @@ import { ItemsImportable } from '../Items/ItemsImportable'; import { ItemCategoriesImportable } from '../ItemCategories/ItemCategoriesImportable'; import { ManualJournalImportable } from '../ManualJournals/ManualJournalsImport'; 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() export class ImportableResources { @@ -32,6 +36,10 @@ export class ImportableResources { { resource: 'ItemCategory', importable: ItemCategoriesImportable }, { resource: 'ManualJournal', importable: ManualJournalImportable }, { resource: 'Bill', importable: BillsImportable }, + { resource: 'Expense', importable: ExpensesImportable }, + { resource: 'SaleInvoice', importable: SaleInvoicesImportable }, + { resource: 'SaleEstimate', importable: SaleEstimatesImportable }, + { resource: 'BillPayment', importable: BillPaymentsImportable }, ]; public get registry() { diff --git a/packages/server/src/services/ItemCategories/ItemCategoriesService.ts b/packages/server/src/services/ItemCategories/ItemCategoriesService.ts index 344ce1240..26c7d4256 100644 --- a/packages/server/src/services/ItemCategories/ItemCategoriesService.ts +++ b/packages/server/src/services/ItemCategories/ItemCategoriesService.ts @@ -102,7 +102,10 @@ export default class ItemCategoriesService implements IItemCategoriesService { } }); if (foundItemCategory) { - throw new ServiceError(ERRORS.CATEGORY_NAME_EXISTS); + throw new ServiceError( + ERRORS.CATEGORY_NAME_EXISTS, + 'The item category name is already exist.' + ); } } diff --git a/packages/server/src/services/ManualJournals/CommandManualJournalValidators.ts b/packages/server/src/services/ManualJournals/CommandManualJournalValidators.ts index 5cc3e94c4..e8adb84b6 100644 --- a/packages/server/src/services/ManualJournals/CommandManualJournalValidators.ts +++ b/packages/server/src/services/ManualJournals/CommandManualJournalValidators.ts @@ -82,7 +82,10 @@ export class CommandManualJournalValidators { } }); if (journals.length > 0) { - throw new ServiceError(ERRORS.JOURNAL_NUMBER_EXISTS); + throw new ServiceError( + ERRORS.JOURNAL_NUMBER_EXISTS, + 'The journal number is already exist.' + ); } } diff --git a/packages/server/src/services/Purchases/BillPayments/BillPaymentsImportable.ts b/packages/server/src/services/Purchases/BillPayments/BillPaymentsImportable.ts new file mode 100644 index 000000000..343d4e532 --- /dev/null +++ b/packages/server/src/services/Purchases/BillPayments/BillPaymentsImportable.ts @@ -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; + } +} diff --git a/packages/server/src/services/Purchases/BillPayments/CreateBillPayment.ts b/packages/server/src/services/Purchases/BillPayments/CreateBillPayment.ts index f2456f217..81eb3753c 100644 --- a/packages/server/src/services/Purchases/BillPayments/CreateBillPayment.ts +++ b/packages/server/src/services/Purchases/BillPayments/CreateBillPayment.ts @@ -48,7 +48,8 @@ export class CreateBillPayment { */ public async createBillPayment( tenantId: number, - billPaymentDTO: IBillPaymentDTO + billPaymentDTO: IBillPaymentDTO, + trx?: Knex.Transaction ): Promise { const { BillPayment, Contact } = this.tenancy.models(tenantId); @@ -97,28 +98,32 @@ export class CreateBillPayment { ); // Writes bill payment transacation with associated transactions // under unit-of-work envirement. - return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { - // Triggers `onBillPaymentCreating` event. - await this.eventPublisher.emitAsync(events.billPayment.onCreating, { - tenantId, - billPaymentDTO, - trx, - } as IBillPaymentCreatingPayload); + return this.uow.withTransaction( + tenantId, + async (trx: Knex.Transaction) => { + // Triggers `onBillPaymentCreating` event. + await this.eventPublisher.emitAsync(events.billPayment.onCreating, { + tenantId, + billPaymentDTO, + trx, + } as IBillPaymentCreatingPayload); - // Writes the bill payment graph to the storage. - const billPayment = await BillPayment.query(trx).insertGraphAndFetch({ - ...billPaymentObj, - }); + // Writes the bill payment graph to the storage. + const billPayment = await BillPayment.query(trx).insertGraphAndFetch({ + ...billPaymentObj, + }); - // Triggers `onBillPaymentCreated` event. - await this.eventPublisher.emitAsync(events.billPayment.onCreated, { - tenantId, - billPayment, - billPaymentId: billPayment.id, - trx, - } as IBillPaymentEventCreatedPayload); + // Triggers `onBillPaymentCreated` event. + await this.eventPublisher.emitAsync(events.billPayment.onCreated, { + tenantId, + billPayment, + billPaymentId: billPayment.id, + trx, + } as IBillPaymentEventCreatedPayload); - return billPayment; - }); + return billPayment; + }, + trx + ); } } diff --git a/packages/server/src/services/Purchases/BillPayments/constants.ts b/packages/server/src/services/Purchases/BillPayments/constants.ts index 98cd2540b..29c93b1f5 100644 --- a/packages/server/src/services/Purchases/BillPayments/constants.ts +++ b/packages/server/src/services/Purchases/BillPayments/constants.ts @@ -15,3 +15,36 @@ export const ERRORS = { }; 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, + }, +]; diff --git a/packages/server/src/services/Purchases/Bills/BillsImportable.ts b/packages/server/src/services/Purchases/Bills/BillsImportable.ts index a67f93546..c356d3348 100644 --- a/packages/server/src/services/Purchases/Bills/BillsImportable.ts +++ b/packages/server/src/services/Purchases/Bills/BillsImportable.ts @@ -3,6 +3,7 @@ import { Knex } from 'knex'; import { Importable } from '@/services/Import/Importable'; import { CreateBill } from './CreateBill'; import { IBillDTO } from '@/interfaces'; +import { BillsSampleData } from './constants'; @Service() export class BillsImportable extends Importable { @@ -20,7 +21,6 @@ export class BillsImportable extends Importable { createAccountDTO: IBillDTO, trx?: Knex.Transaction ) { - console.log(JSON.stringify(createAccountDTO)); return this.createBillService.createBill( tenantId, createAccountDTO, @@ -41,6 +41,6 @@ export class BillsImportable extends Importable { * Retrieves the sample data that used to download accounts sample sheet. */ public sampleData(): any[] { - return []; + return BillsSampleData; } } diff --git a/packages/server/src/services/Purchases/Bills/BillsValidators.ts b/packages/server/src/services/Purchases/Bills/BillsValidators.ts index 9f209e40d..b9ff8989e 100644 --- a/packages/server/src/services/Purchases/Bills/BillsValidators.ts +++ b/packages/server/src/services/Purchases/Bills/BillsValidators.ts @@ -23,12 +23,12 @@ export class BillsValidators { /** * Validates the bill amount is bigger than paid amount. - * @param {number} billAmount - * @param {number} paidAmount + * @param {number} billAmount + * @param {number} paidAmount */ public validateBillAmountBiggerPaidAmount( billAmount: number, - paidAmount: number, + paidAmount: number ) { if (billAmount < paidAmount) { throw new ServiceError(ERRORS.BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT); @@ -53,7 +53,10 @@ export class BillsValidators { }); if (foundBills.length > 0) { - throw new ServiceError(ERRORS.BILL_NUMBER_EXISTS); + throw new ServiceError( + ERRORS.BILL_NUMBER_EXISTS, + 'The bill number is not unique.' + ); } } diff --git a/packages/server/src/services/Purchases/Bills/constants.ts b/packages/server/src/services/Purchases/Bills/constants.ts index 9cc8566c8..fe61c5857 100644 --- a/packages/server/src/services/Purchases/Bills/constants.ts +++ b/packages/server/src/services/Purchases/Bills/constants.ts @@ -75,3 +75,49 @@ export const DEFAULT_VIEWS = [ 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.', + }, +]; diff --git a/packages/server/src/services/Resource/ResourceService.ts b/packages/server/src/services/Resource/ResourceService.ts index edf97cd70..653599d05 100644 --- a/packages/server/src/services/Resource/ResourceService.ts +++ b/packages/server/src/services/Resource/ResourceService.ts @@ -105,11 +105,14 @@ export default class ResourceService { const $enumerationType = (field) => field.fieldType === 'enumeration' ? field : undefined; + const $hasFields = (field) => 'undefined' !== typeof field.fields ? field : undefined; + const naviagations = [ ['fields', qim.$each, 'name'], - ['fields2', qim.$each, 'name'], ['fields', qim.$each, $enumerationType, 'options', qim.$each, 'label'], + ['fields2', qim.$each, 'name'], ['fields2', qim.$each, $enumerationType, 'options', qim.$each, 'label'], + ['fields2', qim.$each, $hasFields, 'fields', qim.$each, 'name'], ]; return this.i18nService.i18nApply(naviagations, meta, tenantId); } diff --git a/packages/server/src/services/Sales/Estimates/CreateSaleEstimate.ts b/packages/server/src/services/Sales/Estimates/CreateSaleEstimate.ts index 190962151..c7fed6e36 100644 --- a/packages/server/src/services/Sales/Estimates/CreateSaleEstimate.ts +++ b/packages/server/src/services/Sales/Estimates/CreateSaleEstimate.ts @@ -43,7 +43,8 @@ export class CreateSaleEstimate { */ public async createEstimate( tenantId: number, - estimateDTO: ISaleEstimateDTO + estimateDTO: ISaleEstimateDTO, + trx?: Knex.Transaction ): Promise { const { SaleEstimate, Contact } = this.tenancy.models(tenantId); @@ -75,28 +76,32 @@ export class CreateSaleEstimate { estimateDTO.entries ); // Creates a sale estimate transaction with associated transactions as UOW. - return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { - // Triggers `onSaleEstimateCreating` event. - await this.eventPublisher.emitAsync(events.saleEstimate.onCreating, { - estimateDTO, - tenantId, - trx, - } as ISaleEstimateCreatingPayload); + return this.uow.withTransaction( + tenantId, + async (trx: Knex.Transaction) => { + // Triggers `onSaleEstimateCreating` event. + await this.eventPublisher.emitAsync(events.saleEstimate.onCreating, { + estimateDTO, + tenantId, + trx, + } as ISaleEstimateCreatingPayload); - // Upsert the sale estimate graph to the storage. - const saleEstimate = await SaleEstimate.query(trx).upsertGraphAndFetch({ - ...estimateObj, - }); - // Triggers `onSaleEstimateCreated` event. - await this.eventPublisher.emitAsync(events.saleEstimate.onCreated, { - tenantId, - saleEstimate, - saleEstimateId: saleEstimate.id, - saleEstimateDTO: estimateDTO, - trx, - } as ISaleEstimateCreatedPayload); + // Upsert the sale estimate graph to the storage. + const saleEstimate = await SaleEstimate.query(trx).upsertGraphAndFetch({ + ...estimateObj, + }); + // Triggers `onSaleEstimateCreated` event. + await this.eventPublisher.emitAsync(events.saleEstimate.onCreated, { + tenantId, + saleEstimate, + saleEstimateId: saleEstimate.id, + saleEstimateDTO: estimateDTO, + trx, + } as ISaleEstimateCreatedPayload); - return saleEstimate; - }); + return saleEstimate; + }, + trx + ); } } diff --git a/packages/server/src/services/Sales/Estimates/SaleEstimateValidators.ts b/packages/server/src/services/Sales/Estimates/SaleEstimateValidators.ts index 447df7795..b37ca1298 100644 --- a/packages/server/src/services/Sales/Estimates/SaleEstimateValidators.ts +++ b/packages/server/src/services/Sales/Estimates/SaleEstimateValidators.ts @@ -41,7 +41,10 @@ export class SaleEstimateValidators { } }); 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.' + ); } } diff --git a/packages/server/src/services/Sales/Estimates/SaleEstimatesImportable.ts b/packages/server/src/services/Sales/Estimates/SaleEstimatesImportable.ts new file mode 100644 index 000000000..d0bcdaf9e --- /dev/null +++ b/packages/server/src/services/Sales/Estimates/SaleEstimatesImportable.ts @@ -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; + } +} diff --git a/packages/server/src/services/Sales/Estimates/constants.ts b/packages/server/src/services/Sales/Estimates/constants.ts index 6b689a0e1..1870c01d2 100644 --- a/packages/server/src/services/Sales/Estimates/constants.ts +++ b/packages/server/src/services/Sales/Estimates/constants.ts @@ -122,3 +122,54 @@ export const DEFAULT_VIEWS = [ 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.', + }, +]; diff --git a/packages/server/src/services/Sales/Invoices/CreateSaleInvoice.ts b/packages/server/src/services/Sales/Invoices/CreateSaleInvoice.ts index 2fb6c0176..38ac09e8f 100644 --- a/packages/server/src/services/Sales/Invoices/CreateSaleInvoice.ts +++ b/packages/server/src/services/Sales/Invoices/CreateSaleInvoice.ts @@ -51,7 +51,8 @@ export class CreateSaleInvoice { public createSaleInvoice = async ( tenantId: number, saleInvoiceDTO: ISaleInvoiceCreateDTO, - authorizedUser: ITenantUser + authorizedUser: ITenantUser, + trx?: Knex.Transaction ): Promise => { const { SaleInvoice, SaleEstimate, Contact } = this.tenancy.models(tenantId); @@ -96,33 +97,37 @@ export class CreateSaleInvoice { ); } // Creates a new sale invoice and associated transactions under unit of work env. - return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { - // Triggers `onSaleInvoiceCreating` event. - await this.eventPublisher.emitAsync(events.saleInvoice.onCreating, { - saleInvoiceDTO, - tenantId, - trx, - } as ISaleInvoiceCreatingPaylaod); + return this.uow.withTransaction( + tenantId, + async (trx: Knex.Transaction) => { + // Triggers `onSaleInvoiceCreating` event. + await this.eventPublisher.emitAsync(events.saleInvoice.onCreating, { + saleInvoiceDTO, + tenantId, + trx, + } as ISaleInvoiceCreatingPaylaod); - // Create sale invoice graph to the storage. - const saleInvoice = await SaleInvoice.query(trx).upsertGraph( - saleInvoiceObj - ); - const eventPayload: ISaleInvoiceCreatedPayload = { - tenantId, - saleInvoice, - saleInvoiceDTO, - saleInvoiceId: saleInvoice.id, - authorizedUser, - trx, - }; - // Triggers the event `onSaleInvoiceCreated`. - await this.eventPublisher.emitAsync( - events.saleInvoice.onCreated, - eventPayload - ); - return saleInvoice; - }); + // Create sale invoice graph to the storage. + const saleInvoice = await SaleInvoice.query(trx).upsertGraph( + saleInvoiceObj + ); + const eventPayload: ISaleInvoiceCreatedPayload = { + tenantId, + saleInvoice, + saleInvoiceDTO, + saleInvoiceId: saleInvoice.id, + authorizedUser, + trx, + }; + // Triggers the event `onSaleInvoiceCreated`. + await this.eventPublisher.emitAsync( + events.saleInvoice.onCreated, + eventPayload + ); + return saleInvoice; + }, + trx + ); }; /** diff --git a/packages/server/src/services/Sales/Invoices/SaleInvoicesImportable.ts b/packages/server/src/services/Sales/Invoices/SaleInvoicesImportable.ts new file mode 100644 index 000000000..d6281cd53 --- /dev/null +++ b/packages/server/src/services/Sales/Invoices/SaleInvoicesImportable.ts @@ -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 []; + } +} diff --git a/packages/webapp/src/containers/Expenses/ExpensesImport.tsx b/packages/webapp/src/containers/Expenses/ExpensesImport.tsx new file mode 100644 index 000000000..d6da7e0e5 --- /dev/null +++ b/packages/webapp/src/containers/Expenses/ExpensesImport.tsx @@ -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 ( + + + + ); +} diff --git a/packages/webapp/src/containers/Expenses/ExpensesLanding/ExpenseActionsBar.tsx b/packages/webapp/src/containers/Expenses/ExpensesLanding/ExpenseActionsBar.tsx index 17a9e17be..e5c7368e4 100644 --- a/packages/webapp/src/containers/Expenses/ExpensesLanding/ExpenseActionsBar.tsx +++ b/packages/webapp/src/containers/Expenses/ExpensesLanding/ExpenseActionsBar.tsx @@ -79,6 +79,11 @@ function ExpensesActionsBar({ refresh(); }; + // Handle the import button click. + const handleImportBtnClick = () => { + history.push('/expenses/import'); + } + // Handle table row size change. const handleTableRowSizeChange = (size) => { addSetting('expenses', 'tableSize', size); @@ -135,6 +140,7 @@ function ExpensesActionsBar({ className={Classes.MINIMAL} icon={} text={} + onClick={handleImportBtnClick} />