feat: organization setup.

This commit is contained in:
a.bouhuolia
2021-09-04 10:04:07 +02:00
parent f2c51c6023
commit 8141674da8
18 changed files with 188 additions and 252 deletions

View File

@@ -5,10 +5,10 @@ export const getSetupWizardSteps = () => [
label: intl.get('setup.plan.plans'), label: intl.get('setup.plan.plans'),
}, },
{ {
label: intl.get('setup.plan.initializing'), label: intl.get('setup.plan.getting_started'),
}, },
{ {
label: intl.get('setup.plan.getting_started'), label: intl.get('setup.plan.initializing'),
}, },
{ {
label: intl.get('setup.plan.congrats'), label: intl.get('setup.plan.congrats'),

View File

@@ -2,37 +2,30 @@ import { connect } from 'react-redux';
import { import {
getOrganizationByIdFactory, getOrganizationByIdFactory,
isOrganizationReadyFactory, isOrganizationReadyFactory,
isOrganizationSeededFactory,
isOrganizationBuiltFactory, isOrganizationBuiltFactory,
isOrganizationSeedingFactory,
isOrganizationInitializingFactory,
isOrganizationSubscribedFactory, isOrganizationSubscribedFactory,
isOrganizationCongratsFactory, isOrganizationCongratsFactory,
isOrganizationBuildRunningFactory
} from 'store/organizations/organizations.selectors'; } from 'store/organizations/organizations.selectors';
export default (mapState) => { export default (mapState) => {
const getOrganizationById = getOrganizationByIdFactory(); const getOrganizationById = getOrganizationByIdFactory();
const isOrganizationReady = isOrganizationReadyFactory(); const isOrganizationReady = isOrganizationReadyFactory();
const isOrganizationSeeded = isOrganizationSeededFactory();
const isOrganizationBuilt = isOrganizationBuiltFactory(); const isOrganizationBuilt = isOrganizationBuiltFactory();
const isOrganizationInitializing = isOrganizationInitializingFactory();
const isOrganizationSeeding = isOrganizationSeedingFactory();
const isOrganizationSubscribed = isOrganizationSubscribedFactory(); const isOrganizationSubscribed = isOrganizationSubscribedFactory();
const isOrganizationCongrats = isOrganizationCongratsFactory(); const isOrganizationCongrats = isOrganizationCongratsFactory();
const isOrganizationBuildRunning = isOrganizationBuildRunningFactory();
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
const mapped = { const mapped = {
organization: getOrganizationById(state, props), organization: getOrganizationById(state, props),
isOrganizationReady: isOrganizationReady(state, props), isOrganizationReady: isOrganizationReady(state, props),
isOrganizationSeeded: isOrganizationSeeded(state, props),
isOrganizationInitialized: isOrganizationBuilt(state, props), isOrganizationInitialized: isOrganizationBuilt(state, props),
isOrganizationSeeding: isOrganizationInitializing(state, props),
isOrganizationInitializing: isOrganizationSeeding(state, props),
isOrganizationSubscribed: isOrganizationSubscribed(state, props), isOrganizationSubscribed: isOrganizationSubscribed(state, props),
isOrganizationSetupCompleted: isOrganizationCongrats(state, props), isOrganizationSetupCompleted: isOrganizationCongrats(state, props),
isOrganizationBuildRunning: isOrganizationBuildRunning(state, props)
}; };
return (mapState) ? mapState(mapped, state, props) : mapped; return (mapState) ? mapState(mapped, state, props) : mapped;
}; };

View File

@@ -1,62 +1,108 @@
import React, { useEffect } from 'react'; import React from 'react';
import { ProgressBar, Intent } from '@blueprintjs/core'; import { ProgressBar, Intent } from '@blueprintjs/core';
import { useBuildTenant } from 'hooks/query'; import * as R from 'ramda';
import { useJob, useCurrentOrganization } from 'hooks/query';
import { FormattedMessage as T } from 'components'; import { FormattedMessage as T } from 'components';
import withOrganizationActions from 'containers/Organization/withOrganizationActions';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
import withOrganization from '../Organization/withOrganization';
import 'style/pages/Setup/Initializing.scss'; import 'style/pages/Setup/Initializing.scss';
/** /**
* Setup initializing step form. * Setup initializing step form.
*/ */
export default function SetupInitializingForm() { function SetupInitializingForm({
setOrganizationSetupCompleted,
organization,
}) {
const { refetch, isSuccess } = useCurrentOrganization({ enabled: false });
const [isJobDone, setIsJobDone] = React.useState(false);
const { const {
mutateAsync: buildTenantMutate, data: { running, queued, failed, completed },
isLoading, } = useJob(organization?.build_job_id, {
isError, refetchInterval: 2000,
} = useBuildTenant(); enabled: !!organization?.build_job_id,
});
React.useEffect(() => { React.useEffect(() => {
buildTenantMutate(); if (completed) {
}, [buildTenantMutate]); refetch();
setIsJobDone(true);
}
}, [refetch, completed, setOrganizationSetupCompleted]);
React.useEffect(() => {
if (isSuccess && isJobDone) {
setOrganizationSetupCompleted(true);
setIsJobDone(false);
}
}, [setOrganizationSetupCompleted, isJobDone, isSuccess]);
return ( return (
<div class="setup-initializing-form"> <div class="setup-initializing-form">
{isLoading && <ProgressBar intent={Intent.PRIMARY} value={null} />} <ProgressBar intent={Intent.PRIMARY} value={null} />
<div className={'setup-initializing-form__title'}> <div className={'setup-initializing-form__title'}>
{isLoading ? ( {failed ? (
<> <SetupInitializingFailed />
<h1> ) : running || queued ? (
<T id={'setup.initializing.title'} /> <SetupInitializingRunning />
</h1> ) : completed ? (
<p className={'paragraph'}> <SetupInitializingCompleted />
<T id={'setup.initializing.description'} /> ) : null}
</p>
</>
) : isError ? (
<>
<h1>
<T id={'setup.initializing.something_went_wrong'} />
</h1>
<p class="paragraph">
<T id={'setup.initializing.please_refresh_the_page'} />
</p>
</>
) : (
<>
<h1>
<T id={'setup.initializing.waiting_to_redirect'} />
</h1>
<p class="paragraph">
<T
id={
'setup.initializing.refresh_the_page_if_redirect_not_worked'
}
/>
</p>
</>
)}
</div> </div>
</div> </div>
); );
} }
export default R.compose(
withOrganizationActions,
withCurrentOrganization(({ organizationTenantId }) => ({
organizationId: organizationTenantId,
})),
withOrganization(({ organization }) => ({ organization })),
)(SetupInitializingForm);
function SetupInitializingFailed() {
return (
<div class="failed">
<h1>
<T id={'setup.initializing.something_went_wrong'} />
</h1>
<p class="paragraph">
<T id={'setup.initializing.please_refresh_the_page'} />
</p>
</div>
);
}
function SetupInitializingRunning() {
return (
<div class="running">
<h1>
<T id={'setup.initializing.title'} />
</h1>
<p className={'paragraph'}>
<T id={'setup.initializing.description'} />
</p>
</div>
);
}
function SetupInitializingCompleted() {
return (
<div class="completed">
<h1>
<T id={'setup.initializing.waiting_to_redirect'} />
</h1>
<p class="paragraph">
<T id={'setup.initializing.refresh_the_page_if_redirect_not_worked'} />
</p>
</div>
);
}

View File

@@ -11,5 +11,5 @@ export const getSetupOrganizationValidation = () =>
baseCurrency: Yup.string().required().label(intl.get('base_currency_')), baseCurrency: Yup.string().required().label(intl.get('base_currency_')),
language: Yup.string().required().label(intl.get('language')), language: Yup.string().required().label(intl.get('language')),
fiscalYear: Yup.string().required().label(intl.get('fiscal_year_')), fiscalYear: Yup.string().required().label(intl.get('fiscal_year_')),
timeZone: Yup.string().required().label(intl.get('time_zone_')), timezone: Yup.string().required().label(intl.get('time_zone_')),
}); });

