fix after review

This commit is contained in:
MaKarin
2026-03-24 12:49:23 +03:00
parent 5b8d8a85c4
commit b9b936c9ea
18 changed files with 461 additions and 246 deletions

View File

@@ -1,6 +1,6 @@
import { DataProvider, fetchUtils } from 'react-admin'; import { DataProvider, fetchUtils } from 'react-admin';
const apiUrl = 'http://localhost:3001'; const apiUrl = 'http://localhost:3000';
const httpClient = fetchUtils.fetchJson; const httpClient = fetchUtils.fetchJson;
function buildQueryString(query: Record<string, unknown>) { function buildQueryString(query: Record<string, unknown>) {

View File

@@ -1,14 +1,14 @@
import { Create, SimpleForm, TextInput } from 'react-admin'; import { Create, SimpleForm, TextInput, NumberInput } from 'react-admin';
export const EquipmentTypeCreate = () => ( export const EquipmentTypeCreate = () => (
<Create> <Create>
<SimpleForm> <SimpleForm>
<TextInput source="code" label="code" isRequired /> <TextInput source="code" label="Код вида оборудования" isRequired />
<TextInput source="name" label="name" isRequired /> <TextInput source="name" label="Наименование вида" isRequired />
<TextInput source="manufacturer" label="manufacturer" /> <TextInput source="manufacturer" label="Производитель" />
<TextInput source="maintenanceIntervalHours" label="maintenanceIntervalHours" /> <NumberInput source="maintenanceIntervalHours" label="Периодичность ТО, моточасов" />
<TextInput source="overhaulIntervalHours" label="overhaulIntervalHours" /> <NumberInput source="overhaulIntervalHours" label="Периодичность КР, моточасов" />
</SimpleForm> </SimpleForm>
</Create> </Create>
); );

View File

@@ -1,14 +1,14 @@
import { Edit, SimpleForm, TextInput } from 'react-admin'; import { Edit, SimpleForm, TextInput, NumberInput } from 'react-admin';
export const EquipmentTypeEdit = () => ( export const EquipmentTypeEdit = () => (
<Edit> <Edit>
<SimpleForm> <SimpleForm>
<TextInput source="code" label="code" disabled /> <TextInput source="code" label="Код вида оборудования" disabled />
<TextInput source="name" label="name" isRequired /> <TextInput source="name" label="Наименование вида" isRequired />
<TextInput source="manufacturer" label="manufacturer" /> <TextInput source="manufacturer" label="Производитель" />
<TextInput source="maintenanceIntervalHours" label="maintenanceIntervalHours" /> <NumberInput source="maintenanceIntervalHours" label="Периодичность ТО, моточасов" />
<TextInput source="overhaulIntervalHours" label="overhaulIntervalHours" /> <NumberInput source="overhaulIntervalHours" label="Периодичность КР, моточасов" />
</SimpleForm> </SimpleForm>
</Edit> </Edit>
); );

View File

@@ -13,8 +13,8 @@ import {
const equipmentTypeFilters = [ const equipmentTypeFilters = [
<TextInput key="q" source="q" label="Поиск" alwaysOn />, <TextInput key="q" source="q" label="Поиск" alwaysOn />,
<TextInput key="name" source="name" label="name" />, <TextInput key="name" source="name" label="Наименование вида" />,
<TextInput key="manufacturer" source="manufacturer" label="manufacturer" /> <TextInput key="manufacturer" source="manufacturer" label="Производитель" />
]; ];
const EquipmentTypeListActions = () => ( const EquipmentTypeListActions = () => (
@@ -28,11 +28,11 @@ const EquipmentTypeListActions = () => (
export const EquipmentTypeList = () => ( export const EquipmentTypeList = () => (
<List actions={<EquipmentTypeListActions />} filters={equipmentTypeFilters} sort={{ field: 'code', order: 'ASC' }}> <List actions={<EquipmentTypeListActions />} filters={equipmentTypeFilters} sort={{ field: 'code', order: 'ASC' }}>
<Datagrid rowClick="show"> <Datagrid rowClick="show">
<TextField source="code" label="code" /> <TextField source="code" label="Код вида оборудования" />
<TextField source="name" label="name" /> <TextField source="name" label="Наименование вида" />
<TextField source="manufacturer" label="manufacturer" /> <TextField source="manufacturer" label="Производитель" />
<NumberField source="maintenanceIntervalHours" label="maintenanceIntervalHours" /> <NumberField source="maintenanceIntervalHours" label="Периодичность ТО, моточасов" />
<NumberField source="overhaulIntervalHours" label="overhaulIntervalHours" /> <NumberField source="overhaulIntervalHours" label="Периодичность КР, моточасов" />
</Datagrid> </Datagrid>
</List> </List>
); );

View File

@@ -1,28 +1,28 @@
import { Create, SimpleForm, TextInput, SelectInput, ReferenceInput, AutocompleteInput } from 'react-admin'; import { Create, SimpleForm, TextInput, NumberInput, DateInput, SelectInput, ReferenceInput, AutocompleteInput } from 'react-admin';
const statusChoices = [ const statusChoices = [
{ id: 'Active', name: 'Active' }, { id: 'Active', name: 'В эксплуатации' },
{ id: 'Repair', name: 'Repair' }, { id: 'Repair', name: 'В ремонте' },
{ id: 'Reserve', name: 'Reserve' }, { id: 'Reserve', name: 'В резерве' },
{ id: 'WriteOff', name: 'WriteOff' }, { id: 'WriteOff', name: 'Списано' },
]; ];
export const EquipmentCreate = () => ( export const EquipmentCreate = () => (
<Create> <Create>
<SimpleForm> <SimpleForm>
<TextInput source="inventoryNumber" label="inventoryNumber" isRequired /> <TextInput source="inventoryNumber" label="Инвентарный номер" isRequired />
<TextInput source="serialNumber" label="serialNumber" /> <TextInput source="serialNumber" label="Заводской (серийный) номер" />
<TextInput source="name" label="name" isRequired /> <TextInput source="name" label="Наименование единицы оборудования" isRequired />
<ReferenceInput source="equipmentTypeCode" reference="equipment-types" label="equipmentTypeCode"> <ReferenceInput source="equipmentTypeCode" reference="equipment-types">
<AutocompleteInput optionText={(record) => record.code ? `${record.code}${record.name ?? record.code}` : (record.name ?? record.id)} filterToQuery={(searchText) => ({ q: searchText })} /> <AutocompleteInput label="Вид оборудования" optionText={(record) => record.code ? `${record.code}${record.name ?? record.code}` : (record.name ?? record.id)} filterToQuery={(searchText) => ({ q: searchText })} />
</ReferenceInput> </ReferenceInput>
<SelectInput source="status" label="status" choices={statusChoices} emptyText="Не выбрано" /> <SelectInput source="status" label="Текущий статус" choices={statusChoices} emptyText="Не выбрано" />
<TextInput source="location" label="location" /> <TextInput source="location" label="Место эксплуатации / скважина / куст" />
<TextInput source="commissionedAt" label="commissionedAt" /> <DateInput source="commissionedAt" label="Дата ввода в эксплуатацию" />
<TextInput source="totalEngineHours" label="totalEngineHours" /> <NumberInput source="totalEngineHours" label="Общая наработка, моточасов" />
<TextInput source="engineHoursSinceLastRepair" label="engineHoursSinceLastRepair" /> <NumberInput source="engineHoursSinceLastRepair" label="Наработка с последнего ремонта, моточасов" />
<TextInput source="lastRepairAt" label="lastRepairAt" /> <DateInput source="lastRepairAt" label="Дата последнего ремонта" />
<TextInput source="notes" label="notes" /> <TextInput source="notes" label="Примечания" />
</SimpleForm> </SimpleForm>
</Create> </Create>
); );

View File

@@ -1,29 +1,29 @@
import { Edit, SimpleForm, TextInput, SelectInput, ReferenceInput, AutocompleteInput } from 'react-admin'; import { Edit, SimpleForm, TextInput, NumberInput, DateInput, SelectInput, ReferenceInput, AutocompleteInput } from 'react-admin';
const statusChoices = [ const statusChoices = [
{ id: 'Active', name: 'Active' }, { id: 'Active', name: 'В эксплуатации' },
{ id: 'Repair', name: 'Repair' }, { id: 'Repair', name: 'В ремонте' },
{ id: 'Reserve', name: 'Reserve' }, { id: 'Reserve', name: 'В резерве' },
{ id: 'WriteOff', name: 'WriteOff' }, { id: 'WriteOff', name: 'Списано' },
]; ];
export const EquipmentEdit = () => ( export const EquipmentEdit = () => (
<Edit> <Edit>
<SimpleForm> <SimpleForm>
<TextInput source="id" label="id" disabled /> <TextInput source="id" label="id" disabled />
<TextInput source="inventoryNumber" label="inventoryNumber" isRequired /> <TextInput source="inventoryNumber" label="Инвентарный номер" isRequired />
<TextInput source="serialNumber" label="serialNumber" /> <TextInput source="serialNumber" label="Заводской (серийный) номер" />
<TextInput source="name" label="name" isRequired /> <TextInput source="name" label="Наименование единицы оборудования" isRequired />
<ReferenceInput source="equipmentTypeCode" reference="equipment-types" label="equipmentTypeCode"> <ReferenceInput source="equipmentTypeCode" reference="equipment-types">
<AutocompleteInput optionText={(record) => record.code ? `${record.code}${record.name ?? record.code}` : (record.name ?? record.id)} filterToQuery={(searchText) => ({ q: searchText })} /> <AutocompleteInput label="Вид оборудования" optionText={(record) => record.code ? `${record.code}${record.name ?? record.code}` : (record.name ?? record.id)} filterToQuery={(searchText) => ({ q: searchText })} />
</ReferenceInput> </ReferenceInput>
<SelectInput source="status" label="status" choices={statusChoices} emptyText="Не выбрано" /> <SelectInput source="status" label="Текущий статус" choices={statusChoices} emptyText="Не выбрано" />
<TextInput source="location" label="location" /> <TextInput source="location" label="Место эксплуатации / скважина / куст" />
<TextInput source="commissionedAt" label="commissionedAt" /> <DateInput source="commissionedAt" label="Дата ввода в эксплуатацию" />
<TextInput source="totalEngineHours" label="totalEngineHours" /> <NumberInput source="totalEngineHours" label="Общая наработка, моточасов" />
<TextInput source="engineHoursSinceLastRepair" label="engineHoursSinceLastRepair" /> <NumberInput source="engineHoursSinceLastRepair" label="Наработка с последнего ремонта, моточасов" />
<TextInput source="lastRepairAt" label="lastRepairAt" /> <DateInput source="lastRepairAt" label="Дата последнего ремонта" />
<TextInput source="notes" label="notes" /> <TextInput source="notes" label="Примечания" />
</SimpleForm> </SimpleForm>
</Edit> </Edit>
); );

View File

@@ -13,27 +13,60 @@ import {
ReferenceField, ReferenceField,
SelectArrayInput, SelectArrayInput,
ReferenceInput, ReferenceInput,
AutocompleteInput AutocompleteInput,
} from 'react-admin'; } from "react-admin";
const statusChoices = [ const statusChoices = [
{ id: 'Active', name: 'Active' }, { id: "Active", name: "В эксплуатации" },
{ id: 'Repair', name: 'Repair' }, { id: "Repair", name: "В ремонте" },
{ id: 'Reserve', name: 'Reserve' }, { id: "Reserve", name: "В резерве" },
{ id: 'WriteOff', name: 'WriteOff' }, { id: "WriteOff", name: "Списано" },
]; ];
const equipmentFilters = [ const equipmentFilters = [
<TextInput key="q" source="q" label="Поиск" alwaysOn />, <TextInput key="q" source="q" label="Поиск" alwaysOn />,
<TextInput key="inventoryNumber" source="inventoryNumber" label="inventoryNumber" />, <TextInput
<TextInput key="serialNumber" source="serialNumber" label="serialNumber" />, key="inventoryNumber"
<TextInput key="name" source="name" label="name" />, source="inventoryNumber"
<ReferenceInput key="equipmentTypeCode" source="equipmentTypeCode" reference="equipment-types" label="equipmentTypeCode"> label="Инвентарный номер"
<AutocompleteInput optionText={(record) => record.code ? `${record.code}${record.name ?? record.code}` : (record.name ?? record.id)} filterToQuery={(searchText) => ({ q: searchText })} /> />,
<TextInput
key="serialNumber"
source="serialNumber"
label="Заводской (серийный) номер"
/>,
<TextInput
key="name"
source="name"
label="Наименование единицы оборудования"
/>,
<ReferenceInput
key="equipmentTypeCode"
source="equipmentTypeCode"
reference="equipment-types"
label="Вид оборудования"
>
<AutocompleteInput
optionText={(record) =>
record.code
? `${record.code}${record.name ?? record.code}`
: (record.name ?? record.id)
}
filterToQuery={(searchText) => ({ q: searchText })}
/>
</ReferenceInput>, </ReferenceInput>,
<SelectArrayInput key="status" source="status" label="status" choices={statusChoices} />, <SelectArrayInput
<TextInput key="location" source="location" label="location" />, key="status"
<TextInput key="notes" source="notes" label="notes" /> source="status"
label="Текущий статус"
choices={statusChoices}
/>,
<TextInput
key="location"
source="location"
label="Место эксплуатации / скважина / куст"
/>,
<TextInput key="notes" source="notes" label="Примечания" />,
]; ];
const EquipmentListActions = () => ( const EquipmentListActions = () => (
@@ -45,22 +78,44 @@ const EquipmentListActions = () => (
); );
export const EquipmentList = () => ( export const EquipmentList = () => (
<List actions={<EquipmentListActions />} filters={equipmentFilters} sort={{ field: 'id', order: 'ASC' }}> <List
actions={<EquipmentListActions />}
filters={equipmentFilters}
sort={{ field: "inventoryNumber", order: "ASC" }}
>
<Datagrid rowClick="show"> <Datagrid rowClick="show">
<TextField source="id" label="id" /> <TextField source="id" label="id" />
<TextField source="inventoryNumber" label="inventoryNumber" /> <TextField source="inventoryNumber" label="Инвентарный номер" />
<TextField source="serialNumber" label="serialNumber" /> <TextField source="serialNumber" label="Заводской (серийный) номер" />
<TextField source="name" label="name" /> <TextField source="name" label="Наименование единицы оборудования" />
<ReferenceField source="equipmentTypeCode" reference="equipment-types" label="equipmentTypeCode" link="show"> <ReferenceField
<TextField source="name" /> source="equipmentTypeCode"
reference="equipment-types"
label="Вид оборудования"
link="show"
>
<TextField source="code" />
</ReferenceField> </ReferenceField>
<SelectField source="status" label="status" choices={statusChoices} /> <SelectField
<TextField source="location" label="location" /> source="status"
<DateField source="commissionedAt" label="commissionedAt" /> label="Текущий статус"
<NumberField source="totalEngineHours" label="totalEngineHours" /> choices={statusChoices}
<NumberField source="engineHoursSinceLastRepair" label="engineHoursSinceLastRepair" /> />
<DateField source="lastRepairAt" label="lastRepairAt" /> <TextField
<TextField source="notes" label="notes" /> source="location"
label="Место эксплуатации / скважина / куст"
/>
<DateField source="commissionedAt" label="Дата ввода в эксплуатацию" />
<NumberField
source="totalEngineHours"
label="Общая наработка, моточасов"
/>
<NumberField
source="engineHoursSinceLastRepair"
label="Наработка с последнего ремонта, моточасов"
/>
<DateField source="lastRepairAt" label="Дата последнего ремонта" />
<TextField source="notes" label="Примечания" />
</Datagrid> </Datagrid>
</List> </List>
); );

View File

@@ -1,38 +1,38 @@
import { Create, SimpleForm, TextInput, SelectInput, ReferenceInput, AutocompleteInput } from 'react-admin'; import { Create, SimpleForm, TextInput, NumberInput, DateInput, SelectInput, ReferenceInput, AutocompleteInput } from 'react-admin';
const repairKindChoices = [ const repairKindChoices = [
{ id: 'TO', name: 'TO' }, { id: 'TO', name: 'Техническое обслуживание' },
{ id: 'TR', name: 'TR' }, { id: 'TR', name: 'Текущий ремонт' },
{ id: 'TRE', name: 'TRE' }, { id: 'TRE', name: 'Текущий расширенный ремонт' },
{ id: 'KR', name: 'KR' }, { id: 'KR', name: 'Капитальный ремонт' },
{ id: 'AR', name: 'AR' }, { id: 'AR', name: 'Аварийный ремонт' },
{ id: 'MP', name: 'MP' }, { id: 'MP', name: 'Метрологическая поверка' },
]; ];
const statusChoices = [ const statusChoices = [
{ id: 'Draft', name: 'Draft' }, { id: 'Draft', name: 'Черновик' },
{ id: 'Approved', name: 'Approved' }, { id: 'Approved', name: 'Утверждена' },
{ id: 'InWork', name: 'InWork' }, { id: 'InWork', name: 'В работе' },
{ id: 'Done', name: 'Done' }, { id: 'Done', name: 'Выполнена' },
{ id: 'Cancelled', name: 'Cancelled' }, { id: 'Cancelled', name: 'Отменена' },
]; ];
export const RepairOrderCreate = () => ( export const RepairOrderCreate = () => (
<Create> <Create>
<SimpleForm> <SimpleForm>
<TextInput source="number" label="number" isRequired /> <TextInput source="number" label="Номер заявки" isRequired />
<ReferenceInput source="equipmentId" reference="equipment" label="equipmentId"> <ReferenceInput source="equipmentId" reference="equipment">
<AutocompleteInput optionText={(record) => record.code ? `${record.code}${record.name ?? record.code}` : (record.name ?? record.id)} filterToQuery={(searchText) => ({ q: searchText })} /> <AutocompleteInput label="Оборудование" optionText={(record) => record.inventoryNumber ? `${record.inventoryNumber}${record.name ?? record.inventoryNumber}` : (record.name ?? record.id)} filterToQuery={(searchText) => ({ q: searchText })} />
</ReferenceInput> </ReferenceInput>
<SelectInput source="repairKind" label="repairKind" choices={repairKindChoices} emptyText="Не выбрано" /> <SelectInput source="repairKind" label="Вид ремонта" choices={repairKindChoices} emptyText="Не выбрано" />
<SelectInput source="status" label="status" choices={statusChoices} emptyText="Не выбрано" /> <SelectInput source="status" label="Статус" choices={statusChoices} emptyText="Не выбрано" />
<TextInput source="plannedAt" label="plannedAt" /> <DateInput source="plannedAt" label="Плановая дата начала" />
<TextInput source="startedAt" label="startedAt" /> <DateInput source="startedAt" label="Фактическая дата начала" />
<TextInput source="completedAt" label="completedAt" /> <DateInput source="completedAt" label="Фактическая дата завершения" />
<TextInput source="contractor" label="contractor" /> <TextInput source="contractor" label="Подрядная организация (если внешний ремонт)" />
<TextInput source="engineHoursAtRepair" label="engineHoursAtRepair" /> <NumberInput source="engineHoursAtRepair" label="Наработка на момент ремонта, моточасов" />
<TextInput source="description" label="description" /> <TextInput source="description" label="Описание работ / дефекта" />
<TextInput source="notes" label="notes" /> <TextInput source="notes" label="Примечания" />
</SimpleForm> </SimpleForm>
</Create> </Create>
); );

View File

@@ -1,39 +1,39 @@
import { Edit, SimpleForm, TextInput, SelectInput, ReferenceInput, AutocompleteInput } from 'react-admin'; import { Edit, SimpleForm, TextInput, NumberInput, DateInput, SelectInput, ReferenceInput, AutocompleteInput } from 'react-admin';
const repairKindChoices = [ const repairKindChoices = [
{ id: 'TO', name: 'TO' }, { id: 'TO', name: 'Техническое обслуживание' },
{ id: 'TR', name: 'TR' }, { id: 'TR', name: 'Текущий ремонт' },
{ id: 'TRE', name: 'TRE' }, { id: 'TRE', name: 'Текущий расширенный ремонт' },
{ id: 'KR', name: 'KR' }, { id: 'KR', name: 'Капитальный ремонт' },
{ id: 'AR', name: 'AR' }, { id: 'AR', name: 'Аварийный ремонт' },
{ id: 'MP', name: 'MP' }, { id: 'MP', name: 'Метрологическая поверка' },
]; ];
const statusChoices = [ const statusChoices = [
{ id: 'Draft', name: 'Draft' }, { id: 'Draft', name: 'Черновик' },
{ id: 'Approved', name: 'Approved' }, { id: 'Approved', name: 'Утверждена' },
{ id: 'InWork', name: 'InWork' }, { id: 'InWork', name: 'В работе' },
{ id: 'Done', name: 'Done' }, { id: 'Done', name: 'Выполнена' },
{ id: 'Cancelled', name: 'Cancelled' }, { id: 'Cancelled', name: 'Отменена' },
]; ];
export const RepairOrderEdit = () => ( export const RepairOrderEdit = () => (
<Edit> <Edit>
<SimpleForm> <SimpleForm>
<TextInput source="id" label="id" disabled /> <TextInput source="id" label="id" disabled />
<TextInput source="number" label="number" isRequired /> <TextInput source="number" label="Номер заявки" isRequired />
<ReferenceInput source="equipmentId" reference="equipment" label="equipmentId"> <ReferenceInput source="equipmentId" reference="equipment">
<AutocompleteInput optionText={(record) => record.code ? `${record.code}${record.name ?? record.code}` : (record.name ?? record.id)} filterToQuery={(searchText) => ({ q: searchText })} /> <AutocompleteInput label="Оборудование" optionText={(record) => record.inventoryNumber ? `${record.inventoryNumber}${record.name ?? record.inventoryNumber}` : (record.name ?? record.id)} filterToQuery={(searchText) => ({ q: searchText })} />
</ReferenceInput> </ReferenceInput>
<SelectInput source="repairKind" label="repairKind" choices={repairKindChoices} emptyText="Не выбрано" /> <SelectInput source="repairKind" label="Вид ремонта" choices={repairKindChoices} emptyText="Не выбрано" />
<SelectInput source="status" label="status" choices={statusChoices} emptyText="Не выбрано" /> <SelectInput source="status" label="Статус" choices={statusChoices} emptyText="Не выбрано" />
<TextInput source="plannedAt" label="plannedAt" /> <DateInput source="plannedAt" label="Плановая дата начала" />
<TextInput source="startedAt" label="startedAt" /> <DateInput source="startedAt" label="Фактическая дата начала" />
<TextInput source="completedAt" label="completedAt" /> <DateInput source="completedAt" label="Фактическая дата завершения" />
<TextInput source="contractor" label="contractor" /> <TextInput source="contractor" label="Подрядная организация (если внешний ремонт)" />
<TextInput source="engineHoursAtRepair" label="engineHoursAtRepair" /> <NumberInput source="engineHoursAtRepair" label="Наработка на момент ремонта, моточасов" />
<TextInput source="description" label="description" /> <TextInput source="description" label="Описание работ / дефекта" />
<TextInput source="notes" label="notes" /> <TextInput source="notes" label="Примечания" />
</SimpleForm> </SimpleForm>
</Edit> </Edit>
); );

View File

@@ -18,33 +18,33 @@ import {
} from 'react-admin'; } from 'react-admin';
const repairKindChoices = [ const repairKindChoices = [
{ id: 'TO', name: 'TO' }, { id: 'TO', name: 'Техническое обслуживание' },
{ id: 'TR', name: 'TR' }, { id: 'TR', name: 'Текущий ремонт' },
{ id: 'TRE', name: 'TRE' }, { id: 'TRE', name: 'Текущий расширенный ремонт' },
{ id: 'KR', name: 'KR' }, { id: 'KR', name: 'Капитальный ремонт' },
{ id: 'AR', name: 'AR' }, { id: 'AR', name: 'Аварийный ремонт' },
{ id: 'MP', name: 'MP' }, { id: 'MP', name: 'Метрологическая поверка' },
]; ];
const statusChoices = [ const statusChoices = [
{ id: 'Draft', name: 'Draft' }, { id: 'Draft', name: 'Черновик' },
{ id: 'Approved', name: 'Approved' }, { id: 'Approved', name: 'Утверждена' },
{ id: 'InWork', name: 'InWork' }, { id: 'InWork', name: 'В работе' },
{ id: 'Done', name: 'Done' }, { id: 'Done', name: 'Выполнена' },
{ id: 'Cancelled', name: 'Cancelled' }, { id: 'Cancelled', name: 'Отменена' },
]; ];
const repairOrderFilters = [ const repairOrderFilters = [
<TextInput key="q" source="q" label="Поиск" alwaysOn />, <TextInput key="q" source="q" label="Поиск" alwaysOn />,
<TextInput key="number" source="number" label="number" />, <TextInput key="number" source="number" label="Номер заявки" />,
<ReferenceInput key="equipmentId" source="equipmentId" reference="equipment" label="equipmentId"> <ReferenceInput key="equipmentId" source="equipmentId" reference="equipment" label="Оборудование">
<AutocompleteInput optionText={(record) => record.code ? `${record.code}${record.name ?? record.code}` : (record.name ?? record.id)} filterToQuery={(searchText) => ({ q: searchText })} /> <AutocompleteInput optionText={(record) => record.inventoryNumber ? `${record.inventoryNumber}${record.name ?? record.inventoryNumber}` : (record.name ?? record.id)} filterToQuery={(searchText) => ({ q: searchText })} />
</ReferenceInput>, </ReferenceInput>,
<SelectInput key="repairKind" source="repairKind" label="repairKind" choices={repairKindChoices} emptyText="Все" />, <SelectInput key="repairKind" source="repairKind" label="Вид ремонта" choices={repairKindChoices} emptyText="Все" />,
<SelectArrayInput key="status" source="status" label="status" choices={statusChoices} />, <SelectArrayInput key="status" source="status" label="Статус" choices={statusChoices} />,
<TextInput key="contractor" source="contractor" label="contractor" />, <TextInput key="contractor" source="contractor" label="Подрядная организация (если внешний ремонт)" />,
<TextInput key="description" source="description" label="description" />, <TextInput key="description" source="description" label="Описание работ / дефекта" />,
<TextInput key="notes" source="notes" label="notes" /> <TextInput key="notes" source="notes" label="Примечания" />
]; ];
const RepairOrderListActions = () => ( const RepairOrderListActions = () => (
@@ -56,22 +56,22 @@ const RepairOrderListActions = () => (
); );
export const RepairOrderList = () => ( export const RepairOrderList = () => (
<List actions={<RepairOrderListActions />} filters={repairOrderFilters} sort={{ field: 'id', order: 'ASC' }}> <List actions={<RepairOrderListActions />} filters={repairOrderFilters} sort={{ field: 'number', order: 'ASC' }}>
<Datagrid rowClick="show"> <Datagrid rowClick="show">
<TextField source="id" label="id" /> <TextField source="id" label="id" />
<TextField source="number" label="number" /> <TextField source="number" label="Номер заявки" />
<ReferenceField source="equipmentId" reference="equipment" label="equipmentId" link="show"> <ReferenceField source="equipmentId" reference="equipment" label="Оборудование" link="show">
<TextField source="name" /> <TextField source="inventoryNumber" />
</ReferenceField> </ReferenceField>
<SelectField source="repairKind" label="repairKind" choices={repairKindChoices} /> <SelectField source="repairKind" label="Вид ремонта" choices={repairKindChoices} />
<SelectField source="status" label="status" choices={statusChoices} /> <SelectField source="status" label="Статус" choices={statusChoices} />
<DateField source="plannedAt" label="plannedAt" /> <DateField source="plannedAt" label="Плановая дата начала" />
<DateField source="startedAt" label="startedAt" /> <DateField source="startedAt" label="Фактическая дата начала" />
<DateField source="completedAt" label="completedAt" /> <DateField source="completedAt" label="Фактическая дата завершения" />
<TextField source="contractor" label="contractor" /> <TextField source="contractor" label="Подрядная организация (если внешний ремонт)" />
<NumberField source="engineHoursAtRepair" label="engineHoursAtRepair" /> <NumberField source="engineHoursAtRepair" label="Наработка на момент ремонта, моточасов" />
<TextField source="description" label="description" /> <TextField source="description" label="Описание работ / дефекта" />
<TextField source="notes" label="notes" /> <TextField source="notes" label="Примечания" />
</Datagrid> </Datagrid>
</List> </List>
); );

View File

@@ -8,7 +8,7 @@ services:
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
POSTGRES_DB: toir POSTGRES_DB: toir
ports: ports:
- "5433:5432" - "5432:5432"
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data

View File

@@ -74,14 +74,20 @@ function parseBlocks(text, kind) {
function parseEnum(body) { function parseEnum(body) {
const values = []; const values = [];
const re = /^\s*value\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{/gm; const labels = {};
const re = /value\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{([\s\S]*?)\}/gm;
let m; let m;
while ((m = re.exec(body))) values.push(m[1]); while ((m = re.exec(body))) {
return { values }; values.push(m[1]);
const label = (m[2].match(/label\s+"([^"]+)"/m) || [])[1];
labels[m[1]] = label || m[1];
}
return { values, labels };
} }
function parseEntity(body) { function parseEntity(body) {
const attrs = []; const attrs = [];
const entityLabel = (body.match(/description\s+"([^"]+)"/m) || [])[1];
const lines = body.split(/\r?\n/); const lines = body.split(/\r?\n/);
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
const m = lines[i].match(/^\s*attribute\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{\s*$/); const m = lines[i].match(/^\s*attribute\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{\s*$/);
@@ -106,10 +112,12 @@ function parseEntity(body) {
const isPrimary = /^\s*key primary\s*;/m.test(abody); const isPrimary = /^\s*key primary\s*;/m.test(abody);
const defaultValue = (abody.match(/^\s*default\s+([A-Za-z_][A-Za-z0-9_]*)\s*;/m) || [])[1]; const defaultValue = (abody.match(/^\s*default\s+([A-Za-z_][A-Za-z0-9_]*)\s*;/m) || [])[1];
const foreignRel = (abody.match(/relates\s+([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)\s*;/m) || []).slice(1); const foreignRel = (abody.match(/relates\s+([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)\s*;/m) || []).slice(1);
const description = (abody.match(/description\s+"([^"]+)"/m) || [])[1];
attrs.push({ attrs.push({
name, name,
type, type,
label: description || name,
isRequired, isRequired,
isUnique, isUnique,
isPrimary, isPrimary,
@@ -122,7 +130,7 @@ function parseEntity(body) {
const pk = attrs.find((a) => a.isPrimary); const pk = attrs.find((a) => a.isPrimary);
if (!pk) throw new Error('Entity missing primary key attribute'); if (!pk) throw new Error('Entity missing primary key attribute');
return { attributes: attrs, primaryKey: pk.name }; return { attributes: attrs, primaryKey: pk.name, label: entityLabel };
} }
function parseDomainDSL(dslText) { function parseDomainDSL(dslText) {
@@ -138,6 +146,45 @@ function parseDomainDSL(dslText) {
return { enums, entities }; return { enums, entities };
} }
function getEntityAttrNames(entity) {
return new Set(entity.attributes.map((a) => a.name));
}
function getBestSortField(entity, pk) {
const attrs = getEntityAttrNames(entity);
if (attrs.has('inventoryNumber')) return 'inventoryNumber';
if (attrs.has('number')) return 'number';
if (attrs.has('code')) return 'code';
if (attrs.has('name')) return 'name';
return pk;
}
function getReferenceDisplayExpr(foreignEntity) {
const attrs = getEntityAttrNames(foreignEntity);
if (attrs.has('inventoryNumber')) {
return "(record) => record.inventoryNumber ? `${record.inventoryNumber} — ${record.name ?? record.inventoryNumber}` : (record.name ?? record.id)";
}
if (attrs.has('code')) {
return "(record) => record.code ? `${record.code} — ${record.name ?? record.code}` : (record.name ?? record.id)";
}
if (attrs.has('number')) {
return "(record) => record.number ? `${record.number} — ${record.name ?? record.number}` : (record.name ?? record.id)";
}
if (attrs.has('name')) {
return "(record) => record.name ?? record.id";
}
return "(record) => record.id";
}
function getAttributeLabel(attr, allEntities) {
if (attr.label && attr.label !== attr.name) return attr.label;
if (attr.name === 'status') return 'Статус';
if (attr.name === 'equipmentId') return 'Оборудование';
if (attr.name === 'equipmentTypeCode') return 'Вид оборудования';
if (attr.foreign) return allEntities[attr.foreign.entity]?.label || attr.name;
return attr.name;
}
function prismaScalarType(dslType) { function prismaScalarType(dslType) {
switch (dslType) { switch (dslType) {
case 'string': case 'string':
@@ -281,23 +328,44 @@ function renderBackendModule(entityName, entity, resourceName, pk) {
if (pk !== 'id') updateDtoLines.push(` id?: string;`); if (pk !== 'id') updateDtoLines.push(` id?: string;`);
for (const a of entity.attributes) { for (const a of entity.attributes) {
if (pk !== 'id' && a.name === 'id') continue; if (pk !== 'id' && a.name === 'id') continue;
updateDtoLines.push(` ${a.name}?: ${dtoType(a)} | null;`); updateDtoLines.push(` ${a.name}?: ${dtoType(a)};`);
} }
updateDtoLines.push('}'); updateDtoLines.push('}');
const controller = `import { Controller, Get, Post, Patch, Delete, Param, Body, Query, Res } from '@nestjs/common';\nimport { Response } from 'express';\nimport { ${serviceName} } from './${folder}.service';\nimport { Create${className}Dto } from './dto/create-${folder}.dto';\nimport { Update${className}Dto } from './dto/update-${folder}.dto';\n\n@Controller('${resourceName}')\nexport class ${controllerName} {\n constructor(private readonly service: ${serviceName}) {}\n\n @Get()\n async findAll(@Query() query: any, @Res() res: Response) {\n const result = await this.service.findAll(query);\n res.set('Content-Range', \`${resourceName} \${query._start || 0}-\${query._end || result.total}/\${result.total}\`);\n res.set('Access-Control-Expose-Headers', 'Content-Range');\n return res.json(result.data);\n }\n\n @Get(':${pk}')\n findOne(@Param('${pk}') id: string) {\n return this.service.findOne(id);\n }\n\n @Post()\n create(@Body() dto: Create${className}Dto) {\n return this.service.create(dto);\n }\n\n @Patch(':${pk}')\n update(@Param('${pk}') id: string, @Body() dto: Update${className}Dto) {\n return this.service.update(id, dto);\n }\n\n @Delete(':${pk}')\n remove(@Param('${pk}') id: string) {\n return this.service.remove(id);\n }\n}\n`; const controller = `import { Controller, Get, Post, Patch, Delete, Param, Body, Query, Res } from '@nestjs/common';\nimport { Response } from 'express';\nimport { ${serviceName} } from './${folder}.service';\nimport { Create${className}Dto } from './dto/create-${folder}.dto';\nimport { Update${className}Dto } from './dto/update-${folder}.dto';\n\n@Controller('${resourceName}')\nexport class ${controllerName} {\n constructor(private readonly service: ${serviceName}) {}\n\n @Get()\n async findAll(@Query() query: any, @Res() res: Response) {\n const result = await this.service.findAll(query);\n res.set('Content-Range', \`${resourceName} \${query._start || 0}-\${query._end || result.total}/\${result.total}\`);\n res.set('Access-Control-Expose-Headers', 'Content-Range');\n return res.json(result.data);\n }\n\n @Get(':${pk}')\n findOne(@Param('${pk}') id: string) {\n return this.service.findOne(id);\n }\n\n @Post()\n create(@Body() dto: Create${className}Dto) {\n return this.service.create(dto);\n }\n\n @Patch(':${pk}')\n update(@Param('${pk}') id: string, @Body() dto: Update${className}Dto) {\n return this.service.update(id, dto);\n }\n\n @Delete(':${pk}')\n remove(@Param('${pk}') id: string) {\n return this.service.remove(id);\n }\n}\n`;
const service = `import { Injectable } from '@nestjs/common';\nimport { Prisma } from '@prisma/client';\nimport { PrismaService } from '../../prisma/prisma.service';\nimport { Create${className}Dto } from './dto/create-${folder}.dto';\nimport { Update${className}Dto } from './dto/update-${folder}.dto';\n\n@Injectable()\nexport class ${serviceName} {\n constructor(private readonly prisma: PrismaService) {}\n\n async findAll(query: { _start?: string; _end?: string; _sort?: string; _order?: string; [key: string]: any }) {\n const start = parseInt(query._start) || 0;\n const end = parseInt(query._end) || 10;\n const take = end - start;\n const skip = start;\n const sortField = query._sort || '${pk}';\n const sortOrder = (query._order || 'ASC').toLowerCase() as 'asc' | 'desc';\n\n const where: any = {};\n\n if (query.q) {\n const q = String(query.q);\n const ors: any[] = [];\n ${entity.attributes const service = `import { Injectable } from '@nestjs/common';\nimport { Prisma } from '@prisma/client';\nimport { PrismaService } from '../../prisma/prisma.service';\nimport { Create${className}Dto } from './dto/create-${folder}.dto';\nimport { Update${className}Dto } from './dto/update-${folder}.dto';\n\nfunction serializeRecord(record: any) {\n return {\n ...record,\n${entity.attributes
.filter((a) => a.type === 'decimal')
.map((a) => ` ${a.name}: record.${a.name}?.toString() ?? null,`)
.join('\n')}\n${entity.attributes
.filter((a) => a.type === 'date')
.map((a) => ` ${a.name}: record.${a.name}?.toISOString() ?? null,`)
.join('\n')}\n };\n}\n\n@Injectable()\nexport class ${serviceName} {\n constructor(private readonly prisma: PrismaService) {}\n\n async findAll(query: { _start?: string; _end?: string; _sort?: string; _order?: string; [key: string]: any }) {\n const start = parseInt(query._start) || 0;\n const end = parseInt(query._end) || 10;\n const take = end - start;\n const skip = start;\n const sortField = query._sort || '${getBestSortField(entity, pk)}';\n const sortOrder = (query._order || 'ASC').toLowerCase() as 'asc' | 'desc';\n\n const where: any = {};\n\n if (query.q) {\n const q = String(query.q);\n const ors: any[] = [];\n ${entity.attributes
.filter((a) => ['string', 'text'].includes(a.type)) .filter((a) => ['string', 'text'].includes(a.type))
.slice(0, 6) .slice(0, 6)
.map((a) => `ors.push({ ${a.name}: { contains: q, mode: 'insensitive' } });`) .map((a) => `ors.push({ ${a.name}: { contains: q, mode: 'insensitive' } });`)
.join('\n ')}\n if (ors.length) where.OR = ors;\n }\n\n ${entity.attributes .join('\n ')}\n if (ors.length) where.OR = ors;\n }\n\n ${entity.attributes
.filter((a) => ['string', 'text'].includes(a.type)) .filter((a) => ['string', 'text'].includes(a.type) && !a.foreign)
.map((a) => `if (query.${a.name}) where.${a.name} = { contains: query.${a.name}, mode: 'insensitive' };`) .map((a) => `if (query.${a.name}) where.${a.name} = { contains: query.${a.name}, mode: 'insensitive' };`)
.join('\n ')}\n\n ${entity.attributes
.filter((a) => a.foreign)
.map((a) => `if (query.${a.name}) where.${a.name} = query.${a.name};`)
.join('\n ')}\n\n // Enum multi-value support (e.g. status=A&status=B)\n ${entity.attributes .join('\n ')}\n\n // Enum multi-value support (e.g. status=A&status=B)\n ${entity.attributes
.filter((a) => !['string', 'text', 'uuid', 'integer', 'decimal', 'date'].includes(a.type)) .filter((a) => !['string', 'text', 'uuid', 'integer', 'decimal', 'date'].includes(a.type))
.map((a) => `if (query.${a.name}) { const vals = Array.isArray(query.${a.name}) ? query.${a.name} : [query.${a.name}]; where.${a.name} = vals.length > 1 ? { in: vals } : vals[0]; }`) .map((a) => `if (query.${a.name}) { const vals = Array.isArray(query.${a.name}) ? query.${a.name} : [query.${a.name}]; where.${a.name} = vals.length > 1 ? { in: vals } : vals[0]; }`)
.join('\n ')}\n\n if (query.id) {\n const ids = Array.isArray(query.id) ? query.id : [query.id];\n where.${pk} = { in: ids };\n }\n\n const [data, total] = await Promise.all([\n this.prisma.${lowerFirst(className)}.findMany({ where, skip, take, orderBy: { [sortField]: sortOrder } }),\n this.prisma.${lowerFirst(className)}.count({ where }),\n ]);\n\n const mapped = ${pk === 'id' ? 'data' : `data.map((r: any) => ({ id: r.${pk}, ...r }))`};\n return { data: mapped, total };\n }\n\n async findOne(id: string) {\n const record = await this.prisma.${lowerFirst(className)}.findUniqueOrThrow({ where: { ${pk}: id } as any });\n return ${pk === 'id' ? 'record' : `{ id: (record as any).${pk}, ...record }`};\n }\n\n async create(dto: Create${className}Dto) {\n const record = await this.prisma.${lowerFirst(className)}.create({ data: dto as any });\n return ${pk === 'id' ? 'record' : `{ id: (record as any).${pk}, ...record }`};\n }\n\n async update(id: string, dto: Update${className}Dto) {\n const data: any = { ...(dto as any) };\n delete data.id;\n delete data.${pk};\n const record = await this.prisma.${lowerFirst(className)}.update({ where: { ${pk}: id } as any, data });\n return ${pk === 'id' ? 'record' : `{ id: (record as any).${pk}, ...record }`};\n }\n\n async remove(id: string) {\n const record = await this.prisma.${lowerFirst(className)}.delete({ where: { ${pk}: id } as any });\n return ${pk === 'id' ? 'record' : `{ id: (record as any).${pk}, ...record }`};\n }\n}\n`; .join('\n ')}\n\n if (query.id) {\n const ids = Array.isArray(query.id) ? query.id : [query.id];\n where.${pk} = { in: ids };\n }\n\n const [data, total] = await Promise.all([\n this.prisma.${lowerFirst(className)}.findMany({ where, skip, take, orderBy: { [sortField]: sortOrder } }),\n this.prisma.${lowerFirst(className)}.count({ where }),\n ]);\n\n const mapped = ${pk === 'id' ? 'data.map(serializeRecord)' : `data.map((r: any) => ({ id: r.${pk}, ...serializeRecord(r) }))`};\n return { data: mapped, total };\n }\n\n async findOne(id: string) {\n const record = await this.prisma.${lowerFirst(className)}.findUniqueOrThrow({ where: { ${pk}: id } as any });\n return ${pk === 'id' ? 'serializeRecord(record)' : `{ id: (record as any).${pk}, ...serializeRecord(record) }`};\n }\n\n async create(dto: Create${className}Dto) {\n const data: any = { ...(dto as any) };\n${entity.attributes
.filter((a) => a.type === 'date')
.map((a) => ` if (data.${a.name}) data.${a.name} = new Date(data.${a.name});`)
.join('\n')}\n${entity.attributes
.filter((a) => a.type === 'decimal')
.map((a) => ` if (data.${a.name}) data.${a.name} = new Prisma.Decimal(data.${a.name});`)
.join('\n')}\n\n const record = await this.prisma.${lowerFirst(className)}.create({ data });\n return ${pk === 'id' ? 'serializeRecord(record)' : `{ id: (record as any).${pk}, ...serializeRecord(record) }`};\n }\n\n async update(id: string, dto: Update${className}Dto) {\n const data: any = { ...(dto as any) };\n delete data.id;\n delete data.${pk};\n${entity.attributes
.filter((a) => a.type === 'date')
.map((a) => ` if (data.${a.name}) data.${a.name} = new Date(data.${a.name});`)
.join('\n')}\n${entity.attributes
.filter((a) => a.type === 'decimal')
.map((a) => ` if (data.${a.name} !== undefined && data.${a.name} !== null) data.${a.name} = new Prisma.Decimal(data.${a.name});`)
.join('\n')}\n\n const record = await this.prisma.${lowerFirst(className)}.update({ where: { ${pk}: id } as any, data });\n return ${pk === 'id' ? 'serializeRecord(record)' : `{ id: (record as any).${pk}, ...serializeRecord(record) }`};\n }\n\n async remove(id: string) {\n const record = await this.prisma.${lowerFirst(className)}.delete({ where: { ${pk}: id } as any });\n return ${pk === 'id' ? 'serializeRecord(record)' : `{ id: (record as any).${pk}, ...serializeRecord(record) }`};\n }\n}\n`;
const mod = `import { Module } from '@nestjs/common';\nimport { ${controllerName} } from './${folder}.controller';\nimport { ${serviceName} } from './${folder}.service';\n\n@Module({\n controllers: [${controllerName}],\n providers: [${serviceName}],\n})\nexport class ${moduleName} {}\n`; const mod = `import { Module } from '@nestjs/common';\nimport { ${controllerName} } from './${folder}.controller';\nimport { ${serviceName} } from './${folder}.service';\n\n@Module({\n controllers: [${controllerName}],\n providers: [${serviceName}],\n})\nexport class ${moduleName} {}\n`;
@@ -315,7 +383,7 @@ function renderBackendModule(entityName, entity, resourceName, pk) {
}; };
} }
function renderFrontendResource(entityName, entity, resourceName, pk, enums) { function renderFrontendResource(entityName, entity, resourceName, pk, enums, allEntities) {
const folder = toKebab(entityName); const folder = toKebab(entityName);
const className = entityName; const className = entityName;
const enumAttrs = entity.attributes.filter( const enumAttrs = entity.attributes.filter(
@@ -325,6 +393,7 @@ function renderFrontendResource(entityName, entity, resourceName, pk, enums) {
const identBase = toIdentifierFromKebab(folder); const identBase = toIdentifierFromKebab(folder);
const filtersIdent = `${identBase}Filters`; const filtersIdent = `${identBase}Filters`;
const sortField = getBestSortField(entity, pk);
const hasNumber = entity.attributes.some((a) => ['integer', 'decimal'].includes(a.type)); const hasNumber = entity.attributes.some((a) => ['integer', 'decimal'].includes(a.type));
const hasDate = entity.attributes.some((a) => a.type === 'date'); const hasDate = entity.attributes.some((a) => a.type === 'date');
@@ -357,14 +426,15 @@ function renderFrontendResource(entityName, entity, resourceName, pk, enums) {
for (const a of enumAttrs) { for (const a of enumAttrs) {
const enumName = a.type; const enumName = a.type;
const values = enums?.[enumName]?.values ?? []; const values = enums?.[enumName]?.values ?? [];
const labels = enums?.[enumName]?.labels ?? {};
const constName = `${a.name}Choices`; const constName = `${a.name}Choices`;
if (a.name === 'status') { if (a.name === 'status') {
choiceConsts.push( choiceConsts.push(
`const statusChoices = [\n${values.map((v) => ` { id: '${v}', name: '${v}' },`).join('\n')}\n];\n` `const statusChoices = [\n${values.map((v) => ` { id: '${v}', name: '${labels[v] ?? v}' },`).join('\n')}\n];\n`
); );
} else { } else {
choiceConsts.push( choiceConsts.push(
`const ${constName} = [\n${values.map((v) => ` { id: '${v}', name: '${v}' },`).join('\n')}\n];\n` `const ${constName} = [\n${values.map((v) => ` { id: '${v}', name: '${labels[v] ?? v}' },`).join('\n')}\n];\n`
); );
} }
} }
@@ -375,66 +445,82 @@ function renderFrontendResource(entityName, entity, resourceName, pk, enums) {
filterInputs.push(`<TextInput key="q" source="q" label="Поиск" alwaysOn />`); filterInputs.push(`<TextInput key="q" source="q" label="Поиск" alwaysOn />`);
} }
for (const a of entity.attributes) { for (const a of entity.attributes) {
const label = getAttributeLabel(a, allEntities);
if (a.name === pk) continue; if (a.name === pk) continue;
if (a.foreign) { if (a.foreign) {
const referenceDisplay = getReferenceDisplayExpr(allEntities[a.foreign.entity]);
filterInputs.push( filterInputs.push(
`<ReferenceInput key="${a.name}" source="${a.name}" reference="${pluralize(toKebab(a.foreign.entity))}" label="${a.name}">\n <AutocompleteInput optionText={(record) => record.code ? \`\${record.code} — \${record.name ?? record.code}\` : (record.name ?? record.id)} filterToQuery={(searchText) => ({ q: searchText })} />\n </ReferenceInput>` `<ReferenceInput key="${a.name}" source="${a.name}" reference="${pluralize(toKebab(a.foreign.entity))}" label="${label}">\n <AutocompleteInput optionText={${referenceDisplay}} filterToQuery={(searchText) => ({ q: searchText })} />\n </ReferenceInput>`
); );
continue; continue;
} }
if (['string', 'text', 'uuid'].includes(a.type)) { if (['string', 'text', 'uuid'].includes(a.type)) {
filterInputs.push(`<TextInput key="${a.name}" source="${a.name}" label="${a.name}" />`); filterInputs.push(`<TextInput key="${a.name}" source="${a.name}" label="${label}" />`);
continue; continue;
} }
if (['integer', 'decimal'].includes(a.type)) continue; if (['integer', 'decimal'].includes(a.type)) continue;
if (a.type === 'date') continue; if (a.type === 'date') continue;
// enum // enum
if (a.name === 'status') { if (a.name === 'status') {
filterInputs.push(`<SelectArrayInput key="${a.name}" source="${a.name}" label="${a.name}" choices={statusChoices} />`); filterInputs.push(`<SelectArrayInput key="${a.name}" source="${a.name}" label="${label}" choices={statusChoices} />`);
} else { } else {
filterInputs.push(`<SelectInput key="${a.name}" source="${a.name}" label="${a.name}" choices={${a.name}Choices} emptyText="Все" />`); filterInputs.push(`<SelectInput key="${a.name}" source="${a.name}" label="${label}" choices={${a.name}Choices} emptyText="Все" />`);
} }
} }
const listFields = []; const listFields = [];
for (const a of entity.attributes) { for (const a of entity.attributes) {
const label = getAttributeLabel(a, allEntities);
if (a.foreign) { if (a.foreign) {
const referenceEntity = allEntities[a.foreign.entity];
const referenceAttrs = getEntityAttrNames(referenceEntity);
const fieldSource = referenceAttrs.has('inventoryNumber')
? 'inventoryNumber'
: referenceAttrs.has('code')
? 'code'
: referenceAttrs.has('number')
? 'number'
: 'name';
listFields.push( listFields.push(
`<ReferenceField source="${a.name}" reference="${pluralize(toKebab(a.foreign.entity))}" label="${a.name}" link="show">\n <TextField source="name" />\n </ReferenceField>` `<ReferenceField source="${a.name}" reference="${pluralize(toKebab(a.foreign.entity))}" label="${label}" link="show">\n <TextField source="${fieldSource}" />\n </ReferenceField>`
); );
continue; continue;
} }
if (a.type === 'date') { if (a.type === 'date') {
listFields.push(`<DateField source="${a.name}" label="${a.name}" />`); listFields.push(`<DateField source="${a.name}" label="${label}" />`);
} else if (['integer', 'decimal'].includes(a.type)) { } else if (['integer', 'decimal'].includes(a.type)) {
listFields.push(`<NumberField source="${a.name}" label="${a.name}" />`); listFields.push(`<NumberField source="${a.name}" label="${label}" />`);
} else if (!['string', 'text', 'uuid', 'integer', 'decimal', 'date'].includes(a.type)) { } else if (!['string', 'text', 'uuid', 'integer', 'decimal', 'date'].includes(a.type)) {
listFields.push(`<SelectField source="${a.name}" label="${a.name}" choices={${a.name === 'status' ? 'statusChoices' : `${a.name}Choices`}} />`); listFields.push(`<SelectField source="${a.name}" label="${label}" choices={${a.name === 'status' ? 'statusChoices' : `${a.name}Choices`}} />`);
} else { } else {
listFields.push(`<TextField source="${a.name}" label="${a.name}" />`); listFields.push(`<TextField source="${a.name}" label="${label}" />`);
} }
} }
const list = `import {\n ${listImports.join(',\n ')}\n} from 'react-admin';\n\n${choiceConsts.join('\n')}\nconst ${filtersIdent} = [\n ${filterInputs.join(',\n ')}\n];\n\nconst ${className}ListActions = () => (\n <TopToolbar>\n <FilterButton filters={${filtersIdent}} />\n <CreateButton />\n <ExportButton />\n </TopToolbar>\n);\n\nexport const ${className}List = () => (\n <List actions={<${className}ListActions />} filters={${filtersIdent}} sort={{ field: '${pk}', order: 'ASC' }}>\n <Datagrid rowClick=\"show\">\n ${listFields.join('\n ')}\n </Datagrid>\n </List>\n);\n`; const list = `import {\n ${listImports.join(',\n ')}\n} from 'react-admin';\n\n${choiceConsts.join('\n')}\nconst ${filtersIdent} = [\n ${filterInputs.join(',\n ')}\n];\n\nconst ${className}ListActions = () => (\n <TopToolbar>\n <FilterButton filters={${filtersIdent}} />\n <CreateButton />\n <ExportButton />\n </TopToolbar>\n);\n\nexport const ${className}List = () => (\n <List actions={<${className}ListActions />} filters={${filtersIdent}} sort={{ field: '${sortField}', order: 'ASC' }}>\n <Datagrid rowClick=\"show\">\n ${listFields.join('\n ')}\n </Datagrid>\n </List>\n);\n`;
const formField = (a, mode) => { const formField = (a, mode) => {
const label = getAttributeLabel(a, allEntities);
if (a.isPrimary && mode === 'create' && a.type === 'uuid') return null; if (a.isPrimary && mode === 'create' && a.type === 'uuid') return null;
if (a.isPrimary && mode === 'edit') { if (a.isPrimary && mode === 'edit') {
return `<TextInput source="${a.name}" label="${a.name}" disabled />`; return `<TextInput source="${a.name}" label="${label}" disabled />`;
} }
if (a.foreign) { if (a.foreign) {
return `<ReferenceInput source="${a.name}" reference="${pluralize(toKebab(a.foreign.entity))}" label="${a.name}">\n <AutocompleteInput optionText={(record) => record.code ? \`\${record.code} — \${record.name ?? record.code}\` : (record.name ?? record.id)} filterToQuery={(searchText) => ({ q: searchText })} />\n </ReferenceInput>`; const referenceDisplay = getReferenceDisplayExpr(allEntities[a.foreign.entity]);
return `<ReferenceInput source="${a.name}" reference="${pluralize(toKebab(a.foreign.entity))}">\n <AutocompleteInput label="${label}" optionText={${referenceDisplay}} filterToQuery={(searchText) => ({ q: searchText })} />\n </ReferenceInput>`;
} }
if (a.type === 'date') return `<TextInput source="${a.name}" label="${a.name}" />`; if (a.type === 'date') return `<DateInput source="${a.name}" label="${label}" />`;
if (['integer', 'decimal'].includes(a.type)) return `<TextInput source="${a.name}" label="${a.name}" />`; if (['integer', 'decimal'].includes(a.type)) return `<NumberInput source="${a.name}" label="${label}" />`;
if (!['string', 'text', 'uuid', 'integer', 'decimal', 'date'].includes(a.type)) { if (!['string', 'text', 'uuid', 'integer', 'decimal', 'date'].includes(a.type)) {
if (a.name === 'status' && statusEnumAttr) return `<SelectInput source="${a.name}" label="${a.name}" choices={statusChoices} emptyText="Не выбрано" />`; if (a.name === 'status' && statusEnumAttr) return `<SelectInput source="${a.name}" label="${label}" choices={statusChoices} emptyText="Не выбрано" />`;
return `<SelectInput source="${a.name}" label="${a.name}" choices={${a.name}Choices} emptyText="Не выбрано" />`; return `<SelectInput source="${a.name}" label="${label}" choices={${a.name}Choices} emptyText="Не выбрано" />`;
} }
return `<TextInput source="${a.name}" label="${a.name}" ${a.isRequired ? 'isRequired' : ''} />`; return `<TextInput source="${a.name}" label="${label}" ${a.isRequired ? 'isRequired' : ''} />`;
}; };
const formImportSet = new Set(['SimpleForm', 'TextInput']); const formImportSet = new Set(['SimpleForm', 'TextInput']);
if (hasNumber) formImportSet.add('NumberInput');
if (hasDate) formImportSet.add('DateInput');
if (enumAttrs.length) formImportSet.add('SelectInput'); if (enumAttrs.length) formImportSet.add('SelectInput');
if (hasFK) { if (hasFK) {
formImportSet.add('ReferenceInput'); formImportSet.add('ReferenceInput');
@@ -474,10 +560,15 @@ function ensureAppModule(apply, backendModules) {
let out = src; let out = src;
for (const m of backendModules) { for (const m of backendModules) {
if (!out.includes(`import { ${m.moduleName} }`)) { if (!out.includes(`import { ${m.moduleName} }`)) {
out = out.replace( const importLine = `import { ${m.moduleName} } from '${m.importPath}';`;
/import\s+\{\s*RepairOrderModule\s*\}[^;]*;\s*/m, const importMatches = [...out.matchAll(/^import\s+.*;$/gm)];
(x) => `${x}import { ${m.moduleName} } from '${m.importPath}';\n` if (importMatches.length) {
); const lastImport = importMatches[importMatches.length - 1];
const insertAt = lastImport.index + lastImport[0].length;
out = `${out.slice(0, insertAt)}\n${importLine}${out.slice(insertAt)}`;
} else {
out = `${importLine}\n${out}`;
}
} }
} }
out = out.replace(/imports:\s*\[\s*([\s\S]*?)\s*\],/m, (match, inner) => { out = out.replace(/imports:\s*\[\s*([\s\S]*?)\s*\],/m, (match, inner) => {
@@ -504,7 +595,14 @@ function ensureClientApp(apply, frontendResources) {
]; ];
for (const imp of imports) { for (const imp of imports) {
if (!out.includes(imp)) { if (!out.includes(imp)) {
out = out.replace(/import\s+\{\s*RepairOrderShow\s*\}[^;]*;\s*/m, (x) => `${x}\n${imports.join('\n')}\n`); const importMatches = [...out.matchAll(/^import\s+.*;$/gm)];
if (importMatches.length) {
const lastImport = importMatches[importMatches.length - 1];
const insertAt = lastImport.index + lastImport[0].length;
out = `${out.slice(0, insertAt)}\n${imports.join('\n')}${out.slice(insertAt)}`;
} else {
out = `${imports.join('\n')}\n${out}`;
}
break; break;
} }
} }
@@ -540,7 +638,7 @@ function main() {
const pk = ent.primaryKey; const pk = ent.primaryKey;
const resource = pluralize(toKebab(entityName)); const resource = pluralize(toKebab(entityName));
const be = renderBackendModule(entityName, ent, resource, pk); const be = renderBackendModule(entityName, ent, resource, pk);
const fe = renderFrontendResource(entityName, ent, resource, pk, parsed.enums); const fe = renderFrontendResource(entityName, ent, resource, pk, parsed.enums, parsed.entities);
backendModules.push(be); backendModules.push(be);
frontendResources.push(fe); frontendResources.push(fe);

View File

@@ -1,8 +1,8 @@
export class UpdateEquipmentTypeDto { export class UpdateEquipmentTypeDto {
id?: string; id?: string;
code?: string | null; code?: string;
name?: string | null; name?: string;
manufacturer?: string | null; manufacturer?: string;
maintenanceIntervalHours?: number | null; maintenanceIntervalHours?: number;
overhaulIntervalHours?: number | null; overhaulIntervalHours?: number;
} }

View File

@@ -4,6 +4,14 @@ import { PrismaService } from '../../prisma/prisma.service';
import { CreateEquipmentTypeDto } from './dto/create-equipment-type.dto'; import { CreateEquipmentTypeDto } from './dto/create-equipment-type.dto';
import { UpdateEquipmentTypeDto } from './dto/update-equipment-type.dto'; import { UpdateEquipmentTypeDto } from './dto/update-equipment-type.dto';
function serializeRecord(record: any) {
return {
...record,
};
}
@Injectable() @Injectable()
export class EquipmentTypeService { export class EquipmentTypeService {
constructor(private readonly prisma: PrismaService) {} constructor(private readonly prisma: PrismaService) {}
@@ -31,6 +39,8 @@ export class EquipmentTypeService {
if (query.name) where.name = { contains: query.name, mode: 'insensitive' }; if (query.name) where.name = { contains: query.name, mode: 'insensitive' };
if (query.manufacturer) where.manufacturer = { contains: query.manufacturer, mode: 'insensitive' }; if (query.manufacturer) where.manufacturer = { contains: query.manufacturer, mode: 'insensitive' };
// Enum multi-value support (e.g. status=A&status=B) // Enum multi-value support (e.g. status=A&status=B)
@@ -44,30 +54,37 @@ export class EquipmentTypeService {
this.prisma.equipmentType.count({ where }), this.prisma.equipmentType.count({ where }),
]); ]);
const mapped = data.map((r: any) => ({ id: r.code, ...r })); const mapped = data.map((r: any) => ({ id: r.code, ...serializeRecord(r) }));
return { data: mapped, total }; return { data: mapped, total };
} }
async findOne(id: string) { async findOne(id: string) {
const record = await this.prisma.equipmentType.findUniqueOrThrow({ where: { code: id } as any }); const record = await this.prisma.equipmentType.findUniqueOrThrow({ where: { code: id } as any });
return { id: (record as any).code, ...record }; return { id: (record as any).code, ...serializeRecord(record) };
} }
async create(dto: CreateEquipmentTypeDto) { async create(dto: CreateEquipmentTypeDto) {
const record = await this.prisma.equipmentType.create({ data: dto as any }); const data: any = { ...(dto as any) };
return { id: (record as any).code, ...record };
const record = await this.prisma.equipmentType.create({ data });
return { id: (record as any).code, ...serializeRecord(record) };
} }
async update(id: string, dto: UpdateEquipmentTypeDto) { async update(id: string, dto: UpdateEquipmentTypeDto) {
const data: any = { ...(dto as any) }; const data: any = { ...(dto as any) };
delete data.id; delete data.id;
delete data.code; delete data.code;
const record = await this.prisma.equipmentType.update({ where: { code: id } as any, data }); const record = await this.prisma.equipmentType.update({ where: { code: id } as any, data });
return { id: (record as any).code, ...record }; return { id: (record as any).code, ...serializeRecord(record) };
} }
async remove(id: string) { async remove(id: string) {
const record = await this.prisma.equipmentType.delete({ where: { code: id } as any }); const record = await this.prisma.equipmentType.delete({ where: { code: id } as any });
return { id: (record as any).code, ...record }; return { id: (record as any).code, ...serializeRecord(record) };
} }
} }

View File

@@ -1,14 +1,14 @@
export class UpdateEquipmentDto { export class UpdateEquipmentDto {
id?: string | null; id?: string;
inventoryNumber?: string | null; inventoryNumber?: string;
serialNumber?: string | null; serialNumber?: string;
name?: string | null; name?: string;
equipmentTypeCode?: string | null; equipmentTypeCode?: string;
status?: string | null; status?: string;
location?: string | null; location?: string;
commissionedAt?: string | null; commissionedAt?: string;
totalEngineHours?: string | null; totalEngineHours?: string;
engineHoursSinceLastRepair?: string | null; engineHoursSinceLastRepair?: string;
lastRepairAt?: string | null; lastRepairAt?: string;
notes?: string | null; notes?: string;
} }

View File

@@ -4,6 +4,16 @@ import { PrismaService } from '../../prisma/prisma.service';
import { CreateEquipmentDto } from './dto/create-equipment.dto'; import { CreateEquipmentDto } from './dto/create-equipment.dto';
import { UpdateEquipmentDto } from './dto/update-equipment.dto'; import { UpdateEquipmentDto } from './dto/update-equipment.dto';
function serializeRecord(record: any) {
return {
...record,
totalEngineHours: record.totalEngineHours?.toString() ?? null,
engineHoursSinceLastRepair: record.engineHoursSinceLastRepair?.toString() ?? null,
commissionedAt: record.commissionedAt?.toISOString() ?? null,
lastRepairAt: record.lastRepairAt?.toISOString() ?? null,
};
}
@Injectable() @Injectable()
export class EquipmentService { export class EquipmentService {
constructor(private readonly prisma: PrismaService) {} constructor(private readonly prisma: PrismaService) {}
@@ -13,7 +23,7 @@ export class EquipmentService {
const end = parseInt(query._end) || 10; const end = parseInt(query._end) || 10;
const take = end - start; const take = end - start;
const skip = start; const skip = start;
const sortField = query._sort || 'id'; const sortField = query._sort || 'inventoryNumber';
const sortOrder = (query._order || 'ASC').toLowerCase() as 'asc' | 'desc'; const sortOrder = (query._order || 'ASC').toLowerCase() as 'asc' | 'desc';
const where: any = {}; const where: any = {};
@@ -33,10 +43,11 @@ export class EquipmentService {
if (query.inventoryNumber) where.inventoryNumber = { contains: query.inventoryNumber, mode: 'insensitive' }; if (query.inventoryNumber) where.inventoryNumber = { contains: query.inventoryNumber, mode: 'insensitive' };
if (query.serialNumber) where.serialNumber = { contains: query.serialNumber, mode: 'insensitive' }; if (query.serialNumber) where.serialNumber = { contains: query.serialNumber, mode: 'insensitive' };
if (query.name) where.name = { contains: query.name, mode: 'insensitive' }; if (query.name) where.name = { contains: query.name, mode: 'insensitive' };
if (query.equipmentTypeCode) where.equipmentTypeCode = { contains: query.equipmentTypeCode, mode: 'insensitive' };
if (query.location) where.location = { contains: query.location, mode: 'insensitive' }; if (query.location) where.location = { contains: query.location, mode: 'insensitive' };
if (query.notes) where.notes = { contains: query.notes, mode: 'insensitive' }; if (query.notes) where.notes = { contains: query.notes, mode: 'insensitive' };
if (query.equipmentTypeCode) where.equipmentTypeCode = query.equipmentTypeCode;
// Enum multi-value support (e.g. status=A&status=B) // Enum multi-value support (e.g. status=A&status=B)
if (query.status) { const vals = Array.isArray(query.status) ? query.status : [query.status]; where.status = vals.length > 1 ? { in: vals } : vals[0]; } if (query.status) { const vals = Array.isArray(query.status) ? query.status : [query.status]; where.status = vals.length > 1 ? { in: vals } : vals[0]; }
@@ -50,30 +61,41 @@ export class EquipmentService {
this.prisma.equipment.count({ where }), this.prisma.equipment.count({ where }),
]); ]);
const mapped = data; const mapped = data.map(serializeRecord);
return { data: mapped, total }; return { data: mapped, total };
} }
async findOne(id: string) { async findOne(id: string) {
const record = await this.prisma.equipment.findUniqueOrThrow({ where: { id: id } as any }); const record = await this.prisma.equipment.findUniqueOrThrow({ where: { id: id } as any });
return record; return serializeRecord(record);
} }
async create(dto: CreateEquipmentDto) { async create(dto: CreateEquipmentDto) {
const record = await this.prisma.equipment.create({ data: dto as any }); const data: any = { ...(dto as any) };
return record; if (data.commissionedAt) data.commissionedAt = new Date(data.commissionedAt);
if (data.lastRepairAt) data.lastRepairAt = new Date(data.lastRepairAt);
if (data.totalEngineHours) data.totalEngineHours = new Prisma.Decimal(data.totalEngineHours);
if (data.engineHoursSinceLastRepair) data.engineHoursSinceLastRepair = new Prisma.Decimal(data.engineHoursSinceLastRepair);
const record = await this.prisma.equipment.create({ data });
return serializeRecord(record);
} }
async update(id: string, dto: UpdateEquipmentDto) { async update(id: string, dto: UpdateEquipmentDto) {
const data: any = { ...(dto as any) }; const data: any = { ...(dto as any) };
delete data.id; delete data.id;
delete data.id; delete data.id;
if (data.commissionedAt) data.commissionedAt = new Date(data.commissionedAt);
if (data.lastRepairAt) data.lastRepairAt = new Date(data.lastRepairAt);
if (data.totalEngineHours !== undefined && data.totalEngineHours !== null) data.totalEngineHours = new Prisma.Decimal(data.totalEngineHours);
if (data.engineHoursSinceLastRepair !== undefined && data.engineHoursSinceLastRepair !== null) data.engineHoursSinceLastRepair = new Prisma.Decimal(data.engineHoursSinceLastRepair);
const record = await this.prisma.equipment.update({ where: { id: id } as any, data }); const record = await this.prisma.equipment.update({ where: { id: id } as any, data });
return record; return serializeRecord(record);
} }
async remove(id: string) { async remove(id: string) {
const record = await this.prisma.equipment.delete({ where: { id: id } as any }); const record = await this.prisma.equipment.delete({ where: { id: id } as any });
return record; return serializeRecord(record);
} }
} }

View File

@@ -1,14 +1,14 @@
export class UpdateRepairOrderDto { export class UpdateRepairOrderDto {
id?: string | null; id?: string;
number?: string | null; number?: string;
equipmentId?: string | null; equipmentId?: string;
repairKind?: string | null; repairKind?: string;
status?: string | null; status?: string;
plannedAt?: string | null; plannedAt?: string;
startedAt?: string | null; startedAt?: string;
completedAt?: string | null; completedAt?: string;
contractor?: string | null; contractor?: string;
engineHoursAtRepair?: string | null; engineHoursAtRepair?: string;
description?: string | null; description?: string;
notes?: string | null; notes?: string;
} }

View File

@@ -4,6 +4,16 @@ import { PrismaService } from '../../prisma/prisma.service';
import { CreateRepairOrderDto } from './dto/create-repair-order.dto'; import { CreateRepairOrderDto } from './dto/create-repair-order.dto';
import { UpdateRepairOrderDto } from './dto/update-repair-order.dto'; import { UpdateRepairOrderDto } from './dto/update-repair-order.dto';
function serializeRecord(record: any) {
return {
...record,
engineHoursAtRepair: record.engineHoursAtRepair?.toString() ?? null,
plannedAt: record.plannedAt?.toISOString() ?? null,
startedAt: record.startedAt?.toISOString() ?? null,
completedAt: record.completedAt?.toISOString() ?? null,
};
}
@Injectable() @Injectable()
export class RepairOrderService { export class RepairOrderService {
constructor(private readonly prisma: PrismaService) {} constructor(private readonly prisma: PrismaService) {}
@@ -13,7 +23,7 @@ export class RepairOrderService {
const end = parseInt(query._end) || 10; const end = parseInt(query._end) || 10;
const take = end - start; const take = end - start;
const skip = start; const skip = start;
const sortField = query._sort || 'id'; const sortField = query._sort || 'number';
const sortOrder = (query._order || 'ASC').toLowerCase() as 'asc' | 'desc'; const sortOrder = (query._order || 'ASC').toLowerCase() as 'asc' | 'desc';
const where: any = {}; const where: any = {};
@@ -33,6 +43,8 @@ export class RepairOrderService {
if (query.description) where.description = { contains: query.description, mode: 'insensitive' }; if (query.description) where.description = { contains: query.description, mode: 'insensitive' };
if (query.notes) where.notes = { contains: query.notes, mode: 'insensitive' }; if (query.notes) where.notes = { contains: query.notes, mode: 'insensitive' };
if (query.equipmentId) where.equipmentId = query.equipmentId;
// Enum multi-value support (e.g. status=A&status=B) // Enum multi-value support (e.g. status=A&status=B)
if (query.repairKind) { const vals = Array.isArray(query.repairKind) ? query.repairKind : [query.repairKind]; where.repairKind = vals.length > 1 ? { in: vals } : vals[0]; } if (query.repairKind) { const vals = Array.isArray(query.repairKind) ? query.repairKind : [query.repairKind]; where.repairKind = vals.length > 1 ? { in: vals } : vals[0]; }
if (query.status) { const vals = Array.isArray(query.status) ? query.status : [query.status]; where.status = vals.length > 1 ? { in: vals } : vals[0]; } if (query.status) { const vals = Array.isArray(query.status) ? query.status : [query.status]; where.status = vals.length > 1 ? { in: vals } : vals[0]; }
@@ -47,30 +59,41 @@ export class RepairOrderService {
this.prisma.repairOrder.count({ where }), this.prisma.repairOrder.count({ where }),
]); ]);
const mapped = data; const mapped = data.map(serializeRecord);
return { data: mapped, total }; return { data: mapped, total };
} }
async findOne(id: string) { async findOne(id: string) {
const record = await this.prisma.repairOrder.findUniqueOrThrow({ where: { id: id } as any }); const record = await this.prisma.repairOrder.findUniqueOrThrow({ where: { id: id } as any });
return record; return serializeRecord(record);
} }
async create(dto: CreateRepairOrderDto) { async create(dto: CreateRepairOrderDto) {
const record = await this.prisma.repairOrder.create({ data: dto as any }); const data: any = { ...(dto as any) };
return record; if (data.plannedAt) data.plannedAt = new Date(data.plannedAt);
if (data.startedAt) data.startedAt = new Date(data.startedAt);
if (data.completedAt) data.completedAt = new Date(data.completedAt);
if (data.engineHoursAtRepair) data.engineHoursAtRepair = new Prisma.Decimal(data.engineHoursAtRepair);
const record = await this.prisma.repairOrder.create({ data });
return serializeRecord(record);
} }
async update(id: string, dto: UpdateRepairOrderDto) { async update(id: string, dto: UpdateRepairOrderDto) {
const data: any = { ...(dto as any) }; const data: any = { ...(dto as any) };
delete data.id; delete data.id;
delete data.id; delete data.id;
if (data.plannedAt) data.plannedAt = new Date(data.plannedAt);
if (data.startedAt) data.startedAt = new Date(data.startedAt);
if (data.completedAt) data.completedAt = new Date(data.completedAt);
if (data.engineHoursAtRepair !== undefined && data.engineHoursAtRepair !== null) data.engineHoursAtRepair = new Prisma.Decimal(data.engineHoursAtRepair);
const record = await this.prisma.repairOrder.update({ where: { id: id } as any, data }); const record = await this.prisma.repairOrder.update({ where: { id: id } as any, data });
return record; return serializeRecord(record);
} }
async remove(id: string) { async remove(id: string) {
const record = await this.prisma.repairOrder.delete({ where: { id: id } as any }); const record = await this.prisma.repairOrder.delete({ where: { id: id } as any });
return record; return serializeRecord(record);
} }
} }