feat: customer form styling.

feat: customers list.
This commit is contained in:
Ahmed Bouhuolia
2020-11-08 21:44:20 +02:00
parent 982420c8e5
commit 9241f8b8a5
17 changed files with 305 additions and 282 deletions

View File

@@ -12,8 +12,6 @@ import {
Checkbox,
Spinner,
ContextMenu,
Menu,
MenuItem,
} from '@blueprintjs/core';
import classnames from 'classnames';
import { FixedSizeList } from 'react-window';

View File

@@ -12,11 +12,7 @@ const CustomerBillingAddress = ({
getFieldProps,
}) => {
return (
<div
className={
'customer-form__tabs-section customer-form__tabs-section--address'
}
>
<div className={'tab-panel--address'}>
<Row>
<Col xs={6}>
<h4>

View File

@@ -28,7 +28,7 @@ export default function CustomerFinancialPanel({
);
return (
<div className={'customer-form__tabs-section customer-form__tabs-section--financial'}>
<div className={'tab-panel--financial'}>
<Row>
<Col xs={6}>
<FormGroup

View File

@@ -143,10 +143,46 @@ function CustomerForm({
useEffect(() => {
customer && customer.id
? changePageTitle(formatMessage({ id: 'edit_customer_details' }))
? changePageTitle(formatMessage({ id: 'edit_customer' }))
: changePageTitle(formatMessage({ id: 'new_customer' }));
}, [changePageTitle, customer, formatMessage]);
const handleFormSubmit = (values, { setSubmitting, resetForm, setErrors }) => {
const formValues = { ...values, status: payload.publish };
if (customer && customer.id) {
requestEditCustomer(customer.id, formValues)
.then((response) => {
AppToaster.show({
message: formatMessage({
id: 'the_item_customer_has_been_successfully_edited',
}),
intent: Intent.SUCCESS,
});
setSubmitting(false);
resetForm();
saveInvokeSubmit({ action: 'update', ...payload });
})
.catch((errors) => {
setSubmitting(false);
});
} else {
requestSubmitCustomer(formValues)
.then((response) => {
AppToaster.show({
message: formatMessage({
id: 'the_customer_has_been_successfully_created',
}),
intent: Intent.SUCCESS,
});
setSubmitting(false);
saveInvokeSubmit({ action: 'new', ...payload });
})
.catch((errors) => {
setSubmitting(false);
});
}
};
const {
setFieldValue,
getFieldProps,
@@ -160,42 +196,7 @@ function CustomerForm({
initialValues: {
...initialValues,
},
onSubmit: (values, { setSubmitting, resetForm, setErrors }) => {
const formValues = { ...values, status: payload.publish };
if (customer && customer.id) {
requestEditCustomer(customer.id, formValues)
.then((response) => {
AppToaster.show({
message: formatMessage({
id: 'the_item_customer_has_been_successfully_edited',
}),
intent: Intent.SUCCESS,
});
setSubmitting(false);
resetForm();
saveInvokeSubmit({ action: 'update', ...payload });
})
.catch((errors) => {
setSubmitting(false);
});
} else {
requestSubmitCustomer(formValues)
.then((response) => {
AppToaster.show({
message: formatMessage({
id: 'the_customer_has_been_successfully_created',
}),
intent: Intent.SUCCESS,
});
// history.push('/customers');
setSubmitting(false);
saveInvokeSubmit({ action: 'new', ...payload });
})
.catch((errors) => {
setSubmitting(false);
});
}
},
onSubmit: handleFormSubmit,
});

View File

@@ -32,13 +32,14 @@ export default function CustomerFormPrimarySection({
);
// Handle salutation field select.
const handleSalutationSelect = (salutation) => {
const handleSalutationSelect = useCallback((salutation) => {
setFieldValue('salutation', salutation.label);
};
}, [setFieldValue]);
// Handle display name field select.
const handleDisplayNameSelect = (displayName) => {
const handleDisplayNameSelect = useCallback((displayName) => {
setFieldValue('display_name', displayName.label);
};
}, [setFieldValue]);
return (
<div className={'customer-form__primary-section-content'}>
@@ -116,6 +117,7 @@ export default function CustomerFormPrimarySection({
firstName={values.first_name}
lastName={values.last_name}
company={values.company_name}
salutation={values.salutation}
onItemSelect={handleDisplayNameSelect}
popoverProps={{ minimal: true }}
/>

View File

@@ -7,28 +7,21 @@ import ErrorMessage from 'components/ErrorMessage';
export default function CustomerNotePanel({ errors, touched, getFieldProps }) {
return (
<div
className={
'customer-form__tabs-section customer-form__tabs-section--note'
}
>
<Row>
<Col xs={6}>
<FormGroup
label={<T id={'note'} />}
className={classNames('form-group--select-list', Classes.FILL)}
intent={errors.note && touched.note && Intent.DANGER}
helperText={
<ErrorMessage name="payment_date" {...{ errors, touched }} />
}
>
<TextArea
intent={errors.note && touched.note && Intent.DANGER}
{...getFieldProps('note')}
/>
</FormGroup>
</Col>
</Row>
<div className={'tab-panel--note'}>
<FormGroup
label={<T id={'note'} />}
className={classNames('form-group--note', Classes.FILL)}
intent={errors.note && touched.note && Intent.DANGER}
helperText={
<ErrorMessage name="payment_date" {...{ errors, touched }} />
}
>
<TextArea
intent={errors.note && touched.note && Intent.DANGER}
{...getFieldProps('note')}
/>
</FormGroup>
</div>
);
}

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import React, { useState, useCallback, useMemo } from 'react';
import {
Button,
Popover,
@@ -6,17 +6,23 @@ import {
MenuItem,
MenuDivider,
Position,
Intent,
} from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import DataTable from 'components/DataTable';
import Icon from 'components/Icon';
import { Money } from 'components';
import { useUpdateEffect } from 'hooks';
import LoadingIndicator from 'components/LoadingIndicator';
import withCustomers from './withCustomers';
import { compose } from 'utils';
import { compose, firstLettersArgs, saveInvoke } from 'utils';
const AvatarCell = (row) => {
return <span className="avatar">{firstLettersArgs(row.display_name)}</span>;
};
const CustomerTable = ({
loading,
@@ -32,7 +38,6 @@ const CustomerTable = ({
onSelectedRowsChange,
}) => {
const { formatMessage } = useIntl();
const [initialMount, setInitialMount] = useState(false);
useUpdateEffect(() => {
@@ -41,36 +46,64 @@ const CustomerTable = ({
}
}, [customersLoading, setInitialMount]);
const handleEditCustomer = useCallback(
(customer) => () => {
onEditCustomer && onEditCustomer(customer);
// Customers actions list.
const renderContextMenu = useMemo(
() => ({ customer, onEditCustomer, onDeleteCustomer }) => {
const handleEditCustomer = () => {
saveInvoke(onEditCustomer, customer);
};
const handleDeleteCustomer = () => {
saveInvoke(onDeleteCustomer, customer);
};
return (
<Menu>
<MenuItem
icon={<Icon icon="reader-18" />}
text={formatMessage({ id: 'view_details' })}
/>
<MenuDivider />
<MenuItem
icon={<Icon icon="pen-18" />}
text={formatMessage({ id: 'edit_customer' })}
onClick={handleEditCustomer}
/>
<MenuItem
icon={<Icon icon="trash-16" iconSize={16} />}
text={formatMessage({ id: 'delete_customer' })}
intent={Intent.DANGER}
onClick={handleDeleteCustomer}
/>
</Menu>
);
},
[onEditCustomer],
[formatMessage],
);
const handleDeleteCustomer = useCallback(
(customer) => () => {
onDeleteCustomer && onDeleteCustomer(customer);
},
[onDeleteCustomer],
);
const actionMenuList = useCallback((customer) => (
<Menu>
<MenuItem text={<T id={'view_details'} />} />
<MenuDivider />
<MenuItem
text={<T id={'edit_customer'} />}
onClick={handleEditCustomer(customer)}
/>
<MenuItem
text={<T id={'delete_customer'} />}
onClick={handleDeleteCustomer(customer)}
/>
</Menu>
));
// Renders actions table cell.
const renderActionsCell = useMemo(() => ({ cell }) => (
<Popover
content={renderContextMenu({
customer: cell.row.original,
onEditCustomer,
onDeleteCustomer,
})}
position={Position.RIGHT_BOTTOM}
>
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
</Popover>
), [onDeleteCustomer, onEditCustomer, renderContextMenu]);
const columns = useMemo(
() => [
{
id: 'avatar',
Header: '',
accessor: AvatarCell,
className: 'avatar',
width: 50,
disableResizing: true,
disableSortBy: true,
},
{
id: 'display_name',
Header: formatMessage({ id: 'display_name' }),
@@ -95,26 +128,20 @@ const CustomerTable = ({
{
id: 'receivable_balance',
Header: formatMessage({ id: 'receivable_balance' }),
// accessor: '',
accessor: (r) => <Money amount={r.closing_balance} currency={'USD'} />,
className: 'receivable_balance',
width: 100,
},
{
id: 'actions',
Cell: ({ cell }) => (
<Popover
content={actionMenuList(cell.row.original)}
position={Position.RIGHT_BOTTOM}
>
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
</Popover>
),
Cell: renderActionsCell,
className: 'actions',
width: 50,
width: 70,
disableResizing: true,
disableSortBy: true,
},
],
[actionMenuList, formatMessage],
[formatMessage, renderActionsCell],
);
const selectionColumn = useMemo(
@@ -138,6 +165,13 @@ const CustomerTable = ({
[onSelectedRowsChange],
);
const rowContextMenu = (cell) =>
renderContextMenu({
customer: cell.row.original,
onEditCustomer,
onDeleteCustomer,
});
return (
<LoadingIndicator loading={loading} mount={false}>
<DataTable
@@ -146,11 +180,12 @@ const CustomerTable = ({
data={customers}
selectionColumn={selectionColumn}
onFetchData={handleFetchDate}
expandable={true}
treeGraph={true}
expandable={false}
treeGraph={false}
onSelectedRowsChange={handleSelectedRowsChange}
loading={customersLoading && !initialMount}
spinnerProps={{ size: 30 }}
rowContextMenu={rowContextMenu}
/>
</LoadingIndicator>
);

View File

@@ -135,11 +135,10 @@ function CustomersList({
filter_roles: filterConditions || '',
});
},
[fetchCustomers],
[addCustomersTableQueries],
);
// Handle Customers bulk delete button click.,
const handleBulkDelete = useCallback(
(customersIds) => {
setBulkDelete(customersIds);
@@ -184,7 +183,7 @@ function CustomersList({
<DashboardPageContent>
<CustomersTable
loadong={tableLoading}
loading={tableLoading}
onDeleteCustomer={handleDeleteCustomer}
onEditCustomer={handleEditCustomer}
onfetchData={handleFetchData}

View File

@@ -501,7 +501,6 @@ export default {
billing_address: 'Billing Address',
shipping_address: 'Shipping Address',
customers_list: 'Customers List',
edit_customer_details: 'Edit Customer Details',
receivable_balance: 'Receivable balance',
the_customer_has_been_successfully_created:
'The customer has been successfully created.',
@@ -804,4 +803,6 @@ export default {
' Changing full amount will change all credits and payment were applied, Is this okay?',
address_line_1: 'Address line 1',
address_line_2: 'Address line 2',
website: 'Website',
notes: 'Notes',
};

View File

@@ -47,7 +47,7 @@ export const fetchCustomers = ({ query }) => {
new Promise((resolve, reject) => {
const pageQuery = getState().items.tableQuery;
dispatch({
type: t.ITEMS_TABLE_LOADING,
type: t.CUSTOMERS_TABLE_LOADING,
payload: { loading: true },
});
ApiService.get(`customers`, { params: { ...pageQuery, ...query } })

View File

@@ -13,37 +13,36 @@
overflow: hidden;
}
#{$self}__header{
.bp3-form-group{
max-width: 500px;
.bp3-form-group{
max-width: 500px;
&.bp3-inline{
.bp3-label{
min-width: 150px;
}
.bp3-form-content{
width: 100%;
}
}
.bp3-form-content{
width: 100%;
}
}
.form-group--contact_name{
max-width: 100%;
.form-group--contact_name{
max-width: 600px;
.bp3-control-group > *{
flex-shrink: unset;
.bp3-control-group > *{
flex-shrink: unset;
&:not(:last-child) {
padding-right: 10px;
}
&:not(:last-child) {
padding-right: 10px;
}
&.input-group--salutation-list{
width: 25%;
}
&.input-group--first-name,
&.input-group--last-name{
width: 37%;
}
&.input-group--salutation-list{
width: 25%;
}
&.input-group--first-name,
&.input-group--last-name{
width: 37%;
}
}
}
@@ -76,29 +75,52 @@
margin-top: 20px;
max-width: 1000px;
.bp3-form-group{
max-width: 440px;
.bp3-label{
min-width: 145px;
}
.bp3-form-content{
width: 100%;
}
textarea.bp3-input{
max-width: 100%;
width: 100%;
min-height: 50px;
}
}
h4{
font-weight: 500;
color: #888;
margin-bottom: 1.2rem;
font-size: 14px;
}
// Tab panels.
.tab-panel{
&--address{
.bp3-form-group{
max-width: 440px;
&.bp3-inline{
.bp3-label{
min-width: 145px;
}
}
.bp3-form-content{
width: 100%;
}
textarea.bp3-input{
max-width: 100%;
width: 100%;
min-height: 50px;
}
}
}
&--note{
.form-group--note{
.bp3-form-group{
max-width: 600px;
}
textarea{
width: 100%;
min-height: 100px;
}
}
}
}
.dropzone-container{
max-width: 600px;
}
}
.bp3-tabs{
@@ -136,115 +158,24 @@
}
}
.dashboard__insider--customers-list{
.customer-form{
.bigcapital-datatable{
.avatar.td{
&__primary-section{
background-color: #fafafa;
padding: 40px 22px 5px;
margin: -20px -20px 26px;
&-content{
width: 600px;
.avatar{
height: 30px;
width: 30px;
display: inline-block;
background: #f3e2f6;
border-radius: 50%;
line-height: 30px;
text-align: center;
font-weight: 400;
font-size: 14px;
color: #93639a;
}
}
}
&__after-primary-section{
&-content{
width: 600px;
}
}
}
// .customer-form {
// padding: 25px;
// padding-bottom: 90px;
// width: 100%;
// margin-bottom: 30px;
// .form-group--customer-type {
// .bp3-label {
// position: relative;
// display: inline-block;
// margin: 0 50px 30px 0px;
// }
// }
// // .form-group--contact-name {
// // .bp3-input-group .bp3-input {
// // position: relative;
// // // display: none;
// // width: 50%;
// // }
// // // .row {
// // // width: fit-content;
// // // }
// // // .#{$ns}-form-content{
// // // width: 350px;
// // // }
// // }
// h1 {
// font-size: 14px;
// margin-bottom: 20px;
// }
// &__primary-section {
//
// }
// &__tabs-section {
// position: relative;
// h4 {
// margin: 0;
// font-weight: 500;
// margin-bottom: 20px;
// font-size: 14px;
// color: #828282;
// }
// > div:first-of-type {
// padding-right: 40px !important;
// }
// > div ~ div {
// padding-left: 40px !important;
// }
// }
// .dropzone-container {
// align-self: end;
// }
// .dropzone {
// width: 300px;
// height: 100px;
// margin-right: 20px;
// }
// }
// .form-group--contact-name {
// .bp3-form-group.bp3-inline label.bp3-label {
// line-height: 30px;
// display: inline-block;
// margin: 0 45px 0 0;
// width: 200px;
// }
// .bp3-input-group .bp3-input {
// position: relative;
// // display: none;
// width: 100%;
// // margin-left: 30px;
// }
// // .row {
// // width: fit-content;
// // }
// // .#{$ns}-form-content{
// // width: 350px;
// // }
// }

View File

@@ -17,17 +17,21 @@ export default class ContactsController extends BaseController {
check('work_phone').optional().trim().escape(),
check('personal_phone').optional().trim().escape(),
check('billing_address_1').optional().trim().escape(),
check('billing_address_2').optional().trim().escape(),
check('billing_address_city').optional().trim().escape(),
check('billing_address_country').optional().trim().escape(),
check('billing_address_email').optional().isEmail().trim().escape(),
check('billing_address_zipcode').optional().trim().escape(),
check('billing_address_postcode').optional().trim().escape(),
check('billing_address_phone').optional().trim().escape(),
check('billing_address_state').optional().trim().escape(),
check('shipping_address_1').optional().trim().escape(),
check('shipping_address_2').optional().trim().escape(),
check('shipping_address_city').optional().trim().escape(),
check('shipping_address_country').optional().trim().escape(),
check('shipping_address_email').optional().isEmail().trim().escape(),
check('shipping_address_zip_code').optional().trim().escape(),
check('shipping_address_postcode').optional().trim().escape(),
check('shipping_address_phone').optional().trim().escape(),
check('shipping_address_state').optional().trim().escape(),

View File

@@ -26,6 +26,7 @@ export default class CustomersController extends ContactsController {
...this.contactDTOSchema,
...this.contactNewDTOSchema,
...this.customerDTOSchema,
...this.createCustomerDTOSchema,
],
this.validationResult,
asyncMiddleware(this.newCustomer.bind(this)),
@@ -77,10 +78,24 @@ export default class CustomersController extends ContactsController {
get customerDTOSchema() {
return [
check('customer_type').exists().trim().escape(),
check('opening_balance').optional().isNumeric().toInt(),
];
}
/**
* Create customer DTO schema.
*/
get createCustomerDTOSchema() {
return [
check('opening_balance').optional().isNumeric().toInt(),
check('opening_balance_at').optional().isISO8601(),
check('currency_code').optional().trim().escape(),
];
}
/**
* List param query schema.
*/
get validateListQuerySchema() {
return [
query('column_sort_by').optional().trim().escape(),

View File

@@ -7,7 +7,10 @@ exports.up = function(knex) {
table.string('contact_type');
table.decimal('balance', 13, 3).defaultTo(0);
table.string('currency_code', 3);
table.decimal('opening_balance', 13, 3).defaultTo(0);
table.date('opening_balance_at');
table.string('first_name').nullable();
table.string('last_name').nullable();
@@ -19,12 +22,12 @@ exports.up = function(knex) {
table.string('work_phone').nullable();
table.string('personal_phone').nullable();
table.string('billing_address_1').nullable();
table.string('billing_address_2').nullable();
table.string('billing_address1').nullable();
table.string('billing_address2').nullable();
table.string('billing_address_city').nullable();
table.string('billing_address_country').nullable();
table.string('billing_address_email').nullable();
table.string('billing_address_zipcode').nullable();
table.string('billing_address_postcode').nullable();
table.string('billing_address_phone').nullable();
table.string('billing_address_state').nullable(),
@@ -33,7 +36,7 @@ exports.up = function(knex) {
table.string('shipping_address_city').nullable();
table.string('shipping_address_country').nullable();
table.string('shipping_address_email').nullable();
table.string('shipping_address_zipcode').nullable();
table.string('shipping_address_postcode').nullable();
table.string('shipping_address_phone').nullable();
table.string('shipping_address_state').nullable();

View File

@@ -47,7 +47,10 @@ export interface IContact extends IContactAddress{
contactType: string,
balance: number,
currencyCode: string,
openingBalance: number,
openingBalanceAt: Date,
firstName: string,
lastName: string,
@@ -64,7 +67,10 @@ export interface IContact extends IContactAddress{
export interface IContactNewDTO {
contactType?: string,
currencyCode?: string,
openingBalance?: number,
openingBalanceAt?: string,
firstName?: string,
lastName?: string,
@@ -81,8 +87,6 @@ export interface IContactNewDTO {
export interface IContactEditDTO {
contactType?: string,
openingBalance?: number,
firstName?: string,
lastName?: string,
companyName?: string,
@@ -104,7 +108,10 @@ export interface ICustomer extends IContact {
export interface ICustomerNewDTO extends IContactAddressDTO {
customerType: string,
currencyCode: string,
openingBalance?: number,
openingBalanceAt?: string,
firstName?: string,
lastName?: string,
@@ -121,8 +128,6 @@ export interface ICustomerNewDTO extends IContactAddressDTO {
export interface ICustomerEditDTO extends IContactAddressDTO {
customerType: string,
openingBalance?: number,
firstName?: string,
lastName?: string,
companyName?: string,
@@ -142,7 +147,10 @@ export interface IVendor extends IContact {
contactService: 'vendor',
}
export interface IVendorNewDTO extends IContactAddressDTO {
currencyCode: string,
openingBalance?: number,
openingBalanceAt?: string,
firstName?: string,
lastName?: string,
@@ -157,8 +165,6 @@ export interface IVendorNewDTO extends IContactAddressDTO {
active?: boolean,
};
export interface IVendorEditDTO extends IContactAddressDTO {
openingBalance?: number,
firstName?: string,
lastName?: string,
companyName?: string,

View File

@@ -1,5 +1,5 @@
import { Inject, Service } from 'typedi';
import { difference, upperFirst } from 'lodash';
import { difference, upperFirst, omit } from 'lodash';
import { ServiceError } from "exceptions";
import TenancyService from 'services/Tenancy/TenancyService';
import {
@@ -38,17 +38,39 @@ export default class ContactsService {
return contact;
}
/**
* Converts contact DTO object to model object attributes to insert or update.
* @param {IContactNewDTO | IContactEditDTO} contactDTO
*/
private transformContactObj(contactDTO: IContactNewDTO | IContactEditDTO) {
return {
...omit(contactDTO, [
'billingAddress1', 'billingAddress2',
'shippingAddress1', 'shippingAddress2',
]),
billing_address_1: contactDTO?.billingAddress1,
billing_address_2: contactDTO?.billingAddress2,
shipping_address_1: contactDTO?.shippingAddress1,
shipping_address_2: contactDTO?.shippingAddress2,
};
}
/**
* Creates a new contact on the storage.
* @param {number} tenantId
* @param {TContactService} contactService
* @param {IContactDTO} contactDTO
*/
async newContact(tenantId: number, contactDTO: IContactNewDTO, contactService: TContactService) {
async newContact(
tenantId: number,
contactDTO: IContactNewDTO,
contactService: TContactService,
) {
const { contactRepository } = this.tenancy.repositories(tenantId);
const contactObj = this.transformContactObj(contactDTO);
this.logger.info('[contacts] trying to insert contact to the storage.', { tenantId, contactDTO });
const contact = await contactRepository.insert({ contactService, ...contactDTO });
const contact = await contactRepository.insert({ contactService, ...contactObj });
this.logger.info('[contacts] contact inserted successfully.', { tenantId, contact });
return contact;
@@ -63,10 +85,12 @@ export default class ContactsService {
*/
async editContact(tenantId: number, contactId: number, contactDTO: IContactEditDTO, contactService: TContactService) {
const { Contact } = this.tenancy.models(tenantId);
const contactObj = this.transformContactObj(contactDTO);
const contact = await this.getContactByIdOrThrowError(tenantId, contactId, contactService);
this.logger.info('[contacts] trying to edit the given contact details.', { tenantId, contactId, contactDTO });
await Contact.query().findById(contactId).patch({ ...contactDTO })
await Contact.query().findById(contactId).patch({ ...contactObj })
}
/**

View File

@@ -12,12 +12,15 @@ import {
ICustomerEditDTO,
ICustomer,
IPaginationMeta,
ICustomersFilter
ICustomersFilter,
IContactNewDTO,
IContactEditDTO
} from 'interfaces';
import { ServiceError } from 'exceptions';
import TenancyService from 'services/Tenancy/TenancyService';
import DynamicListingService from 'services/DynamicListing/DynamicListService';
import events from 'subscribers/events';
import moment from 'moment';
@Service()
export default class CustomersService {
@@ -41,7 +44,7 @@ export default class CustomersService {
* @param {ICustomerNewDTO|ICustomerEditDTO} customerDTO
* @returns {IContactDTO}
*/
private customerToContactDTO(customerDTO: ICustomerNewDTO | ICustomerEditDTO) {
private customerToContactDTO(customerDTO: ICustomerNewDTO|ICustomerEditDTO): IContactNewDTO|IContactEditDTO {
return {
...omit(customerDTO, ['customerType']),
contactType: customerDTO.customerType,
@@ -50,6 +53,18 @@ export default class CustomersService {
};
}
/**
* Transforms new customer DTO to contact.
* @param customerDTO
*/
private transformNewCustomerDTO(customerDTO: ICustomerNewDTO): IContactNewDTO {
return {
...this.customerToContactDTO(customerDTO),
openingBalanceAt: customerDTO?.openingBalanceAt
? moment(customerDTO.openingBalanceAt).toMySqlDateTime() : null,
}
}
/**
* Creates a new customer.
* @param {number} tenantId
@@ -62,8 +77,8 @@ export default class CustomersService {
): Promise<ICustomer> {
this.logger.info('[customer] trying to create a new customer.', { tenantId, customerDTO });
const contactDTO = this.customerToContactDTO(customerDTO)
const customer = await this.contactService.newContact(tenantId, contactDTO, 'customer');
const customerObj = this.transformNewCustomerDTO(customerDTO);
const customer = await this.contactService.newContact(tenantId, customerObj, 'customer');
this.logger.info('[customer] created successfully.', { tenantId, customerDTO });
await this.eventDispatcher.dispatch(events.customers.onCreated, {