Skip to content

Commit a973e09

Browse files
authored
OBPIH-6969 Improve approach to product creation and stock data (#61)
* OBPIH-6969 Add import file * OBPIH-6969 Add util for reading csv files * OBPIH-6969 Add parsing json to csv * OBPIH-6969 Add service method for importing products * OBPIH-6969 Add data import setup file * OBPIH-6969 Include data import in configuration * OBPIH-6969 Replace hardcoded products with date from csv file * OBPIH-6969 Add inventories import * OBPIH-6969 Fix usages of product services * OBPIH-6969 Change import url * OBH-6969 Add a more meaningful error message * OBPIH-6969 Fix incorrectly assigned product
1 parent d77d062 commit a973e09

54 files changed

Lines changed: 472 additions & 320 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

playwright.config.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export default defineConfig({
3232

3333
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
3434
trace: 'retain-on-failure',
35-
35+
3636
launchOptions: {
3737
// slowMo: 1000,
3838
},
@@ -60,14 +60,23 @@ export default defineConfig({
6060
storageState: appConfig.users['main'].storagePath,
6161
},
6262
},
63+
{
64+
name: 'data-import-setup',
65+
testMatch: 'dataImport.setup.ts',
66+
testDir: './src/setup',
67+
dependencies: ['create-data-setup'],
68+
use: {
69+
storageState: appConfig.users['main'].storagePath,
70+
},
71+
},
6372
{
6473
name: 'chromium',
6574
use: {
6675
...devices['Desktop Chrome'],
6776
viewport: { width: 1366, height: 768 },
6877
storageState: appConfig.users['main'].storagePath,
6978
},
70-
dependencies: ['auth-setup', 'create-data-setup'],
79+
dependencies: ['auth-setup', 'create-data-setup', 'data-import-setup'],
7180
},
7281
],
7382
});

src/api/InventoryService.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import BaseServiceModel from '@/api/BaseServiceModel';
2+
import { jsonToCsv } from '@/utils/ServiceUtils';
3+
4+
class InventoryService extends BaseServiceModel {
5+
async importInventories(data: Record<string, string>[], facilityId: string): Promise<void> {
6+
try {
7+
const csvContent = jsonToCsv(data);
8+
9+
const response = await this.request.post(`./api/facilities/${facilityId}/inventories/import`, {
10+
data: csvContent,
11+
headers: { 'Content-Type': 'text/csv' },
12+
});
13+
14+
if (!response.ok()) {
15+
throw new Error(`Import failed with status ${response.status()}: ${await response.text()}`);
16+
}
17+
} catch (error) {
18+
throw new Error(`Problem importing inventories: ${error instanceof Error ? error.message : String(error)}`);
19+
}
20+
}
21+
}
22+
23+
export default InventoryService;

src/api/ProductService.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import BaseServiceModel from '@/api/BaseServiceModel';
22
import { ApiResponse, ProductDemandResponse, ProductResponse } from '@/types';
3-
import { parseRequestToJSON } from '@/utils/ServiceUtils';
3+
import { jsonToCsv, parseRequestToJSON } from '@/utils/ServiceUtils';
44

55
class ProductService extends BaseServiceModel {
66
async getDemand(id: string): Promise<ApiResponse<ProductDemandResponse>> {
@@ -22,6 +22,24 @@ class ProductService extends BaseServiceModel {
2222
throw new Error('Problem fetching product data');
2323
}
2424
}
25+
26+
async importProducts(data: Record<string, string>[]): Promise<ApiResponse<ProductResponse[]>> {
27+
try {
28+
const csvContent = jsonToCsv(data);
29+
30+
const apiResponse = await this.request.post(
31+
'./api/products/import',
32+
{
33+
data: csvContent,
34+
headers: { 'Content-Type': 'text/csv' }
35+
}
36+
);
37+
38+
return await parseRequestToJSON(apiResponse);
39+
} catch (error) {
40+
throw new Error(`Problem importing products: ${error instanceof Error ? error.message : String(error)}`);
41+
}
42+
}
2543
}
2644

