mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-15 19:45:46 +00:00
* Adjust playwright test * Add StocktakeReport table in the "admin" section * Allow deletion of individual stocktake reports * Add placeholder buttons * Adds placeholder panel for stocktake data * Implement <PartStocktakeTable /> * Add modal to generate a new report * Generate stocktake report from part table * Adjust table value * panel display tweaks * Improve query efficiency for supplier price breaks * Refator part stocktake detail panel * Fix role checks * Cleanup code * Fix "double loader" in <InvenTreeTable /> * API efficiency improvements * Bump API version * Tweak playwright test * Update playwright test --------- Co-authored-by: Matthias Mair <code@mjmair.com>
201 lines
5.2 KiB
TypeScript
201 lines
5.2 KiB
TypeScript
import { t } from '@lingui/macro';
|
|
import { LineChart } from '@mantine/charts';
|
|
import { Center, Loader, SimpleGrid } from '@mantine/core';
|
|
import { useCallback, useMemo, useState } from 'react';
|
|
|
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
|
import { formatPriceRange } from '../../defaults/formatters';
|
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|
import { UserRoles } from '../../enums/Roles';
|
|
import {
|
|
generateStocktakeReportFields,
|
|
partStocktakeFields
|
|
} from '../../forms/PartForms';
|
|
import {
|
|
useCreateApiFormModal,
|
|
useDeleteApiFormModal,
|
|
useEditApiFormModal
|
|
} from '../../hooks/UseForm';
|
|
import { useTable } from '../../hooks/UseTable';
|
|
import { apiUrl } from '../../states/ApiState';
|
|
import { useUserState } from '../../states/UserState';
|
|
import { TableColumn } from '../../tables/Column';
|
|
import { InvenTreeTable } from '../../tables/InvenTreeTable';
|
|
import { RowDeleteAction, RowEditAction } from '../../tables/RowActions';
|
|
|
|
export default function PartStocktakeDetail({ partId }: { partId: number }) {
|
|
const user = useUserState();
|
|
const table = useTable('part-stocktake');
|
|
|
|
const stocktakeFields = useMemo(() => partStocktakeFields(), []);
|
|
|
|
const [selectedStocktake, setSelectedStocktake] = useState<
|
|
number | undefined
|
|
>(undefined);
|
|
|
|
const editStocktakeEntry = useEditApiFormModal({
|
|
pk: selectedStocktake,
|
|
url: ApiEndpoints.part_stocktake_list,
|
|
title: t`Edit Stocktake Entry`,
|
|
fields: stocktakeFields,
|
|
table: table
|
|
});
|
|
|
|
const deleteStocktakeEntry = useDeleteApiFormModal({
|
|
pk: selectedStocktake,
|
|
url: ApiEndpoints.part_stocktake_list,
|
|
title: t`Delete Stocktake Entry`,
|
|
table: table
|
|
});
|
|
|
|
const generateReport = useCreateApiFormModal({
|
|
url: ApiEndpoints.part_stocktake_report_generate,
|
|
title: t`Generate Stocktake Report`,
|
|
fields: generateStocktakeReportFields(),
|
|
initialData: {
|
|
part: partId
|
|
},
|
|
successMessage: t`Stocktake report scheduled`
|
|
});
|
|
|
|
const tableColumns: TableColumn[] = useMemo(() => {
|
|
return [
|
|
{
|
|
accessor: 'quantity',
|
|
sortable: true,
|
|
switchable: false
|
|
},
|
|
{
|
|
accessor: 'item_count',
|
|
title: t`Stock Items`,
|
|
switchable: true,
|
|
sortable: true
|
|
},
|
|
{
|
|
accessor: 'cost',
|
|
title: t`Stock Value`,
|
|
render: (record: any) => {
|
|
return formatPriceRange(record.cost_min, record.cost_max, {
|
|
currency: record.cost_min_currency
|
|
});
|
|
}
|
|
},
|
|
{
|
|
accessor: 'date',
|
|
sortable: true
|
|
},
|
|
{
|
|
accessor: 'note'
|
|
}
|
|
];
|
|
}, []);
|
|
|
|
const tableActions = useMemo(() => {
|
|
return [
|
|
<AddItemButton
|
|
tooltip={t`New Stocktake Report`}
|
|
onClick={() => generateReport.open()}
|
|
hidden={!user.hasAddRole(UserRoles.stocktake)}
|
|
/>
|
|
];
|
|
}, [user]);
|
|
|
|
const rowActions = useCallback(
|
|
(record: any) => {
|
|
return [
|
|
RowEditAction({
|
|
hidden: !user.hasChangeRole(UserRoles.stocktake),
|
|
onClick: () => {
|
|
setSelectedStocktake(record.pk);
|
|
editStocktakeEntry.open();
|
|
}
|
|
}),
|
|
RowDeleteAction({
|
|
hidden: !user.hasDeleteRole(UserRoles.stocktake),
|
|
onClick: () => {
|
|
setSelectedStocktake(record.pk);
|
|
deleteStocktakeEntry.open();
|
|
}
|
|
})
|
|
];
|
|
},
|
|
[user]
|
|
);
|
|
|
|
const chartData = useMemo(() => {
|
|
let records =
|
|
table.records?.map((record: any) => {
|
|
return {
|
|
date: record.date,
|
|
quantity: record.quantity,
|
|
value_min: record.cost_min,
|
|
value_max: record.cost_max
|
|
};
|
|
}) ?? [];
|
|
|
|
// Sort records to ensure correct date order
|
|
records.sort((a, b) => {
|
|
return new Date(a.date) < new Date(b.date) ? -1 : 1;
|
|
});
|
|
|
|
return records;
|
|
}, [table.records]);
|
|
|
|
return (
|
|
<>
|
|
{generateReport.modal}
|
|
{editStocktakeEntry.modal}
|
|
{deleteStocktakeEntry.modal}
|
|
<SimpleGrid cols={2}>
|
|
<InvenTreeTable
|
|
url={apiUrl(ApiEndpoints.part_stocktake_list)}
|
|
tableState={table}
|
|
columns={tableColumns}
|
|
props={{
|
|
params: {
|
|
part: partId
|
|
},
|
|
rowActions: rowActions,
|
|
tableActions: tableActions
|
|
}}
|
|
/>
|
|
{table.isLoading ? (
|
|
<Center>
|
|
<Loader />
|
|
</Center>
|
|
) : (
|
|
<LineChart
|
|
data={chartData}
|
|
dataKey="date"
|
|
withLegend
|
|
withYAxis
|
|
withRightYAxis
|
|
yAxisLabel={t`Quantity`}
|
|
rightYAxisLabel={t`Stock Value`}
|
|
series={[
|
|
{
|
|
name: 'quantity',
|
|
label: t`Quantity`,
|
|
color: 'blue.6',
|
|
yAxisId: 'left'
|
|
},
|
|
{
|
|
name: 'value_min',
|
|
label: t`Min Value`,
|
|
color: 'teal.6',
|
|
yAxisId: 'right'
|
|
},
|
|
{
|
|
name: 'value_max',
|
|
label: t`Max Value`,
|
|
color: 'red.6',
|
|
yAxisId: 'right'
|
|
}
|
|
]}
|
|
/>
|
|
)}
|
|
</SimpleGrid>
|
|
</>
|
|
);
|
|
}
|