diff --git a/packages/server/src/api/controllers/Items/Items.ts b/packages/server/src/api/controllers/Items/Items.ts index 66deaffe4..a77f991e9 100644 --- a/packages/server/src/api/controllers/Items/Items.ts +++ b/packages/server/src/api/controllers/Items/Items.ts @@ -149,6 +149,11 @@ export default class ItemsController extends BaseController { .trim() .escape() .isLength({ max: DATATYPES_LENGTH.TEXT }), + check('sell_tax_rate_id').optional({ nullable: true }).isInt().toInt(), + check('purchase_tax_rate_id') + .optional({ nullable: true }) + .isInt() + .toInt(), check('category_id') .optional({ nullable: true }) .isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 }) @@ -508,6 +513,28 @@ export default class ItemsController extends BaseController { ], }); } + if (error.errorType === 'PURCHASE_TAX_RATE_NOT_FOUND') { + return res.status(400).send({ + errors: [ + { + type: 'PURCHASE_TAX_RATE_NOT_FOUND', + message: 'Purchase tax rate has not found.', + code: 410, + }, + ], + }); + } + if (error.errorType === 'SELL_TAX_RATE_NOT_FOUND') { + return res.status(400).send({ + errors: [ + { + type: 'SELL_TAX_RATE_NOT_FOUND', + message: 'Sell tax rate is not found.', + code: 420, + }, + ], + }); + } } next(error); } diff --git a/packages/server/src/database/migrations/20231004020636_add_sell_purchase_tax_to_items_table.js b/packages/server/src/database/migrations/20231004020636_add_sell_purchase_tax_to_items_table.js new file mode 100644 index 000000000..51b842df2 --- /dev/null +++ b/packages/server/src/database/migrations/20231004020636_add_sell_purchase_tax_to_items_table.js @@ -0,0 +1,18 @@ +exports.up = (knex) => { + return knex.schema.table('items', (table) => { + table + .integer('sell_tax_rate_id') + .unsigned() + .references('id') + .inTable('tax_rates'); + table + .integer('purchase_tax_rate_id') + .unsigned() + .references('id') + .inTable('tax_rates'); + }); +}; + +exports.down = (knex) => { + return knex.schema.dropTableIfExists('tax_rates'); +}; diff --git a/packages/server/src/interfaces/Item.ts b/packages/server/src/interfaces/Item.ts index 748fccefb..9fdfa374d 100644 --- a/packages/server/src/interfaces/Item.ts +++ b/packages/server/src/interfaces/Item.ts @@ -22,6 +22,9 @@ export interface IItem { sellDescription: string; purchaseDescription: string; + sellTaxRateId: number; + purchaseTaxRateId: number; + quantityOnHand: number; note: string; @@ -54,6 +57,9 @@ export interface IItemDTO { sellDescription: string; purchaseDescription: string; + sellTaxRateId: number; + purchaseTaxRateId: number; + quantityOnHand: number; note: string; diff --git a/packages/server/src/interfaces/TaxRate.ts b/packages/server/src/interfaces/TaxRate.ts index 7bd912877..d1e27383d 100644 --- a/packages/server/src/interfaces/TaxRate.ts +++ b/packages/server/src/interfaces/TaxRate.ts @@ -36,6 +36,7 @@ export interface ITaxRateCreatedPayload { } export interface ITaxRateEditingPayload { + oldTaxRate: ITaxRate; editTaxRateDTO: IEditTaxRateDTO; tenantId: number; trx: Knex.Transaction; diff --git a/packages/server/src/loaders/eventEmitter.ts b/packages/server/src/loaders/eventEmitter.ts index 47589de5f..fa1942b72 100644 --- a/packages/server/src/loaders/eventEmitter.ts +++ b/packages/server/src/loaders/eventEmitter.ts @@ -83,6 +83,7 @@ import { SaleInvoiceTaxRateValidateSubscriber } from '@/services/TaxRates/subscr import { WriteInvoiceTaxTransactionsSubscriber } from '@/services/TaxRates/subscribers/WriteInvoiceTaxTransactionsSubscriber'; import { BillTaxRateValidateSubscriber } from '@/services/TaxRates/subscribers/BillTaxRateValidateSubscriber'; import { WriteBillTaxTransactionsSubscriber } from '@/services/TaxRates/subscribers/WriteBillTaxTransactionsSubscriber'; +import { SyncItemTaxRateOnEditTaxSubscriber } from '@/services/TaxRates/SyncItemTaxRateOnEditTaxSubscriber'; export default () => { return new EventPublisher(); @@ -197,5 +198,7 @@ export const susbcribers = () => { // Tax Rates - Bills BillTaxRateValidateSubscriber, WriteBillTaxTransactionsSubscriber, + + SyncItemTaxRateOnEditTaxSubscriber ]; }; diff --git a/packages/server/src/models/Item.ts b/packages/server/src/models/Item.ts index 4f2fe1fd5..45e5afd2f 100644 --- a/packages/server/src/models/Item.ts +++ b/packages/server/src/models/Item.ts @@ -65,6 +65,7 @@ export default class Item extends mixin(TenantModel, [ const ItemEntry = require('models/ItemEntry'); const WarehouseTransferEntry = require('models/WarehouseTransferEntry'); const InventoryAdjustmentEntry = require('models/InventoryAdjustmentEntry'); + const TaxRate = require('models/TaxRate'); return { /** @@ -178,11 +179,35 @@ export default class Item extends mixin(TenantModel, [ to: 'media.id', }, }, + + /** + * Item may has sell tax rate. + */ + sellTaxRate: { + relation: Model.BelongsToOneRelation, + modelClass: TaxRate.default, + join: { + from: 'items.sellTaxRateId', + to: 'tax_rates.id', + }, + }, + + /** + * Item may has purchase tax rate. + */ + purchaseTaxRate: { + relation: Model.BelongsToOneRelation, + modelClass: TaxRate.default, + join: { + from: 'items.purchaseTaxRateId', + to: 'tax_rates.id', + }, + }, }; } /** - * + * */ static get secureDeleteRelations() { return [ diff --git a/packages/server/src/services/Items/CreateItem.ts b/packages/server/src/services/Items/CreateItem.ts index ffebdae3d..4ecb984b3 100644 --- a/packages/server/src/services/Items/CreateItem.ts +++ b/packages/server/src/services/Items/CreateItem.ts @@ -55,6 +55,18 @@ export class CreateItem { itemDTO.inventoryAccountId ); } + if (itemDTO.purchaseTaxRateId) { + await this.validators.validatePurchaseTaxRateExistance( + tenantId, + itemDTO.purchaseTaxRateId + ); + } + if (itemDTO.sellTaxRateId) { + await this.validators.validateSellTaxRateExistance( + tenantId, + itemDTO.sellTaxRateId + ); + } } /** diff --git a/packages/server/src/services/Items/EditItem.ts b/packages/server/src/services/Items/EditItem.ts index 8227f3b5f..6e230921f 100644 --- a/packages/server/src/services/Items/EditItem.ts +++ b/packages/server/src/services/Items/EditItem.ts @@ -76,6 +76,20 @@ export class EditItem { itemDTO.inventoryAccountId ); } + // Validate the purchase tax rate id existance. + if (itemDTO.purchaseTaxRateId) { + await this.validators.validatePurchaseTaxRateExistance( + tenantId, + itemDTO.purchaseTaxRateId + ); + } + // Validate the sell tax rate id existance. + if (itemDTO.sellTaxRateId) { + await this.validators.validateSellTaxRateExistance( + tenantId, + itemDTO.sellTaxRateId + ); + } // Validate inventory account should be modified in inventory item // has inventory transactions. await this.validators.validateItemInvnetoryAccountModified( diff --git a/packages/server/src/services/Items/GetItem.ts b/packages/server/src/services/Items/GetItem.ts index 7b078bac5..af07e6cca 100644 --- a/packages/server/src/services/Items/GetItem.ts +++ b/packages/server/src/services/Items/GetItem.ts @@ -27,6 +27,8 @@ export class GetItem { .withGraphFetched('category') .withGraphFetched('costAccount') .withGraphFetched('itemWarehouses.warehouse') + .withGraphFetched('sellTaxRate') + .withGraphFetched('purchaseTaxRate') .throwIfNotFound(); return this.transformer.transform(tenantId, item, new ItemTransformer()); diff --git a/packages/server/src/services/Items/ItemValidators.ts b/packages/server/src/services/Items/ItemValidators.ts index 486c08a8d..cdf8a652e 100644 --- a/packages/server/src/services/Items/ItemValidators.ts +++ b/packages/server/src/services/Items/ItemValidators.ts @@ -241,4 +241,40 @@ export class ItemsValidators { throw new ServiceError(ERRORS.ITEM_CANNOT_CHANGE_INVENTORY_TYPE); } } + + /** + * Validate the purchase tax rate id existance. + * @param {number} tenantId - + * @param {number} taxRateId - + */ + public async validatePurchaseTaxRateExistance( + tenantId: number, + taxRateId: number + ) { + const { TaxRate } = this.tenancy.models(tenantId); + + const foundTaxRate = await TaxRate.query().findById(taxRateId); + + if (!foundTaxRate) { + throw new ServiceError(ERRORS.PURCHASE_TAX_RATE_NOT_FOUND); + } + } + + /** + * Validate the sell tax rate id existance. + * @param {number} tenantId + * @param {number} taxRateId + */ + public async validateSellTaxRateExistance( + tenantId: number, + taxRateId: number + ) { + const { TaxRate } = this.tenancy.models(tenantId); + + const foundTaxRate = await TaxRate.query().findById(taxRateId); + + if (!foundTaxRate) { + throw new ServiceError(ERRORS.SELL_TAX_RATE_NOT_FOUND); + } + } } diff --git a/packages/server/src/services/Items/constants.ts b/packages/server/src/services/Items/constants.ts index e3dd283a7..1acbc70c6 100644 --- a/packages/server/src/services/Items/constants.ts +++ b/packages/server/src/services/Items/constants.ts @@ -22,7 +22,10 @@ export const ERRORS = { TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS: 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS', INVENTORY_ACCOUNT_CANNOT_MODIFIED: 'INVENTORY_ACCOUNT_CANNOT_MODIFIED', - ITEM_HAS_ASSOCIATED_TRANSACTIONS: 'ITEM_HAS_ASSOCIATED_TRANSACTIONS' + ITEM_HAS_ASSOCIATED_TRANSACTIONS: 'ITEM_HAS_ASSOCIATED_TRANSACTIONS', + + PURCHASE_TAX_RATE_NOT_FOUND: 'PURCHASE_TAX_RATE_NOT_FOUND', + SELL_TAX_RATE_NOT_FOUND: 'SELL_TAX_RATE_NOT_FOUND', }; export const DEFAULT_VIEW_COLUMNS = []; diff --git a/packages/server/src/services/TaxRates/EditTaxRate.ts b/packages/server/src/services/TaxRates/EditTaxRate.ts index c2d0e5c1a..210529026 100644 --- a/packages/server/src/services/TaxRates/EditTaxRate.ts +++ b/packages/server/src/services/TaxRates/EditTaxRate.ts @@ -115,6 +115,7 @@ export class EditTaxRateService { // Triggers `onTaxRateEdited` event. await this.eventPublisher.emitAsync(events.taxRates.onEdited, { editTaxRateDTO, + oldTaxRate, taxRate, tenantId, trx, diff --git a/packages/server/src/services/TaxRates/SyncItemTaxRateOnEditTaxRate.ts b/packages/server/src/services/TaxRates/SyncItemTaxRateOnEditTaxRate.ts new file mode 100644 index 000000000..aed00f9b7 --- /dev/null +++ b/packages/server/src/services/TaxRates/SyncItemTaxRateOnEditTaxRate.ts @@ -0,0 +1,55 @@ +import { Knex } from 'knex'; +import { Inject, Service } from 'typedi'; +import HasTenancyService from '../Tenancy/TenancyService'; + +@Service() +export class SyncItemTaxRateOnEditTaxRate { + @Inject() + private tenancy: HasTenancyService; + + /** + * Syncs the new tax rate created to item default sell tax rate. + * @param {number} tenantId + * @param {number} itemId + * @param {number} sellTaxRateId + */ + public updateItemSellTaxRate = async ( + tenantId: number, + oldSellTaxRateId: number, + sellTaxRateId: number, + trx?: Knex.Transaction + ) => { + const { Item } = this.tenancy.models(tenantId); + + // Can't continue if the old and new sell tax rate id are equal. + if (oldSellTaxRateId === sellTaxRateId) return; + + await Item.query().where('sellTaxRateId', oldSellTaxRateId).update({ + sellTaxRateId, + }); + }; + + /** + * Syncs the new tax rate created to item default purchase tax rate. + * @param {number} tenantId + * @param {number} itemId + * @param {number} purchaseTaxRateId + */ + public updateItemPurchaseTaxRate = async ( + tenantId: number, + oldPurchaseTaxRateId: number, + purchaseTaxRateId: number, + trx?: Knex.Transaction + ) => { + const { Item } = this.tenancy.models(tenantId); + + // Can't continue if the old and new sell tax rate id are equal. + if (oldPurchaseTaxRateId === purchaseTaxRateId) return; + + await Item.query(trx) + .where('purchaseTaxRateId', oldPurchaseTaxRateId) + .update({ + purchaseTaxRateId, + }); + }; +} diff --git a/packages/server/src/services/TaxRates/SyncItemTaxRateOnEditTaxSubscriber.ts b/packages/server/src/services/TaxRates/SyncItemTaxRateOnEditTaxSubscriber.ts new file mode 100644 index 000000000..4a7bc9263 --- /dev/null +++ b/packages/server/src/services/TaxRates/SyncItemTaxRateOnEditTaxSubscriber.ts @@ -0,0 +1,45 @@ +import { Inject, Service } from 'typedi'; +import { SyncItemTaxRateOnEditTaxRate } from './SyncItemTaxRateOnEditTaxRate'; +import events from '@/subscribers/events'; +import { ITaxRateEditedPayload } from '@/interfaces'; +import { runAfterTransaction } from '../UnitOfWork/TransactionsHooks'; + +@Service() +export class SyncItemTaxRateOnEditTaxSubscriber { + @Inject() + private syncItemRateOnEdit: SyncItemTaxRateOnEditTaxRate; + + /** + * Attaches events with handles. + */ + public attach(bus) { + bus.subscribe( + events.taxRates.onEdited, + this.handleSyncNewTaxRateToItemTaxRate + ); + } + + /** + * Syncs the new tax rate created to default item tax rates. + * @param {ITaxRateEditedPayload} payload - + */ + private handleSyncNewTaxRateToItemTaxRate = async ({ + taxRate, + tenantId, + oldTaxRate, + trx, + }: ITaxRateEditedPayload) => { + runAfterTransaction(trx, async () => { + await this.syncItemRateOnEdit.updateItemPurchaseTaxRate( + tenantId, + oldTaxRate.id, + taxRate.id + ); + await this.syncItemRateOnEdit.updateItemSellTaxRate( + tenantId, + oldTaxRate.id, + taxRate.id + ); + }); + }; +} diff --git a/packages/webapp/src/components/TaxRates/TaxRatesSelect.tsx b/packages/webapp/src/components/TaxRates/TaxRatesSelect.tsx new file mode 100644 index 000000000..f533c6cba --- /dev/null +++ b/packages/webapp/src/components/TaxRates/TaxRatesSelect.tsx @@ -0,0 +1,62 @@ +// @ts-nocheck +import * as R from 'ramda'; +import intl from 'react-intl-universal'; +import { FSelect } from '@/components'; +import { DialogsName } from '@/constants/dialogs'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import { MenuItem } from '@blueprintjs/core'; + +// Create new account renderer. +const createNewItemRenderer = (query, active, handleClick) => { + return ( + + ); +}; + +// Create new item from the given query string. +const createNewItemFromQuery = (name) => ({ name }); + +/** + * Tax rates select field binded with Formik form. + * @returns {JSX.Element} + */ +function TaxRatesSelectRoot({ + // #withDialogActions + openDialog, + + // #ownProps + allowCreate, + + ...restProps +}) { + // Maybe inject new item props to select component. + const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null; + const maybeCreateNewItemFromQuery = allowCreate + ? createNewItemFromQuery + : null; + + // Handles the create item click. + const handleCreateItemClick = () => { + openDialog(DialogsName.TaxRateForm); + }; + + return ( + + ); +} + +export const TaxRatesSelect = R.compose(withDialogActions)(TaxRatesSelectRoot); diff --git a/packages/webapp/src/containers/Drawers/ItemDetailDrawer/ItemDetailHeader.tsx b/packages/webapp/src/containers/Drawers/ItemDetailDrawer/ItemDetailHeader.tsx index 06b7ce5b4..5e10c3507 100644 --- a/packages/webapp/src/containers/Drawers/ItemDetailDrawer/ItemDetailHeader.tsx +++ b/packages/webapp/src/containers/Drawers/ItemDetailDrawer/ItemDetailHeader.tsx @@ -67,6 +67,14 @@ export default function ItemDetailHeader() { label={intl.get('cost_account_id')} children={defaultTo(item.cost_account?.name, '-')} /> + + { // Update the rate, description and quantity data of the row. const newRows = composeRowsOnNewRow(rowIndex, newRow, localValue); diff --git a/packages/webapp/src/containers/Entries/utils.tsx b/packages/webapp/src/containers/Entries/utils.tsx index bfc62138e..50f6555e3 100644 --- a/packages/webapp/src/containers/Entries/utils.tsx +++ b/packages/webapp/src/containers/Entries/utils.tsx @@ -1,5 +1,5 @@ // @ts-nocheck -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import * as R from 'ramda'; import { sumBy, isEmpty, last, keyBy, groupBy } from 'lodash'; import { useItem } from '@/hooks/query'; @@ -116,19 +116,20 @@ export function useFetchItemRow({ landedCost, itemType, notifyNewRow }) { ? item.purchase_description : item.sell_description; + // Detarmines whether the landed cost checkbox should be disabled. + const landedCostDisabled = isLandedCostDisabled(item); + const taxRateId = itemType === ITEM_TYPE.PURCHASABLE ? item.purchase_tax_rate_id : item.sell_tax_rate_id; - // Detarmines whether the landed cost checkbox should be disabled. - const landedCostDisabled = isLandedCostDisabled(item); - // The new row. const newRow = { rate: price, description, quantity: 1, + tax_rate_id: taxRateId, ...(landedCost ? { landed_cost: false, @@ -164,13 +165,21 @@ export const composeRowsOnEditCell = R.curry( /** * Compose table rows when insert a new row to table rows. */ -export const composeRowsOnNewRow = R.curry((rowIndex, newRow, rows) => { - return compose( - orderingLinesIndexes, - updateItemsEntriesTotal, - updateTableRow(rowIndex, newRow), - )(rows); -}); +export const useComposeRowsOnNewRow = () => { + const { taxRates, isInclusiveTax } = useItemEntriesTableContext(); + + return React.useMemo(() => { + return R.curry((rowIndex, newRow, rows) => { + return compose( + assignEntriesTaxAmount(isInclusiveTax), + assignEntriesTaxRate(taxRates), + orderingLinesIndexes, + updateItemsEntriesTotal, + updateTableRow(rowIndex, newRow), + )(rows); + }); + }, [isInclusiveTax, taxRates]); +}; /** * Associate tax rate to entries. diff --git a/packages/webapp/src/containers/Items/ItemFormBody.tsx b/packages/webapp/src/containers/Items/ItemFormBody.tsx index 5d7355ea8..7e5782599 100644 --- a/packages/webapp/src/containers/Items/ItemFormBody.tsx +++ b/packages/webapp/src/containers/Items/ItemFormBody.tsx @@ -29,14 +29,16 @@ import { costPriceFieldShouldUpdate, costAccountFieldShouldUpdate, purchaseDescFieldShouldUpdate, + taxRateFieldShouldUpdate, } from './utils'; import { compose, inputIntent } from '@/utils'; +import { TaxRatesSelect } from '@/components/TaxRates/TaxRatesSelect'; /** * Item form body. */ function ItemFormBody({ organization: { base_currency } }) { - const { accounts } = useItemFormContext(); + const { accounts, taxRates } = useItemFormContext(); const { values } = useFormikContext(); return ( @@ -111,7 +113,20 @@ function ItemFormBody({ organization: { base_currency } }) { filterByParentTypes={[ACCOUNT_PARENT_TYPE.INCOME]} fill={true} allowCreate={true} - fastField={true} + fastField={true} + /> + + + {/*------------- Sell Tax Rate ------------- */} + + @@ -213,6 +228,24 @@ function ItemFormBody({ organization: { base_currency } }) { /> + {/*------------- Purchase Tax Rate ------------- */} + + + + { ); }; +export const taxRateFieldShouldUpdate = (newProps, oldProps) => { + return ( + newProps.shouldUpdateDeps.taxRates !== oldProps.shouldUpdateDeps.taxRates || + defaultFastFieldShouldUpdate(newProps, oldProps) + ); +}; + export function transformItemsTableState(tableState) { return { ...transformTableStateToQuery(tableState), diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceItemsEntriesEditorField.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceItemsEntriesEditorField.tsx index 0a0270b58..08f3a1a81 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceItemsEntriesEditorField.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceItemsEntriesEditorField.tsx @@ -5,6 +5,7 @@ import ItemsEntriesTable from '@/containers/Entries/ItemsEntriesTable'; import { useInvoiceFormContext } from './InvoiceFormProvider'; import { entriesFieldShouldUpdate } from './utils'; import { TaxType } from '@/interfaces/TaxRates'; +import { ITEM_TYPE } from '@/containers/Entries/utils'; /** * Invoice items entries editor field. @@ -31,6 +32,7 @@ export default function InvoiceItemsEntriesEditorField() { }} items={items} taxRates={taxRates} + itemType={ITEM_TYPE.SELLABLE} errors={error} linesNumber={4} currencyCode={values.currency_code} diff --git a/packages/webapp/src/containers/TaxRates/dialogs/TaxRateFormDialog/TaxRateFormDialogBoot.tsx b/packages/webapp/src/containers/TaxRates/dialogs/TaxRateFormDialog/TaxRateFormDialogBoot.tsx index 6c3981016..166b2ffe2 100644 --- a/packages/webapp/src/containers/TaxRates/dialogs/TaxRateFormDialog/TaxRateFormDialogBoot.tsx +++ b/packages/webapp/src/containers/TaxRates/dialogs/TaxRateFormDialog/TaxRateFormDialogBoot.tsx @@ -1,7 +1,7 @@ // @ts-nocheck import React from 'react'; import { DialogContent } from '@/components'; -import { useTaxRate, useTaxRates } from '@/hooks/query/taxRates'; +import { useTaxRate } from '@/hooks/query/taxRates'; import { DialogsName } from '@/constants/dialogs'; const TaxRateFormDialogContext = React.createContext(); diff --git a/packages/webapp/src/hooks/query/taxRates.ts b/packages/webapp/src/hooks/query/taxRates.ts index 6a731b4e0..8b4dc1f47 100644 --- a/packages/webapp/src/hooks/query/taxRates.ts +++ b/packages/webapp/src/hooks/query/taxRates.ts @@ -59,6 +59,8 @@ export function useEditTaxRate(props) { onSuccess: (res, id) => { commonInvalidateQueries(queryClient); queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES, id]); + queryClient.invalidateQueries(QUERY_TYPES.ITEM); + queryClient.invalidateQueries(QUERY_TYPES.ITEMS); }, ...props, }, diff --git a/packages/webapp/src/lang/en/index.json b/packages/webapp/src/lang/en/index.json index 68cb44364..1b2e25f1f 100644 --- a/packages/webapp/src/lang/en/index.json +++ b/packages/webapp/src/lang/en/index.json @@ -334,6 +334,8 @@ "currency_name_": "Currency name", "cost_account_id": "Cost account", "sell_account_id": "Sell account", + "item.details.sell_tax_rate": "Sell Tax Rate", + "item.details.purchase_tax_rate": "Purchase Tax Rate", "item_type_": "Item type", "item_name_": "Item name", "organization_industry_": "Organization industry",