2745
export default ProductService;

src/config/AppConfig.ts

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import TestUserConfig from '@/config/TestUserConfig';
88
import { ActivityCode } from '@/constants/ActivityCodes';
99
import { LocationTypeCode } from '@/constants/LocationTypeCode';
1010
import RoleType from '@/constants/RoleTypes';
11+
import { readCsvFile } from '@/utils/FileIOUtils';
1112
import UniqueIdentifier from '@/utils/UniqueIdentifier';
1213

1314
export enum USER_KEY {
@@ -52,6 +53,12 @@ class AppConfig {
5253

5354
public static TEST_DATA_FILE_PATH = path.join(process.cwd(), '.data.json');
5455

56+
public static DATA_IMPORT_DIRECTORY_PATH = path.join(process.cwd(), 'src/setup/dataImport');
57+
58+
public static PRODUCTS_IMPORT_FILE_PATH = path.join(AppConfig.DATA_IMPORT_DIRECTORY_PATH, '/products.csv');
59+
60+
public static INVENTORY_IMPORT_FILE_PATH = path.join(AppConfig.DATA_IMPORT_DIRECTORY_PATH, '/inventory.csv');
61+
5562
// Base URL to use in actions like `await page.goto('./dashboard')`.
5663
public appURL!: string;
5764

@@ -65,7 +72,7 @@ class AppConfig {
6572
public locations!: Record<LOCATION_KEY, LocationConfig>;
6673

6774
// test products used in all of the tests
68-
public products!: Record<PRODUCT_KEY, ProductConfig>;
75+
public products: Record<string, ProductConfig> = {};
6976

7077
//recivingbin configurable prefix
7178
public receivingBinPrefix!: string;
@@ -258,44 +265,16 @@ class AppConfig {
258265
}),
259266
};
260267

261-
this.products = {
262-
productOne: new ProductConfig({
263-
id: env.get('PRODUCT_ONE').asString(),
264-
key: PRODUCT_KEY.ONE,
265-
name: this.uniqueIdentifier.generateUniqueString('product-one'),
266-
quantity: 122,
267-
required: false,
268-
}),
269-
productTwo: new ProductConfig({
270-
id: env.get('PRODUCT_TWO').asString(),
271-
key: PRODUCT_KEY.TWO,
272-
name: this.uniqueIdentifier.generateUniqueString('product-two'),
273-
quantity: 123,
274-
required: false,
275-
}),
276-
productThree: new ProductConfig({
277-
id: env.get('PRODUCT_THREE').asString(),
278-
key: PRODUCT_KEY.THREE,
279-
name: this.uniqueIdentifier.generateUniqueString('product-three'),
280-
quantity: 150,
268+
// Fulfill products data in app config dynamically based on the products.csv
269+
const productsData = readCsvFile(AppConfig.PRODUCTS_IMPORT_FILE_PATH);
270+
productsData.forEach((productData) => {
271+
this.products[productData['ProductCode']] = new ProductConfig({
272+
key: productData['ProductCode'],
273+
name: productData['Name'],
274+
quantity: parseInt(productData['Quantity']),
281275
required: false,
282-
}),
283-
productFour: new ProductConfig({
284-
id: env.get('PRODUCT_FOUR').asString(),
285-
key: PRODUCT_KEY.FOUR,
286-
name: this.uniqueIdentifier.generateUniqueString('product-four'),
287-
quantity: 100,
288-
required: false,
289-
}),
290-
productFive: new ProductConfig({
291-
id: env.get('PRODUCT_FIVE').asString(),
292-
key: PRODUCT_KEY.FIVE,
293-
name: this.uniqueIdentifier.generateUniqueString('aa-product-five'),
294-
//'aa' part was added to improve visibility of ordering products alphabetically
295-
quantity: 160,
296-
required: false,
297-
}),
298-
};
276+
})
277+
})
299278

300279
this.receivingBinPrefix = env
301280
.get('RECEIVING_BIN_PREFIX')

src/config/ProductConfig.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class ProductConfig {
4444
* @returns {boolean}
4545
*/
4646
get isCreateNew() {
47-
return !this.id;
47+
return !this.readId();
4848
}
4949

5050
/**

src/fixtures/fixtures.ts

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import LocationChooser from '@/components/LocationChooser';
1010
import Navbar from '@/components/Navbar';
1111
import AppConfig, {
1212
LOCATION_KEY,
13-
PRODUCT_KEY,
1413
USER_KEY,
1514
} from '@/config/AppConfig';
1615
import CreateInbound from '@/pages/inbound/create/CreateInboundPage';
@@ -82,11 +81,7 @@ type Fixtures = {
8281
wardLocationService: LocationData;
8382
noPickAndPutawayStockDepotService: LocationData;
8483
// PRODUCT DATA
85-
mainProductService: ProductData;
86-
otherProductService: ProductData;
87-
thirdProductService: ProductData;
88-
fourthProductService: ProductData;
89-
fifthProductService: ProductData;
84+
productService: ProductData;
9085
// USERS DATA
9186
mainUserService: UserData;
9287
altUserService: UserData;
@@ -159,16 +154,8 @@ export const test = baseTest.extend<Fixtures>({
159154
noPickAndPutawayStockDepotService: async ({ page }, use) =>
160155
use(new LocationData(LOCATION_KEY.NO_PICK_AND_PUTAWAY_STOCK, page.request)),
161156
// PRODUCTS
162-
mainProductService: async ({ page }, use) =>
163-
use(new ProductData(PRODUCT_KEY.ONE, page.request)),
164-
otherProductService: async ({ page }, use) =>
165-
use(new ProductData(PRODUCT_KEY.TWO, page.request)),
166-
thirdProductService: async ({ page }, use) =>
167-
use(new ProductData(PRODUCT_KEY.THREE, page.request)),
168-
fourthProductService: async ({ page }, use) =>
169-
use(new ProductData(PRODUCT_KEY.FOUR, page.request)),
170-
fifthProductService: async ({ page }, use) =>
171-
use(new ProductData(PRODUCT_KEY.FIVE, page.request)),
157+
productService: async ({ page }, use) =>
158+
use(new ProductData(page.request)),
172159
// USERS
173160
mainUserService: async ({ page }, use) =>
174161
use(new UserData(USER_KEY.MAIN, page.request)),

src/setup/createData.setup.ts

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,19 @@
11
import AppConfig from '@/config/AppConfig';
22
import { test } from '@/fixtures/fixtures';
33
import { readFile, writeToFile } from '@/utils/FileIOUtils';
4-
import { parseUrl } from '@/utils/UrlUtils';
54

65
test('create data', async ({
7-
page,
8-
createProductPage,
9-
productShowPage,
106
locationService,
117
mainLocationService,
128
}) => {
139
// eslint-disable-next-line playwright/no-conditional-in-test
1410
const data = readFile(AppConfig.TEST_DATA_FILE_PATH) || {};
1511

16-
const seedData: Record<'products' | 'locations', Record<string, string>> = {
12+
const seedData: Record<'locations', Record<string, string>> = {
1713
...data,
18-
products: {},
1914
locations: {},
2015
};
2116

22-
// // PRODUCST
23-
const products = Object.values(AppConfig.instance.products).filter(
24-
(product) => product.isCreateNew
25-
);
26-
27-
for (const product of products) {
28-
await test.step(`create product ${product.key}`, async () => {
29-
await createProductPage.goToPage();
30-
await createProductPage.waitForUrl();
31-
await createProductPage.productDetails.nameField.fill(product.name);
32-
await createProductPage.productDetails.categorySelect.click();
33-
await createProductPage.productDetails.categorySelectDropdown
34-
.getByRole('listitem')
35-
.first()
36-
.click();
37-
await createProductPage.saveButton.click();
38-
39-
await productShowPage.recordStockButton.click();
40-
41-
await productShowPage.recordStock.lineItemsTable
42-
.row(1)
43-
.newQuantity.getByRole('textbox')
44-
.fill(`${product.quantity}`);
45-
await productShowPage.recordStock.lineItemsTable.saveButton.click();
46-
await productShowPage.showStockCardButton.click();
47-
48-
const productUrl = parseUrl(
49-
page.url(),
50-
'/openboxes/inventoryItem/showStockCard/$id'
51-
);
52-
seedData.products[`${product.key}`] = productUrl.id;
53-
});
54-
}
55-
5617
// LOCATIONS
5718
const { organization } = await mainLocationService.getLocation();
5819
const { data: locationTypes } = await locationService.getLocationTypes();

src/setup/dataImport.setup.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import InventoryService from '@/api/InventoryService';
2+
import ProductService from '@/api/ProductService';
3+
import AppConfig from '@/config/AppConfig';
4+
import { test } from '@/fixtures/fixtures';
5+
import { readCsvFile, readFile, writeToFile } from '@/utils/FileIOUtils';
6+
7+
test('import data', async ({ request }) => {
8+
// eslint-disable-next-line playwright/no-conditional-in-test
9+
const data = readFile(AppConfig.TEST_DATA_FILE_PATH) || {};
10+
11+
const seedData: Record<'products', Record<string, string>> = {
12+
...data,
13+
products: {},
14+
};
15+
16+
// PRODUCTS
17+
const productService = new ProductService(request);
18+
19+
const productsData = readCsvFile(AppConfig.PRODUCTS_IMPORT_FILE_PATH);
20+
21+
await test.step(`importing ${productsData.length} products`, async () => {
22+
const importedData = await productService.importProducts(productsData);
23+
importedData.data.forEach((product) => {
24+
seedData.products[product.productCode] = product.id;
25+
})
26+
})
27+
28+
// INVENTORIES
29+
const inventoryService = new InventoryService(request);
30+
31+
const inventoriesData = readCsvFile(AppConfig.INVENTORY_IMPORT_FILE_PATH);
32+
33+
await test.step(`importing ${inventoriesData.length} inventories`, async () => {
34+
await inventoryService.importInventories(
35+
inventoriesData,
36+
AppConfig.instance.locations.main.id,
37+
);
38+
});
39+
40+
writeToFile(AppConfig.TEST_DATA_FILE_PATH, seedData);
41+
})

src/setup/dataImport/inventory.csv

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Product code,Product,Lot number,Expiration date,Bin location,Quantity,Comment
2+
1,E2E-product-one,,,,122,
3+
2,E2E-product-two,,,,123,

src/setup/dataImport/products.csv

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Id,ProductCode,ProductType,Name,ProductFamily,Category,GLAccount,Description,UnitOfMeasure,Tags,UnitCost,LotAndExpiryControl,ColdChain,ControlledSubstance,HazardousMaterial,Reconditioned,Manufacturer,BrandName,ManufacturerCode,ManufacturerName,Vendor,VendorCode,VendorName,UPC,NDC,Created,Updated
2+
,1,Default,E2E-product-one,,ARVS,,,,,,,,,,,,,,,,,,,,,
3+
,2,Default,E2E-product-two,,ARVS,,,,,,,,,,,,,,,,,,,,,
4+
,3,Default,E2E-product-three,,ARVS,,,,,,,,,,,,,,,,,,,,,
5+
,4,Default,E2E-product-four,,ARVS,,,,,,,,,,,,,,,,,,,,,
6+
,5,Default,E2E-product-five,,ARVS,,,,,,,,,,,,,,,,,,,,,

0 commit comments

Comments
 (0)