fix: subtitle issue with sales transaction number.

This commit is contained in:
Ahmed Bouhuolia
2020-11-24 11:33:29 +02:00
parent 218d90c571
commit 9779591029
24 changed files with 218 additions and 76 deletions

View File

@@ -27,7 +27,12 @@ import Dragzone from 'components/Dragzone';
import withMediaActions from 'containers/Media/withMediaActions';
import useMedia from 'hooks/useMedia';
import { compose, repeatValue, orderingLinesIndexes } from 'utils';
import {
compose,
repeatValue,
orderingLinesIndexes,
defaultToTransform,
} from 'utils';
import withManualJournalsActions from './withManualJournalsActions';
import withManualJournals from './withManualJournals';
@@ -97,13 +102,18 @@ function MakeJournalEntriesForm({
: journalNextNumber;
useEffect(() => {
const transactionNumber = manualJournal
? manualJournal.journal_number
: journalNumber;
if (manualJournal && manualJournal.id) {
changePageTitle(formatMessage({ id: 'edit_journal' }));
changePageSubtitle(`No. ${manualJournal.journal_number}`);
} else {
changePageSubtitle(`No. ${journalNumber}`);
changePageTitle(formatMessage({ id: 'new_journal' }));
}
changePageSubtitle(
defaultToTransform(transactionNumber, `No. ${transactionNumber}`, ''),
);
}, [
changePageTitle,
changePageSubtitle,
@@ -383,7 +393,9 @@ function MakeJournalEntriesForm({
useEffect(() => {
if (journalNumberChanged) {
setFieldValue('journal_number', journalNumber);
changePageSubtitle(`No. ${journalNumber}`);
changePageSubtitle(
defaultToTransform(journalNumber, `No. ${journalNumber}`, ''),
);
setJournalNumberChanged(false);
}
}, [
@@ -440,7 +452,9 @@ function MakeJournalEntriesForm({
// Handle journal number field change.
const handleJournalNumberChanged = useCallback(
(journalNumber) => {
changePageSubtitle(`No. ${journalNumber}`);
changePageSubtitle(
defaultToTransform(journalNumber, `No. ${journalNumber}`, '')
);
},
[changePageSubtitle],
);

View File

@@ -345,6 +345,7 @@ function AccountFormDialogContent({
onAccountSelected={onChangeSubaccount}
defaultSelectText={<T id={'select_parent_account'} />}
selectedAccountId={values.parent_account_id}
popoverFill={true}
/>
</FormGroup>
</If>

View File

@@ -49,7 +49,7 @@ function PaymentMadeActionsBar({
const { formatMessage } = useIntl();
const handleClickNewPaymentMade = useCallback(() => {
history.push('/payment-made/new');
history.push('/payment-mades/new');
}, [history]);
// const filterDropdown = FilterDropdown({

View File

@@ -13,6 +13,7 @@ const Schema = Yup.object().shape({
.required()
.label(formatMessage({ id: 'payment_account_' })),
payment_number: Yup.string()
.nullable()
.max(DATATYPES_LENGTH.STRING)
.label(formatMessage({ id: 'payment_no_' })),
reference: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(),

View File

@@ -107,6 +107,7 @@ function PaymentMadeFormHeader({
defaultSelectText={ <T id={'select_vender_account'} /> }
onContactSelected={onChangeSelect('vendor_id')}
disabled={!isNewMode}
popoverFill={true}
/>
</FormGroup>

View File

@@ -31,7 +31,12 @@ import Dragzone from 'components/Dragzone';
import useMedia from 'hooks/useMedia';
import { ERROR } from 'common/errors';
import { compose, repeatValue, orderingLinesIndexes } from 'utils';
import {
compose,
repeatValue,
defaultToTransform,
orderingLinesIndexes,
} from 'utils';
const MIN_LINES_NUMBER = 4;
@@ -98,16 +103,20 @@ const EstimateForm = ({
: estimateNextNumber;
useEffect(() => {
if (estimate && estimate.id) {
const transNumber = !isNewMode ? estimate.estimate_number : estimateNumber;
if (isNewMode) {
changePageTitle(formatMessage({ id: 'edit_estimate' }));
changePageSubtitle(`No. ${estimate.estimate_number}`);
} else {
changePageSubtitle(`No. ${estimateNumber}`);
changePageTitle(formatMessage({ id: 'new_estimate' }));
}
changePageSubtitle(
defaultToTransform(estimateNumber, `No. ${transNumber}`, ''),
);
}, [
estimate,
estimateNumber,
isNewMode,
formatMessage,
changePageTitle,
changePageSubtitle,
@@ -211,18 +220,26 @@ const EstimateForm = ({
const handleEstimateNumberChange = useCallback(
(estimateNumber) => {
changePageSubtitle(`No. ${estimateNumber}`);
changePageSubtitle(
defaultToTransform(estimateNumber, `No. ${estimateNumber}`, ''),
);
},
[changePageSubtitle],
);
const handleSubmitClick = useCallback((event) => {
setSubmitPayload({ redirect: true });
}, [setSubmitPayload]);
const handleSubmitClick = useCallback(
(event) => {
setSubmitPayload({ redirect: true });
},
[setSubmitPayload],
);
const handleCancelClick = useCallback((event) => {
history.goBack();
}, [history]);
const handleCancelClick = useCallback(
(event) => {
history.goBack();
},
[history],
);
return (
<div className={classNames(CLASSES.PAGE_FORM, CLASSES.PAGE_FORM_ESTIMATE)}>
@@ -238,8 +255,8 @@ const EstimateForm = ({
<EstimateFormHeader
onEstimateNumberChanged={handleEstimateNumberChange}
/>
<EstimateNumberWatcher estimateNumber={estimateNumber} />
<EditableItemsEntriesTable />
<EstimateNumberWatcher estimateNumber={estimateNumber} />
<EditableItemsEntriesTable filterSellableItems={true} />
<EstimateFormFooter />
<EstimateFloatingActions
isSubmiting={isSubmitting}

View File

@@ -66,6 +66,7 @@ function EstimateFormHeader({
onContactSelected={(customer) => {
form.setFieldValue('customer_id', customer.id);
}}
popoverFill={true}
/>
</FormGroup>
)}

View File

@@ -30,7 +30,12 @@ import { AppToaster } from 'components';
import useMedia from 'hooks/useMedia';
import { ERROR } from 'common/errors';
import { compose, repeatValue, saveInvoke, orderingLinesIndexes } from 'utils';
import {
compose,
repeatValue,
defaultToTransform,
orderingLinesIndexes,
} from 'utils';
import { useHistory } from 'react-router-dom';
const MIN_LINES_NUMBER = 4;
@@ -94,13 +99,16 @@ function InvoiceForm({
: invoiceNextNumber;
useEffect(() => {
const transactionNumber = invoice ? invoice.invoice_no : invoiceNumber;
if (invoice && invoice.id) {
changePageTitle(formatMessage({ id: 'edit_invoice' }));
changePageSubtitle(`No. ${invoice.invoice_no}`);
} else {
changePageSubtitle(`No. ${invoiceNumber}`);
changePageTitle(formatMessage({ id: 'new_invoice' }));
}
changePageSubtitle(
defaultToTransform(transactionNumber, `No. ${transactionNumber}`, ''),
);
}, [
changePageTitle,
changePageSubtitle,
@@ -216,7 +224,9 @@ function InvoiceForm({
const handleInvoiceNumberChanged = useCallback(
(invoiceNumber) => {
changePageSubtitle(`No. ${invoiceNumber}`);
changePageSubtitle(
defaultToTransform(invoiceNumber, `No. ${invoiceNumber}`, ''),
);
},
[changePageSubtitle],
);
@@ -236,7 +246,10 @@ function InvoiceForm({
onInvoiceNumberChanged={handleInvoiceNumberChanged}
/>
<InvoiceNumberChangeWatcher invoiceNumber={invoiceNumber} />
<EditableItemsEntriesTable defaultEntry={defaultInvoice} />
<EditableItemsEntriesTable
defaultEntry={defaultInvoice}
filterSellableItems={true}
/>
<InvoiceFormFooter />
<InvoiceFloatingActions
isSubmitting={isSubmitting}

View File

@@ -66,6 +66,7 @@ function InvoiceFormHeader({
onContactSelected={(customer) => {
form.setFieldValue('customer_id', customer.id);
}}
popoverFill={true}
/>
</FormGroup>
)}

View File

@@ -49,7 +49,7 @@ function PaymentReceiveActionsBar({
const { formatMessage } = useIntl();
const handleClickNewPaymentReceive = useCallback(() => {
history.push('/payment-receive/new');
history.push('/payment-receives/new');
}, [history]);
// const filterDropdown = FilterDropdown({

View File

@@ -1,18 +1,11 @@
import React, {
useMemo,
useCallback,
useEffect,
useState,
useRef,
} from 'react';
import * as Yup from 'yup';
import React, { useMemo, useCallback, useEffect, useState } from 'react';
import { useFormik } from 'formik';
import moment from 'moment';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { pick, sumBy, omit } from 'lodash';
import { Intent, Alert } from '@blueprintjs/core';
import classNames from 'classnames';
import { useHistory } from 'react-router-dom';
import { CLASSES } from 'common/classes';
import PaymentReceiveHeader from './PaymentReceiveFormHeader';
@@ -32,7 +25,7 @@ import {
} from './PaymentReceiveForm.schema';
import { AppToaster } from 'components';
import { compose } from 'utils';
import { compose, defaultToTransform } from 'utils';
/**
* Payment Receive form.
@@ -62,35 +55,45 @@ function PaymentReceiveForm({
changePageTitle,
changePageSubtitle,
}) {
const history = useHistory();
const [amountChangeAlert, setAmountChangeAlert] = useState(false);
const [clearLinesAlert, setClearLinesAlert] = useState(false);
const [fullAmount, setFullAmount] = useState(null);
const [clearFormAlert, setClearFormAlert] = useState(false);
const { formatMessage } = useIntl();
const isNewMode = !paymentReceiveId;
const [localPaymentEntries, setLocalPaymentEntries] = useState(
paymentReceiveEntries,
);
const isNewMode = !paymentReceiveId;
const paymentReceiveNumber = paymentReceiveNumberPrefix
? `${paymentReceiveNumberPrefix}-${paymentReceiveNextNumber}`
: paymentReceiveNextNumber;
useEffect(() => {
const transactionNumber = !isNewMode
? paymentReceive.payment_receive_no
: paymentReceiveNumber;
if (paymentReceive && paymentReceiveId) {
changePageTitle(formatMessage({ id: 'edit_payment_receive' }));
changePageSubtitle(`No. ${paymentReceive.payment_receive_no}`);
} else {
changePageSubtitle(`No. ${paymentReceiveNumber}`);
changePageTitle(formatMessage({ id: 'payment_receive' }));
}
changePageSubtitle(
defaultToTransform(transactionNumber, `No. ${transactionNumber}`, ''),
);
}, [
isNewMode,
changePageTitle,
changePageSubtitle,
paymentReceive,
paymentReceiveId,
formatMessage,
paymentReceiveNumber,
]);
useEffect(() => {
@@ -103,7 +106,7 @@ function PaymentReceiveForm({
const validationSchema = isNewMode
? CreatePaymentReceiveFormSchema
: EditPaymentReceiveFormSchema;
// Default payment receive entry.
const defaultPaymentReceiveEntry = {
id: null,
@@ -167,8 +170,8 @@ function PaymentReceiveForm({
AppToaster.show({
message: formatMessage({
id: 'you_cannot_make_payment_with_zero_total_amount',
intent: Intent.WARNING,
}),
intent: Intent.DANGER,
});
setSubmitting(false);
return;
@@ -180,13 +183,14 @@ function PaymentReceiveForm({
AppToaster.show({
message: formatMessage({
id: paymentReceiveId
? 'the_payment_has_been_received_successfully_edited'
: 'the_payment_has_been_received_successfully_created',
? 'the_payment_receive_transaction_has_been_edited'
: 'the_payment_receive_transaction_has_been_created',
}),
intent: Intent.SUCCESS,
});
setSubmitting(false);
resetForm();
history.push('/payment-receives');
};
// Handle request response errors.
const onError = (errors) => {
@@ -321,7 +325,13 @@ function PaymentReceiveForm({
useEffect(() => {
if (paymentReceiveNumberChanged) {
setFieldValue('payment_receive_no', paymentReceiveNumber);
changePageSubtitle(`No. ${paymentReceiveNumber}`);
changePageSubtitle(
defaultToTransform(
paymentReceiveNumber,
`No. ${paymentReceiveNumber}`,
'',
),
);
setPaymentReceiveNumberChanged(false);
}
}, [
@@ -329,11 +339,14 @@ function PaymentReceiveForm({
paymentReceiveNumberChanged,
setFieldValue,
changePageSubtitle,
setPaymentReceiveNumberChanged,
]);
const handlePaymentReceiveNumberChanged = useCallback(
(payment_receive_no) => {
changePageSubtitle(`No.${payment_receive_no}`);
changePageSubtitle(
defaultToTransform(payment_receive_no, `No.${payment_receive_no}`, ''),
);
},
[changePageSubtitle],
);

View File

@@ -14,6 +14,7 @@ const Schema = Yup.object().shape({
.label(formatMessage({ id: 'deposit_account_' })),
full_amount: Yup.number().nullable(),
payment_receive_no: Yup.string()
.nullable()
.max(DATATYPES_LENGTH.STRING)
.label(formatMessage({ id: 'payment_receive_no_' })),
reference_no: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(),

View File

@@ -114,6 +114,7 @@ function PaymentReceiveFormHeader({
selectedContactId={values.customer_id}
defaultSelectText={<T id={'select_customer_account'} />}
onContactSelected={onChangeSelect('customer_id')}
popoverFill={true}
/>
</FormGroup>

View File

@@ -21,7 +21,7 @@ export default function PaymentReceivesEmptyStatus() {
intent={Intent.PRIMARY}
large={true}
onClick={() => {
history.push('/payment-receive/new');
history.push('/payment-receives/new');
}}
>
New payment receive

View File

@@ -32,7 +32,12 @@ import { AppToaster } from 'components';
import Dragzone from 'components/Dragzone';
import useMedia from 'hooks/useMedia';
import { compose, repeatValue, orderingLinesIndexes } from 'utils';
import {
compose,
repeatValue,
orderingLinesIndexes,
defaultToTransform,
} from 'utils';
const MIN_LINES_NUMBER = 4;
@@ -97,14 +102,20 @@ function ReceiptForm({
: receiptNextNumber;
useEffect(() => {
const transactionNumber = !isNewMode
? receipt.receipt_number
: receiptNumber;
if (receipt && receipt.id) {
changePageTitle(formatMessage({ id: 'edit_receipt' }));
changePageSubtitle(`No. ${receipt.receipt_number}`);
} else {
changePageSubtitle(`No. ${receiptNumber}`);
changePageTitle(formatMessage({ id: 'new_receipt' }));
}
changePageSubtitle(
defaultToTransform(transactionNumber, `No. ${transactionNumber}`, ''),
);
}, [
isNewMode,
changePageTitle,
changePageSubtitle,
receipt,
@@ -213,7 +224,9 @@ function ReceiptForm({
const handleReceiptNumberChanged = useCallback(
(receiptNumber) => {
changePageSubtitle(`No. ${receiptNumber}`);
changePageSubtitle(
defaultToTransform(receiptNumber, `No. ${receiptNumber}`, ''),
);
},
[changePageSubtitle],
);
@@ -244,7 +257,7 @@ function ReceiptForm({
onReceiptNumberChanged={handleReceiptNumberChanged}
/>
<ReceiptNumberWatcher receiptNumber={receiptNumber} />
<EditableItemsEntriesTable />
<EditableItemsEntriesTable filterSellableItems={true} />
<ReceiptFormFooter />
<ReceiptFormFloatingActions
receiptId={receiptId}

View File

@@ -76,6 +76,7 @@ function ReceiptFormHeader({
onContactSelected={(contact) => {
form.setFieldValue('customer_id', contact.id);
}}
popoverFill={true}
/>
</FormGroup>
)}
@@ -103,6 +104,7 @@ function ReceiptFormHeader({
defaultSelectText={<T id={'select_deposit_account'} />}
selectedAccountId={value}
filterByTypes={['current_asset']}
popoverFill={true}
/>
</FormGroup>
)}

View File

@@ -91,7 +91,6 @@ export default {
count: 'Count',
item_type: 'Item Type',
item_name: 'Item Name',
sku: 'SKU',
category: 'Category',
account: 'Account',
sales_information: 'Sales Information',
@@ -689,12 +688,12 @@ export default {
payment_receive_no_: 'Payment receive no',
receive_amount: 'Receive Amount',
receive_amount_: 'Receive amount',
the_payment_has_been_received_successfully_created:
'The payment has been received successfully created.',
the_payment_receive_transaction_has_been_created:
'The payment receive transaction has been created successfully.',
the_payment_receive_has_been_successfully_deleted:
'The payment receive has been successfully deleted.',
the_payment_has_been_received_successfully_edited:
'The payment has been received successfully edited.',
the_payment_receive_transaction_has_been_edited:
'The payment receive transaction has been edited successfully.',
once_delete_this_payment_receive_you_will_able_to_restore_it: `Once you delete this payment receive, you won\'t be able to restore it later. Are you sure you want to delete this payment receive?`,
select_invoice: 'Select Invoice',
payment_mades: 'Payment Mades',
@@ -841,4 +840,6 @@ export default {
once_delete_these_vendors_you_will_not_able_restore_them:
"Once you delete these vendors, you won't be able to retrieve them later. Are you sure you want to delete them?",
vendor_has_bills: 'Vendor has bills',
the_item_has_been_edited_successfully: 'The item has been edited successfully.',
you_cannot_make_payment_with_zero_total_amount: 'You cannot record payment transaction with zero total amount',
};

View File

@@ -89,13 +89,17 @@ $sidebar-submenu-item-bg-color: #01287d;
margin-top: 3px;
color: rgba(255, 255, 255, 0.25);
}
&-label{
&-labeler{
display: block;
color: $sidebar-menu-label-color;
font-size: 12px;
padding: 6px 16px;
margin-top: 4px;
}
&:hover .bp3-button.menu-item__add-btn{
display: inline-block;
}
}
.#{$ns}-submenu {
@@ -188,4 +192,42 @@ $sidebar-submenu-item-bg-color: #01287d;
}
}
}
.bp3-button.menu-item__add-btn{
width: auto;
padding: 2px;
margin-right: 0px;
position: relative;
top: 1px;
border-radius: 3px;
display: none;
vertical-align: top;
&:not([class*="bp3-intent-"]):not(.bp3-minimal):not(:disabled){
.bp3-icon{
color: rgba(255, 255, 255, 0.4);
}
&,
&:hover{
min-height: auto;
min-width: auto;
outline: 0;
background-color: transparent;
}
&:hover{
background-color: rgba(255, 255, 255, 0.12);
.bp3-icon{
color: rgba(255, 255, 255, 0.6);
}
}
}
.bp3-icon{
margin: 0;
display: block;
}
}
}

View File

@@ -1,4 +1,4 @@
import { check, param, query, ValidationChain } from 'express-validator';
import { check, param, query, body, ValidationChain } from 'express-validator';
import BaseController from "api/controllers/BaseController";
export default class ContactsController extends BaseController {
@@ -48,7 +48,8 @@ export default class ContactsController extends BaseController {
*/
get contactNewDTOSchema(): ValidationChain[] {
return [
check('balance').optional().isNumeric().toInt(),
check('opening_balance').optional({ nullable: true }).isNumeric().toInt(),
body('opening_balance_at').if(body('opening_balance').exists()).exists(),
];
}

View File

@@ -90,9 +90,6 @@ export default class CustomersController extends ContactsController {
*/
get createCustomerDTOSchema() {
return [
check('opening_balance').optional({ nullable: true }).isNumeric().toInt(),
check('opening_balance_at').optional({ nullable: true }).isISO8601(),
check('currency_code').optional().trim().escape(),
];
}

View File

@@ -1,6 +1,7 @@
import { Request, Response, Router, NextFunction } from 'express';
import { Service, Inject } from 'typedi';
import { check, query, ValidationChain } from 'express-validator';
import { body, query, ValidationChain, check } from 'express-validator';
import ContactsController from 'api/controllers/Contacts/Contacts';
import VendorsService from 'services/Contacts/VendorsService';
import { ServiceError } from 'exceptions';
@@ -72,7 +73,7 @@ export default class VendorsController extends ContactsController {
*/
get vendorDTOSchema(): ValidationChain[] {
return [
check('opening_balance').optional().isNumeric().toInt(),
check('currency_code').optional().trim().escape(),
];
}
@@ -105,7 +106,11 @@ export default class VendorsController extends ContactsController {
try {
const vendor = await this.vendorsService.newVendor(tenantId, contactDTO);
return res.status(200).send({ id: vendor.id });
return res.status(200).send({
id: vendor.id,
message: 'The vendor has been created successfully.',
});
} catch (error) {
next(error);
}
@@ -124,7 +129,11 @@ export default class VendorsController extends ContactsController {
try {
await this.vendorsService.editVendor(tenantId, contactId, contactDTO);
return res.status(200).send({ id: contactId });
return res.status(200).send({
id: contactId,
message: 'The vendor has been edited successfully.',
});
} catch (error) {
next(error);
}
@@ -142,7 +151,11 @@ export default class VendorsController extends ContactsController {
try {
await this.vendorsService.deleteVendor(tenantId, contactId)
return res.status(200).send({ id: contactId });
return res.status(200).send({
id: contactId,
message: 'The vendor has been deleted successfully.',
});
} catch (error) {
next(error);
}
@@ -198,7 +211,11 @@ export default class VendorsController extends ContactsController {
};
try {
const { vendors, pagination, filterMeta } = await this.vendorsService.getVendorsList(tenantId, vendorsFilter);
const {
vendors,
pagination,
filterMeta,
} = await this.vendorsService.getVendorsList(tenantId, vendorsFilter);
return res.status(200).send({
vendors,
@@ -219,21 +236,27 @@ export default class VendorsController extends ContactsController {
*/
handlerServiceErrors(error, req: Request, res: Response, next: NextFunction) {
if (error instanceof ServiceError) {
if (error.errorType === 'contact_not_found') {
return res.boom.badRequest(null, {
errors: [{ type: 'VENDOR.NOT.FOUND', code: 100 }],
});
}
if (error.errorType === 'contacts_not_found') {
return res.boom.badRequest(null, {
errors: [{ type: 'VENDORS.NOT.FOUND', code: 100 }],
errors: [{ type: 'VENDORS.NOT.FOUND', code: 200 }],
});
}
if (error.errorType === 'some_vendors_have_bills') {
return res.boom.badRequest(null, {
errors: [{ type: 'SOME.VENDORS.HAVE.BILLS', code: 200 }],
errors: [{ type: 'SOME.VENDORS.HAVE.BILLS', code: 300 }],
});
}
if (error.errorType === 'vendor_has_bills') {
return res.status(400).send({
errors: [{ type: 'VENDOR.HAS.BILLS', code: 200 }],
errors: [{ type: 'VENDOR.HAS.BILLS', code: 400 }],
});
}
}
next(error);
}
}

View File

@@ -84,7 +84,7 @@ export default class PaymentReceivesController extends BaseController {
check('payment_date').exists(),
check('reference_no').optional(),
check('deposit_account_id').exists().isNumeric().toInt(),
check('payment_receive_no').exists().trim().escape(),
check('payment_receive_no').optional({ nullable: true }).trim().escape(),
check('statement').optional().trim().escape(),
check('entries').isArray({ min: 1 }),

View File

@@ -1,5 +1,5 @@
import { Inject, Service } from 'typedi';
import { omit, difference } from 'lodash';
import { omit, difference, defaultTo } from 'lodash';
import {
EventDispatcher,
EventDispatcherInterface,
@@ -51,8 +51,7 @@ export default class CustomersService {
return {
...omit(customerDTO, ['customerType']),
contactType: customerDTO.customerType,
active: (typeof customerDTO.active === 'undefined') ?
true : customerDTO.active,
active: defaultTo(customerDTO.active, true),
};
}

View File

@@ -1,5 +1,5 @@
import { Inject, Service } from 'typedi';
import { difference, rest } from 'lodash';
import { difference, defaultTo } from 'lodash';
import {
EventDispatcher,
EventDispatcherInterface,
@@ -45,8 +45,7 @@ export default class VendorsService {
private vendorToContactDTO(vendorDTO: IVendorNewDTO|IVendorEditDTO) {
return {
...vendorDTO,
active: (typeof vendorDTO.active === 'undefined') ?
true : vendorDTO.active,
active: defaultTo(vendorDTO.active, true),
};
}
@@ -62,6 +61,7 @@ export default class VendorsService {
const contactDTO = this.vendorToContactDTO(vendorDTO);
const vendor = await this.contactService.newContact(tenantId, contactDTO, 'vendor');
// Triggers `onVendorCreated` event.
await this.eventDispatcher.dispatch(events.vendors.onCreated, {
tenantId, vendorId: vendor.id, vendor,
});