View File

@@ -195,7 +195,7 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
</FastField> </FastField>
{/* ---------- Time zone ---------- */} {/* ---------- Time zone ---------- */}
<FastField name={'timeZone'}> <FastField name={'timezone'}>
{({ {({
form: { setFieldValue }, form: { setFieldValue },
field: { value }, field: { value },
@@ -209,12 +209,12 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
Classes.FILL, Classes.FILL,
)} )}
intent={inputIntent({ error, touched })} intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'timeZone'} />} helperText={<ErrorMessage name={'timezone'} />}
> >
<TimezonePicker <TimezonePicker
value={value} value={value}
onChange={(item) => { onChange={(item) => {
setFieldValue('timeZone', item); setFieldValue('timezone', item);
}} }}
valueDisplayFormat="composite" valueDisplayFormat="composite"
showLocalTimezone={true} showLocalTimezone={true}

View File

@@ -3,7 +3,6 @@ import { Formik } from 'formik';
import moment from 'moment'; import moment from 'moment';
import { FormattedMessage as T } from 'components'; import { FormattedMessage as T } from 'components';
import 'style/pages/Setup/Organization.scss'; import 'style/pages/Setup/Organization.scss';
import SetupOrganizationForm from './SetupOrganizationForm'; import SetupOrganizationForm from './SetupOrganizationForm';
@@ -15,7 +14,6 @@ import withOrganizationActions from 'containers/Organization/withOrganizationAct
import { compose, transfromToSnakeCase } from 'utils'; import { compose, transfromToSnakeCase } from 'utils';
import { getSetupOrganizationValidation } from './SetupOrganization.schema'; import { getSetupOrganizationValidation } from './SetupOrganization.schema';
// Initial values. // Initial values.
const defaultValues = { const defaultValues = {
organization_name: '', organization_name: '',
@@ -23,7 +21,7 @@ const defaultValues = {
baseCurrency: '', baseCurrency: '',
language: 'en', language: 'en',
fiscalYear: '', fiscalYear: '',
timeZone: '', timezone: '',
}; };
/** /**
@@ -43,9 +41,6 @@ function SetupOrganizationPage({ wizard, setOrganizationSetupCompleted }) {
// Handle the form submit. // Handle the form submit.
const handleSubmit = (values, { setSubmitting, setErrors }) => { const handleSubmit = (values, { setSubmitting, setErrors }) => {
organizationSetupMutate({ ...transfromToSnakeCase(values) }) organizationSetupMutate({ ...transfromToSnakeCase(values) })
.then(() => {
return setOrganizationSetupCompleted(true);
})
.then((response) => { .then((response) => {
setSubmitting(false); setSubmitting(false);
wizard.next(); wizard.next();

View File

@@ -47,11 +47,13 @@ export default compose(
isOrganizationInitialized, isOrganizationInitialized,
isOrganizationSeeded, isOrganizationSeeded,
isOrganizationSetupCompleted, isOrganizationSetupCompleted,
isOrganizationBuildRunning,
}) => ({ }) => ({
organization, organization,
isOrganizationInitialized, isOrganizationInitialized,
isOrganizationSeeded, isOrganizationSeeded,
isOrganizationSetupCompleted, isOrganizationSetupCompleted,
isOrganizationBuildRunning,
}), }),
), ),
withSubscriptions( withSubscriptions(

View File

@@ -19,8 +19,8 @@ export default function SetupWizardContent({ setupStepIndex, setupStepId }) {
<div class="setup-page-form"> <div class="setup-page-form">
<SetupSteps step={{ id: setupStepId }}> <SetupSteps step={{ id: setupStepId }}>
<SetupSubscription id="subscription" /> <SetupSubscription id="subscription" />
<SetupInitializingForm id={'initializing'} />
<SetupOrganizationPage id="organization" /> <SetupOrganizationPage id="organization" />
<SetupInitializingForm id={'initializing'} />
<SetupCongratsPage id="congrats" /> <SetupCongratsPage id="congrats" />
</SetupSteps> </SetupSteps>
</div> </div>

View File

@@ -26,3 +26,4 @@ export * from './organization';
export * from './landedCost'; export * from './landedCost';
export * from './UniversalSearch/UniversalSearch'; export * from './UniversalSearch/UniversalSearch';
export * from './GenericResource'; export * from './GenericResource';
export * from './jobs';

View File

@@ -0,0 +1,16 @@
import { useRequestQuery } from '../useQueryRequest';
/**
* Retrieve the job metadata.
*/
export function useJob(jobId, props = {}) {
return useRequestQuery(
['JOB', jobId],
{ method: 'get', url: `jobs/${jobId}` },
{
select: (res) => res.data.job,
defaultData: {},
...props,
},
);
}

View File

@@ -35,7 +35,7 @@ export function useCurrentOrganization(props) {
return useRequestQuery( return useRequestQuery(
[t.ORGANIZATION_CURRENT], [t.ORGANIZATION_CURRENT],
{ method: 'get', url: `organization/current` }, { method: 'get', url: `organization` },
{ {
select: (res) => res.data.organization, select: (res) => res.data.organization,
defaultData: {}, defaultData: {},
@@ -55,37 +55,6 @@ export function useCurrentOrganization(props) {
); );
} }
/**
* Builds the current tenant.
*/
export function useBuildTenant(props) {
const apiRequest = useApiRequest();
const queryClient = useQueryClient();
return useMutation((values) => apiRequest.post('organization/build'), {
onSuccess: (res, values) => {
queryClient.invalidateQueries(t.ORGANIZATION_CURRENT);
queryClient.invalidateQueries(t.ORGANIZATIONS);
},
...props,
});
}
/**
* Seeds the current tenant
*/
export function useSeedTenant() {
const apiRequest = useApiRequest();
const queryClient = useQueryClient();
return useMutation((values) => apiRequest.post('organization/seed'), {
onSuccess: (res) => {
queryClient.invalidateQueries(t.ORGANIZATION_CURRENT);
queryClient.invalidateQueries(t.ORGANIZATIONS);
},
});
}
/** /**
* Organization setup. * Organization setup.
*/ */
@@ -94,7 +63,7 @@ export function useOrganizationSetup() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation(
(values) => apiRequest.post(`setup/organization`, values), (values) => apiRequest.post(`organization/build`, values),
{ {
onSuccess: (res) => { onSuccess: (res) => {
queryClient.invalidateQueries(t.ORGANIZATION_CURRENT); queryClient.invalidateQueries(t.ORGANIZATION_CURRENT);

View File

@@ -8,66 +8,34 @@ export const setOrganizations = (organizations) => {
organizations, organizations,
}, },
}; };
} };
export const fetchOrganizations = () => (dispatch) =>
new Promise((resolve, reject) => {
ApiService.get('organization/all')
.then((response) => {
dispatch({
type: t.ORGANIZATIONS_LIST_SET,
payload: {
organizations: response.data.organizations,
},
});
resolve(response);
})
.catch((error) => {
reject(error);
});
});
export const setOrganizationSetupCompleted =
(congrats) => (dispatch, getState) => {
const organizationId = getState().authentication.organizationId;
export const fetchOrganizations = () => (dispatch) => new Promise((resolve, reject) => {
ApiService.get('organization/all').then((response) => {
dispatch({ dispatch({
type: t.ORGANIZATIONS_LIST_SET, type: t.SET_ORGANIZATION_CONGRATS,
payload: { payload: {
organizations: response.data.organizations, organizationId,
congrats,
}, },
}); });
resolve(response) };
}).catch(error => { reject(error); });
});
export const buildTenant = () => (dispatch, getState) => new Promise((resolve, reject) => {
const organizationId = getState().authentication.organizationId;
dispatch({
type: t.SET_ORGANIZATION_INITIALIZING,
payload: { organizationId }
});
ApiService.post(`organization/build`).then((response) => {
resolve(response);
dispatch({
type: t.SET_ORGANIZATION_INITIALIZED,
payload: { organizationId }
});
})
.catch((error) => {
reject(error.response.data.errors || []);
});
});
export const seedTenant = () => (dispatch, getState) => new Promise((resolve, reject) => {
const organizationId = getState().authentication.organizationId;
dispatch({
type: t.SET_ORGANIZATION_SEEDING,
payload: { organizationId }
});
ApiService.post(`organization/seed/`).then((response) => {
dispatch({
type: t.SET_ORGANIZATION_SEEDED,
payload: { organizationId }
});
resolve(response);
})
.catch((error) => {
reject(error.response.data.errors || []);
});
});
export const setOrganizationSetupCompleted = (congrats) => (dispatch, getState) => {
const organizationId = getState().authentication.organizationId;
dispatch({
type: t.SET_ORGANIZATION_CONGRATS,
payload: {
organizationId,
congrats
},
});
};

View File

@@ -24,45 +24,6 @@ const reducer = createReducer(initialState, {
state.byOrganizationId = _dataByOrganizationId; state.byOrganizationId = _dataByOrganizationId;
}, },
[t.SET_ORGANIZATION_SEEDING]: (state, action) => {
const { organizationId } = action.payload;
state.data[organizationId] = {
...(state.data[organizationId] || {}),
is_seeding: true,
};
},
[t.SET_ORGANIZATION_SEEDED]: (state, action) => {
const { organizationId } = action.payload;
state.data[organizationId] = {
...(state.data[organizationId] || {}),
is_seeding: false,
seeded_at: new Date().toISOString(),
is_ready: true,
};
},
[t.SET_ORGANIZATION_INITIALIZING]: (state, action) => {
const { organizationId } = action.payload;
state.data[organizationId] = {
...(state.data[organizationId] || {}),
is_initializing: true,
};
},
[t.SET_ORGANIZATION_INITIALIZED]: (state, action) => {
const { organizationId } = action.payload;
state.data[organizationId] = {
...(state.data[organizationId] || {}),
is_initializing: false,
initialized_at: new Date().toISOString(),
};
},
[t.SET_ORGANIZATION_CONGRATS]: (state, action) => { [t.SET_ORGANIZATION_CONGRATS]: (state, action) => {
const { organizationId, congrats } = action.payload; const { organizationId, congrats } = action.payload;

View File

@@ -21,20 +21,6 @@ export const isOrganizationBuiltFactory = () => createSelector(
}, },
); );
export const isOrganizationInitializingFactory = () => createSelector(
organizationSelector,
(organization) => {
return organization?.is_initializing;
},
);
export const isOrganizationSeedingFactory = () => createSelector(
organizationSelector,
(organization) => {
return organization?.is_seeding;
},
);
export const isOrganizationReadyFactory = () => createSelector( export const isOrganizationReadyFactory = () => createSelector(
organizationSelector, organizationSelector,
(organization) => { (organization) => {
@@ -55,3 +41,10 @@ export const isOrganizationCongratsFactory = () => createSelector(
return !!organization?.is_congrats; return !!organization?.is_congrats;
} }
); );
export const isOrganizationBuildRunningFactory = () => createSelector(
organizationSelector,
(organization) => {
return !!organization?.is_build_running;
}
)

View File

@@ -1,12 +1,5 @@
export default { export default {
ORGANIZATIONS_LIST_SET: 'ORGANIZATIONS_LIST_SET', ORGANIZATIONS_LIST_SET: 'ORGANIZATIONS_LIST_SET',
SET_ORGANIZATION_SEEDING: 'SET_ORGANIZATION_SEEDING',
SET_ORGANIZATION_SEEDED: 'SET_ORGANIZATION_SEEDED',
SET_ORGANIZATION_INITIALIZED: 'SET_ORGANIZATION_INITIALIZED',
SET_ORGANIZATION_INITIALIZING: 'SET_ORGANIZATION_INITIALIZING',
SET_ORGANIZATION_CONGRATS: 'SET_ORGANIZATION_CONGRATS' SET_ORGANIZATION_CONGRATS: 'SET_ORGANIZATION_CONGRATS'
}; };

View File

@@ -5,28 +5,27 @@ export default (mapState) => {
const { const {
isOrganizationSetupCompleted, isOrganizationSetupCompleted,
isOrganizationInitialized, isOrganizationInitialized,
isOrganizationSeeded, isSubscriptionActive,
isSubscriptionActive isOrganizationBuildRunning
} = props; } = props;
const condits = { const condits = {
isCongratsStep: isOrganizationSetupCompleted, isCongratsStep: isOrganizationSetupCompleted,
isSubscriptionStep: !isSubscriptionActive, isSubscriptionStep: !isSubscriptionActive,
isInitializingStep: isSubscriptionActive && !isOrganizationInitialized, isInitializingStep: isOrganizationBuildRunning,
isOrganizationStep: isOrganizationInitialized && !isOrganizationSeeded, isOrganizationStep: !isOrganizationInitialized && !isOrganizationBuildRunning,
}; };
const scenarios = [ const scenarios = [
{ condition: condits.isCongratsStep, step: 'congrats' },
{ condition: condits.isSubscriptionStep, step: 'subscription' }, { condition: condits.isSubscriptionStep, step: 'subscription' },
{ condition: condits.isInitializingStep, step: 'initializing' },
{ condition: condits.isOrganizationStep, step: 'organization' }, { condition: condits.isOrganizationStep, step: 'organization' },
{ condition: condits.isInitializingStep, step: 'initializing' },
{ condition: condits.isCongratsStep, step: 'congrats' },
]; ];
const setupStep = scenarios.find((scenario) => scenario.condition); const setupStep = scenarios.find((scenario) => scenario.condition);
const mapped = { const mapped = {
...condits, ...condits,
setupStepId: setupStep?.step, setupStepId: setupStep?.step,
setupStepIndex: scenarios.indexOf(setupStep) , setupStepIndex: scenarios.indexOf(setupStep) + 1,
}; };
return mapState ? mapState(mapped, state, props) : mapped; return mapState ? mapState(mapped, state, props) : mapped;
}; };

View File

@@ -59,7 +59,7 @@ export default class OrganizationController extends BaseController {
check('organization_name').exists().trim(), check('organization_name').exists().trim(),
check('base_currency').exists().isIn(BASE_CURRENCY), check('base_currency').exists().isIn(BASE_CURRENCY),
check('timezone').exists(), check('timezone').exists(),
check('fiscal_year').exists().isISO8601(), check('fiscal_year').exists(),
check('industry').optional().isString(), check('industry').optional().isString(),
check('date_format').optional().isIn(DATE_FORMATS), check('date_format').optional().isIn(DATE_FORMATS),
]; ];

View File

@@ -2,40 +2,40 @@ import Container from 'typedi';
import TenancyService from 'services/Tenancy/TenancyService'; import TenancyService from 'services/Tenancy/TenancyService';
exports.up = (knex) => { exports.up = (knex) => {
const tenancyService = Container.get(TenancyService); // const tenancyService = Container.get(TenancyService);
const settings = tenancyService.settings(knex.userParams.tenantId); // const settings = tenancyService.settings(knex.userParams.tenantId);
// Orgnization settings. // // Orgnization settings.
settings.set({ group: 'organization', key: 'accounting_basis', value: 'accural' }); // settings.set({ group: 'organization', key: 'accounting_basis', value: 'accural' });
// Accounts settings. // // Accounts settings.
settings.set({ group: 'accounts', key: 'account_code_unique', value: true }); // settings.set({ group: 'accounts', key: 'account_code_unique', value: true });
// Manual journals settings. // // Manual journals settings.
settings.set({ group: 'manual_journals', key: 'next_number', value: '00001' }); // settings.set({ group: 'manual_journals', key: 'next_number', value: '00001' });
settings.set({ group: 'manual_journals', key: 'auto_increment', value: true }); // settings.set({ group: 'manual_journals', key: 'auto_increment', value: true });
// Sale invoices settings. // // Sale invoices settings.
settings.set({ group: 'sales_invoices', key: 'next_number', value: '00001' }); // settings.set({ group: 'sales_invoices', key: 'next_number', value: '00001' });
settings.set({ group: 'sales_invoices', key: 'number_prefix', value: 'INV-' }); // settings.set({ group: 'sales_invoices', key: 'number_prefix', value: 'INV-' });
settings.set({ group: 'sales_invoices', key: 'auto_increment', value: true }); // settings.set({ group: 'sales_invoices', key: 'auto_increment', value: true });
// Sale receipts settings. // // Sale receipts settings.
settings.set({ group: 'sales_receipts', key: 'next_number', value: '00001' }); // settings.set({ group: 'sales_receipts', key: 'next_number', value: '00001' });
settings.set({ group: 'sales_receipts', key: 'number_prefix', value: 'REC-' }); // settings.set({ group: 'sales_receipts', key: 'number_prefix', value: 'REC-' });
settings.set({ group: 'sales_receipts', key: 'auto_increment', value: true }); // settings.set({ group: 'sales_receipts', key: 'auto_increment', value: true });
// Sale estimates settings. // // Sale estimates settings.
settings.set({ group: 'sales_estimates', key: 'next_number', value: '00001' }); // settings.set({ group: 'sales_estimates', key: 'next_number', value: '00001' });
settings.set({ group: 'sales_estimates', key: 'number_prefix', value: 'EST-' }); // settings.set({ group: 'sales_estimates', key: 'number_prefix', value: 'EST-' });
settings.set({ group: 'sales_estimates', key: 'auto_increment', value: true }); // settings.set({ group: 'sales_estimates', key: 'auto_increment', value: true });
// Payment receives settings. // // Payment receives settings.
settings.set({ group: 'payment_receives', key: 'number_prefix', value: 'PAY-' }); // settings.set({ group: 'payment_receives', key: 'number_prefix', value: 'PAY-' });
settings.set({ group: 'payment_receives', key: 'next_number', value: '00001' }); // settings.set({ group: 'payment_receives', key: 'next_number', value: '00001' });
settings.set({ group: 'payment_receives', key: 'auto_increment', value: true }); // settings.set({ group: 'payment_receives', key: 'auto_increment', value: true });
return settings.save(); // return settings.save();
}; };
exports.down = (knex) => {}; exports.down = (knex) => {};