From ba3bac10a7651e62deb6ee36fd33f98775e1b468 Mon Sep 17 00:00:00 2001
From: Oliver <oliver.henry.walters@gmail.com>
Date: Wed, 23 Oct 2024 23:24:21 +1100
Subject: [PATCH] PUI: Show "Return from customer" option (#8345)

* PUI: Show "Return from customer" option

* Extend playwright tests

* Additional unit tests
---
 src/frontend/src/pages/stock/StockDetail.tsx | 16 ++++---
 src/frontend/tests/pages/pui_stock.spec.ts   | 46 ++++++++++++++++++++
 2 files changed, 56 insertions(+), 6 deletions(-)

diff --git a/src/frontend/src/pages/stock/StockDetail.tsx b/src/frontend/src/pages/stock/StockDetail.tsx
index fded8ad13f..1a9c72368f 100644
--- a/src/frontend/src/pages/stock/StockDetail.tsx
+++ b/src/frontend/src/pages/stock/StockDetail.tsx
@@ -631,6 +631,7 @@ export default function StockDetail() {
   });
 
   const stockActions = useMemo(() => {
+    const inStock = stockitem.in_stock;
     const serial = stockitem.serial;
     const serialized =
       serial != null &&
@@ -654,13 +655,12 @@ export default function StockDetail() {
       />,
       <ActionDropdown
         tooltip={t`Stock Operations`}
-        hidden={!stockitem.in_stock}
         icon={<IconPackages />}
         actions={[
           {
             name: t`Count`,
             tooltip: t`Count stock`,
-            hidden: serialized,
+            hidden: serialized || !inStock,
             icon: (
               <InvenTreeIcon icon="stocktake" iconProps={{ color: 'blue' }} />
             ),
@@ -671,7 +671,7 @@ export default function StockDetail() {
           {
             name: t`Add`,
             tooltip: t`Add Stock`,
-            hidden: serialized,
+            hidden: serialized || !inStock,
             icon: <InvenTreeIcon icon="add" iconProps={{ color: 'green' }} />,
             onClick: () => {
               stockitem.pk && addStockItem.open();
@@ -680,7 +680,7 @@ export default function StockDetail() {
           {
             name: t`Remove`,
             tooltip: t`Remove Stock`,
-            hidden: serialized,
+            hidden: serialized || !inStock,
             icon: <InvenTreeIcon icon="remove" iconProps={{ color: 'red' }} />,
             onClick: () => {
               stockitem.pk && removeStockItem.open();
@@ -689,7 +689,10 @@ export default function StockDetail() {
           {
             name: t`Serialize`,
             tooltip: t`Serialize stock`,
-            hidden: serialized || stockitem?.part_detail?.trackable != true,
+            hidden:
+              !inStock ||
+              serialized ||
+              stockitem?.part_detail?.trackable != true,
             icon: <InvenTreeIcon icon="serial" iconProps={{ color: 'blue' }} />,
             onClick: () => {
               serializeStockItem.open();
@@ -698,6 +701,7 @@ export default function StockDetail() {
           {
             name: t`Transfer`,
             tooltip: t`Transfer Stock`,
+            hidden: !inStock,
             icon: (
               <InvenTreeIcon icon="transfer" iconProps={{ color: 'blue' }} />
             ),
@@ -708,7 +712,7 @@ export default function StockDetail() {
           {
             name: t`Return`,
             tooltip: t`Return from customer`,
-            hidden: !stockitem.customer,
+            hidden: !stockitem.sales_order,
             icon: (
               <InvenTreeIcon
                 icon="return_orders"
diff --git a/src/frontend/tests/pages/pui_stock.spec.ts b/src/frontend/tests/pages/pui_stock.spec.ts
index 69ce860679..24f807bd06 100644
--- a/src/frontend/tests/pages/pui_stock.spec.ts
+++ b/src/frontend/tests/pages/pui_stock.spec.ts
@@ -111,3 +111,49 @@ test('Stock - Serial Numbers', async ({ page }) => {
   // Close the form
   await page.getByRole('button', { name: 'Cancel' }).click();
 });
+
+/**
+ * Test various 'actions' on the stock detail page
+ */
+test('Stock - Stock Actions', async ({ page }) => {
+  await doQuickLogin(page);
+
+  // Find an in-stock, untracked item
+  await page.goto(
+    `${baseUrl}/stock/location/index/stock-items?in_stock=1&serialized=0`
+  );
+  await page.getByText('530470210').first().click();
+  await page
+    .locator('div')
+    .filter({ hasText: /^Quantity: 270$/ })
+    .first()
+    .waitFor();
+
+  // Check for expected action sections
+  await page.getByLabel('action-menu-barcode-actions').click();
+  await page.getByLabel('action-menu-barcode-actions-link-barcode').click();
+  await page.getByRole('banner').getByRole('button').click();
+
+  await page.getByLabel('action-menu-printing-actions').click();
+  await page.getByLabel('action-menu-printing-actions-print-labels').click();
+  await page.getByRole('button', { name: 'Cancel' }).click();
+
+  await page.getByLabel('action-menu-stock-operations').click();
+  await page.getByLabel('action-menu-stock-operations-count').waitFor();
+  await page.getByLabel('action-menu-stock-operations-add').waitFor();
+  await page.getByLabel('action-menu-stock-operations-remove').waitFor();
+  await page.getByLabel('action-menu-stock-operations-transfer').click();
+  await page.getByLabel('text-field-notes').fill('test notes');
+  await page.getByRole('button', { name: 'Submit' }).click();
+  await page.getByText('This field is required.').first().waitFor();
+  await page.getByRole('button', { name: 'Cancel' }).click();
+
+  // Find an item which has been sent to a customer
+  await page.goto(`${baseUrl}/stock/item/1012/details`);
+  await page.getByText('Batch Code: 2022-11-12').waitFor();
+  await page.getByText('Unavailable').waitFor();
+  await page.getByLabel('action-menu-stock-operations').click();
+  await page.getByLabel('action-menu-stock-operations-return').click();
+
+  await page.waitForTimeout(2500);
+});