860 lines
40 KiB
TypeScript
860 lines
40 KiB
TypeScript
import { Head, useForm, Link } from '@inertiajs/react';
|
|
import { useState } from 'react';
|
|
import { InventoryNotice, InventoryPagination } from '@/components/inventory';
|
|
import {
|
|
PageTitle,
|
|
Panel,
|
|
FormErrors,
|
|
controlClass,
|
|
} from '@/components/procurement';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { quantity } from '@/types/inventory';
|
|
import type { StockItem, Page } from '@/types/inventory';
|
|
|
|
type Level = {
|
|
id: number;
|
|
code: string;
|
|
name: string;
|
|
criteria: string;
|
|
is_critical: boolean;
|
|
};
|
|
type Asset = {
|
|
id: number;
|
|
code: string;
|
|
description: string;
|
|
area: string;
|
|
serial_number: string | null;
|
|
status: string;
|
|
department_id: number;
|
|
stock_item_id: number | null;
|
|
criticality_level_id: number;
|
|
version: number;
|
|
};
|
|
type Part = {
|
|
id: number;
|
|
stock_item_id: number;
|
|
criticality_level_id: number;
|
|
minimum_milli: number;
|
|
balance_milli: number;
|
|
below_minimum: boolean;
|
|
version: number;
|
|
};
|
|
type History = {
|
|
id: number;
|
|
reference: string;
|
|
quantity_milli: number;
|
|
snapshot: {
|
|
equipment?: { code: string; description: string };
|
|
stock_item: { name: string; unit: string };
|
|
reason?: string;
|
|
};
|
|
};
|
|
type Props = {
|
|
equipment: Asset[];
|
|
parts: Part[];
|
|
levels: Level[];
|
|
links: { equipment_id: number; spare_part_id: number }[];
|
|
items: StockItem[];
|
|
departments: { id: number; name: string }[];
|
|
history: Page<History>;
|
|
canManage: boolean;
|
|
canConfigure: boolean;
|
|
demo?: boolean;
|
|
};
|
|
function Field({
|
|
label,
|
|
children,
|
|
}: {
|
|
label: string;
|
|
children: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<label className="grid gap-2 text-sm">
|
|
{label}
|
|
{children}
|
|
</label>
|
|
);
|
|
}
|
|
const statuses: Record<string, string> = {
|
|
active: 'Em serviço',
|
|
stopped: 'Parado',
|
|
retired: 'Retirado',
|
|
};
|
|
export default function EquipmentPage({
|
|
equipment,
|
|
parts,
|
|
levels,
|
|
links,
|
|
items,
|
|
departments,
|
|
history,
|
|
canManage,
|
|
canConfigure,
|
|
demo = false,
|
|
}: Props) {
|
|
const [message, setMessage] = useState('');
|
|
const category = useForm({
|
|
code: '',
|
|
name: '',
|
|
criteria: '',
|
|
is_critical: '',
|
|
});
|
|
const asset = useForm({
|
|
id: '',
|
|
version: '',
|
|
code: '',
|
|
description: '',
|
|
area: '',
|
|
serial_number: '',
|
|
department_id: '',
|
|
stock_item_id: '',
|
|
criticality_level_id: '',
|
|
status: 'active',
|
|
});
|
|
const part = useForm({
|
|
id: '',
|
|
version: '',
|
|
stock_item_id: '',
|
|
criticality_level_id: '',
|
|
minimum_quantity: '',
|
|
});
|
|
const association = useForm({ equipment_id: '', spare_part_id: '' });
|
|
const stock = (id: number) => items.find((i) => i.id === id);
|
|
const criticality = (id: number) => levels.find((l) => l.id === id);
|
|
function simulate() {
|
|
setMessage(
|
|
'Simulação revista. Nenhum cadastro ou movimento real foi criado.',
|
|
);
|
|
}
|
|
return (
|
|
<>
|
|
<Head title="Equipamentos e peças" />
|
|
<div className="mx-auto flex w-full max-w-7xl flex-col gap-6 p-5 lg:p-8">
|
|
<PageTitle
|
|
eyebrow="Produção e armazém"
|
|
title="Equipamentos e peças sobressalentes"
|
|
description="Cadastro, peças compatíveis, mínimos explícitos e rastreabilidade das saídas."
|
|
/>
|
|
<InventoryNotice />
|
|
{demo && (
|
|
<p className="rounded-lg border bg-teal-50 p-4 text-sm text-teal-900">
|
|
Dados fictícios para revisão sem login. Formulários
|
|
apenas de simulação.
|
|
</p>
|
|
)}
|
|
<Panel
|
|
title="Regras e validação técnica"
|
|
description="Os critérios de criticidade e mínimos são definidos explicitamente pela empresa, sem valores predefinidos."
|
|
>
|
|
<p className="text-sm">
|
|
O responsável da área (chefe do departamento), distinto
|
|
do solicitante, valida tecnicamente a versão do pedido
|
|
de equipamento. Finanças é obrigatória para qualquer
|
|
valor. A adjudicação e emissão de equipamento exigem
|
|
regra de investimento vigente, com duas aprovações
|
|
administrativas, e reserva em dotação de investimento. A
|
|
validação técnica não substitui estes controlos.
|
|
</p>
|
|
<Link
|
|
className="mt-3 inline-block text-sm font-semibold text-teal-700"
|
|
href={demo ? '/consulta/requisicoes' : '/requisicoes'}
|
|
>
|
|
Consultar requisições →
|
|
</Link>
|
|
</Panel>
|
|
<Panel title="Equipamentos">
|
|
<div className="grid gap-4 lg:grid-cols-2">
|
|
{equipment.map((e) => (
|
|
<article
|
|
className="rounded-lg border p-4 text-sm"
|
|
key={e.id}
|
|
>
|
|
<strong>
|
|
{e.code} · {e.description}
|
|
</strong>
|
|
<p className="mt-2">
|
|
{e.area} ·{' '}
|
|
{
|
|
departments.find(
|
|
(d) => d.id === e.department_id,
|
|
)?.name
|
|
}{' '}
|
|
· {statuses[e.status]}
|
|
</p>
|
|
<p>
|
|
Série: {e.serial_number || 'Não indicada'} ·
|
|
Criticidade:{' '}
|
|
{criticality(e.criticality_level_id)?.name}
|
|
</p>
|
|
{e.stock_item_id && (
|
|
<p>
|
|
Artigo: {stock(e.stock_item_id)?.code}
|
|
</p>
|
|
)}
|
|
<p className="mt-2">
|
|
Peças associadas:{' '}
|
|
{links
|
|
.filter((l) => l.equipment_id === e.id)
|
|
.map(
|
|
(l) =>
|
|
stock(
|
|
parts.find(
|
|
(p) =>
|
|
p.id ===
|
|
l.spare_part_id,
|
|
)?.stock_item_id ?? 0,
|
|
)?.code,
|
|
)
|
|
.join(', ') || 'Nenhuma'}
|
|
</p>
|
|
{canManage && (
|
|
<Button
|
|
className="mt-3"
|
|
variant="outline"
|
|
onClick={() =>
|
|
asset.setData({
|
|
id: String(e.id),
|
|
version: String(e.version),
|
|
code: e.code,
|
|
description: e.description,
|
|
area: e.area,
|
|
serial_number:
|
|
e.serial_number ?? '',
|
|
department_id: String(
|
|
e.department_id,
|
|
),
|
|
stock_item_id: e.stock_item_id
|
|
? String(e.stock_item_id)
|
|
: '',
|
|
criticality_level_id: String(
|
|
e.criticality_level_id,
|
|
),
|
|
status: e.status,
|
|
})
|
|
}
|
|
>
|
|
Editar ficha
|
|
</Button>
|
|
)}
|
|
</article>
|
|
))}
|
|
</div>
|
|
{equipment.length === 0 && (
|
|
<p className="text-sm">
|
|
Sem equipamentos. Configure a criticidade antes do
|
|
cadastro.
|
|
</p>
|
|
)}
|
|
</Panel>
|
|
<Panel
|
|
title="Peças e alertas de mínimo"
|
|
description="Saldo total por artigo nos armazéns da empresa. Cada peça reutiliza um artigo existente; associá-la a várias máquinas não duplica o stock."
|
|
>
|
|
<div className="space-y-3">
|
|
{parts.map((p) => (
|
|
<article
|
|
key={p.id}
|
|
className={`rounded-lg border p-4 text-sm ${p.below_minimum ? 'border-amber-300 bg-amber-50 text-amber-950' : ''}`}
|
|
>
|
|
<strong>
|
|
{stock(p.stock_item_id)?.code} ·{' '}
|
|
{stock(p.stock_item_id)?.name}
|
|
</strong>
|
|
<p className="mt-2">
|
|
Stock: {quantity(p.balance_milli)} · Mínimo
|
|
definido: {quantity(p.minimum_milli)}{' '}
|
|
{stock(p.stock_item_id)?.unit} ·{' '}
|
|
{criticality(p.criticality_level_id)?.name}
|
|
</p>
|
|
<p className="mt-1 font-semibold">
|
|
{p.below_minimum
|
|
? 'Abaixo do mínimo — rever reposição'
|
|
: 'Mínimo assegurado'}
|
|
</p>
|
|
<p>
|
|
Critério:{' '}
|
|
{
|
|
criticality(p.criticality_level_id)
|
|
?.criteria
|
|
}
|
|
</p>
|
|
{canManage && (
|
|
<Button
|
|
className="mt-3"
|
|
variant="outline"
|
|
onClick={() =>
|
|
part.setData({
|
|
id: String(p.id),
|
|
version: String(p.version),
|
|
stock_item_id: String(
|
|
p.stock_item_id,
|
|
),
|
|
criticality_level_id: String(
|
|
p.criticality_level_id,
|
|
),
|
|
minimum_quantity: (
|
|
p.minimum_milli / 1000
|
|
).toFixed(3),
|
|
})
|
|
}
|
|
>
|
|
Editar mínimo e criticidade
|
|
</Button>
|
|
)}
|
|
</article>
|
|
))}
|
|
</div>
|
|
{parts.length === 0 && (
|
|
<p className="text-sm">Sem peças registadas.</p>
|
|
)}
|
|
</Panel>
|
|
{canManage && (
|
|
<div className="grid gap-6 lg:grid-cols-2">
|
|
<Panel
|
|
title={
|
|
asset.data.id
|
|
? 'Editar equipamento'
|
|
: 'Cadastrar equipamento'
|
|
}
|
|
>
|
|
<form
|
|
className="grid gap-4"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
if (demo) {
|
|
simulate();
|
|
return;
|
|
}
|
|
asset.post('/equipamentos/activos', {
|
|
onSuccess: () => asset.reset(),
|
|
});
|
|
}}
|
|
>
|
|
<FormErrors errors={asset.errors} />
|
|
{(
|
|
[
|
|
'code',
|
|
'description',
|
|
'area',
|
|
'serial_number',
|
|
] as const
|
|
).map((k) => (
|
|
<Field
|
|
key={k}
|
|
label={
|
|
{
|
|
code: 'Código',
|
|
description: 'Descrição',
|
|
area: 'Área / linha de produção',
|
|
serial_number:
|
|
'Número de série',
|
|
}[k]
|
|
}
|
|
>
|
|
<Input
|
|
required={k !== 'serial_number'}
|
|
value={asset.data[k]}
|
|
onChange={(e) =>
|
|
asset.setData(k, e.target.value)
|
|
}
|
|
/>
|
|
</Field>
|
|
))}
|
|
<Field label="Departamento">
|
|
<select
|
|
required
|
|
className={controlClass}
|
|
value={asset.data.department_id}
|
|
onChange={(e) =>
|
|
asset.setData(
|
|
'department_id',
|
|
e.target.value,
|
|
)
|
|
}
|
|
>
|
|
<option value="">Seleccionar</option>
|
|
{departments.map((d) => (
|
|
<option key={d.id} value={d.id}>
|
|
{d.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
<Field label="Artigo de stock (opcional)">
|
|
<select
|
|
className={controlClass}
|
|
value={asset.data.stock_item_id}
|
|
onChange={(e) =>
|
|
asset.setData(
|
|
'stock_item_id',
|
|
e.target.value,
|
|
)
|
|
}
|
|
>
|
|
<option value="">
|
|
Sem artigo associado
|
|
</option>
|
|
{items.map((i) => (
|
|
<option key={i.id} value={i.id}>
|
|
{i.code} · {i.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
<Field label="Criticidade definida pela empresa">
|
|
<select
|
|
required
|
|
className={controlClass}
|
|
value={asset.data.criticality_level_id}
|
|
onChange={(e) =>
|
|
asset.setData(
|
|
'criticality_level_id',
|
|
e.target.value,
|
|
)
|
|
}
|
|
>
|
|
<option value="">Seleccionar</option>
|
|
{levels.map((l) => (
|
|
<option key={l.id} value={l.id}>
|
|
{l.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
<Field label="Estado">
|
|
<select
|
|
className={controlClass}
|
|
value={asset.data.status}
|
|
onChange={(e) =>
|
|
asset.setData(
|
|
'status',
|
|
e.target.value,
|
|
)
|
|
}
|
|
>
|
|
{Object.entries(statuses).map(
|
|
([k, v]) => (
|
|
<option key={k} value={k}>
|
|
{v}
|
|
</option>
|
|
),
|
|
)}
|
|
</select>
|
|
</Field>
|
|
<Button
|
|
disabled={
|
|
asset.processing || levels.length === 0
|
|
}
|
|
>
|
|
Guardar equipamento
|
|
</Button>
|
|
{asset.data.id && (
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => asset.reset()}
|
|
>
|
|
Novo cadastro
|
|
</Button>
|
|
)}
|
|
<p className="text-xs text-muted-foreground">
|
|
O cadastro não cria entradas nem retira
|
|
existências. Os movimentos usam o fluxo de
|
|
stock.
|
|
</p>
|
|
</form>
|
|
</Panel>
|
|
<div className="space-y-6">
|
|
<Panel
|
|
title={
|
|
part.data.id
|
|
? 'Editar peça'
|
|
: 'Cadastrar peça sobre artigo existente'
|
|
}
|
|
>
|
|
<form
|
|
className="grid gap-4"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
if (demo) {
|
|
simulate();
|
|
return;
|
|
}
|
|
part.post('/equipamentos/pecas', {
|
|
onSuccess: () => part.reset(),
|
|
});
|
|
}}
|
|
>
|
|
<FormErrors errors={part.errors} />
|
|
<Field label="Artigo / referência">
|
|
<select
|
|
required
|
|
disabled={!!part.data.id}
|
|
className={controlClass}
|
|
value={part.data.stock_item_id}
|
|
onChange={(e) =>
|
|
part.setData(
|
|
'stock_item_id',
|
|
e.target.value,
|
|
)
|
|
}
|
|
>
|
|
<option value="">
|
|
Seleccionar
|
|
</option>
|
|
{items.map((i) => (
|
|
<option key={i.id} value={i.id}>
|
|
{i.code} · {i.name} (
|
|
{i.unit})
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
<Field label="Criticidade">
|
|
<select
|
|
required
|
|
className={controlClass}
|
|
value={
|
|
part.data.criticality_level_id
|
|
}
|
|
onChange={(e) =>
|
|
part.setData(
|
|
'criticality_level_id',
|
|
e.target.value,
|
|
)
|
|
}
|
|
>
|
|
<option value="">
|
|
Seleccionar
|
|
</option>
|
|
{levels.map((l) => (
|
|
<option key={l.id} value={l.id}>
|
|
{l.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
<Field label="Quantidade mínima explícita (pode ser zero)">
|
|
<Input
|
|
required
|
|
type="number"
|
|
min="0"
|
|
step="0.001"
|
|
value={part.data.minimum_quantity}
|
|
onChange={(e) =>
|
|
part.setData(
|
|
'minimum_quantity',
|
|
e.target.value,
|
|
)
|
|
}
|
|
/>
|
|
</Field>
|
|
<Button
|
|
disabled={
|
|
part.processing ||
|
|
levels.length === 0
|
|
}
|
|
>
|
|
Guardar peça
|
|
</Button>
|
|
{part.data.id && (
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => part.reset()}
|
|
>
|
|
Nova peça
|
|
</Button>
|
|
)}
|
|
</form>
|
|
</Panel>
|
|
<Panel title="Associar peça a equipamento">
|
|
<form
|
|
className="grid gap-4"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
if (demo) {
|
|
simulate();
|
|
return;
|
|
}
|
|
association.post(
|
|
'/equipamentos/associacoes',
|
|
{
|
|
onSuccess: () =>
|
|
association.reset(),
|
|
},
|
|
);
|
|
}}
|
|
>
|
|
<FormErrors errors={association.errors} />
|
|
<Field label="Equipamento">
|
|
<select
|
|
required
|
|
className={controlClass}
|
|
value={
|
|
association.data.equipment_id
|
|
}
|
|
onChange={(e) =>
|
|
association.setData(
|
|
'equipment_id',
|
|
e.target.value,
|
|
)
|
|
}
|
|
>
|
|
<option value="">
|
|
Seleccionar
|
|
</option>
|
|
{equipment.map((e) => (
|
|
<option key={e.id} value={e.id}>
|
|
{e.code} · {e.description}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
<Field label="Peça">
|
|
<select
|
|
required
|
|
className={controlClass}
|
|
value={
|
|
association.data.spare_part_id
|
|
}
|
|
onChange={(e) =>
|
|
association.setData(
|
|
'spare_part_id',
|
|
e.target.value,
|
|
)
|
|
}
|
|
>
|
|
<option value="">
|
|
Seleccionar
|
|
</option>
|
|
{parts.map((p) => (
|
|
<option key={p.id} value={p.id}>
|
|
{
|
|
stock(p.stock_item_id)
|
|
?.code
|
|
}{' '}
|
|
·{' '}
|
|
{
|
|
stock(p.stock_item_id)
|
|
?.name
|
|
}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
<Button disabled={association.processing}>
|
|
Associar sem duplicar stock
|
|
</Button>
|
|
</form>
|
|
</Panel>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{canConfigure && (
|
|
<Panel
|
|
title="Configurar critérios de criticidade"
|
|
description="Sem categorias automáticas. Registe a decisão da empresa; os critérios guardados não são reescritos. Para novos critérios, crie uma nova categoria."
|
|
>
|
|
<form
|
|
className="grid gap-4 sm:grid-cols-2"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
if (demo) {
|
|
simulate();
|
|
return;
|
|
}
|
|
category.post('/equipamentos/categorias', {
|
|
onSuccess: () => category.reset(),
|
|
});
|
|
}}
|
|
>
|
|
<Field label="Código">
|
|
<Input
|
|
required
|
|
value={category.data.code}
|
|
onChange={(e) =>
|
|
category.setData('code', e.target.value)
|
|
}
|
|
/>
|
|
</Field>
|
|
<Field label="Nome da categoria">
|
|
<Input
|
|
required
|
|
value={category.data.name}
|
|
onChange={(e) =>
|
|
category.setData('name', e.target.value)
|
|
}
|
|
/>
|
|
</Field>
|
|
<Field label="Critério e fundamento da classificação">
|
|
<textarea
|
|
required
|
|
className={controlClass}
|
|
value={category.data.criteria}
|
|
onChange={(e) =>
|
|
category.setData(
|
|
'criteria',
|
|
e.target.value,
|
|
)
|
|
}
|
|
/>
|
|
</Field>
|
|
<Field label="É crítica para produção segundo este critério?">
|
|
<select
|
|
required
|
|
className={controlClass}
|
|
value={category.data.is_critical}
|
|
onChange={(e) =>
|
|
category.setData(
|
|
'is_critical',
|
|
e.target.value,
|
|
)
|
|
}
|
|
>
|
|
<option value="">Decisão explícita</option>
|
|
<option value="1">Sim</option>
|
|
<option value="0">Não</option>
|
|
</select>
|
|
</Field>
|
|
<FormErrors errors={category.errors} />
|
|
<Button disabled={category.processing}>
|
|
Guardar critério
|
|
</Button>
|
|
</form>
|
|
<div className="mt-4 space-y-2 text-sm">
|
|
{levels.map((l) => (
|
|
<p key={l.id}>
|
|
<strong>
|
|
{l.code} · {l.name}
|
|
</strong>
|
|
: {l.criteria} ·{' '}
|
|
{l.is_critical ? 'Crítica' : 'Não crítica'}
|
|
</p>
|
|
))}
|
|
</div>
|
|
</Panel>
|
|
)}
|
|
<p role="status" className="text-sm">
|
|
{message}
|
|
</p>
|
|
<Panel title="Saídas rastreáveis por equipamento">
|
|
<Link
|
|
href={
|
|
demo
|
|
? '/consulta/operacoes-stock'
|
|
: '/stock/operacoes'
|
|
}
|
|
className="text-sm font-semibold text-teal-700"
|
|
>
|
|
Registar saída com equipamento de destino →
|
|
</Link>
|
|
<div className="mt-4 space-y-3">
|
|
{history.data.map((h) => (
|
|
<article
|
|
key={h.id}
|
|
className="rounded-lg border p-4 text-sm"
|
|
>
|
|
<strong>
|
|
{h.reference} · {h.snapshot.equipment?.code}
|
|
</strong>
|
|
<p>
|
|
{h.snapshot.equipment?.description} ·{' '}
|
|
{h.snapshot.stock_item.name} ·{' '}
|
|
{quantity(h.quantity_milli)}{' '}
|
|
{h.snapshot.stock_item.unit}
|
|
</p>
|
|
<p>{h.snapshot.reason}</p>
|
|
</article>
|
|
))}
|
|
{history.data.length === 0 && (
|
|
<p className="text-sm">
|
|
Sem saídas associadas a equipamentos.
|
|
</p>
|
|
)}
|
|
</div>
|
|
<InventoryPagination page={history} />
|
|
</Panel>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
export function EquipmentDemo() {
|
|
return (
|
|
<EquipmentPage
|
|
demo
|
|
canManage
|
|
canConfigure
|
|
equipment={[
|
|
{
|
|
id: 1,
|
|
code: 'EQ-DEMO-01',
|
|
description: 'Compressor fictício',
|
|
area: 'Linha de produção fictícia A',
|
|
serial_number: 'SERIE-DEMO',
|
|
status: 'stopped',
|
|
department_id: 1,
|
|
stock_item_id: null,
|
|
criticality_level_id: 1,
|
|
version: 1,
|
|
},
|
|
]}
|
|
parts={[
|
|
{
|
|
id: 1,
|
|
stock_item_id: 1,
|
|
criticality_level_id: 1,
|
|
minimum_milli: 3000,
|
|
balance_milli: 1000,
|
|
below_minimum: true,
|
|
version: 1,
|
|
},
|
|
]}
|
|
links={[{ equipment_id: 1, spare_part_id: 1 }]}
|
|
levels={[
|
|
{
|
|
id: 1,
|
|
code: 'CRIT-DEMO',
|
|
name: 'Crítica (exemplo fictício)',
|
|
criteria:
|
|
'Exemplo apenas: falha interrompe a linha fictícia. A empresa deve definir os seus critérios.',
|
|
is_critical: true,
|
|
},
|
|
]}
|
|
items={[
|
|
{
|
|
id: 1,
|
|
code: 'ROL-DEMO',
|
|
name: 'Rolamento fictício',
|
|
unit: 'un',
|
|
},
|
|
]}
|
|
departments={[{ id: 1, name: 'Produção fictícia' }]}
|
|
history={{
|
|
data: [
|
|
{
|
|
id: 1,
|
|
reference: 'SAI-DEMO-PECA',
|
|
quantity_milli: 1000,
|
|
snapshot: {
|
|
equipment: {
|
|
code: 'EQ-DEMO-01',
|
|
description:
|
|
'Compressor fictício no momento da saída',
|
|
},
|
|
stock_item: {
|
|
name: 'Rolamento fictício',
|
|
unit: 'un',
|
|
},
|
|
reason: 'Substituição de peça numa paragem fictícia.',
|
|
},
|
|
},
|
|
],
|
|
prev_page_url: null,
|
|
next_page_url: null,
|
|
}}
|
|
/>
|
|
);
|
|
}
|