From d28d4d62834fb2a1115c4289e2ffe217e268ae8f Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Tue, 31 May 2022 12:38:07 -0700 Subject: [PATCH] IO-1914 Added inventory page. --- bodyshop_translations.babel | 168 ++++++++++++++++++ .../bill-form/bill-form.totals.utility.js | 1 - .../components/header/header.component.jsx | 9 +- .../inventory-list.component.jsx | 156 ++++++++++++++++ .../inventory-list.container.jsx | 67 +++++++ .../components/rbac-wrapper/rbac-defaults.js | 4 +- .../shop-info/shop-info.rbac.component.jsx | 12 ++ client/src/graphql/inventory.queries.js | 50 ++++++ client/src/pages/inventory/inventory.page.jsx | 32 ++++ .../pages/manage/manage.page.component.jsx | 6 + client/src/translations/en_us/common.json | 10 +- client/src/translations/es/common.json | 10 +- client/src/translations/fr/common.json | 10 +- hasura/metadata/functions.yaml | 3 + hasura/metadata/tables.yaml | 1 + .../1654022042838_run_sql_migration/down.sql | 40 +++++ .../1654022042838_run_sql_migration/up.sql | 38 ++++ .../1654023497348_run_sql_migration/down.sql | 37 ++++ .../1654023497348_run_sql_migration/up.sql | 35 ++++ .../1654023506265_run_sql_migration/down.sql | 37 ++++ .../1654023506265_run_sql_migration/up.sql | 35 ++++ .../1654023641223_run_sql_migration/down.sql | 37 ++++ .../1654023641223_run_sql_migration/up.sql | 35 ++++ .../1654023762220_run_sql_migration/down.sql | 3 + .../1654023762220_run_sql_migration/up.sql | 1 + 25 files changed, 830 insertions(+), 7 deletions(-) create mode 100644 client/src/components/inventory-list/inventory-list.component.jsx create mode 100644 client/src/components/inventory-list/inventory-list.container.jsx create mode 100644 client/src/pages/inventory/inventory.page.jsx create mode 100644 hasura/migrations/1654022042838_run_sql_migration/down.sql create mode 100644 hasura/migrations/1654022042838_run_sql_migration/up.sql create mode 100644 hasura/migrations/1654023497348_run_sql_migration/down.sql create mode 100644 hasura/migrations/1654023497348_run_sql_migration/up.sql create mode 100644 hasura/migrations/1654023506265_run_sql_migration/down.sql create mode 100644 hasura/migrations/1654023506265_run_sql_migration/up.sql create mode 100644 hasura/migrations/1654023641223_run_sql_migration/down.sql create mode 100644 hasura/migrations/1654023641223_run_sql_migration/up.sql create mode 100644 hasura/migrations/1654023762220_run_sql_migration/down.sql create mode 100644 hasura/migrations/1654023762220_run_sql_migration/up.sql diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index ec94be6a6..eac8b6536 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -17292,6 +17292,69 @@ labels + + consumedbyjob + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + frombillinvoicenumber + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + fromvendor + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + inventory false @@ -17313,6 +17376,48 @@ + + showall + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + showavailable + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + @@ -30064,6 +30169,27 @@ + + inventory + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + jobs false @@ -42492,6 +42618,27 @@ + + inventory + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + jobs false @@ -43313,6 +43460,27 @@ + + inventory + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + jobs false diff --git a/client/src/components/bill-form/bill-form.totals.utility.js b/client/src/components/bill-form/bill-form.totals.utility.js index 05396e66c..df84ec86e 100644 --- a/client/src/components/bill-form/bill-form.totals.utility.js +++ b/client/src/components/bill-form/bill-form.totals.utility.js @@ -18,7 +18,6 @@ export const CalculateBillTotal = (invoice) => { amount: Math.round((i.actual_cost || 0) * 100), }).multiply(i.quantity || 1); - console.log(i, itemTotal.toFormat); subtotal = subtotal.add(itemTotal); if (i.applicable_taxes.federal) { federalTax = federalTax.add( diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index 2a4e9698e..63af81751 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -200,6 +200,13 @@ function Header({ {t("menus.header.enterbills")} + } + > + {t("menus.header.inventory")} + + }> {t("menus.header.allpayments")} @@ -216,7 +223,6 @@ function Header({ {t("menus.header.enterpayment")} - }> {t("menus.header.timetickets")} @@ -235,7 +241,6 @@ function Header({ {t("menus.header.entertimeticket")} - ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); + +export function JobsList({ bodyshop, refetch, loading, jobs, total }) { + const search = queryString.parse(useLocation().search); + const { page, sortcolumn, sortorder, invfilters } = search; + const history = useHistory(); + + const { t } = useTranslation(); + const columns = [ + { + title: t("billlines.fields.line_desc"), + dataIndex: "line_desc", + key: "line_desc", + + sorter: true, //(a, b) => alphaSort(a.line_desc, b.line_desc), + sortOrder: sortcolumn === "line_desc" && sortorder, + }, + { + title: t("inventory.labels.frombillinvoicenumber"), + dataIndex: "vendorname", + key: "vendorname", + ellipsis: true, + //sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), + + //sortOrder: sortcolumn === "ownr_ln" && sortorder, + render: (text, record) => record.billline?.bill?.invoice_number, + }, + { + title: t("inventory.labels.fromvendor"), + dataIndex: "vendorname", + key: "vendorname", + ellipsis: true, + //sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), + + //sortOrder: sortcolumn === "ownr_ln" && sortorder, + render: (text, record) => record.billline?.bill?.vendor?.name, + }, + { + title: t("billlines.fields.actual_price"), + dataIndex: "actual_price", + key: "actual_price", + + render: (text, record) => ( + {record.actual_price} + ), + }, + { + title: t("billlines.fields.actual_cost"), + dataIndex: "actual_cost", + key: "actual_cost", + + render: (text, record) => ( + {record.actual_cost} + ), + }, + { + title: t("inventory.labels.consumedbyjob"), + dataIndex: "consumedbyjob", + key: "consumedbyjob", + + ellipsis: true, + render: (text, record) => record.bill?.job?.ro_number, + }, + ]; + + const handleTableChange = (pagination, filters, sorter) => { + search.page = pagination.current; + search.sortcolumn = sorter.column && sorter.column.key; + search.sortorder = sorter.order; + history.push({ search: queryString.stringify(search) }); + }; + + return ( + + {search.search && ( + <> + + {t("general.labels.searchresults", { search: search.search })} + + + + )} + + + + + { + search.search = value; + history.push({ search: queryString.stringify(search) }); + }} + enterButton + /> + + } + > + + + ); +} +export default connect(mapStateToProps, mapDispatchToProps)(JobsList); diff --git a/client/src/components/inventory-list/inventory-list.container.jsx b/client/src/components/inventory-list/inventory-list.container.jsx new file mode 100644 index 000000000..568b3162a --- /dev/null +++ b/client/src/components/inventory-list/inventory-list.container.jsx @@ -0,0 +1,67 @@ +import { useQuery } from "@apollo/client"; +import queryString from "query-string"; +import React from "react"; +import { connect } from "react-redux"; +import { useLocation } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { QUERY_INVENTORY_PAGINATED } from "../../graphql/inventory.queries"; +import { + setBreadcrumbs, + setSelectedHeader, +} from "../../redux/application/application.actions"; +import AlertComponent from "../alert/alert.component"; +import InventoryListPaginated from "./inventory-list.component"; +import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; + +const mapStateToProps = createStructuredSelector({ + //bodyshop: selectBodyshop, +}); + +const mapDispatchToProps = (dispatch) => ({ + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), +}); + +export function InventoryList({ setBreadcrumbs, setSelectedHeader }) { + const searchParams = queryString.parse(useLocation().search); + const { page, sortcolumn, sortorder, search, showall } = searchParams; + + const { loading, error, data, refetch } = useQuery( + QUERY_INVENTORY_PAGINATED, + { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + variables: { + search: search || "", + offset: page ? (page - 1) * 25 : 0, + limit: 25, + consumedIsNull: showall === "true" ? null : true, + order: [ + { + [sortcolumn || "created_at"]: + sortorder && sortorder !== "false" + ? sortorder === "descend" + ? "desc" + : "asc" + : "desc", + }, + ], + }, + } + ); + + if (error) return ; + return ( + + + + ); +} + +export default connect(mapStateToProps, mapDispatchToProps)(InventoryList); diff --git a/client/src/components/rbac-wrapper/rbac-defaults.js b/client/src/components/rbac-wrapper/rbac-defaults.js index 089e8954e..2f83443ac 100644 --- a/client/src/components/rbac-wrapper/rbac-defaults.js +++ b/client/src/components/rbac-wrapper/rbac-defaults.js @@ -25,7 +25,7 @@ const ret = { "jobs:detail": 1, "jobs:partsqueue": 4, "jobs:checklist-view": 2, - + "jobs:list-ready": 1, "bills:enter": 2, "bills:view": 2, "bills:list": 2, @@ -66,5 +66,7 @@ const ret = { "timetickets:shiftedit": 5, "users:editaccess": 4, + + "inventory:list": 1, }; export default ret; diff --git a/client/src/components/shop-info/shop-info.rbac.component.jsx b/client/src/components/shop-info/shop-info.rbac.component.jsx index 3c0f75c88..4c425ff69 100644 --- a/client/src/components/shop-info/shop-info.rbac.component.jsx +++ b/client/src/components/shop-info/shop-info.rbac.component.jsx @@ -633,6 +633,18 @@ export default function ShopInfoRbacComponent({ form }) { > + + + ); diff --git a/client/src/graphql/inventory.queries.js b/client/src/graphql/inventory.queries.js index 8c5bb7a4b..a2c36f6c1 100644 --- a/client/src/graphql/inventory.queries.js +++ b/client/src/graphql/inventory.queries.js @@ -47,3 +47,53 @@ export const QUERY_OUTSTANDING_INVENTORY = gql` } } `; + +export const QUERY_INVENTORY_PAGINATED = gql` + query QUERY_INVENTORY_PAGINATED( + $search: String + $offset: Int + $limit: Int + $order: [inventory_order_by!] + $consumedIsNull: Boolean + ) { + search_inventory( + args: { search: $search } + offset: $offset + limit: $limit + order_by: $order + where: { consumedbybillid: { _is_null: $consumedIsNull } } + ) { + id + line_desc + actual_price + actual_cost + bill { + id + invoice_number + job { + ro_number + id + } + } + billline { + id + bill { + id + invoice_number + vendor { + id + name + } + } + } + } + search_inventory_aggregate( + args: { search: $search } + where: { consumedbybillid: { _is_null: $consumedIsNull } } + ) { + aggregate { + count(distinct: true) + } + } + } +`; diff --git a/client/src/pages/inventory/inventory.page.jsx b/client/src/pages/inventory/inventory.page.jsx new file mode 100644 index 000000000..26cc2f2dc --- /dev/null +++ b/client/src/pages/inventory/inventory.page.jsx @@ -0,0 +1,32 @@ +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; +import { + setBreadcrumbs, + setSelectedHeader, +} from "../../redux/application/application.actions"; +import InventoryList from "../../components/inventory-list/inventory-list.container"; + +const mapDispatchToProps = (dispatch) => ({ + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), +}); + +export function InventoryPage({ setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); + + useEffect(() => { + document.title = t("titles.inventory"); + setSelectedHeader("inventory"); + setBreadcrumbs([{ link: "/manage/jobs", label: t("titles.bc.inventory") }]); + }, [t, setBreadcrumbs, setSelectedHeader]); + + return ( + + + + ); +} + +export default connect(null, mapDispatchToProps)(InventoryPage); diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx index 0fc635598..82017ffae 100644 --- a/client/src/pages/manage/manage.page.component.jsx +++ b/client/src/pages/manage/manage.page.component.jsx @@ -34,6 +34,7 @@ const JobsPage = lazy(() => import("../jobs/jobs.page")); const JobsDetailPage = lazy(() => import("../jobs-detail/jobs-detail.page.container") ); +const InventoryListPage = lazy(() => import("../inventory/inventory.page")); const ProfilePage = lazy(() => import("../profile/profile.container.page")); const JobsAvailablePage = lazy(() => import("../jobs-available/jobs-available.page.container") @@ -250,6 +251,11 @@ export function Manage({ match, conflict, bodyshop }) { +